renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
233 lines • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoProxyDatasource = void 0;
exports.pseudoVersionToRelease = pseudoVersionToRelease;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const logger_1 = require("../../../logger");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const decorator_1 = require("../../../util/cache/package/decorator");
const env_1 = require("../../../util/env");
const filter_map_1 = require("../../../util/filter-map");
const http_1 = require("../../../util/http");
const p = tslib_1.__importStar(require("../../../util/promises"));
const regex_1 = require("../../../util/regex");
const timestamp_1 = require("../../../util/timestamp");
const url_1 = require("../../../util/url");
const go_mod_directive_1 = tslib_1.__importDefault(require("../../versioning/go-mod-directive"));
const datasource_1 = require("../datasource");
const base_1 = require("./base");
const common_1 = require("./common");
const goproxy_parser_1 = require("./goproxy-parser");
const releases_direct_1 = require("./releases-direct");
const modRegex = (0, regex_1.regEx)(/^(?<baseMod>.*?)(?:[./]v(?<majorVersion>\d+))?$/);
/**
* @see https://go.dev/ref/mod#pseudo-versions
*/
const pseudoVersionRegex = (0, regex_1.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;
const releaseTimestamp = (0, timestamp_1.asTimestamp)(timestamp);
return {
version: pseudoVersion,
newDigest,
releaseTimestamp,
};
}
class GoProxyDatasource extends datasource_1.Datasource {
static id = 'go-proxy';
constructor() {
super(GoProxyDatasource.id);
}
direct = new releases_direct_1.GoDirectDatasource();
async getReleases(config) {
const { packageName } = config;
logger_1.logger.trace(`goproxy.getReleases(${packageName})`);
const goproxy = (0, env_1.getEnv)().GOPROXY ?? 'https://proxy.golang.org,direct';
if (goproxy === 'direct') {
return this.direct.getReleases(config);
}
const proxyList = (0, goproxy_parser_1.parseGoproxy)(goproxy);
const noproxy = (0, goproxy_parser_1.parseNoproxy)();
let result = null;
if (noproxy?.test(packageName)) {
logger_1.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);
if (res.releases.length) {
result = res;
break;
}
}
catch (err) {
const potentialHttpError = err instanceof external_host_error_1.ExternalHostError ? err.err : err;
const statusCode = potentialHttpError?.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_1.logger.debug({ err }, msg);
if (!canFallback) {
break;
}
}
}
if (result && !result.sourceUrl) {
try {
const datasource = await base_1.BaseGoDatasource.getDatasource(packageName);
const sourceUrl = (0, common_1.getSourceUrl)(datasource);
if (sourceUrl) {
result.sourceUrl = sourceUrl;
}
}
catch (err) {
logger_1.logger.trace({ err }, `Can't get datasource for ${packageName}`);
}
}
return result;
}
/**
* Avoid ambiguity when serving from case-insensitive file systems.
*
* @see https://golang.org/ref/mod#goproxy-protocol
*/
encodeCase(input) {
return input.replace((0, regex_1.regEx)(/([A-Z])/g), (x) => `!${x.toLowerCase()}`);
}
async listVersions(baseUrl, packageName) {
const url = (0, url_1.joinUrlParts)(baseUrl, this.encodeCase(packageName), '@v', 'list');
const { body } = await this.http.getText(url);
return (0, filter_map_1.filterMap)(body.split(regex_1.newlineRegex), (str) => {
if (!is_1.default.nonEmptyStringAndNotWhitespace(str)) {
return null;
}
const [version, timestamp] = str.trim().split((0, regex_1.regEx)(/\s+/));
const release = pseudoVersionToRelease(version) ?? { version };
const releaseTimestamp = (0, timestamp_1.asTimestamp)(timestamp);
if (releaseTimestamp) {
release.releaseTimestamp = releaseTimestamp;
}
return release;
});
}
async versionInfo(baseUrl, packageName, version) {
const url = (0, url_1.joinUrlParts)(baseUrl, this.encodeCase(packageName), '@v', `${version}.info`);
const res = await this.http.getJsonUnchecked(url);
const result = {
version: res.body.Version,
};
const releaseTimestamp = (0, timestamp_1.asTimestamp)(res.body.Time);
if (releaseTimestamp) {
result.releaseTimestamp = releaseTimestamp;
}
return result;
}
async getLatestVersion(baseUrl, packageName) {
try {
const url = (0, url_1.joinUrlParts)(baseUrl, this.encodeCase(packageName), '@latest');
const res = await this.http.getJsonUnchecked(url);
return res.body.Version;
}
catch (err) {
logger_1.logger.trace({ err }, 'Failed to get latest version');
return null;
}
}
async getVersionsWithInfo(baseUrl, packageName) {
const isGopkgin = packageName.startsWith('gopkg.in/');
const majorSuffixSeparator = isGopkgin ? '.' : '/';
const modParts = packageName.match(modRegex)?.groups;
const baseMod = modParts?.baseMod ?? /* istanbul ignore next */ packageName;
const packageMajor = parseInt(modParts?.majorVersion ?? '0');
const result = { releases: [] };
for (let major = packageMajor;; major += 1) {
let pkg = `${baseMod}${majorSuffixSeparator}v${major}`;
if (!isGopkgin && major < 2) {
pkg = baseMod;
major += 1; // v0 and v1 are the same module
}
let releases = [];
try {
const res = await this.listVersions(baseUrl, pkg);
// Artifactory returns all versions in any major (past and future),
// so starting from v2, we filter them in order to avoid the infinite loop
const filteredReleases = res.filter(({ version }) => {
if (major < 2) {
return true;
}
return (version.split((0, regex_1.regEx)(/[^\d]+/)).find(is_1.default.truthy) === major.toString());
});
releases = await p.map(filteredReleases, async (versionInfo) => {
const { version, newDigest, releaseTimestamp } = versionInfo;
if (releaseTimestamp) {
return { version, newDigest, releaseTimestamp };
}
try {
return await this.versionInfo(baseUrl, pkg, version);
}
catch (err) {
logger_1.logger.trace({ err }, `Can't obtain data from ${baseUrl}`);
return { version };
}
});
result.releases.push(...releases);
}
catch (err) {
const potentialHttpError = err instanceof external_host_error_1.ExternalHostError ? err.err : err;
if (potentialHttpError instanceof http_1.HttpError &&
potentialHttpError.response?.statusCode === 404 &&
major !== packageMajor) {
break;
}
throw err;
}
const latestVersion = await this.getLatestVersion(baseUrl, pkg);
if (latestVersion) {
result.tags ??= {};
result.tags.latest ??= latestVersion;
if (go_mod_directive_1.default.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 }) {
const goproxy = (0, env_1.getEnv)().GOPROXY;
const noproxy = (0, goproxy_parser_1.parseNoproxy)();
// TODO: types (#22198)
return `${packageName}@@${goproxy}@@${noproxy?.toString()}`;
}
}
exports.GoProxyDatasource = GoProxyDatasource;
tslib_1.__decorate([
(0, decorator_1.cache)({
namespace: `datasource-${GoProxyDatasource.id}`,
key: (config) => GoProxyDatasource.getCacheKey(config),
})
], GoProxyDatasource.prototype, "getReleases", null);
//# sourceMappingURL=releases-goproxy.js.map