feat: extend dryRun options to extract lookup full (#14555)

This commit is contained in:
MaronHatoum 2022-03-28 14:55:26 +03:00 committed by GitHub
parent 9933dda182
commit 39471b57ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 174 additions and 35 deletions

View file

@ -292,6 +292,17 @@ Like this:
## dryRun ## dryRun
Use `dryRun` to preview the behavior of Renovate in logs, without making any changes to the repository files.
You can choose from the following behaviors for the `dryRun` config option:
- `null`: Default behavior - Performs a regular Renovate run including creating/updating/deleting branches and PRs
- `"extract"`: Performs a very quick package file scan to identify the extracted dependencies
- `"lookup"`: Performs a package file scan to identify the extracted dependencies and updates available
- `"full"`: Performs a dry run by logging messages instead of creating/updating/deleting branches and PRs
Information provided mainly in debug log level.
## endpoint ## endpoint
## executionTimeout ## executionTimeout

View file

@ -245,9 +245,10 @@ const options: RenovateOptions[] = [
name: 'dryRun', name: 'dryRun',
description: description:
'If enabled, perform a dry run by logging messages instead of creating/updating/deleting branches and PRs.', 'If enabled, perform a dry run by logging messages instead of creating/updating/deleting branches and PRs.',
type: 'boolean', type: 'string',
globalOnly: true, globalOnly: true,
default: false, allowedValues: ['extract', 'lookup', 'full'],
default: null,
}, },
{ {
name: 'printConfig', name: 'printConfig',

View file

@ -11,6 +11,7 @@ export type RenovateConfigStage =
| 'pr'; | 'pr';
export type RepositoryCacheConfig = 'disabled' | 'enabled' | 'reset'; export type RepositoryCacheConfig = 'disabled' | 'enabled' | 'reset';
export type DryRunConfig = 'extract' | 'lookup' | 'full';
export interface GroupConfig extends Record<string, unknown> { export interface GroupConfig extends Record<string, unknown> {
branchName?: string; branchName?: string;
@ -104,7 +105,7 @@ export interface RepoGlobalConfig {
dockerChildPrefix?: string; dockerChildPrefix?: string;
dockerImagePrefix?: string; dockerImagePrefix?: string;
dockerUser?: string; dockerUser?: string;
dryRun?: boolean; dryRun?: DryRunConfig;
executionTimeout?: number; executionTimeout?: number;
exposeAllEnv?: boolean; exposeAllEnv?: boolean;
migratePresets?: Record<string, string>; migratePresets?: Record<string, string>;

View file

@ -135,5 +135,35 @@ describe('workers/global/config/parse/index', () => {
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv); const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed.hostRules).toContainEqual({ matchHost: 'example.org' }); expect(parsed.hostRules).toContainEqual({ matchHost: 'example.org' });
}); });
it('env dryRun = true replaced to full', async () => {
const env: NodeJS.ProcessEnv = {
...defaultEnv,
RENOVATE_DRY_RUN: 'true',
};
const parsedConfig = await configParser.parseConfigs(env, defaultArgv);
expect(parsedConfig).toContainEntries([['dryRun', 'full']]);
});
it('cli dryRun = true replaced to full', async () => {
defaultArgv = defaultArgv.concat(['--dry-run=true']);
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed).toContainEntries([['dryRun', 'full']]);
});
it('env dryRun = false replaced to null', async () => {
const env: NodeJS.ProcessEnv = {
...defaultEnv,
RENOVATE_DRY_RUN: 'false',
};
const parsedConfig = await configParser.parseConfigs(env, defaultArgv);
expect(parsedConfig).toContainEntries([['dryRun', null]]);
});
it('cli dryRun = false replaced to null', async () => {
defaultArgv = defaultArgv.concat(['--dry-run=false']);
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed).toContainEntries([['dryRun', null]]);
});
}); });
}); });

View file

@ -22,6 +22,27 @@ export async function parseConfigs(
const cliConfig = cliParser.getConfig(argv); const cliConfig = cliParser.getConfig(argv);
const envConfig = envParser.getConfig(env); const envConfig = envParser.getConfig(env);
if (cliConfig?.dryRun === 'true') {
logger.warn('cli config dryRun property has been changed to full');
cliConfig.dryRun = 'full';
}
if (envConfig?.dryRun === 'true') {
logger.warn('env config dryRun property has been changed to full');
envConfig.dryRun = 'full';
}
if (cliConfig?.dryRun === 'false' || cliConfig?.dryRun === 'null') {
logger.warn(
'cli config dryRun property has been changed to null, running with normal mode.'
);
cliConfig.dryRun = null;
}
if (envConfig?.dryRun === 'false' || envConfig?.dryRun === 'null') {
logger.warn(
'env config dryRun property has been changed to null, running with normal mode.'
);
envConfig.dryRun = null;
}
let config: AllConfig = mergeChildConfig(fileConfig, envConfig); let config: AllConfig = mergeChildConfig(fileConfig, envConfig);
config = mergeChildConfig(config, cliConfig); config = mergeChildConfig(config, cliConfig);

View file

@ -32,7 +32,7 @@ async function dryRun(
ensureIssueCalls = 0 ensureIssueCalls = 0
) { ) {
jest.clearAllMocks(); jest.clearAllMocks();
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
await dependencyDashboard.ensureDependencyDashboard(config, branches); await dependencyDashboard.ensureDependencyDashboard(config, branches);
expect(platform.ensureIssueClosing).toHaveBeenCalledTimes( expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(
ensureIssueClosingCalls ensureIssueClosingCalls

View file

@ -350,7 +350,7 @@ export async function ensureDependencyDashboard(
{ title: config.dependencyDashboardTitle }, { title: config.dependencyDashboardTitle },
'DRY-RUN: Would ensure Dependency Dashboard' 'DRY-RUN: Would ensure Dependency Dashboard'
); );
} else { } else if (!GlobalConfig.get('dryRun')) {
await platform.ensureIssue({ await platform.ensureIssue({
title: config.dependencyDashboardTitle, title: config.dependencyDashboardTitle,
reuseTitle, reuseTitle,

View file

@ -32,7 +32,7 @@ describe('workers/repository/error-config', () => {
error.validationSource = 'package.json'; error.validationSource = 'package.json';
error.validationMessage = 'some-message'; error.validationMessage = 'some-message';
platform.ensureIssue.mockResolvedValueOnce('created'); platform.ensureIssue.mockResolvedValueOnce('created');
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
const res = await raiseConfigWarningIssue(config, error); const res = await raiseConfigWarningIssue(config, error);
expect(res).toBeUndefined(); expect(res).toBeUndefined();
}); });
@ -57,7 +57,7 @@ describe('workers/repository/error-config', () => {
number: 1, number: 1,
state: PrState.Open, state: PrState.Open,
}); });
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
const res = await raiseConfigWarningIssue(config, error); const res = await raiseConfigWarningIssue(config, error);
expect(res).toBeUndefined(); expect(res).toBeUndefined();
}); });

View file

@ -69,7 +69,7 @@ describe('workers/repository/finalise/prune', () => {
}); });
it('does nothing on dryRun', async () => { it('does nothing on dryRun', async () => {
config.branchList = ['renovate/a', 'renovate/b']; config.branchList = ['renovate/a', 'renovate/b'];
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
git.getBranchList.mockReturnValueOnce( git.getBranchList.mockReturnValueOnce(
config.branchList.concat(['renovate/c']) config.branchList.concat(['renovate/c'])
); );
@ -107,7 +107,7 @@ describe('workers/repository/finalise/prune', () => {
}); });
it('skips comment if dry run', async () => { it('skips comment if dry run', async () => {
config.branchList = ['renovate/a', 'renovate/b']; config.branchList = ['renovate/a', 'renovate/b'];
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
git.getBranchList.mockReturnValueOnce( git.getBranchList.mockReturnValueOnce(
config.branchList.concat(['renovate/c']) config.branchList.concat(['renovate/c'])
); );
@ -122,7 +122,7 @@ describe('workers/repository/finalise/prune', () => {
}); });
it('dry run delete branch no PR', async () => { it('dry run delete branch no PR', async () => {
config.branchList = ['renovate/a', 'renovate/b']; config.branchList = ['renovate/a', 'renovate/b'];
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
git.getBranchList.mockReturnValueOnce( git.getBranchList.mockReturnValueOnce(
config.branchList.concat(['renovate/c']) config.branchList.concat(['renovate/c'])
); );

View file

@ -42,23 +42,28 @@ export async function renovateRepository(
const { branches, branchList, packageFiles } = await extractDependencies( const { branches, branchList, packageFiles } = await extractDependencies(
config config
); );
await ensureOnboardingPr(config, packageFiles, branches); if (
const res = await updateRepo(config, branches); GlobalConfig.get('dryRun') !== 'lookup' &&
setMeta({ repository: config.repository }); GlobalConfig.get('dryRun') !== 'extract'
addSplit('update'); ) {
await setBranchCache(branches); await ensureOnboardingPr(config, packageFiles, branches);
if (res === 'automerged') { const res = await updateRepo(config, branches);
if (canRetry) { setMeta({ repository: config.repository });
logger.info('Renovating repository again after automerge result'); addSplit('update');
const recursiveRes = await renovateRepository(repoConfig, false); await setBranchCache(branches);
return recursiveRes; if (res === 'automerged') {
if (canRetry) {
logger.info('Renovating repository again after automerge result');
const recursiveRes = await renovateRepository(repoConfig, false);
return recursiveRes;
}
logger.debug(`Automerged but already retried once`);
} else {
await ensureDependencyDashboard(config, branches);
} }
logger.debug(`Automerged but already retried once`); await finaliseRepo(config, branchList);
} else { repoResult = processResult(config, res);
await ensureDependencyDashboard(config, branches);
} }
await finaliseRepo(config, branchList);
repoResult = processResult(config, res);
} catch (err) /* istanbul ignore next */ { } catch (err) /* istanbul ignore next */ {
setMeta({ repository: config.repository }); setMeta({ repository: config.repository });
const errorRes = await handleError(config, err); const errorRes = await handleError(config, err);

View file

@ -159,7 +159,7 @@ describe('workers/repository/onboarding/pr/index', () => {
expect(platform.createPr).toHaveBeenCalledTimes(1); expect(platform.createPr).toHaveBeenCalledTimes(1);
}); });
it('dryrun of updates PR when modified', async () => { it('dryrun of updates PR when modified', async () => {
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
config.baseBranch = 'some-branch'; config.baseBranch = 'some-branch';
platform.getBranchPr.mockResolvedValueOnce( platform.getBranchPr.mockResolvedValueOnce(
partial<Pr>({ partial<Pr>({
@ -178,7 +178,7 @@ describe('workers/repository/onboarding/pr/index', () => {
); );
}); });
it('dryrun of creates PR', async () => { it('dryrun of creates PR', async () => {
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
await ensureOnboardingPr(config, packageFiles, branches); await ensureOnboardingPr(config, packageFiles, branches);
expect(logger.info).toHaveBeenCalledWith( expect(logger.info).toHaveBeenCalledWith(
'DRY-RUN: Would check branch renovate/configure' 'DRY-RUN: Would check branch renovate/configure'

View file

@ -5,9 +5,11 @@ import {
mocked, mocked,
platform, platform,
} from '../../../../test/util'; } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import { CONFIG_VALIDATION } from '../../../constants/error-messages'; import { CONFIG_VALIDATION } from '../../../constants/error-messages';
import { getCache } from '../../../util/cache/repository'; import { getCache } from '../../../util/cache/repository';
import * as _extractUpdate from './extract-update'; import * as _extractUpdate from './extract-update';
import { lookup } from './extract-update';
import { extractDependencies, updateRepo } from '.'; import { extractDependencies, updateRepo } from '.';
jest.mock('../../../util/git'); jest.mock('../../../util/git');
@ -98,5 +100,17 @@ describe('workers/repository/process/index', () => {
CONFIG_VALIDATION CONFIG_VALIDATION
); );
}); });
it('processes baseBranches dryRun extract', async () => {
extract.mockResolvedValue({} as never);
GlobalConfig.set({ dryRun: 'extract' });
const res = await extractDependencies(config);
await updateRepo(config, res.branches);
expect(res).toEqual({
branchList: [],
branches: [],
packageFiles: {},
});
expect(lookup).toHaveBeenCalledTimes(0);
});
}); });
}); });

View file

@ -1,4 +1,5 @@
import { mergeChildConfig } from '../../../config'; import { mergeChildConfig } from '../../../config';
import { GlobalConfig } from '../../../config/global';
import type { RenovateConfig } from '../../../config/types'; import type { RenovateConfig } from '../../../config/types';
import { CONFIG_VALIDATION } from '../../../constants/error-messages'; import { CONFIG_VALIDATION } from '../../../constants/error-messages';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
@ -103,6 +104,11 @@ export async function extractDependencies(
logger.debug('No baseBranches'); logger.debug('No baseBranches');
const packageFiles = await extract(config); const packageFiles = await extract(config);
addSplit('extract'); addSplit('extract');
if (GlobalConfig.get('dryRun') === 'extract') {
res.packageFiles = packageFiles;
logger.info({ packageFiles }, 'Extracted dependencies');
return res;
}
res = await lookup(config, packageFiles); res = await lookup(config, packageFiles);
} }
addSplit('lookup'); addSplit('lookup');

View file

@ -1,4 +1,5 @@
import { RenovateConfig, getConfig, git, mocked } from '../../../../test/util'; import { RenovateConfig, getConfig, git, mocked } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import { Limit, isLimitReached } from '../../global/limits'; import { Limit, isLimitReached } from '../../global/limits';
import { BranchConfig, BranchResult } from '../../types'; import { BranchConfig, BranchResult } from '../../types';
import * as _branchWorker from '../update/branch'; import * as _branchWorker from '../update/branch';
@ -48,6 +49,7 @@ describe('workers/repository/process/write', () => {
branchExists: false, branchExists: false,
result: BranchResult.Automerged, result: BranchResult.Automerged,
}); });
GlobalConfig.set({ dryRun: 'full' });
const res = await writeUpdates(config, branches); const res = await writeUpdates(config, branches);
expect(res).toBe('automerged'); expect(res).toBe('automerged');
expect(branchWorker.processBranch).toHaveBeenCalledTimes(4); expect(branchWorker.processBranch).toHaveBeenCalledTimes(4);
@ -62,6 +64,7 @@ describe('workers/repository/process/write', () => {
git.branchExists.mockReturnValueOnce(true); git.branchExists.mockReturnValueOnce(true);
limits.getBranchesRemaining.mockResolvedValueOnce(1); limits.getBranchesRemaining.mockResolvedValueOnce(1);
expect(isLimitReached(Limit.Branches)).toBeFalse(); expect(isLimitReached(Limit.Branches)).toBeFalse();
GlobalConfig.set({ dryRun: 'full' });
await writeUpdates({ config }, branches); await writeUpdates({ config }, branches);
expect(isLimitReached(Limit.Branches)).toBeTrue(); expect(isLimitReached(Limit.Branches)).toBeTrue();
}); });

View file

@ -31,7 +31,7 @@ describe('workers/repository/update/branch/artifacts', () => {
}); });
it('skips status (dry-run)', async () => { it('skips status (dry-run)', async () => {
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
platform.getBranchStatusCheck.mockResolvedValueOnce(null); platform.getBranchStatusCheck.mockResolvedValueOnce(null);
await setArtifactErrorStatus(config); await setArtifactErrorStatus(config);
expect(platform.setBranchStatus).not.toHaveBeenCalled(); expect(platform.setBranchStatus).not.toHaveBeenCalled();

View file

@ -63,7 +63,7 @@ describe('workers/repository/update/branch/automerge', () => {
it('returns true if automerge succeeds (dry-run)', async () => { it('returns true if automerge succeeds (dry-run)', async () => {
config.automerge = true; config.automerge = true;
config.automergeType = 'branch'; config.automergeType = 'branch';
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
expect(await tryBranchAutomerge(config)).toBe('automerged'); expect(await tryBranchAutomerge(config)).toBe('automerged');
}); });

View file

@ -55,7 +55,7 @@ describe('workers/repository/update/branch/commit', () => {
expect(platform.commitFiles.mock.calls).toMatchSnapshot(); expect(platform.commitFiles.mock.calls).toMatchSnapshot();
}); });
it('dry runs', async () => { it('dry runs', async () => {
GlobalConfig.set({ dryRun: true }); GlobalConfig.set({ dryRun: 'full' });
config.updatedPackageFiles.push({ config.updatedPackageFiles.push({
type: 'addition', type: 'addition',
path: 'package.json', path: 'package.json',

View file

@ -487,7 +487,7 @@ describe('workers/repository/update/branch/index', () => {
git.branchExists.mockReturnValue(true); git.branchExists.mockReturnValue(true);
commit.commitFilesToBranch.mockResolvedValueOnce(null); commit.commitFilesToBranch.mockResolvedValueOnce(null);
automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged'); automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
GlobalConfig.set({ ...adminConfig, dryRun: true }); GlobalConfig.set({ ...adminConfig, dryRun: 'full' });
await branchWorker.processBranch(config); await branchWorker.processBranch(config);
expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1); expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(1);
expect(prWorker.ensurePr).toHaveBeenCalledTimes(0); expect(prWorker.ensurePr).toHaveBeenCalledTimes(0);
@ -831,7 +831,7 @@ describe('workers/repository/update/branch/index', () => {
checkExisting.prAlreadyExisted.mockResolvedValueOnce({ checkExisting.prAlreadyExisted.mockResolvedValueOnce({
state: PrState.Closed, state: PrState.Closed,
} as Pr); } as Pr);
GlobalConfig.set({ ...adminConfig, dryRun: true }); GlobalConfig.set({ ...adminConfig, dryRun: 'full' });
expect(await branchWorker.processBranch(config)).toEqual({ expect(await branchWorker.processBranch(config)).toEqual({
branchExists: false, branchExists: false,
prNo: undefined, prNo: undefined,
@ -845,7 +845,7 @@ describe('workers/repository/update/branch/index', () => {
state: PrState.Open, state: PrState.Open,
} as Pr); } as Pr);
git.isBranchModified.mockResolvedValueOnce(true); git.isBranchModified.mockResolvedValueOnce(true);
GlobalConfig.set({ ...adminConfig, dryRun: true }); GlobalConfig.set({ ...adminConfig, dryRun: 'full' });
expect(await branchWorker.processBranch(config)).toEqual({ expect(await branchWorker.processBranch(config)).toEqual({
branchExists: true, branchExists: true,
prNo: undefined, prNo: undefined,
@ -871,7 +871,7 @@ describe('workers/repository/update/branch/index', () => {
git.isBranchModified.mockResolvedValueOnce(true); git.isBranchModified.mockResolvedValueOnce(true);
schedule.isScheduledNow.mockReturnValueOnce(false); schedule.isScheduledNow.mockReturnValueOnce(false);
commit.commitFilesToBranch.mockResolvedValueOnce(null); commit.commitFilesToBranch.mockResolvedValueOnce(null);
GlobalConfig.set({ ...adminConfig, dryRun: true }); GlobalConfig.set({ ...adminConfig, dryRun: 'full' });
expect( expect(
await branchWorker.processBranch({ await branchWorker.processBranch({
...config, ...config,
@ -908,7 +908,7 @@ describe('workers/repository/update/branch/index', () => {
pr: {}, pr: {},
} as ResultWithPr); } as ResultWithPr);
commit.commitFilesToBranch.mockResolvedValueOnce(null); commit.commitFilesToBranch.mockResolvedValueOnce(null);
GlobalConfig.set({ ...adminConfig, dryRun: true }); GlobalConfig.set({ ...adminConfig, dryRun: 'full' });
expect( expect(
await branchWorker.processBranch({ await branchWorker.processBranch({
...config, ...config,

View file

@ -1,4 +1,5 @@
import { getConfig, git, mocked, partial } from '../../../../../test/util'; import { getConfig, git, mocked, partial } from '../../../../../test/util';
import { GlobalConfig } from '../../../../config/global';
import { Pr, platform as _platform } from '../../../../modules/platform'; import { Pr, platform as _platform } from '../../../../modules/platform';
import { BranchStatus } from '../../../../types'; import { BranchStatus } from '../../../../types';
import type { BranchConfig } from '../../../types'; import type { BranchConfig } from '../../../types';
@ -105,5 +106,51 @@ describe('workers/repository/update/pr/automerge', () => {
}); });
expect(platform.mergePr).toHaveBeenCalledTimes(0); expect(platform.mergePr).toHaveBeenCalledTimes(0);
}); });
it('dryRun full should not automerge', async () => {
config.automerge = true;
GlobalConfig.set({ dryRun: 'full' });
platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
const res = await prAutomerge.checkAutoMerge(pr, config);
expect(res).toEqual({
automerged: false,
prAutomergeBlockReason: 'DryRun',
});
expect(platform.mergePr).toHaveBeenCalledTimes(0);
});
it('dryRun lookup should not automerge', async () => {
const expectedResult = {
automerged: false,
prAutomergeBlockReason: 'DryRun',
};
platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
GlobalConfig.set({ dryRun: 'lookup' });
const res = await prAutomerge.checkAutoMerge(pr, config);
expect(res).toEqual(expectedResult);
expect(platform.mergePr).toHaveBeenCalledTimes(0);
});
it('dryRun lookup pr-comment', async () => {
config.automergeType = 'pr-comment';
const expectedResult = {
automerged: false,
prAutomergeBlockReason: 'DryRun',
};
platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
GlobalConfig.set({ dryRun: 'lookup' });
const res = await prAutomerge.checkAutoMerge(pr, config);
expect(res).toEqual(expectedResult);
expect(platform.mergePr).toHaveBeenCalledTimes(0);
});
it('dryRun full pr-comment', async () => {
config.automergeType = 'pr-comment';
const expectedResult = {
automerged: false,
prAutomergeBlockReason: 'DryRun',
};
platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
GlobalConfig.set({ dryRun: 'full' });
const res = await prAutomerge.checkAutoMerge(pr, config);
expect(res).toEqual(expectedResult);
expect(platform.mergePr).toHaveBeenCalledTimes(0);
});
}); });
}); });