UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

235 lines (234 loc) • 9.46 kB
import { getEnv } from "../../../util/env.js"; import { newlineRegex, regEx } from "../../../util/regex.js"; import { logger } from "../../../logger/index.js"; import { joinUrlParts } from "../../../util/url.js"; import { ExternalHostError } from "../../../types/errors/external-host-error.js"; import { filterMap } from "../../../util/filter-map.js"; import api from "../../versioning/go-mod-directive/index.js"; import { withCache } from "../../../util/cache/package/with-cache.js"; import { RequestError } from "../../../util/http/got.js"; import "../../../util/http/index.js"; import { asTimestamp } from "../../../util/timestamp.js"; import { Datasource } from "../datasource.js"; import { map } from "../../../util/promises.js"; import { BaseGoDatasource } from "./base.js"; import { parseGoproxy, parseNoproxy } from "./goproxy-parser.js"; import { getSourceUrl } from "./common.js"; import { GoDirectDatasource } from "./releases-direct.js"; import { VersionInfo } from "./schema.js"; import { isNonEmptyStringAndNotWhitespace, isTruthy } from "@sindresorhus/is"; //#region lib/modules/datasource/go/releases-goproxy.ts /** TODO #42566 */ const goVersionRegex = regEx(/^\s*go\s+(?<version>[^\s]+)\s*$/); const modRegex = regEx(/^(?<baseMod>.*?)(?:[./]v(?<majorVersion>\d+))?$/); /** * @see https://go.dev/ref/mod#pseudo-versions */ const pseudoVersionRegex = regEx(/v\d+\.\d+\.\d+-(?:\w+\.)?(?:0\.)?(?<timestamp>\d{14})-(?<digest>[a-f0-9]{12})/i); function pseudoVersionToRelease(pseudoVersion) { const match = pseudoVersion.match(pseudoVersionRegex)?.groups; if (!match) return null; const { digest: newDigest, timestamp } = match; return { version: pseudoVersion, newDigest, releaseTimestamp: asTimestamp(timestamp) }; } var GoProxyDatasource = class GoProxyDatasource extends Datasource { static id = "go-proxy"; constructor() { super(GoProxyDatasource.id); } direct = new GoDirectDatasource(); async _getReleases(config) { const { packageName } = config; logger.trace(`goproxy.getReleases(${packageName})`); const goproxy = getEnv().GOPROXY ?? "https://proxy.golang.org,direct"; if (goproxy === "direct") return this.direct.getReleases(config); const proxyList = parseGoproxy(goproxy); const noproxy = parseNoproxy(); let result = null; if (noproxy?.test(packageName)) { logger.debug(`Fetching ${packageName} via GONOPROXY match`); result = await this.direct.getReleases(config); return result; } for (const { url, fallback } of proxyList) try { if (url === "off") break; else if (url === "direct") { result = await this.direct.getReleases(config); break; } const res = await this.getVersionsWithInfo(url, packageName, config.constraintsFiltering); if (res.releases.length) { result = res; break; } } catch (err) { const statusCode = (err instanceof ExternalHostError ? err.err : err)?.response?.statusCode; const canFallback = fallback === "|" ? true : statusCode === 404 || statusCode === 410; const msg = canFallback ? "Goproxy error: trying next URL provided with GOPROXY" : "Goproxy error: skipping other URLs provided with GOPROXY"; logger.debug({ err }, msg); if (!canFallback) break; } if (result && !result.sourceUrl) try { const sourceUrl = getSourceUrl(await BaseGoDatasource.getDatasource(packageName)); if (sourceUrl) result.sourceUrl = sourceUrl; } catch (err) { logger.trace({ err }, `Can't get datasource for ${packageName}`); } return result; } getReleases(config) { return withCache({ namespace: `datasource-${GoProxyDatasource.id}`, key: GoProxyDatasource.getCacheKey(config), fallback: true }, () => this._getReleases(config)); } /** * Avoid ambiguity when serving from case-insensitive file systems. * * @see https://golang.org/ref/mod#goproxy-protocol */ encodeCase(input) { return input.replace(regEx(/([A-Z])/g), (x) => `!${x.toLowerCase()}`); } async listVersions(baseUrl, packageName) { const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@v", "list"); const { body } = await this.http.getText(url); return filterMap(body.split(newlineRegex), (str) => { if (!isNonEmptyStringAndNotWhitespace(str)) return null; const [version, timestamp] = str.trim().split(regEx(/\s+/)); const release = pseudoVersionToRelease(version) ?? { version }; const releaseTimestamp = asTimestamp(timestamp); if (releaseTimestamp) release.releaseTimestamp = releaseTimestamp; return release; }); } async versionInfo(baseUrl, packageName, version) { const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@v", `${version}.info`); const res = await this.http.getJson(url, VersionInfo); const result = { version: res.body.Version }; const releaseTimestamp = asTimestamp(res.body.Time); if (releaseTimestamp) result.releaseTimestamp = releaseTimestamp; return result; } /** * Retrieve the `go` directive for a given Go Module. * * NOTE that this means the `go` directive, not the `toolchain` directive. */ async retrieveGoDirectiveForModule(baseUrl, packageName, version) { return withCache({ namespace: `datasource-${GoProxyDatasource.id}`, key: GoProxyDatasource.getVersionedCacheKey(packageName, version), ttlMinutes: 2400 * 60 }, () => this._retrieveGoDirectiveForModule(baseUrl, packageName, version)); } async _retrieveGoDirectiveForModule(baseUrl, packageName, version) { const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@v", `${version}.mod`); const res = await this.http.getText(url); let goDirective = void 0; for (const line of res.body.split("\n")) { const goVersionMatches = goVersionRegex.exec(line)?.groups; if (goVersionMatches) { goDirective = goVersionMatches.version; break; } } if (!goDirective) return goDirective; const parts = goDirective.split("."); if (parts.length === 1) return `${parts[0]}.0.0`; else if (parts.length === 2) return `${parts[0]}.${parts[1]}.0`; else return `${parts[0]}.${parts[1]}.${parts[2]}`; } async getLatestVersion(baseUrl, packageName) { try { const url = joinUrlParts(baseUrl, this.encodeCase(packageName), "@latest"); return (await this.http.getJson(url, VersionInfo)).body.Version; } catch (err) { logger.trace({ err }, "Failed to get latest version"); return null; } } async getVersionsWithInfo(baseUrl, packageName, constraintsFiltering) { const isGopkgin = packageName.startsWith("gopkg.in/"); const majorSuffixSeparator = isGopkgin ? "." : "/"; const modParts = packageName.match(modRegex)?.groups; const baseMod = modParts?.baseMod ?? packageName; const packageMajor = parseInt(modParts?.majorVersion ?? "0", 10); const result = { releases: [] }; for (let major = packageMajor;; major += 1) { let pkg = `${baseMod}${majorSuffixSeparator}v${major}`; if (!isGopkgin && major < 2) { pkg = baseMod; major += 1; } let releases = []; try { releases = await map((await this.listVersions(baseUrl, pkg)).filter(({ version }) => { if (major < 2) return true; return version.split(regEx(/[^\d]+/)).find(isTruthy) === major.toString(); }), async (versionInfo) => { const { version, newDigest, releaseTimestamp } = versionInfo; if (releaseTimestamp) return { version, newDigest, releaseTimestamp }; try { return await this.versionInfo(baseUrl, pkg, version); } catch (err) { logger.trace({ err }, `Can't obtain data from ${baseUrl}`); return { version }; } }); if (constraintsFiltering === "strict") releases = await map(releases, async (rel) => { try { const goDirective = await this.retrieveGoDirectiveForModule(baseUrl, pkg, rel.version); if (goDirective) { rel.constraints ??= {}; rel.constraints["%goMod"] ??= []; rel.constraints["%goMod"].push(goDirective); } } catch (err) { logger.trace({ err }, `Can't obtain \`go\` directive from ${baseUrl}`); } return rel; }); result.releases.push(...releases); } catch (err) { const potentialHttpError = err instanceof ExternalHostError ? err.err : err; const status = potentialHttpError.response?.statusCode; if (potentialHttpError instanceof RequestError && (status === 404 || status === 403) && major !== packageMajor) break; throw err; } const latestVersion = await this.getLatestVersion(baseUrl, pkg); if (latestVersion) { result.tags ??= {}; result.tags.latest ??= latestVersion; if (api.isGreaterThan(latestVersion, result.tags.latest)) result.tags.latest = latestVersion; if (!result.releases.length) { const releaseFromLatest = pseudoVersionToRelease(latestVersion); if (releaseFromLatest) result.releases.push(releaseFromLatest); } } if (!releases.length) break; } return result; } static getCacheKey({ packageName, constraintsFiltering }) { const goproxy = getEnv().GOPROXY; const noproxy = parseNoproxy(); const constraintsFilteringKey = constraintsFiltering && constraintsFiltering !== "none" ? `@@${constraintsFiltering}` : ""; return `${packageName}@@${goproxy}@@${noproxy?.toString()}${constraintsFilteringKey}`; } static getVersionedCacheKey(packageName, version) { return `${packageName}@@${version}@@${getEnv().GOPROXY}@@${parseNoproxy()?.toString()}`; } }; //#endregion export { GoProxyDatasource }; //# sourceMappingURL=releases-goproxy.js.map