From 40852754aac4ce7a4bbff2e2fa8b9e49db765801 Mon Sep 17 00:00:00 2001 From: Mike Bryant Date: Tue, 6 Oct 2020 21:02:35 +0100 Subject: [PATCH] feat: Add organization-level presets (#7403) Co-authored-by: Michael Kriese Co-authored-by: Rhys Arkins --- docs/usage/config-presets.md | 4 ++ lib/config/common.ts | 3 +- lib/config/presets/internal/default.ts | 4 +- .../onboarding/branch/config.spec.ts | 43 +++++++++++++++++++ .../repository/onboarding/branch/config.ts | 30 ++++++++++++- .../repository/onboarding/branch/create.ts | 4 +- .../onboarding/branch/index.spec.ts | 1 + .../onboarding/branch/rebase.spec.ts | 1 + .../repository/onboarding/branch/rebase.ts | 2 +- 9 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 lib/workers/repository/onboarding/branch/config.spec.ts diff --git a/docs/usage/config-presets.md b/docs/usage/config-presets.md index 976b67903db7ba..a1c7a60dee2ab0 100644 --- a/docs/usage/config-presets.md +++ b/docs/usage/config-presets.md @@ -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. diff --git a/lib/config/common.ts b/lib/config/common.ts index 4348a5d319680d..f91a223820ab97 100644 --- a/lib/config/common.ts +++ b/lib/config/common.ts @@ -18,6 +18,7 @@ export interface GroupConfig extends Record { // TODO: Proper typings export interface RenovateSharedConfig { + $schema?: string; automerge?: boolean; branchPrefix?: string; branchName?: string; @@ -27,6 +28,7 @@ export interface RenovateSharedConfig { draftPR?: boolean; enabled?: boolean; enabledManagers?: string[]; + extends?: string[]; fileMatch?: string[]; group?: GroupConfig; groupName?: string; @@ -145,7 +147,6 @@ export interface RenovateConfig description?: string | string[]; errors?: ValidationMessage[]; - extends?: string[]; gitAuthor?: string; diff --git a/lib/config/presets/internal/default.ts b/lib/config/presets/internal/default.ts index dbdc507686aaf8..4af637459777d2 100644 --- a/lib/config/presets/internal/default.ts +++ b/lib/config/presets/internal/default.ts @@ -351,7 +351,7 @@ export const presets: Record = { description: 'Run lock file maintenance (updates) early Monday mornings', lockFileMaintenance: { enabled: true, - extends: 'schedule:weekly', + extends: ['schedule:weekly'], }, }, maintainLockFilesMonthly: { @@ -359,7 +359,7 @@ export const presets: Record = { 'Run lock file maintenance (updates) on the first day of each month', lockFileMaintenance: { enabled: true, - extends: 'schedule:monthly', + extends: ['schedule:monthly'], }, }, ignoreUnstable: { diff --git a/lib/workers/repository/onboarding/branch/config.spec.ts b/lib/workers/repository/onboarding/branch/config.spec.ts new file mode 100644 index 00000000000000..8c3c7c3d060f20 --- /dev/null +++ b/lib/workers/repository/onboarding/branch/config.spec.ts @@ -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; + +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); + }); + }); +}); diff --git a/lib/workers/repository/onboarding/branch/config.ts b/lib/workers/repository/onboarding/branch/config.ts index b287c99979cfde..501be0ada6ef60 100644 --- a/lib/workers/repository/onboarding/branch/config.ts +++ b/lib/workers/repository/onboarding/branch/config.ts @@ -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 { + 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'; } diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts index e1792c357be2e7..9df2f009ac7e1f 100644 --- a/lib/workers/repository/onboarding/branch/create.ts +++ b/lib/workers/repository/onboarding/branch/create.ts @@ -7,11 +7,11 @@ import { getOnboardingConfig } from './config'; const defaultConfigFile = configFileNames[0]; -export function createOnboardingBranch( +export async function createOnboardingBranch( config: Partial ): Promise { logger.debug('createOnboardingBranch()'); - const contents = getOnboardingConfig(config); + const contents = await getOnboardingConfig(config); logger.debug('Creating onboarding branch'); let commitMessagePrefix = ''; diff --git a/lib/workers/repository/onboarding/branch/index.spec.ts b/lib/workers/repository/onboarding/branch/index.spec.ts index 7b7a25482c8e98..6d60cd69106fd2 100644 --- a/lib/workers/repository/onboarding/branch/index.spec.ts +++ b/lib/workers/repository/onboarding/branch/index.spec.ts @@ -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 () => { diff --git a/lib/workers/repository/onboarding/branch/rebase.spec.ts b/lib/workers/repository/onboarding/branch/rebase.spec.ts index ed3bc242087c93..383e37a19f7dc3 100644 --- a/lib/workers/repository/onboarding/branch/rebase.spec.ts +++ b/lib/workers/repository/onboarding/branch/rebase.spec.ts @@ -10,6 +10,7 @@ describe('workers/repository/onboarding/branch/rebase', () => { jest.resetAllMocks(); config = { ...defaultConfig, + repository: 'some/repo', }; }); it('does not rebase modified branch', async () => { diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts index 57fb9d034b9a8c..5abec25e16712a 100644 --- a/lib/workers/repository/onboarding/branch/rebase.ts +++ b/lib/workers/repository/onboarding/branch/rebase.ts @@ -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))