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

refactor: global config #12743

Merged
merged 8 commits into from
Nov 23, 2021
23 changes: 13 additions & 10 deletions lib/config/decrypt.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { loadFixture } from '../../test/util';
import { decryptConfig } from './decrypt';
import { setGlobalConfig } from './global';
import { GlobalConfig } from './global';
import type { RenovateConfig } from './types';

const privateKey = loadFixture('private.pem', '.');
Expand All @@ -12,7 +12,7 @@ describe('config/decrypt', () => {
let config: RenovateConfig;
beforeEach(() => {
config = {};
setGlobalConfig();
GlobalConfig.reset();
});
it('returns empty with no privateKey', async () => {
delete config.encrypted;
Expand All @@ -27,19 +27,22 @@ describe('config/decrypt', () => {
});
it('handles invalid encrypted type', async () => {
config.encrypted = 1;
setGlobalConfig({ privateKey });
GlobalConfig.set({ privateKey });
const res = await decryptConfig(config, repository);
expect(res.encrypted).toBeUndefined();
});
it('handles invalid encrypted value', async () => {
config.encrypted = { a: 1 };
setGlobalConfig({ privateKey, privateKeyOld: 'invalid-key' });
GlobalConfig.set({ privateKey, privateKeyOld: 'invalid-key' });
await expect(decryptConfig(config, repository)).rejects.toThrow(
'config-validation'
);
});
it('replaces npm token placeholder in npmrc', async () => {
setGlobalConfig({ privateKey: 'invalid-key', privateKeyOld: privateKey }); // test old key failover
GlobalConfig.set({
privateKey: 'invalid-key',
privateKeyOld: privateKey,
}); // test old key failover
config.npmrc =
'//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n';
config.encrypted = {
Expand All @@ -54,7 +57,7 @@ describe('config/decrypt', () => {
);
});
it('appends npm token in npmrc', async () => {
setGlobalConfig({ privateKey });
GlobalConfig.set({ privateKey });
config.npmrc = 'foo=bar\n';
config.encrypted = {
npmToken:
Expand All @@ -66,7 +69,7 @@ describe('config/decrypt', () => {
expect(res.npmrc).toMatchSnapshot();
});
it('decrypts nested', async () => {
setGlobalConfig({ privateKey });
GlobalConfig.set({ privateKey });
config.packageFiles = [
{
packageFile: 'package.json',
Expand All @@ -93,7 +96,7 @@ describe('config/decrypt', () => {
);
});
it('rejects invalid PGP message', async () => {
setGlobalConfig({ privateKey: privateKeyPgp });
GlobalConfig.set({ privateKey: privateKeyPgp });
config.encrypted = {
token:
'long-but-wrong-wcFMAw+4H7SgaqGOAQ//ZNPgHJ4RQBdfFoDX8Ywe9UxqMlc8k6VasCszQ2JULh/BpEdKdgRUGNaKaeZ+oBKYDBmDwAD5V5FEMlsg+KO2gykp/p2BAwvKGtYK0MtxLh4h9yJbN7TrVnGO3/cC+Inp8exQt0gD6f1Qo/9yQ9NE4/BIbaSs2b2DgeIK7Ed8N675AuSo73UOa6o7t+9pKeAAK5TQwgSvolihbUs8zjnScrLZD+nhvL3y5gpAqK9y//a+bTu6xPA1jdLjsswoCUq/lfVeVsB2GWV2h6eex/0fRKgN7xxNgdMn0a7msrvumhTawP8mPisPY2AAsHRIgQ9vdU5HbOPdGoIwI9n9rMdIRn9Dy7/gcX9Ic+RP2WwS/KnPHLu/CveY4W5bYqYoikWtJs9HsBCyWFiHIRrJF+FnXwtKdoptRfxTfJIkBoLrV6fDIyKo79iL+xxzgrzWs77KEJUJfexZBEGBCnrV2o7mo3SU197S0qx7HNvqrmeCj8CLxq8opXC71TNa+XE6BQUVyhMFxtW9LNxZUHRiNzrTSikArT4hzjyr3f9cb0kZVcs6XJQsm1EskU3WXo7ETD7nsukS9GfbwMn7tfYidB/yHSHl09ih871BcgByDmEKKdmamcNilW2bmTAqB5JmtaYT5/H8jRQWo/VGrEqlmiA4KmwSv7SZPlDnaDFrmzmMZZDSRgHe5KWl283XLmSeE8J0NPqwFH3PeOv4fIbOjJrnbnFBwSAsgsMe2K4OyFDh2COfrho7s8EP1Kl5lBkYJ+VRreGRerdSu24',
Expand Down Expand Up @@ -133,7 +136,7 @@ describe('config/decrypt', () => {
);
});
it('handles PGP org constraint', async () => {
setGlobalConfig({ privateKey: privateKeyPgp });
GlobalConfig.set({ privateKey: privateKeyPgp });
config.encrypted = {
token:
'wcFMAw+4H7SgaqGOAQ/+Lz6RlbEymbnmMhrktuaGiDPWRNPEQFuMRwwYM6/B/r0JMZa9tskAA5RpyYKxGmJJeuRtlA8GkTw02GoZomlJf/KXJZ95FwSbkXMSRJRD8LJ2402Hw2TaOTaSvfamESnm8zhNo8cok627nkKQkyrpk64heVlU5LIbO2+UgYgbiSQjuXZiW+QuJ1hVRjx011FQgEYc59+22yuKYqd8rrni7TrVqhGRlHCAqvNAGjBI4H7uTFh0sP4auunT/JjxTeTkJoNu8KgS/LdrvISpO67TkQziZo9XD5FOzSN7N3e4f8vO4N4fpjgkIDH/9wyEYe0zYz34xMAFlnhZzqrHycRqzBJuMxGqlFQcKWp9IisLMoVJhLrnvbDLuwwcjeqYkhvODjSs7UDKwTE4X4WmvZr0x4kOclOeAAz/pM6oNVnjgWJd9SnYtoa67bZVkne0k6mYjVhosie8v8icijmJ4OyLZUGWnjZCRd/TPkzQUw+B0yvsop9FYGidhCI+4MVx6W5w7SRtCctxVfCjLpmU4kWaBUUJ5YIQ5xm55yxEYuAsQkxOAYDCMFlV8ntWStYwIG1FsBgJX6VPevXuPPMjWiPNedIpJwBH2PLB4blxMfzDYuCeaIqU4daDaEWxxpuFTTK9fLdJKuipwFG6rwE3OuijeSN+2SLszi834DXtUjQdikHSTQG392+oTmZCFPeffLk/OiV2VpdXF3gGL7sr5M9hOWIZ783q0vW1l6nAElZ7UA//kW+L6QRxbnBVTJK5eCmMY6RJmL76zjqC1jQ0FC10',
Expand All @@ -146,7 +149,7 @@ describe('config/decrypt', () => {
);
});
it('handles PGP org/repo constraint', async () => {
setGlobalConfig({ privateKey: privateKeyPgp });
GlobalConfig.set({ privateKey: privateKeyPgp });
config.encrypted = {
token:
'wcFMAw+4H7SgaqGOAQ//Wp7N0PaDZp0uOdwsc1CuqAq0UPcq+IQdHyKpJs3tHiCecXBHogy4P+rY9nGaUrVneCr4HexuKGuyJf1yl0ZqFffAUac5PjF8eDvjukQGOUq4aBlOogJCEefnuuVxVJx+NRR5iF1P6v57bmI1c+zoqZI/EQB30KU6O1BsdGPLUA/+R3dwCZd5Mbd36s34eYBasqcY9/QbqFcpElXMEPMse3kMCsVXPbZ+UMjtPJiBPUmtJq+ifnu1LzDrfshusSQMwgd/QNk7nEsijiYKllkWhHTP6g7zigvJ46x0h6AYS108YiuK3B9XUhXN9m05Ac6KTEEUdRI3E/dK2dQuRkLjXC8wceQm4A19Gm0uHoMIJYOCbiVoBCH6ayvKbZWZV5lZ4D1JbDNGmKeIj6OX9XWEMKiwTx0Xe89V7BdJzwIGrL0TCLtXuYWZ/R2k+UuBqtgzr44BsBqMpKUA0pcGBoqsEou1M05Ae9fJMF6ADezF5UQZPxT1hrMldiTp3p9iHGfWN2tKHeoW/8CqlIqg9JEkTc+Pl/L9E6ndy5Zjf097PvcmSGhxUQBE7XlrZoIlGhiEU/1HPMen0UUIs0LUu1ywpjCex2yTWnU2YmEwy0MQI1sekSr96QFxDDz9JcynYOYbqR/X9pdxEWyzQ+NJ3n6K97nE1Dj9Sgwu7mFGiUdNkf/SUAF0eZi/eXg71qumpMGBd4eWPtgkeMPLHjvMSYw9vBUfcoKFz6RJ4woG0dw5HOFkPnIjXKWllnl/o01EoBp/o8uswsIS9Nb8i+bp27U6tAHE',
Expand Down
4 changes: 2 additions & 2 deletions lib/config/decrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { logger } from '../logger';
import { maskToken } from '../util/mask';
import { regEx } from '../util/regex';
import { add } from '../util/sanitize';
import { getGlobalConfig } from './global';
import { GlobalConfig } from './global';
import type { RenovateConfig } from './types';

export async function tryDecryptPgp(
Expand Down Expand Up @@ -155,7 +155,7 @@ export async function decryptConfig(
): Promise<RenovateConfig> {
logger.trace({ config }, 'decryptConfig()');
const decryptedConfig = { ...config };
const { privateKey, privateKeyOld } = getGlobalConfig();
const { privateKey, privateKeyOld } = GlobalConfig.get();
for (const [key, val] of Object.entries(config)) {
if (key === 'encrypted' && is.object(val)) {
logger.debug({ config: val }, 'Found encrypted config');
Expand Down
86 changes: 51 additions & 35 deletions lib/config/global.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,56 @@
import type { RenovateConfig, RepoGlobalConfig } from './types';

let repoGlobalConfig: RepoGlobalConfig = {};

// TODO: once global config work is complete, add a test to make sure this list includes all options with globalOnly=true (#9603)
const repoGlobalOptions = [
'allowCustomCrateRegistries',
'allowPlugins',
'allowPostUpgradeCommandTemplating',
'allowScripts',
'allowedPostUpgradeCommands',
'binarySource',
'customEnvVariables',
'dockerChildPrefix',
'dockerImagePrefix',
'dockerUser',
'dryRun',
'exposeAllEnv',
'migratePresets',
'privateKey',
'privateKeyOld',
'localDir',
'cacheDir',
];

export function setGlobalConfig(
config: RenovateConfig | RepoGlobalConfig = {}
): RenovateConfig {
repoGlobalConfig = {};
const result = { ...config };
for (const option of repoGlobalOptions) {
repoGlobalConfig[option] = config[option];
delete result[option];
export class GlobalConfig {
// TODO: once global config work is complete, add a test to make sure this list includes all options with globalOnly=true (#9603)
private static readonly OPTIONS = [
viceice marked this conversation as resolved.
Show resolved Hide resolved
'allowCustomCrateRegistries',
'allowedPostUpgradeCommands',
'allowPlugins',
'allowPostUpgradeCommandTemplating',
'allowScripts',
'binarySource',
'cacheDir',
'customEnvVariables',
'dockerChildPrefix',
'dockerImagePrefix',
'dockerUser',
'dryRun',
'exposeAllEnv',
'localDir',
'migratePresets',
'privateKey',
'privateKeyOld',
];

private static config: RepoGlobalConfig = {};

static get(): RepoGlobalConfig;
static get<Key extends keyof RepoGlobalConfig>(
key?: Key
): RepoGlobalConfig[Key];
static get<Key extends keyof RepoGlobalConfig>(
key?: Key
): RepoGlobalConfig | RepoGlobalConfig[Key] {
return key ? GlobalConfig.config[key] : GlobalConfig.config;
}
viceice marked this conversation as resolved.
Show resolved Hide resolved
return result;
}

export function getGlobalConfig(): RepoGlobalConfig {
return repoGlobalConfig;
static set(config: RenovateConfig | RepoGlobalConfig): RenovateConfig {
GlobalConfig.reset();

const result = { ...config };
for (const option of GlobalConfig.OPTIONS) {
GlobalConfig.config[option] = config[option];
delete result[option];
}

return result;
}

static reset(): void {
GlobalConfig.config = {};
}

static get isDryRun(): boolean {
return GlobalConfig.get('dryRun');
}
pret-a-porter marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 2 additions & 2 deletions lib/config/migration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PlatformId } from '../constants';
import { getConfig } from './defaults';
import { setGlobalConfig } from './global';
import { GlobalConfig } from './global';
import * as configMigration from './migration';
import type {
MigratedConfig,
Expand Down Expand Up @@ -703,7 +703,7 @@ describe('config/migration', () => {
});
});
it('it migrates presets', () => {
setGlobalConfig({
GlobalConfig.set({
migratePresets: {
'@org': 'local>org/renovate-config',
'@org2/foo': '',
Expand Down
4 changes: 2 additions & 2 deletions lib/config/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { dequal } from 'dequal';
import { logger } from '../logger';
import { clone } from '../util/clone';
import { regEx } from '../util/regex';
import { getGlobalConfig } from './global';
import { GlobalConfig } from './global';
import { MigrationsService } from './migrations';
import { getOptions } from './options';
import { removedPresets } from './presets/common';
Expand Down Expand Up @@ -42,7 +42,7 @@ export function migrateConfig(
'optionalDependencies',
'peerDependencies',
];
const { migratePresets } = getGlobalConfig();
const { migratePresets } = GlobalConfig.get();
for (const [key, val] of Object.entries(config)) {
if (key === 'pathRules') {
if (is.array(val)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/config/presets/npm/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as httpMock from '../../../../test/http-mock';
import { setGlobalConfig } from '../../global';
import { GlobalConfig } from '../../global';
import * as npm from '.';

jest.mock('registry-auth-token');
Expand All @@ -8,7 +8,7 @@ jest.mock('delay');
describe('config/presets/npm/index', () => {
beforeEach(() => {
jest.resetAllMocks();
setGlobalConfig();
GlobalConfig.reset();
});
afterEach(() => {
delete process.env.RENOVATE_CACHE_NPM_MINUTES;
Expand Down
16 changes: 8 additions & 8 deletions lib/datasource/crate/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { dirname, join } from 'upath';
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { loadFixture } from '../../../test/util';
import { setGlobalConfig } from '../../config/global';
import { GlobalConfig } from '../../config/global';
import type { RepoGlobalConfig } from '../../config/types';
import * as memCache from '../../util/cache/memory';
import { RegistryFlavor, RegistryInfo } from './types';
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('datasource/crate/index', () => {
localDir: join(tmpDir.path, 'local'),
cacheDir: join(tmpDir.path, 'cache'),
};
setGlobalConfig(adminConfig);
GlobalConfig.set(adminConfig);

simpleGit.mockReset();
memCache.init();
Expand All @@ -111,7 +111,7 @@ describe('datasource/crate/index', () => {
afterEach(async () => {
await tmpDir.cleanup();
tmpDir = null;
setGlobalConfig();
GlobalConfig.reset();
});

it('returns null for missing registry url', async () => {
Expand Down Expand Up @@ -247,7 +247,7 @@ describe('datasource/crate/index', () => {
});
it('clones cloudsmith private registry', async () => {
const { mockClone } = setupGitMocks();
setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
const url = 'https://dl.cloudsmith.io/basic/myorg/myrepo/cargo/index.git';
const res = await getPkgReleases({
datasource,
Expand All @@ -261,7 +261,7 @@ describe('datasource/crate/index', () => {
});
it('clones other private registry', async () => {
const { mockClone } = setupGitMocks();
setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
const url = 'https://github.com/mcorbin/testregistry';
const res = await getPkgReleases({
datasource,
Expand All @@ -275,7 +275,7 @@ describe('datasource/crate/index', () => {
});
it('clones once then reuses the cache', async () => {
const { mockClone } = setupGitMocks();
setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
const url = 'https://github.com/mcorbin/othertestregistry';
await getPkgReleases({
datasource,
Expand All @@ -291,7 +291,7 @@ describe('datasource/crate/index', () => {
});
it('guards against race conditions while cloning', async () => {
const { mockClone } = setupGitMocks(250);
setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
const url = 'https://github.com/mcorbin/othertestregistry';

await Promise.all([
Expand All @@ -317,7 +317,7 @@ describe('datasource/crate/index', () => {
});
it('returns null when git clone fails', async () => {
setupErrorGitMock();
setGlobalConfig({ ...adminConfig, allowCustomCrateRegistries: true });
GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true });
const url = 'https://github.com/mcorbin/othertestregistry';

const result = await getPkgReleases({
Expand Down
4 changes: 2 additions & 2 deletions lib/datasource/crate/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import hasha from 'hasha';
import Git from 'simple-git';
import { join } from 'upath';
import { getGlobalConfig } from '../../config/global';
import { GlobalConfig } from '../../config/global';
import { logger } from '../../logger';
import * as memCache from '../../util/cache/memory';
import { cache } from '../../util/cache/package/decorator';
Expand Down Expand Up @@ -186,7 +186,7 @@ export class CrateDatasource extends Datasource {
};

if (flavor !== RegistryFlavor.CratesIo) {
if (!getGlobalConfig().allowCustomCrateRegistries) {
if (!GlobalConfig.get('allowCustomCrateRegistries')) {
logger.warn(
'crate datasource: allowCustomCrateRegistries=true is required for registries other than crates.io, bailing out'
);
Expand Down
8 changes: 4 additions & 4 deletions lib/datasource/npm/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import mockDate from 'mockdate';
import _registryAuthToken from 'registry-auth-token';
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { setGlobalConfig } from '../../config/global';
import { GlobalConfig } from '../../config/global';
import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
import * as hostRules from '../../util/host-rules';
import { id as datasource, getNpmrc, resetCache, setNpmrc } from '.';
Expand All @@ -17,7 +17,7 @@ let npmResponse: any;
describe('datasource/npm/index', () => {
beforeEach(() => {
jest.resetAllMocks();
setGlobalConfig();
GlobalConfig.reset();
hostRules.clear();
resetCache();
setNpmrc();
Expand Down Expand Up @@ -357,7 +357,7 @@ describe('datasource/npm/index', () => {
.reply(200, npmResponse);
process.env.REGISTRY = 'https://registry.from-env.com';
process.env.RENOVATE_CACHE_NPM_MINUTES = '15';
setGlobalConfig({ exposeAllEnv: true });
GlobalConfig.set({ exposeAllEnv: true });

const npmrc = 'registry=${REGISTRY}';
const res = await getPkgReleases({ datasource, depName: 'foobar', npmrc });
Expand All @@ -366,7 +366,7 @@ describe('datasource/npm/index', () => {
});

it('should throw error if necessary env var is not present', () => {
setGlobalConfig({ exposeAllEnv: true });
GlobalConfig.set({ exposeAllEnv: true });

expect(() => setNpmrc('registry=${REGISTRY_MISSING}')).toThrow(
Error('env-replace')
Expand Down
6 changes: 3 additions & 3 deletions lib/datasource/npm/npmrc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mocked } from '../../../test/util';
import { setGlobalConfig } from '../../config/global';
import { GlobalConfig } from '../../config/global';
import * as _sanitize from '../../util/sanitize';
import { getNpmrc, setNpmrc } from './npmrc';

Expand All @@ -10,7 +10,7 @@ const sanitize = mocked(_sanitize);
describe('datasource/npm/npmrc', () => {
beforeEach(() => {
setNpmrc('');
setGlobalConfig();
GlobalConfig.reset();
jest.resetAllMocks();
});

Expand All @@ -37,7 +37,7 @@ describe('datasource/npm/npmrc', () => {
});

it('sanitize _authtoken with high trust', () => {
setGlobalConfig({ exposeAllEnv: true });
GlobalConfig.set({ exposeAllEnv: true });
process.env.TEST_TOKEN = 'test';
setNpmrc(
'//registry.test.com:_authToken=${TEST_TOKEN}\n_authToken=\nregistry=http://localhost'
Expand Down