Skip to content

Commit

Permalink
feat: refactor requireConfig option (#15594)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaronHatoum committed May 18, 2022
1 parent de70eca commit 0cf2ab4
Show file tree
Hide file tree
Showing 15 changed files with 162 additions and 24 deletions.
17 changes: 16 additions & 1 deletion docs/usage/self-hosted-configuration.md
Expand Up @@ -612,7 +612,22 @@ JSON files will be stored inside the `cacheDir` beside the existing file-based p

## requireConfig

If this is set to `false`, it means that Renovate won't require a config file such as `renovate.json` to be present in each repository and will run even if one is missing.
By default, Renovate needs a Renovate config file in each repository where it runs before it will propose any dependency updates.

You can choose any of these settings:

- `"required"` (default): a repository config file must be present
- `"optional"`: if a config file exists, Renovate will use it when it runs
- `"ignored"`: config files in the repo will be ignored, and have no effect

This feature is closely related to the `onboarding` config option.
The combinations of `requireConfig` and `onboarding` are:

| | `onboarding=true` | `onboarding=false` |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `requireConfig=required` | An onboarding PR will be created if no config file exists. If the onboarding PR is closed and there's no config file, then the repository is skipped. | Repository is skipped unless a config file is added manually. |
| `requireConfig=optional` | An onboarding PR will be created if no config file exists. If the onboarding PR is closed and there's no config file, the repository will be processed. | Repository is processed regardless of config file presence. |
| `requireConfig=ignored` | No onboarding PR will be created and repo will be processed while ignoring any config file present. | Repository is processed, any config file is ignored. |

## secrets

Expand Down
26 changes: 26 additions & 0 deletions lib/config/migrations/custom/require-config-migration.spec.ts
@@ -0,0 +1,26 @@
import type { RequiredConfig } from '../../types';
import { RequireConfigMigration } from './require-config-migration';

describe('config/migrations/custom/require-config-migration', () => {
it('should migrate requireConfig=true to requireConfig=required', () => {
expect(RequireConfigMigration).toMigrate(
{
requireConfig: 'true' as RequiredConfig,
},
{
requireConfig: 'required',
}
);
});

it('should migrate requireConfig=false to requireConfig=optional', () => {
expect(RequireConfigMigration).toMigrate(
{
requireConfig: 'false' as RequiredConfig,
},
{
requireConfig: 'optional',
}
);
});
});
13 changes: 13 additions & 0 deletions lib/config/migrations/custom/require-config-migration.ts
@@ -0,0 +1,13 @@
import { AbstractMigration } from '../base/abstract-migration';

export class RequireConfigMigration extends AbstractMigration {
override readonly propertyName = 'requireConfig';

override run(value: unknown): void {
if (value === 'false') {
this.rewrite('optional');
} else {
this.rewrite('required');
}
}
}
2 changes: 2 additions & 0 deletions lib/config/migrations/migrations-service.ts
Expand Up @@ -32,6 +32,7 @@ import { RaiseDeprecationWarningsMigration } from './custom/raise-deprecation-wa
import { RebaseConflictedPrs } from './custom/rebase-conflicted-prs-migration';
import { RebaseStalePrsMigration } from './custom/rebase-stale-prs-migration';
import { RenovateForkMigration } from './custom/renovate-fork-migration';
import { RequireConfigMigration } from './custom/require-config-migration';
import { RequiredStatusChecksMigration } from './custom/required-status-checks-migration';
import { ScheduleMigration } from './custom/schedule-migration';
import { SemanticCommitsMigration } from './custom/semantic-commits-migration';
Expand Down Expand Up @@ -114,6 +115,7 @@ export class MigrationsService {
UpgradeInRangeMigration,
VersionStrategyMigration,
DryRunMigration,
RequireConfigMigration,
];

static run(originalConfig: RenovateConfig): RenovateConfig {
Expand Down
7 changes: 4 additions & 3 deletions lib/config/options/index.ts
Expand Up @@ -404,10 +404,11 @@ const options: RenovateOptions[] = [
{
name: 'requireConfig',
description:
'Set to `false` if it is optional for repositories to contain a config.',
"Controls Renovate's behavior regarding repository config files such as `renovate.json`.",
stage: 'repository',
type: 'boolean',
default: true,
type: 'string',
default: 'required',
allowedValues: ['required', 'optional', 'ignored'],
globalOnly: true,
},
{
Expand Down
3 changes: 2 additions & 1 deletion lib/config/types.ts
Expand Up @@ -12,6 +12,7 @@ export type RenovateConfigStage =

export type RepositoryCacheConfig = 'disabled' | 'enabled' | 'reset';
export type DryRunConfig = 'extract' | 'lookup' | 'full';
export type RequiredConfig = 'required' | 'optional' | 'ignored';

export interface GroupConfig extends Record<string, unknown> {
branchName?: string;
Expand Down Expand Up @@ -134,7 +135,7 @@ export interface LegacyAdminConfig {
onboardingConfigFileName?: string;

platform?: string;
requireConfig?: boolean;
requireConfig?: RequiredConfig;
}
export type ExecutionMode = 'branch' | 'update';

Expand Down
15 changes: 15 additions & 0 deletions lib/workers/global/config/parse/cli.spec.ts
Expand Up @@ -184,5 +184,20 @@ describe('workers/global/config/parse/cli', () => {
argv.push('--dry-run=null');
expect(cli.getConfig(argv)).toEqual({ dryRun: null });
});

it('requireConfig boolean true', () => {
argv.push('--require-config=true');
expect(cli.getConfig(argv)).toEqual({ requireConfig: 'required' });
});

it('requireConfig no value', () => {
argv.push('--require-config');
expect(cli.getConfig(argv)).toEqual({ requireConfig: 'required' });
});

it('requireConfig boolean false', () => {
argv.push('--require-config=false');
expect(cli.getConfig(argv)).toEqual({ requireConfig: 'optional' });
});
});
});
14 changes: 14 additions & 0 deletions lib/workers/global/config/parse/cli.ts
Expand Up @@ -30,6 +30,7 @@ export function getConfig(input: string[]): AllConfig {
.replace('--azure-auto-complete', '--platform-automerge') // migrate: azureAutoComplete
.replace('--git-lab-automerge', '--platform-automerge') // migrate: gitLabAutomerge
.replace(/^--dry-run$/, '--dry-run=true')
.replace(/^--require-config$/, '--require-config=true')
)
.filter((a) => !a.startsWith('--git-fs'));
const options = getOptions();
Expand Down Expand Up @@ -131,6 +132,19 @@ export function getConfig(input: string[]): AllConfig {
config[option.name] = null;
}
}
if (option.name === 'requireConfig') {
if (config[option.name] === 'true') {
logger.warn(
'cli config requireConfig property has been changed to required'
);
config[option.name] = 'required';
} else if (config[option.name] === 'false') {
logger.warn(
'cli config requireConfig property has been changed to optional'
);
config[option.name] = 'optional';
}
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions lib/workers/global/config/parse/env.spec.ts
@@ -1,3 +1,4 @@
import type { RequiredConfig } from '../../../../config/types';
import { PlatformId } from '../../../../constants';
import { logger } from '../../../../logger';
import * as env from './env';
Expand Down Expand Up @@ -298,5 +299,21 @@ describe('workers/global/config/parse/env', () => {
const config = env.getConfig(envParam);
expect(config.dryRun).toBeNull();
});

it('requireConfig boolean true', () => {
const envParam: NodeJS.ProcessEnv = {
RENOVATE_REQUIRE_CONFIG: 'true' as RequiredConfig,
};
const config = env.getConfig(envParam);
expect(config.requireConfig).toBe('required');
});

it('requireConfig boolean false', () => {
const envParam: NodeJS.ProcessEnv = {
RENOVATE_REQUIRE_CONFIG: 'false' as RequiredConfig,
};
const config = env.getConfig(envParam);
expect(config.requireConfig).toBe('optional');
});
});
});
13 changes: 13 additions & 0 deletions lib/workers/global/config/parse/env.ts
Expand Up @@ -121,6 +121,19 @@ export function getConfig(inputEnv: NodeJS.ProcessEnv): AllConfig {
config[option.name] = null;
}
}
if (option.name === 'requireConfig') {
if ((config[option.name] as string) === 'true') {
logger.warn(
'env config requireConfig property has been changed to required'
);
config[option.name] = 'required';
} else if ((config[option.name] as string) === 'false') {
logger.warn(
'env config requireConfig property has been changed to optional'
);
config[option.name] = 'optional';
}
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion lib/workers/repository/init/merge.ts
Expand Up @@ -168,7 +168,10 @@ export async function mergeRenovateConfig(
config: RenovateConfig
): Promise<RenovateConfig> {
let returnConfig = { ...config };
const repoConfig = await detectRepoFileConfig();
let repoConfig: RepoFileConfig = {};
if (config.requireConfig !== 'ignored') {
repoConfig = await detectRepoFileConfig();
}
const configFileParsed = repoConfig?.configFileParsed || {};
if (is.nonEmptyArray(returnConfig.extends)) {
configFileParsed.extends = [
Expand Down
10 changes: 7 additions & 3 deletions lib/workers/repository/onboarding/branch/check.ts
Expand Up @@ -55,10 +55,14 @@ export const isOnboarded = async (config: RenovateConfig): Promise<boolean> => {
const title = `Action required: Add a Renovate config`;
// Repo is onboarded if global config is bypassing onboarding and does not require a
// configuration file.
if (config.requireConfig === false && config.onboarding === false) {
if (config.requireConfig === 'optional' && config.onboarding === false) {
// Return early and avoid checking for config files
return true;
}
if (config.requireConfig === 'ignored') {
logger.debug('Config file will be ignored');
return true;
}
const cache = getCache();
if (cache.configFileName) {
logger.debug('Checking cached config file name');
Expand Down Expand Up @@ -94,7 +98,7 @@ export const isOnboarded = async (config: RenovateConfig): Promise<boolean> => {

// If onboarding has been disabled and config files are required then the
// repository has not been onboarded yet
if (config.requireConfig && config.onboarding === false) {
if (config.requireConfig === 'required' && config.onboarding === false) {
throw new Error(REPOSITORY_NO_CONFIG);
}

Expand All @@ -104,7 +108,7 @@ export const isOnboarded = async (config: RenovateConfig): Promise<boolean> => {
return false;
}
logger.debug('Found closed onboarding PR');
if (!config.requireConfig) {
if (config.requireConfig === 'optional') {
logger.debug('Config not mandatory so repo is considered onboarded');
return true;
}
Expand Down
21 changes: 14 additions & 7 deletions lib/workers/repository/onboarding/branch/index.spec.ts
Expand Up @@ -114,23 +114,30 @@ describe('workers/repository/onboarding/branch/index', () => {
});
});

it('handles skipped onboarding combined with requireConfig = false', async () => {
config.requireConfig = false;
it('handles skipped onboarding combined with requireConfig = optional', async () => {
config.requireConfig = 'optional';
config.onboarding = false;
const res = await checkOnboardingBranch(config);
expect(res.repoIsOnboarded).toBeTrue();
});

it('handles skipped onboarding, requireConfig=true, and a config file', async () => {
config.requireConfig = true;
it('handles skipped onboarding, requireConfig=required, and a config file', async () => {
config.requireConfig = 'required';
config.onboarding = false;
git.getFileList.mockResolvedValueOnce(['renovate.json']);
const res = await checkOnboardingBranch(config);
expect(res.repoIsOnboarded).toBeTrue();
});

it('handles skipped onboarding, requireConfig=ignored', async () => {
config.requireConfig = 'ignored';
config.onboarding = false;
const res = await checkOnboardingBranch(config);
expect(res.repoIsOnboarded).toBeTrue();
});

it('handles skipped onboarding, requireConfig=true, and no config file', async () => {
config.requireConfig = true;
config.requireConfig = 'required';
config.onboarding = false;
git.getFileList.mockResolvedValueOnce(['package.json']);
fs.readLocalFile.mockResolvedValueOnce('{}');
Expand Down Expand Up @@ -174,14 +181,14 @@ describe('workers/repository/onboarding/branch/index', () => {
});

it('detects repo is onboarded via PR', async () => {
config.requireConfig = false;
config.requireConfig = 'optional';
platform.findPr.mockResolvedValueOnce(mock<Pr>());
const res = await checkOnboardingBranch(config);
expect(res.repoIsOnboarded).toBeTrue();
});

it('throws if no required config', async () => {
config.requireConfig = true;
config.requireConfig = 'required';
platform.findPr.mockResolvedValue(mock<Pr>());
platform.getPrList.mockResolvedValueOnce([
{
Expand Down
8 changes: 7 additions & 1 deletion lib/workers/repository/onboarding/pr/index.spec.ts
Expand Up @@ -162,7 +162,13 @@ describe('workers/repository/onboarding/pr/index', () => {
});

it('creates PR (no require config)', async () => {
config.requireConfig = false;
config.requireConfig = 'optional';
await ensureOnboardingPr(config, packageFiles, branches);
expect(platform.createPr).toHaveBeenCalledTimes(1);
});

it('creates PR (require config)', async () => {
config.requireConfig = 'required';
await ensureOnboardingPr(config, packageFiles, branches);
expect(platform.createPr).toHaveBeenCalledTimes(1);
});
Expand Down
15 changes: 8 additions & 7 deletions lib/workers/repository/onboarding/pr/index.ts
Expand Up @@ -34,13 +34,14 @@ export async function ensureOnboardingPr(
const existingPr = await platform.getBranchPr(config.onboardingBranch);
logger.debug('Filling in onboarding PR template');
let prTemplate = `Welcome to [Renovate](${config.productLinks.homepage})! This is an onboarding PR to help you understand and configure settings before regular Pull Requests begin.\n\n`;
prTemplate += config.requireConfig
? emojify(
`:vertical_traffic_light: To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.\n\n`
)
: emojify(
`:vertical_traffic_light: Renovate will begin keeping your dependencies up-to-date only once you merge or close this Pull Request.\n\n`
);
prTemplate +=
config.requireConfig === 'required'
? emojify(
`:vertical_traffic_light: To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.\n\n`
)
: emojify(
`:vertical_traffic_light: Renovate will begin keeping your dependencies up-to-date only once you merge or close this Pull Request.\n\n`
);
prTemplate += emojify(
`
Expand Down

0 comments on commit 0cf2ab4

Please sign in to comment.