Skip to content

Commit

Permalink
fix(config/inherit): resolve presets (#31642)
Browse files Browse the repository at this point in the history
Co-authored-by: HonkingGoose <[email protected]>
Co-authored-by: Rhys Arkins <[email protected]>
  • Loading branch information
3 people authored Dec 12, 2024
1 parent d094afe commit eb07492
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 2 deletions.
13 changes: 13 additions & 0 deletions docs/usage/config-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,19 @@ Inherited config may use all Repository config settings, and any Global config o

For information on how the Mend Renovate App supports Inherited config, see the dedicated "Mend Renovate App Config" section toward the end of this page.

#### Presets handling

If the inherited config contains `extends` presets, then Renovate will:

1. Resolve the presets
1. Add the resolved preset config to the beginning of the inherited config
1. Merge the presets on top of the global config

##### You can not ignore presets from inherited config

You can _not_ use `ignorePresets` in your repository config to ignore presets _within_ inherited config.
This is because inherited config is resolved _before_ the repository config.

### Repository config

Repository config is the config loaded from a config file in the repository.
Expand Down
136 changes: 135 additions & 1 deletion lib/workers/repository/init/inherited.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { platform } from '../../../../test/util';
import { mocked, platform } from '../../../../test/util';
import * as presets_ from '../../../config/presets';
import type { RenovateConfig } from '../../../config/types';
import * as validation from '../../../config/validation';
import {
CONFIG_INHERIT_NOT_FOUND,
CONFIG_INHERIT_PARSE_ERROR,
Expand All @@ -8,6 +10,10 @@ import {
import { logger } from '../../../logger';
import { mergeInheritedConfig } from './inherited';

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

const presets = mocked(presets_);

describe('workers/repository/init/inherited', () => {
let config: RenovateConfig;

Expand Down Expand Up @@ -84,4 +90,132 @@ describe('workers/repository/init/inherited', () => {
expect(res.onboarding).toBeFalse();
expect(logger.warn).not.toHaveBeenCalled();
});

it('should resolve presets found in inherited config', async () => {
platform.getRawFile.mockResolvedValue(
'{"onboarding":false,"labels":["test"],"extends":[":automergeAll"]}',
);
presets.resolveConfigPresets.mockResolvedValue({
onboarding: false,
labels: ['test'],
automerge: true,
});
const res = await mergeInheritedConfig(config);
expect(res.labels).toEqual(['test']);
expect(res.onboarding).toBeFalse();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.debug).toHaveBeenCalledWith(
'Resolving presets found in inherited config',
);
});

it('should warn if presets fails validation with warnings', async () => {
platform.getRawFile.mockResolvedValue(
'{"onboarding":false,"labels":["test"],"extends":[":automergeAll"]}',
);
jest
.spyOn(validation, 'validateConfig')
.mockResolvedValueOnce({
warnings: [],
errors: [],
})
.mockResolvedValueOnce({
warnings: [
{
message: 'some warning',
topic: 'Configuration Error',
},
],
errors: [],
});
presets.resolveConfigPresets.mockResolvedValue({
onboarding: false,
labels: ['test'],
automerge: true,
});
const res = await mergeInheritedConfig(config);
expect(res.binarySource).toBeUndefined();
expect(logger.warn).toHaveBeenCalledWith(
{
warnings: [
{
message: 'some warning',
topic: 'Configuration Error',
},
],
},
'Found warnings in presets inside the inherited configuration.',
);
});

it('should throw error if presets fails validation with errors', async () => {
platform.getRawFile.mockResolvedValue(
'{"labels":["test"],"extends":[":automergeAll"]}',
);
jest
.spyOn(validation, 'validateConfig')
.mockResolvedValueOnce({
warnings: [],
errors: [],
})
.mockResolvedValueOnce({
warnings: [],
errors: [
{
message: 'some error',
topic: 'Configuration Error',
},
],
});
presets.resolveConfigPresets.mockResolvedValue({
labels: ['test'],
automerge: true,
});
await expect(mergeInheritedConfig(config)).rejects.toThrow(
CONFIG_VALIDATION,
);
expect(logger.warn).toHaveBeenCalledWith(
{
errors: [
{
message: 'some error',
topic: 'Configuration Error',
},
],
},
'Found errors in presets inside the inherited configuration.',
);
});

it('should remove global config from presets found in inherited config', async () => {
platform.getRawFile.mockResolvedValue(
'{"labels":["test"],"extends":[":automergeAll"]}',
);
jest.spyOn(validation, 'validateConfig').mockResolvedValue({
warnings: [],
errors: [],
});
presets.resolveConfigPresets.mockResolvedValue({
labels: ['test'],
automerge: true,
binarySource: 'docker', // global config option: should not be here
});
const res = await mergeInheritedConfig(config);
expect(res.labels).toEqual(['test']);
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.debug).toHaveBeenCalledWith(
{
inheritedConfig: {
labels: ['test'],
automerge: true,
binarySource: 'docker',
},
filteredConfig: {
labels: ['test'],
automerge: true,
},
},
'Removed global config from inherited config presets.',
);
});
});
40 changes: 39 additions & 1 deletion lib/workers/repository/init/inherited.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import is from '@sindresorhus/is';
import { dequal } from 'dequal';
import { mergeChildConfig, removeGlobalConfig } from '../../../config';
import { parseFileConfig } from '../../../config/parse';
import { resolveConfigPresets } from '../../../config/presets';
import type { RenovateConfig } from '../../../config/types';
import { validateConfig } from '../../../config/validation';
import {
Expand Down Expand Up @@ -92,12 +93,49 @@ export async function mergeInheritedConfig(
'Found warnings in inherited configuration.',
);
}
const filteredConfig = removeGlobalConfig(inheritedConfig, true);
let filteredConfig = removeGlobalConfig(inheritedConfig, true);
if (!dequal(inheritedConfig, filteredConfig)) {
logger.debug(
{ inheritedConfig, filteredConfig },
'Removed global config from inherited config.',
);
}

if (is.nullOrUndefined(filteredConfig.extends)) {
return mergeChildConfig(config, filteredConfig);
}

logger.debug('Resolving presets found in inherited config');
const resolvedConfig = await resolveConfigPresets(
filteredConfig,
config,
config.ignorePresets,
);
logger.trace({ config: resolvedConfig }, 'Resolved inherited config');

const validationRes = await validateConfig('inherit', resolvedConfig);
if (validationRes.errors.length) {
logger.warn(
{ errors: validationRes.errors },
'Found errors in presets inside the inherited configuration.',
);
throw new Error(CONFIG_VALIDATION);
}
if (validationRes.warnings.length) {
logger.warn(
{ warnings: validationRes.warnings },
'Found warnings in presets inside the inherited configuration.',
);
}

// remove global config options once again, as resolved presets could have added some
filteredConfig = removeGlobalConfig(resolvedConfig, true);
if (!dequal(resolvedConfig, filteredConfig)) {
logger.debug(
{ inheritedConfig: resolvedConfig, filteredConfig },
'Removed global config from inherited config presets.',
);
}

return mergeChildConfig(config, filteredConfig);
}

0 comments on commit eb07492

Please sign in to comment.