diff --git a/lib/types/skip-reason.ts b/lib/types/skip-reason.ts index 1f11d3b848c224b3dce8a88e4b4d64f89c39d1a5..41baf0f14f5462f0318d441c5a1f8cc68d849170 100644 --- a/lib/types/skip-reason.ts +++ b/lib/types/skip-reason.ts @@ -8,6 +8,7 @@ export enum SkipReason { GitDependency = 'git-dependency', GitPlugin = 'git-plugin', Ignored = 'ignored', + InternalError = 'internal-error', InternalPackage = 'internal-package', InvalidConfig = 'invalid-config', InvalidDependencySpecification = 'invalid-dependency-specification', diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index 9f82d0191bd69aa5cba3b0704e7f1446c629e3cc..ad0d46dd9c9619d58dd2f95d584f34abff3b7963 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -1,5 +1,6 @@ import { mergeChildConfig } from '../../../../config'; import type { ValidationMessage } from '../../../../config/types'; +import { CONFIG_VALIDATION } from '../../../../constants/error-messages'; import { Release, getDatasourceList, @@ -12,6 +13,7 @@ import { import { logger } from '../../../../logger'; import { getRangeStrategy } from '../../../../manager'; import { SkipReason } from '../../../../types'; +import { ExternalHostError } from '../../../../types/errors/external-host-error'; import { clone } from '../../../../util/clone'; import { applyPackageRules } from '../../../../util/package-rules'; import { regEx } from '../../../../util/regex'; @@ -42,296 +44,326 @@ export async function lookupUpdates( isVulnerabilityAlert, updatePinnedDependencies, } = config; - logger.trace({ dependency: depName, currentValue }, 'lookupUpdates'); - // Use the datasource's default versioning if none is configured - config.versioning ??= getDefaultVersioning(datasource); - const versioning = allVersioning.get(config.versioning); const res: UpdateResult = { updates: [], warnings: [], - versioning: config.versioning, } as any; - // istanbul ignore if - if ( - !isGetPkgReleasesConfig(config) || - !getDatasourceList().includes(datasource) - ) { - res.skipReason = SkipReason.InvalidConfig; - return res; - } - const isValid = currentValue && versioning.isValid(currentValue); - if (isValid) { - if (!updatePinnedDependencies && versioning.isSingleVersion(currentValue)) { - res.skipReason = SkipReason.IsPinned; - return res; - } - - const dependency = clone(await getPkgReleases(config)); - if (!dependency) { - // If dependency lookup fails then warn and return - const warning: ValidationMessage = { - topic: depName, - message: `Failed to look up dependency ${depName}`, - }; - logger.debug({ dependency: depName, packageFile }, warning.message); - // TODO: return warnings in own field - res.warnings.push(warning); + try { + logger.trace({ dependency: depName, currentValue }, 'lookupUpdates'); + // Use the datasource's default versioning if none is configured + config.versioning ??= getDefaultVersioning(datasource); + const versioning = allVersioning.get(config.versioning); + res.versioning = config.versioning; + // istanbul ignore if + if ( + !isGetPkgReleasesConfig(config) || + !getDatasourceList().includes(datasource) + ) { + res.skipReason = SkipReason.InvalidConfig; return res; } - if (dependency.deprecationMessage) { - logger.debug({ dependency: depName }, 'Found deprecationMessage'); - res.deprecationMessage = dependency.deprecationMessage; - } - res.sourceUrl = dependency?.sourceUrl; - if (dependency.sourceDirectory) { - res.sourceDirectory = dependency.sourceDirectory; - } - res.homepage = dependency.homepage; - res.changelogUrl = dependency.changelogUrl; - res.dependencyUrl = dependency?.dependencyUrl; - - const latestVersion = dependency.tags?.latest; - // Filter out any results from datasource that don't comply with our versioning - let allVersions = dependency.releases.filter((release) => - versioning.isVersion(release.version) - ); - // istanbul ignore if - if (allVersions.length === 0) { - const message = `Found no results from datasource that look like a version`; - logger.debug({ dependency: depName, result: dependency }, message); - if (!currentDigest) { + const isValid = currentValue && versioning.isValid(currentValue); + if (isValid) { + if ( + !updatePinnedDependencies && + versioning.isSingleVersion(currentValue) + ) { + res.skipReason = SkipReason.IsPinned; return res; } - } - // Reapply package rules in case we missed something from sourceUrl - config = applyPackageRules({ ...config, sourceUrl: res.sourceUrl }); - if (followTag) { - const taggedVersion = dependency.tags[followTag]; - if (!taggedVersion) { - res.warnings.push({ + + const dependency = clone(await getPkgReleases(config)); + if (!dependency) { + // If dependency lookup fails then warn and return + const warning: ValidationMessage = { topic: depName, - message: `Can't find version with tag ${followTag} for ${depName}`, - }); + message: `Failed to look up dependency ${depName}`, + }; + logger.debug({ dependency: depName, packageFile }, warning.message); + // TODO: return warnings in own field + res.warnings.push(warning); return res; } - allVersions = allVersions.filter( - (v) => - v.version === taggedVersion || - (v.version === currentValue && - versioning.isGreaterThan(taggedVersion, currentValue)) + if (dependency.deprecationMessage) { + logger.debug({ dependency: depName }, 'Found deprecationMessage'); + res.deprecationMessage = dependency.deprecationMessage; + } + res.sourceUrl = dependency?.sourceUrl; + if (dependency.sourceDirectory) { + res.sourceDirectory = dependency.sourceDirectory; + } + res.homepage = dependency.homepage; + res.changelogUrl = dependency.changelogUrl; + res.dependencyUrl = dependency?.dependencyUrl; + + const latestVersion = dependency.tags?.latest; + // Filter out any results from datasource that don't comply with our versioning + let allVersions = dependency.releases.filter((release) => + versioning.isVersion(release.version) ); - } - // Check that existing constraint can be satisfied - const allSatisfyingVersions = allVersions.filter((v) => - versioning.matches(v.version, currentValue) - ); - if (rollbackPrs && !allSatisfyingVersions.length) { - const rollback = getRollbackUpdate(config, allVersions, versioning); // istanbul ignore if - if (!rollback) { - res.warnings.push({ - topic: depName, - message: `Can't find version matching ${currentValue} for ${depName}`, - }); - return res; + if (allVersions.length === 0) { + const message = `Found no results from datasource that look like a version`; + logger.debug({ dependency: depName, result: dependency }, message); + if (!currentDigest) { + return res; + } } - res.updates.push(rollback); - } - let rangeStrategy = getRangeStrategy(config); - // istanbul ignore next - if ( - isVulnerabilityAlert && - rangeStrategy === 'update-lockfile' && - !lockedVersion - ) { - rangeStrategy = 'bump'; - } - const nonDeprecatedVersions = dependency.releases - .filter((release) => !release.isDeprecated) - .map((release) => release.version); - const currentVersion = - getCurrentVersion( - config, - versioning, - rangeStrategy, - latestVersion, - nonDeprecatedVersions - ) || - getCurrentVersion( - config, - versioning, - rangeStrategy, - latestVersion, - allVersions.map((v) => v.version) - ); - // istanbul ignore if - if (!currentVersion && lockedVersion) { - return res; - } - res.currentVersion = currentVersion; - if ( - currentVersion && - rangeStrategy === 'pin' && - !versioning.isSingleVersion(currentValue) - ) { - res.updates.push({ - updateType: 'pin', - isPin: true, - newValue: versioning.getNewValue({ - currentValue, - rangeStrategy, - currentVersion, - newVersion: currentVersion, - }), - newMajor: versioning.getMajor(currentVersion), - }); - } - let filterStart = currentVersion; - if (lockedVersion && rangeStrategy === 'update-lockfile') { - // Look for versions greater than the current locked version that still satisfy the package.json range - filterStart = lockedVersion; - } - // Filter latest, unstable, etc - let filteredReleases = filterVersions( - config, - filterStart, - latestVersion, - allVersions, - versioning - ).filter((v) => - // Leave only compatible versions - versioning.isCompatible(v.version, currentValue) - ); - if (isVulnerabilityAlert) { - filteredReleases = filteredReleases.slice(0, 1); - } - const buckets: Record<string, [Release]> = {}; - for (const release of filteredReleases) { - const bucket = getBucket( - config, - currentVersion, - release.version, - versioning - ); - if (buckets[bucket]) { - buckets[bucket].push(release); - } else { - buckets[bucket] = [release]; + // Reapply package rules in case we missed something from sourceUrl + config = applyPackageRules({ ...config, sourceUrl: res.sourceUrl }); + if (followTag) { + const taggedVersion = dependency.tags[followTag]; + if (!taggedVersion) { + res.warnings.push({ + topic: depName, + message: `Can't find version with tag ${followTag} for ${depName}`, + }); + return res; + } + allVersions = allVersions.filter( + (v) => + v.version === taggedVersion || + (v.version === currentValue && + versioning.isGreaterThan(taggedVersion, currentValue)) + ); } - } - const depResultConfig = mergeChildConfig(config, res); - for (const [bucket, releases] of Object.entries(buckets)) { - const sortedReleases = releases.sort((r1, r2) => - versioning.sortVersions(r1.version, r2.version) + // Check that existing constraint can be satisfied + const allSatisfyingVersions = allVersions.filter((v) => + versioning.matches(v.version, currentValue) ); - const { release, pendingChecks, pendingReleases } = - await filterInternalChecks( - depResultConfig, + if (rollbackPrs && !allSatisfyingVersions.length) { + const rollback = getRollbackUpdate(config, allVersions, versioning); + // istanbul ignore if + if (!rollback) { + res.warnings.push({ + topic: depName, + message: `Can't find version matching ${currentValue} for ${depName}`, + }); + return res; + } + res.updates.push(rollback); + } + let rangeStrategy = getRangeStrategy(config); + // istanbul ignore next + if ( + isVulnerabilityAlert && + rangeStrategy === 'update-lockfile' && + !lockedVersion + ) { + rangeStrategy = 'bump'; + } + const nonDeprecatedVersions = dependency.releases + .filter((release) => !release.isDeprecated) + .map((release) => release.version); + const currentVersion = + getCurrentVersion( + config, versioning, - bucket, - sortedReleases + rangeStrategy, + latestVersion, + nonDeprecatedVersions + ) || + getCurrentVersion( + config, + versioning, + rangeStrategy, + latestVersion, + allVersions.map((v) => v.version) ); - // istanbul ignore next - if (!release) { + // istanbul ignore if + if (!currentVersion && lockedVersion) { return res; } - const newVersion = release.version; - const update = generateUpdate( + res.currentVersion = currentVersion; + if ( + currentVersion && + rangeStrategy === 'pin' && + !versioning.isSingleVersion(currentValue) + ) { + res.updates.push({ + updateType: 'pin', + isPin: true, + newValue: versioning.getNewValue({ + currentValue, + rangeStrategy, + currentVersion, + newVersion: currentVersion, + }), + newMajor: versioning.getMajor(currentVersion), + }); + } + let filterStart = currentVersion; + if (lockedVersion && rangeStrategy === 'update-lockfile') { + // Look for versions greater than the current locked version that still satisfy the package.json range + filterStart = lockedVersion; + } + // Filter latest, unstable, etc + let filteredReleases = filterVersions( config, - versioning, - rangeStrategy, - lockedVersion || currentVersion, - bucket, - release + filterStart, + latestVersion, + allVersions, + versioning + ).filter((v) => + // Leave only compatible versions + versioning.isCompatible(v.version, currentValue) ); - if (pendingChecks) { - update.pendingChecks = pendingChecks; - } - if (pendingReleases.length) { - update.pendingVersions = pendingReleases.map((r) => r.version); + if (isVulnerabilityAlert) { + filteredReleases = filteredReleases.slice(0, 1); } - if (!update.newValue || update.newValue === currentValue) { - if (!lockedVersion) { - continue; // eslint-disable-line no-continue + const buckets: Record<string, [Release]> = {}; + for (const release of filteredReleases) { + const bucket = getBucket( + config, + currentVersion, + release.version, + versioning + ); + if (buckets[bucket]) { + buckets[bucket].push(release); + } else { + buckets[bucket] = [release]; } - // istanbul ignore if - if (rangeStrategy === 'bump') { - logger.trace( - { depName, currentValue, lockedVersion, newVersion }, - 'Skipping bump because newValue is the same' + } + const depResultConfig = mergeChildConfig(config, res); + for (const [bucket, releases] of Object.entries(buckets)) { + const sortedReleases = releases.sort((r1, r2) => + versioning.sortVersions(r1.version, r2.version) + ); + const { release, pendingChecks, pendingReleases } = + await filterInternalChecks( + depResultConfig, + versioning, + bucket, + sortedReleases ); - continue; // eslint-disable-line no-continue + // istanbul ignore next + if (!release) { + return res; } - res.isSingleVersion = true; - } - res.isSingleVersion = - res.isSingleVersion || !!versioning.isSingleVersion(update.newValue); + const newVersion = release.version; + const update = generateUpdate( + config, + versioning, + rangeStrategy, + lockedVersion || currentVersion, + bucket, + release + ); + if (pendingChecks) { + update.pendingChecks = pendingChecks; + } + if (pendingReleases.length) { + update.pendingVersions = pendingReleases.map((r) => r.version); + } + if (!update.newValue || update.newValue === currentValue) { + if (!lockedVersion) { + continue; // eslint-disable-line no-continue + } + // istanbul ignore if + if (rangeStrategy === 'bump') { + logger.trace( + { depName, currentValue, lockedVersion, newVersion }, + 'Skipping bump because newValue is the same' + ); + continue; // eslint-disable-line no-continue + } + res.isSingleVersion = true; + } + res.isSingleVersion = + res.isSingleVersion || !!versioning.isSingleVersion(update.newValue); - res.updates.push(update); - } - } else if (currentValue) { - logger.debug(`Dependency ${depName} has unsupported value ${currentValue}`); - if (!pinDigests && !currentDigest) { - res.skipReason = SkipReason.InvalidValue; + res.updates.push(update); + } + } else if (currentValue) { + logger.debug( + `Dependency ${depName} has unsupported value ${currentValue}` + ); + if (!pinDigests && !currentDigest) { + res.skipReason = SkipReason.InvalidValue; + } else { + delete res.skipReason; + } } else { - delete res.skipReason; + res.skipReason = SkipReason.InvalidValue; } - } else { - res.skipReason = SkipReason.InvalidValue; - } - // Record if the dep is fixed to a version - if (lockedVersion) { - res.currentVersion = lockedVersion; - res.fixedVersion = lockedVersion; - } else if (currentValue && versioning.isSingleVersion(currentValue)) { - res.fixedVersion = currentValue.replace(regEx(/^=+/), ''); - } - // Add digests if necessary - if (supportsDigests(config)) { - if (currentDigest) { - if (!digestOneAndOnly || !res.updates.length) { - // digest update - res.updates.push({ - updateType: 'digest', - newValue: currentValue, - }); + // Record if the dep is fixed to a version + if (lockedVersion) { + res.currentVersion = lockedVersion; + res.fixedVersion = lockedVersion; + } else if (currentValue && versioning.isSingleVersion(currentValue)) { + res.fixedVersion = currentValue.replace(regEx(/^=+/), ''); + } + // Add digests if necessary + if (supportsDigests(config)) { + if (currentDigest) { + if (!digestOneAndOnly || !res.updates.length) { + // digest update + res.updates.push({ + updateType: 'digest', + newValue: currentValue, + }); + } + } else if (pinDigests) { + // Create a pin only if one doesn't already exists + if (!res.updates.some((update) => update.updateType === 'pin')) { + // pin digest + res.updates.push({ + updateType: 'pin', + newValue: currentValue, + }); + } } - } else if (pinDigests) { - // Create a pin only if one doesn't already exists - if (!res.updates.some((update) => update.updateType === 'pin')) { - // pin digest - res.updates.push({ - updateType: 'pin', - newValue: currentValue, - }); + if (versioning.valueToVersion) { + res.currentVersion = versioning.valueToVersion(res.currentVersion); + for (const update of res.updates || []) { + update.newVersion = versioning.valueToVersion(update.newVersion); + } } - } - if (versioning.valueToVersion) { - res.currentVersion = versioning.valueToVersion(res.currentVersion); - for (const update of res.updates || []) { - update.newVersion = versioning.valueToVersion(update.newVersion); + // update digest for all + for (const update of res.updates) { + if (pinDigests || currentDigest) { + update.newDigest = + update.newDigest || (await getDigest(config, update.newValue)); + } } } - // update digest for all - for (const update of res.updates) { - if (pinDigests || currentDigest) { - update.newDigest = - update.newDigest || (await getDigest(config, update.newValue)); - } + if (res.updates.length) { + delete res.skipReason; } - } - if (res.updates.length) { - delete res.skipReason; - } - // Strip out any non-changed ones - res.updates = res.updates - .filter((update) => update.newDigest !== null) - .filter( - (update) => - update.newValue !== currentValue || - update.isLockfileUpdate || - (update.newDigest && !update.newDigest.startsWith(currentDigest)) + // Strip out any non-changed ones + res.updates = res.updates + .filter((update) => update.newDigest !== null) + .filter( + (update) => + update.newValue !== currentValue || + update.isLockfileUpdate || + (update.newDigest && !update.newDigest.startsWith(currentDigest)) + ); + } catch (err) /* istanbul ignore next */ { + if (err instanceof ExternalHostError || err.message === CONFIG_VALIDATION) { + throw err; + } + logger.error( + { + currentDigest, + currentValue, + datasource, + depName, + digestOneAndOnly, + followTag, + lockedVersion, + packageFile, + pinDigests, + rollbackPrs, + isVulnerabilityAlert, + updatePinnedDependencies, + err, + }, + 'lookupUpdates error' ); + res.skipReason = SkipReason.InternalError; + } return res; } diff --git a/lib/workers/repository/process/lookup/types.ts b/lib/workers/repository/process/lookup/types.ts index 2c44ff188e060b913022930a7f7870f551efc6d3..be3200ffd2c1be10e3e8e54e63e2edaf0c5d0519 100644 --- a/lib/workers/repository/process/lookup/types.ts +++ b/lib/workers/repository/process/lookup/types.ts @@ -57,6 +57,5 @@ export interface UpdateResult { fixedVersion?: string; updates: LookupUpdate[]; warnings: ValidationMessage[]; - - versioning: string; + versioning?: string; }