diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index f80219fd96fde904cc2ab13ef6ee78f0c5a72535..78ea76a020dba67cab909db0aff22e090fff1d86 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -23,6 +23,13 @@ For more information see [the OpenTelemetry docs](opentelemetry.md). If set to any value, Renovate will always paginate requests to GitHub fully, instead of stopping after 10 pages. +## `RENOVATE_STATIC_REPO_CONFIG` + +If set to a _valid_ `JSON` string containing a _valid_ Renovate configuration, it will be applied to the repository config before resolving the actual configuration file within the repository. + +> [!warning] +> An invalid value will result in the scan being aborted. + ## `RENOVATE_X_DOCKER_HUB_DISABLE_LABEL_LOOKUP` If set to any value, Renovate will skip attempting to get release labels (e.g. gitRef, sourceUrl) from manifest annotations for `https://index.docker.io`. diff --git a/lib/workers/global/config/parse/env.ts b/lib/workers/global/config/parse/env.ts index a5543bb1983549ff19146c0f29d4765c97af9b26..79ed110d6b5be095a6332d3b96ddaf8736d30a7a 100644 --- a/lib/workers/global/config/parse/env.ts +++ b/lib/workers/global/config/parse/env.ts @@ -121,16 +121,10 @@ export async function getConfig( inputEnv: NodeJS.ProcessEnv, configEnvKey = 'RENOVATE_CONFIG', ): Promise<AllConfig> { - let env = normalizePrefixes(inputEnv, inputEnv.ENV_PREFIX); - env = massageConvertedExperimentalVars(env); - env = renameEnvKeys(env); - // massage the values of migrated configuration keys - env = massageEnvKeyValues(env); - - const options = getOptions(); - + const env = prepareEnv(inputEnv); const config = await parseAndValidateOrExit(env, configEnvKey); + const options = getOptions(); config.hostRules ??= []; for (const option of options) { @@ -235,7 +229,15 @@ export async function getConfig( return config; } -async function parseAndValidateOrExit( +export function prepareEnv(inputEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + let env = normalizePrefixes(inputEnv, inputEnv.ENV_PREFIX); + env = massageConvertedExperimentalVars(env); + env = renameEnvKeys(env); + // massage the values of migrated configuration keys + return massageEnvKeyValues(env); +} + +export async function parseAndValidateOrExit( env: NodeJS.ProcessEnv, configEnvKey: string, ): Promise<AllConfig> { diff --git a/lib/workers/repository/init/config.spec.ts b/lib/workers/repository/init/config.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..33cedcd071fd81f820a74f1ae1e1921b8fa0b585 --- /dev/null +++ b/lib/workers/repository/init/config.spec.ts @@ -0,0 +1,46 @@ +import type { AllConfig } from '../../../config/types'; +import { mergeStaticRepoEnvConfig } from './config'; + +describe('workers/repository/init/config', () => { + describe('mergeRepoEnvConfig()', () => { + type MergeRepoEnvTestCase = { + name: string; + env: NodeJS.ProcessEnv; + currentConfig: AllConfig; + wantConfig: AllConfig; + }; + + const testCases: MergeRepoEnvTestCase[] = [ + { + name: 'it does nothing', + env: {}, + currentConfig: { repositories: ['some/repo'] }, + wantConfig: { repositories: ['some/repo'] }, + }, + { + name: 'it merges env with the current config', + env: { RENOVATE_STATIC_REPO_CONFIG: '{"dependencyDashboard":true}' }, + currentConfig: { repositories: ['some/repo'] }, + wantConfig: { + dependencyDashboard: true, + repositories: ['some/repo'], + }, + }, + { + name: 'it ignores env with other renovate specific configuration options', + env: { RENOVATE_CONFIG: '{"dependencyDashboard":true}' }, + currentConfig: { repositories: ['some/repo'] }, + wantConfig: { repositories: ['some/repo'] }, + }, + ]; + + it.each(testCases)( + '$name', + async ({ env, currentConfig, wantConfig }: MergeRepoEnvTestCase) => { + const got = await mergeStaticRepoEnvConfig(currentConfig, env); + + expect(got).toEqual(wantConfig); + }, + ); + }); +}); diff --git a/lib/workers/repository/init/config.ts b/lib/workers/repository/init/config.ts index a8f9a14c6dfeceb8b5b3293cdccd72dce10ab32b..fe3122896b32aee3f819b32092c118f644e0800a 100644 --- a/lib/workers/repository/init/config.ts +++ b/lib/workers/repository/init/config.ts @@ -1,4 +1,7 @@ -import type { RenovateConfig } from '../../../config/types'; +import is from '@sindresorhus/is'; +import { mergeChildConfig } from '../../../config'; +import type { AllConfig, RenovateConfig } from '../../../config/types'; +import { parseAndValidateOrExit } from '../../global/config/parse/env'; import { checkOnboardingBranch } from '../onboarding/branch'; import { mergeInheritedConfig } from './inherited'; import { mergeRenovateConfig } from './merge'; @@ -10,7 +13,24 @@ export async function getRepoConfig( let config = { ...config_ }; config.baseBranch = config.defaultBranch; config = await mergeInheritedConfig(config); + config = await mergeStaticRepoEnvConfig(config, process.env); config = await checkOnboardingBranch(config); config = await mergeRenovateConfig(config); return config; } + +export async function mergeStaticRepoEnvConfig( + config: AllConfig, + env: NodeJS.ProcessEnv, +): Promise<AllConfig> { + const repoEnvConfig = await parseAndValidateOrExit( + env, + 'RENOVATE_STATIC_REPO_CONFIG', + ); + + if (!is.nonEmptyObject(repoEnvConfig)) { + return config; + } + + return mergeChildConfig(config, repoEnvConfig); +}