Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config)!: add new option constraintsFiltering #19992

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,19 @@ If you need to _override_ constraints that Renovate detects from the repository,
!!! note
Make sure not to mix this up with the term `compatibility`, which Renovate uses in the context of version releases, e.g. if a Docker image is `node:12.16.0-alpine` then the `-alpine` suffix represents `compatibility`.

## constraintsFiltering

This option controls whether Renovate filters new releases based on configured or detected `constraints`.
Renovate supports two options:

- `none`: No release filtering (all releases allowed)
- `strict`: If the release's constraints match the package file constraints, then it's included

We are working on adding more advanced filtering options.

Note: There must be a `constraints` object in your Renovate config for this to work.
This feature is limited to `pypi` datasource only.

## defaultRegistryUrls

Override a datasource's default registries with this config option.
Expand Down
8 changes: 8 additions & 0 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
{
name: 'constraintsFiltering',
description: 'Perform release filtering based on language constraints.',
type: 'string',
allowedValues: ['none', 'strict'],
cli: false,
default: 'none',
},
{
name: 'repositoryCache',
description:
Expand Down
3 changes: 3 additions & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export interface RegExManager extends RegexManagerTemplates {
}

export type UseBaseBranchConfigType = 'merge' | 'none';
export type ConstraintsFilter = 'strict' | 'none';

// TODO: Proper typings
export interface RenovateConfig
Expand Down Expand Up @@ -250,6 +251,8 @@ export interface RenovateConfig

constraints?: Record<string, string>;
skipInstalls?: boolean;

constraintsFiltering?: ConstraintsFilter;
}

export interface AllConfig
Expand Down
91 changes: 91 additions & 0 deletions lib/modules/datasource/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,97 @@ describe('modules/datasource/index', () => {
expect(res).toBeNull();
});
});

describe('relaseConstraintFiltering', () => {
it('keeps all releases by default', async () => {
const registries = {
'https://foo.bar': {
releases: [
{
version: '0.0.1',
constraints: {
python: ['2.7'],
},
},
{
version: '0.0.2',
},
],
},
} satisfies RegistriesMock;
datasources.set(datasource, new DummyDatasource(registries));
const res = await getPkgReleases({
datasource,
depName,
defaultRegistryUrls: ['https://foo.bar'],
});
expect(res).toMatchObject({
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
});
});

it('keeps all releases if constraints is set but no value defined for constraintsFiltering', async () => {
const registries = {
'https://foo.bar': {
releases: [
{
version: '0.0.1',
constraints: {
python: ['2.7'],
},
},
{
version: '0.0.2',
},
],
},
} satisfies RegistriesMock;
datasources.set(datasource, new DummyDatasource(registries));
const res = await getPkgReleases({
datasource,
depName,
defaultRegistryUrls: ['https://foo.bar'],
constraints: {
python: '2.7.0',
},
});
expect(res).toMatchObject({
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
});
});

it('filters releases if value is strict', async () => {
const registries = {
'https://foo.bar': {
releases: [
{
version: '0.0.1',
constraints: {
python: ['2.7'],
},
},
{
version: '0.0.2',
constraints: {
python: ['1.0'],
},
},
],
},
} satisfies RegistriesMock;
datasources.set(datasource, new DummyDatasource(registries));
const res = await getPkgReleases({
datasource,
depName,
defaultRegistryUrls: ['https://foo.bar'],
constraints: { python: '2.7.0' },
constraintsFiltering: 'strict',
});
expect(res).toMatchObject({
releases: [{ version: '0.0.1' }],
});
});
});
});
});
});
42 changes: 22 additions & 20 deletions lib/modules/datasource/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,26 +395,28 @@ export async function getPkgReleases(
(findRelease) => findRelease.version === filterRelease.version
) === filterIndex
);
// Filter releases for compatibility
for (const [constraintName, constraintValue] of Object.entries(
config.constraints ?? {}
)) {
// Currently we only support if the constraint is a plain version
// TODO: Support range/range compatibility filtering #8476
if (version.isVersion(constraintValue)) {
res.releases = res.releases.filter((release) => {
const constraint = release.constraints?.[constraintName];
if (!is.nonEmptyArray(constraint)) {
// A release with no constraints is OK
return true;
}
return constraint.some(
// If any of the release's constraints match, then it's OK
(releaseConstraint) =>
!releaseConstraint ||
version.matches(constraintValue, releaseConstraint)
);
});
if (config?.constraintsFiltering === 'strict') {
// Filter releases for compatibility
for (const [constraintName, constraintValue] of Object.entries(
config.constraints ?? {}
)) {
// Currently we only support if the constraint is a plain version
// TODO: Support range/range compatibility filtering #8476
if (version.isVersion(constraintValue)) {
res.releases = res.releases.filter((release) => {
const constraint = release.constraints?.[constraintName];
if (!is.nonEmptyArray(constraint)) {
// A release with no constraints is OK
return true;
}
return constraint.some(
// If any of the release's constraints match, then it's OK
(releaseConstraint) =>
!releaseConstraint ||
version.matches(constraintValue, releaseConstraint)
);
});
}
}
}
// Strip constraints from releases result
Expand Down
2 changes: 2 additions & 0 deletions lib/modules/datasource/pypi/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ describe('modules/datasource/pypi/index', () => {
datasource,
constraints: { python: '2.7' },
depName: 'doit',
constraintsFiltering: 'strict',
})
).toMatchSnapshot();
});
Expand Down Expand Up @@ -518,6 +519,7 @@ describe('modules/datasource/pypi/index', () => {
constraints: { python: '2.7' },
...config,
depName: 'dj-database-url',
constraintsFiltering: 'strict',
})
).toMatchSnapshot();
});
Expand Down
2 changes: 2 additions & 0 deletions lib/modules/datasource/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ConstraintsFilter } from '../../config/types';
import type { ModuleApi } from '../../types';

export interface GetDigestInputConfig {
Expand Down Expand Up @@ -37,6 +38,7 @@ export interface GetPkgReleasesConfig {
constraints?: Record<string, string>;
replacementName?: string;
replacementVersion?: string;
constraintsFiltering?: ConstraintsFilter;
}

export interface Release {
Expand Down