Skip to content

Commit

Permalink
feat: Add organization-level presets (#7403)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Kriese <[email protected]>
Co-authored-by: Rhys Arkins <[email protected]>
  • Loading branch information
3 people authored Oct 6, 2020
1 parent 5896d9c commit 4085275
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 8 deletions.
4 changes: 4 additions & 0 deletions docs/usage/config-presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,7 @@ The answer is to host your preset using GitHub or GitLab - not npmjs - and make
## Contributing to presets

Have you configured a rule that you think others might benefit from? Please consider contributing it to the [Renovate](https://github.com/renovatebot/renovate) repository so that it gains higher visibility and saves others from reinventing the same thing.

## Organisation level presets

When repository onboarding happens, if a repository called `renovate-config` exists under the same organization and contains a default Renovate preset, that repository will be suggested as the the sole extended preset, and any existing `onboardingConfig` config will be ignored/overriden. This allows for a seamless onboarding workflow with your own organization settings.
3 changes: 2 additions & 1 deletion lib/config/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface GroupConfig extends Record<string, unknown> {

// TODO: Proper typings
export interface RenovateSharedConfig {
$schema?: string;
automerge?: boolean;
branchPrefix?: string;
branchName?: string;
Expand All @@ -27,6 +28,7 @@ export interface RenovateSharedConfig {
draftPR?: boolean;
enabled?: boolean;
enabledManagers?: string[];
extends?: string[];
fileMatch?: string[];
group?: GroupConfig;
groupName?: string;
Expand Down Expand Up @@ -145,7 +147,6 @@ export interface RenovateConfig
description?: string | string[];

errors?: ValidationMessage[];
extends?: string[];

gitAuthor?: string;

Expand Down
4 changes: 2 additions & 2 deletions lib/config/presets/internal/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,15 @@ export const presets: Record<string, Preset> = {
description: 'Run lock file maintenance (updates) early Monday mornings',
lockFileMaintenance: {
enabled: true,
extends: 'schedule:weekly',
extends: ['schedule:weekly'],
},
},
maintainLockFilesMonthly: {
description:
'Run lock file maintenance (updates) on the first day of each month',
lockFileMaintenance: {
enabled: true,
extends: 'schedule:monthly',
extends: ['schedule:monthly'],
},
},
ignoreUnstable: {
Expand Down
43 changes: 43 additions & 0 deletions lib/workers/repository/onboarding/branch/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { RenovateConfig, getConfig } from '../../../../../test/util';
import * as presets from '../../../../config/presets';
import { PRESET_DEP_NOT_FOUND } from '../../../../config/presets/util';
import { getOnboardingConfig } from './config';

jest.mock('../../../../config/presets');

const mockedPresets = presets as jest.Mocked<typeof presets>;

describe('workers/repository/onboarding/branch', () => {
let config: RenovateConfig;
let onboardingConfig: string;
beforeEach(() => {
jest.clearAllMocks();
config = getConfig();
config.repository = 'some/repo';
});
describe('getOnboardingConfig', () => {
it('handles finding an organization preset', async () => {
onboardingConfig = await getOnboardingConfig(config);
expect(mockedPresets.getPreset).toHaveBeenCalledTimes(1);
expect(JSON.parse(onboardingConfig).extends[0]).toEqual(
'local>some/renovate-config'
);
});
it('handles not finding an organization preset', async () => {
mockedPresets.getPreset.mockRejectedValue(
new Error(PRESET_DEP_NOT_FOUND)
);
onboardingConfig = await getOnboardingConfig(config);
expect(mockedPresets.getPreset).toHaveBeenCalledTimes(1);
expect(JSON.parse(onboardingConfig)).toEqual(config.onboardingConfig);
});
it('ignores an unknown error', async () => {
mockedPresets.getPreset.mockRejectedValue(
new Error('unknown error for test')
);
onboardingConfig = await getOnboardingConfig(config);
expect(mockedPresets.getPreset).toHaveBeenCalledTimes(1);
expect(JSON.parse(onboardingConfig)).toEqual(config.onboardingConfig);
});
});
});
30 changes: 28 additions & 2 deletions lib/workers/repository/onboarding/branch/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
import { RenovateConfig } from '../../../../config';
import { getPreset } from '../../../../config/presets';
import { PRESET_DEP_NOT_FOUND } from '../../../../config/presets/util';
import { logger } from '../../../../logger';
import { clone } from '../../../../util/clone';

export function getOnboardingConfig(config: RenovateConfig): string {
const onboardingConfig = clone(config.onboardingConfig);
export async function getOnboardingConfig(
config: RenovateConfig
): Promise<string> {
let onboardingConfig = clone(config.onboardingConfig);

let organizationConfigRepoExists = false;
const organizationConfigPresetName = `local>${
config.repository.split('/')[0]
}/renovate-config`;

try {
await getPreset(organizationConfigPresetName, config);
organizationConfigRepoExists = true;
} catch (err) {
if (err.message !== PRESET_DEP_NOT_FOUND) {
logger.warn({ err }, 'Unknown error fetching default owner preset');
}
// Organization preset did not exist
}
if (organizationConfigRepoExists) {
onboardingConfig = {
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: [organizationConfigPresetName],
};
}

logger.debug({ config: onboardingConfig }, 'onboarding config');
return JSON.stringify(onboardingConfig, null, 2) + '\n';
}
4 changes: 2 additions & 2 deletions lib/workers/repository/onboarding/branch/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { getOnboardingConfig } from './config';

const defaultConfigFile = configFileNames[0];

export function createOnboardingBranch(
export async function createOnboardingBranch(
config: Partial<RenovateConfig>
): Promise<string | null> {
logger.debug('createOnboardingBranch()');
const contents = getOnboardingConfig(config);
const contents = await getOnboardingConfig(config);
logger.debug('Creating onboarding branch');

let commitMessagePrefix = '';
Expand Down
1 change: 1 addition & 0 deletions lib/workers/repository/onboarding/branch/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('workers/repository/onboarding/branch', () => {
beforeEach(() => {
jest.resetAllMocks();
config = getConfig();
config.repository = 'some/repo';
git.getFileList.mockResolvedValue([]);
});
it('throws if no package files', async () => {
Expand Down
1 change: 1 addition & 0 deletions lib/workers/repository/onboarding/branch/rebase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('workers/repository/onboarding/branch/rebase', () => {
jest.resetAllMocks();
config = {
...defaultConfig,
repository: 'some/repo',
};
});
it('does not rebase modified branch', async () => {
Expand Down
2 changes: 1 addition & 1 deletion lib/workers/repository/onboarding/branch/rebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function rebaseOnboardingBranch(
defaultConfigFile,
config.onboardingBranch
);
const contents = getOnboardingConfig(config);
const contents = await getOnboardingConfig(config);
if (
contents === existingContents &&
!(await isBranchStale(config.onboardingBranch))
Expand Down

0 comments on commit 4085275

Please sign in to comment.