diff --git a/lib/versioning/docker/index.js b/lib/versioning/docker/index.js index f0a3866de36dcf50510de97d35119c9a06b183c6..1124b4f4d564f9138196361305f8b107393e0dd8 100644 --- a/lib/versioning/docker/index.js +++ b/lib/versioning/docker/index.js @@ -1,8 +1,96 @@ -const semver = require('../semver'); +function parse(version) { + const [prefix, suffix] = version.split('-'); + const release = prefix.split('.').map(Number); + if (release.some(Number.isNaN)) { + return null; + } + return { release, suffix: suffix || '' }; +} -const isValid = input => semver.isVersion(input); +function compare(version1, vervion2) { + const parsed1 = parse(version1); + const parsed2 = parse(vervion2); + const length = Math.max(parsed1.release.length, parsed2.release.length); + for (let i = 0; i < length; i += 1) { + const part1 = parsed1.release[i]; + const part2 = parsed2.release[i]; + // shorter is bigger 2.1 > 2.1.1 + if (part1 === undefined) { + return 1; + } + if (part2 === undefined) { + return -1; + } + if (part1 !== part2) { + return part1 - part2; + } + } + // equals + return parsed2.suffix.localeCompare(parsed1.suffix); +} + +function equals(version, other) { + return compare(version, other) === 0; +} +function getPart(version, index) { + const parsed = parse(version); + return parsed && parsed.release.length > index ? parsed.release[index] : null; +} +function getMajor(version) { + return getPart(version, 0); +} +function getMinor(version) { + return getPart(version, 1); +} +function getPatch(version) { + return getPart(version, 2); +} +function isGreaterThan(version, other) { + return compare(version, other) > 0; +} +function isLessThanRange(version, range) { + return compare(version, range) < 0; +} +function isValid(version) { + const parsed = parse(version); + return parsed ? version : null; +} + +// docker does not have ranges, so versions has to be equal +function maxSatisfyingVersion(versions, range) { + return versions.find(v => equals(v, range)) || null; +} +function minSatisfyingVersion(versions, range) { + return versions.find(v => equals(v, range)) || null; +} +function getNewValue(currentValue, rangeStrategy, fromVersion, toVersion) { + return toVersion; +} +function sortVersions(version, other) { + return compare(version, other); +} + +function isCompatible(version, range) { + const parsed1 = parse(version); + const parsed2 = parse(range); + return parsed1.release.length === parsed2.release.length; +} module.exports = { - ...semver, + equals, + getMajor, + getMinor, + getPatch, + isCompatible, + isGreaterThan, + isLessThanRange, + isSingleVersion: isValid, + isStable: isValid, isValid, + isVersion: isValid, + matches: equals, + maxSatisfyingVersion, + minSatisfyingVersion, + getNewValue, + sortVersions, }; diff --git a/lib/versioning/pep440/index.js b/lib/versioning/pep440/index.js index f63c58a2c26233089d704cdcd2667547f13ec651..9b128f514ee4c5519583c687d482c8468a538839 100644 --- a/lib/versioning/pep440/index.js +++ b/lib/versioning/pep440/index.js @@ -45,6 +45,7 @@ module.exports = { getMajor, getMinor, getPatch, + isCompatible: isVersion, isGreaterThan, isSingleVersion, isStable, diff --git a/lib/versioning/semver-composer/index.js b/lib/versioning/semver-composer/index.js index 064caee4202744dbd0d6263e56f405fa2c5298e5..9fee015a7a876bfbb368f111621791b1a5eba286 100644 --- a/lib/versioning/semver-composer/index.js +++ b/lib/versioning/semver-composer/index.js @@ -128,6 +128,7 @@ module.exports = { getMajor, getMinor, getPatch, + isCompatible: isVersion, isGreaterThan, isLessThanRange, isSingleVersion, diff --git a/lib/versioning/semver/index.js b/lib/versioning/semver/index.js index 96ec9bfee7d12aced323ff4868ec075601626304..c2e01356a228f95fcc6e9d19ca54ad1075303bc3 100644 --- a/lib/versioning/semver/index.js +++ b/lib/versioning/semver/index.js @@ -32,6 +32,7 @@ module.exports = { getMajor, getMinor, getPatch, + isCompatible: isVersion, isGreaterThan, isLessThanRange, isSingleVersion, diff --git a/lib/workers/repository/process/lookup/index.js b/lib/workers/repository/process/lookup/index.js index f31c47a784b4638df0103c9da8500136f02d4307..dcc7357a271bcec2bc5f487f8598c2a5235e25bd 100644 --- a/lib/workers/repository/process/lookup/index.js +++ b/lib/workers/repository/process/lookup/index.js @@ -21,6 +21,7 @@ async function lookupUpdates(config) { getMinor, isGreaterThan, isSingleVersion, + isCompatible, isValid, isVersion, matches, @@ -128,6 +129,9 @@ async function lookupUpdates(config) { dependency.latestVersion, allVersions, releases + ).filter(version => + // Leave only compatible versions + isCompatible(version, currentValue) ); const buckets = {}; for (const toVersion of filteredVersions) { diff --git a/test/versioning/docker.spec.js b/test/versioning/docker.spec.js index 215d2f85c81080ad89561ef5917d61dd0dbc0d21..72acd0755e8258248c501a22e3e75b818c963f1d 100644 --- a/test/versioning/docker.spec.js +++ b/test/versioning/docker.spec.js @@ -1,10 +1,121 @@ const docker = require('../../lib/versioning')('docker'); +const semver = require('../../lib/versioning')('semver'); describe('docker.isValid(input)', () => { - it('should return null for short version', () => { - expect(!!docker.isValid('3.7')).toBe(false); + describe('isValid(version)', () => { + it('should support all versions length', () => { + expect(docker.isValid('1.2.3')).toBe('1.2.3'); + expect(docker.isValid('18.04')).toBe('18.04'); + expect(docker.isValid('10.1')).toBe('10.1'); + expect(docker.isValid('3')).toBe('3'); + expect(docker.isValid('foo')).toBe(null); + }); }); - it('should support semver', () => { - expect(!!docker.isValid('1.2.3')).toBe(true); + describe('isValid(version)', () => { + it('should support all versions length', () => { + expect(docker.getMajor('1.2.3')).toBe(1); + expect(docker.getMajor('18.04')).toBe(18); + expect(docker.getMajor('10.1')).toBe(10); + expect(docker.getMajor('3')).toBe(3); + expect(docker.getMajor('foo')).toBe(null); + }); + }); + describe('getMinor(version)', () => { + it('should support all versions length', () => { + expect(docker.getMinor('1.2.3')).toBe(2); + expect(docker.getMinor('18.04')).toBe(4); + expect(docker.getMinor('10.1')).toBe(1); + expect(docker.getMinor('3')).toBe(null); + expect(docker.getMinor('foo')).toBe(null); + }); + }); + describe('getPatch(version)', () => { + it('should support all versions length', () => { + expect(docker.getPatch('1.2.3')).toBe(3); + expect(docker.getPatch('18.04')).toBe(null); + expect(docker.getPatch('10.1')).toBe(null); + expect(docker.getPatch('3')).toBe(null); + expect(docker.getPatch('foo')).toBe(null); + }); + }); + + describe('isGreaterThan(version, other)', () => { + it('should support all versions length', () => { + expect(docker.isGreaterThan('1.2.3', '1.2')).toBe(false); + expect(docker.isGreaterThan('18.04', '18.1')).toBe(true); + expect(docker.isGreaterThan('10.1', '10.1.2')).toBe(true); + expect(docker.isGreaterThan('3', '2')).toBe(true); + expect(docker.isGreaterThan('1.2.3', '1.2.3')).toBe(false); + }); + }); + describe('isLessThanRange(version, range)', () => { + it('should support all versions length', () => { + expect(docker.isLessThanRange('1.2.3', '2.0')).toBe(true); + expect(docker.isLessThanRange('18.04', '18.1')).toBe(false); + expect(docker.isLessThanRange('10.1', '10.0.4')).toBe(false); + expect(docker.isLessThanRange('3', '4.0')).toBe(true); + expect(docker.isLessThanRange('1.2', '1.3.4')).toBe(true); + }); + }); + describe('equals(version, other)', () => { + it('should support all versions length', () => { + expect(docker.equals('1.2.3', '1.2.3')).toBe(true); + expect(docker.equals('18.04', '18.4')).toBe(true); + expect(docker.equals('10.0', '10.0.4')).toBe(false); + expect(docker.equals('3', '4.0')).toBe(false); + expect(docker.equals('1.2', '1.2.3')).toBe(false); + }); + }); + describe('equals(version, other)', () => { + it('should support all versions length', () => { + expect(docker.equals('1.2.3', '1.2.3')).toBe(true); + expect(docker.equals('18.04', '18.4')).toBe(true); + expect(docker.equals('10.0', '10.0.4')).toBe(false); + expect(docker.equals('3', '4.0')).toBe(false); + expect(docker.equals('1.2', '1.2.3')).toBe(false); + }); + }); + describe('maxSatisfyingVersion(versions, range)', () => { + it('should support all versions length', () => { + [docker.minSatisfyingVersion, docker.maxSatisfyingVersion].forEach( + max => { + const versions = [ + '0.9.8', + '1.1.1', + '1.1', + '1.2.3', + '1.2', + '1', + '2.2.2', + '2.2', + '2', + ]; + // returns range if found + expect(max(versions, '1.2.3')).toBe('1.2.3'); + expect(max(versions, '1.2')).toBe('1.2'); + expect(max(versions, '1')).toBe('1'); + // return null if not found + expect(max(versions, '1.3')).toBe(null); + expect(max(versions, '0.9')).toBe(null); + } + ); + }); + }); + describe('sortVersions(v1, v2)', () => { + it('behaves like semver.sortVersions', () => { + [ + ['1.1.1', '1.2.3'], + ['1.2.3', '1.3.4'], + ['2.0.1', '1.2.3'], + ['1.2.3', '0.9.5'], + ].forEach(pair => { + expect(docker.sortVersions(...pair)).toBe(semver.sortVersions(...pair)); + }); + }); + }); + describe('getNewValue(', () => { + it('returns toVersion', () => { + expect(docker.getNewValue(null, null, null, '1.2.3')).toBe('1.2.3'); + }); }); }); diff --git a/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap index b62afc274869ae828daf981442a408111b66b29b..e68236b70763fb795f062ed532b0deb4937c0118 100644 --- a/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap @@ -847,6 +847,116 @@ Array [ exports[`manager/npm/lookup .lookupUpdates() should warn if no version matches dist-tag 1`] = `Array []`; +exports[`manager/npm/lookup .lookupUpdates() skips uncompatible versions for 8 1`] = ` +Object { + "changelogUrl": undefined, + "homepage": undefined, + "releases": Array [ + Object { + "version": "8", + }, + Object { + "version": "9", + }, + ], + "repositoryUrl": null, + "updates": Array [ + Object { + "canBeUnpublished": undefined, + "fromVersion": "8", + "isSingleVersion": true, + "newMajor": 9, + "newMinor": null, + "newValue": "9", + "releaseTimestamp": undefined, + "toVersion": "9", + "updateType": "major", + }, + ], + "warnings": Array [], +} +`; + +exports[`manager/npm/lookup .lookupUpdates() skips uncompatible versions for 8.1 1`] = ` +Object { + "changelogUrl": undefined, + "homepage": undefined, + "releases": Array [ + Object { + "version": "8.1", + }, + Object { + "version": "8.2", + }, + Object { + "version": "9.0", + }, + ], + "repositoryUrl": null, + "updates": Array [ + Object { + "canBeUnpublished": undefined, + "fromVersion": "8.1", + "isSingleVersion": true, + "newMajor": 8, + "newMinor": 2, + "newValue": "8.2", + "releaseTimestamp": undefined, + "toVersion": "8.2", + "updateType": "minor", + }, + Object { + "canBeUnpublished": undefined, + "fromVersion": "8.1", + "isSingleVersion": true, + "newMajor": 9, + "newMinor": 0, + "newValue": "9.0", + "releaseTimestamp": undefined, + "toVersion": "9.0", + "updateType": "major", + }, + ], + "warnings": Array [], +} +`; + +exports[`manager/npm/lookup .lookupUpdates() skips uncompatible versions for 8.1.0 1`] = ` +Object { + "changelogUrl": undefined, + "homepage": undefined, + "releases": Array [ + Object { + "version": "8.1.0", + }, + Object { + "version": "8.1.5", + }, + Object { + "version": "8.2.0", + }, + Object { + "version": "8.2.5", + }, + ], + "repositoryUrl": null, + "updates": Array [ + Object { + "canBeUnpublished": undefined, + "fromVersion": "8.1.0", + "isSingleVersion": true, + "newMajor": 8, + "newMinor": 2, + "newValue": "8.2.5", + "releaseTimestamp": undefined, + "toVersion": "8.2.5", + "updateType": "minor", + }, + ], + "warnings": Array [], +} +`; + exports[`manager/npm/lookup .lookupUpdates() skips undefined values 1`] = ` Object { "skipReason": "unsupported-value", diff --git a/test/workers/repository/process/lookup/index.spec.js b/test/workers/repository/process/lookup/index.spec.js index 6e78b359aaf25e40908c84f77c2eb63f7ae6a682..b15a3ca33b9529f63349bcdaccba0cc2ba35777e 100644 --- a/test/workers/repository/process/lookup/index.spec.js +++ b/test/workers/repository/process/lookup/index.spec.js @@ -1027,6 +1027,29 @@ describe('manager/npm/lookup', () => { const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot(); }); + ['8.1.0', '8.1', '8'].forEach(currentValue => { + it('skips uncompatible versions for ' + currentValue, async () => { + config.currentValue = currentValue; + config.depName = 'node'; + config.purl = 'pkg:docker/node'; + config.versionScheme = 'docker'; + docker.getPkgReleases.mockReturnValueOnce({ + releases: [ + { version: '8.1.0' }, + { version: '8.1.5' }, + { version: '8.1' }, + { version: '8.2.0' }, + { version: '8.2.5' }, + { version: '8.2' }, + { version: '8' }, + { version: '9.0' }, + { version: '9' }, + ], + }); + const res = await lookup.lookupUpdates(config); + expect(res).toMatchSnapshot(); + }); + }); it('handles digest pin for up to date version', async () => { config.currentValue = '8.1.0'; config.depName = 'node';