Skip to content

Commit

Permalink
feat(config): detectGlobalManagerConfig (#11951)
Browse files Browse the repository at this point in the history
  • Loading branch information
rarkins committed Sep 29, 2021
1 parent 79e65bd commit cd72cdf
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/usage/self-hosted-configuration.md
Expand Up @@ -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.

## 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

Adds a custom prefix to the default Renovate sidecar Docker containers name and label.
Expand Down
8 changes: 8 additions & 0 deletions lib/config/options/index.ts
Expand Up @@ -7,6 +7,14 @@ import * as pep440Versioning from '../../versioning/pep440';
import type { RenovateOptions } from '../types';

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',
description: 'If true allow templating for post-upgrade commands.',
Expand Down
8 changes: 8 additions & 0 deletions lib/manager/index.spec.ts
Expand Up @@ -2,6 +2,8 @@ import { loadModules } from '../util/modules';
import type { ManagerApi } from './types';
import * as manager from '.';

jest.mock('../util/fs');

describe('manager/index', () => {
describe('get()', () => {
it('gets something', () => {
Expand Down Expand Up @@ -43,6 +45,12 @@ describe('manager/index', () => {
}
});

describe('detectGlobalConfig()', () => {
it('iterates through managers', async () => {
expect(await manager.detectAllGlobalConfig()).toEqual({});
});
});

describe('extractAllPackageFiles()', () => {
it('returns null', async () => {
manager.getManagers().set('dummy', {
Expand Down
13 changes: 13 additions & 0 deletions lib/manager/index.ts
Expand Up @@ -15,6 +15,7 @@ import type { RangeStrategy } from '../types';
import managers from './api';
import type {
ExtractConfig,
GlobalManagerConfig,
ManagerApi,
PackageFile,
RangeConfig,
Expand Down Expand Up @@ -47,6 +48,18 @@ export const getLanguageList = (): string[] => languageList;
export const getManagerList = (): string[] => managerList;
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(
manager: string,
config: ExtractConfig,
Expand Down
29 changes: 29 additions & 0 deletions lib/manager/npm/detect.spec.ts
@@ -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 changes: 23 additions & 0 deletions lib/manager/npm/detect.ts
@@ -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;
}
1 change: 1 addition & 0 deletions lib/manager/npm/index.ts
@@ -1,6 +1,7 @@
import { LANGUAGE_JAVASCRIPT } from '../../constants/languages';
import * as npmVersioning from '../../versioning/npm';

export { detectGlobalConfig } from './detect';
export { extractAllPackageFiles } from './extract';
export {
bumpPackageVersion,
Expand Down
7 changes: 7 additions & 0 deletions lib/manager/types.ts
Expand Up @@ -220,6 +220,11 @@ export interface UpdateLockedConfig {
newVersion?: string;
}

export interface GlobalManagerConfig {
npmrc?: string;
npmrcMerge?: boolean;
}

export interface ManagerApi {
defaultConfig: Record<string, unknown>;
language?: string;
Expand All @@ -231,6 +236,8 @@ export interface ManagerApi {
bumpVersion: ReleaseType | string
): Result<BumpPackageVersionResult>;

detectGlobalConfig?(): Result<GlobalManagerConfig>;

extractAllPackageFiles?(
config: ExtractConfig,
files: string[]
Expand Down
7 changes: 7 additions & 0 deletions lib/workers/global/config/parse/index.spec.ts
Expand Up @@ -3,6 +3,8 @@ import { readFile } from '../../../../util/fs';
import getArgv from './__fixtures__/argv';

jest.mock('../../../../datasource/npm');
jest.mock('../../../../util/fs');

try {
jest.mock('../../config.js');
} catch (err) {
Expand Down Expand Up @@ -118,5 +120,10 @@ describe('workers/global/config/parse/index', () => {
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
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();
});
});
});
8 changes: 8 additions & 0 deletions lib/workers/global/config/parse/index.ts
Expand Up @@ -2,6 +2,7 @@ import * as defaultsParser from '../../../../config/defaults';
import { AllConfig } from '../../../../config/types';
import { mergeChildConfig } from '../../../../config/utils';
import { addStream, logger, setContext } from '../../../../logger';
import { detectAllGlobalConfig } from '../../../../manager';
import { ensureDir, getSubDirectory, readFile } from '../../../../util/fs';
import { ensureTrailingSlash } from '../../../../util/url';
import * as cliParser from './cli';
Expand Down Expand Up @@ -73,6 +74,13 @@ export async function parseConfigs(
logger.debug({ config: envConfig }, 'Env 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
logger.trace({ config }, 'Full config');

Expand Down

0 comments on commit cd72cdf

Please sign in to comment.