UNPKG

renovate

Version:

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

183 lines (182 loc) • 9.41 kB
import { regEx } from "../../../util/regex.js"; import { logger } from "../../../logger/index.js"; import { getQueryString, joinUrlParts } from "../../../util/url.js"; import { ExternalHostError } from "../../../types/errors/external-host-error.js"; import { id } from "../../versioning/hashicorp/index.js"; import { withCache } from "../../../util/cache/package/with-cache.js"; import { map } from "../../../util/promises.js"; import { TerraformDatasource } from "../terraform-module/base.js"; import { createSDBackendURL } from "../terraform-module/utils.js"; import { OpenTofuProviderDocsResponse, TerraformProviderReleaseBackend, TerraformProviderV2Response, TerraformProviderVersions, TerraformRegistryBuildResponse, TerraformRegistryVersions, VersionDetailResponse } from "./schema.js"; //#region lib/modules/datasource/terraform-provider/index.ts var TerraformProviderDatasource = class TerraformProviderDatasource extends TerraformDatasource { static id = "terraform-provider"; static hashicorpReleaseUrl = "https://releases.hashicorp.com"; static defaultRegistryUrls = [TerraformProviderDatasource.terraformRegistryUrl, TerraformProviderDatasource.hashicorpReleaseUrl]; static repositoryRegex = regEx(/^hashicorp\/(?<packageName>\S+)$/); constructor() { super(TerraformProviderDatasource.id); } defaultRegistryUrls = TerraformProviderDatasource.defaultRegistryUrls; defaultVersioning = id; registryStrategy = "hunt"; releaseTimestampSupport = true; releaseTimestampNote = "The release timestamp is only available for `registry.terraform.io` (v2 API) and `registry.opentofu.org` (via `api.opentofu.org`). Other registries using the Provider Registry Protocol do not provide timestamps."; sourceUrlSupport = "package"; sourceUrlNote = "For `registry.terraform.io`, the source URL is taken from the `source` field of the v2 API response. For `registry.opentofu.org`, it is derived from the package name following the OpenTofu registry policy of `github.com/NAMESPACE/terraform-provider-NAME`."; async _getReleases({ packageName, registryUrl }) { /* v8 ignore next 3 -- should never happen */ if (!registryUrl) return null; logger.trace(`terraform-provider.getDependencies() packageName: ${packageName}`); if (registryUrl === TerraformProviderDatasource.terraformRegistryUrl) return await this.queryTerraformRegistryV2(registryUrl, packageName); if (registryUrl === TerraformProviderDatasource.openTofuRegistryUrl || registryUrl === TerraformProviderDatasource.openTofuApiUrl) return await this.queryOpenTofuRegistry(packageName); if (registryUrl === TerraformProviderDatasource.hashicorpReleaseUrl) return await this.queryReleaseBackend(packageName, registryUrl); return await this.queryProviderRegistry(registryUrl, packageName); } getReleases(config) { const url = config.registryUrl; const repo = TerraformProviderDatasource.getRepository(config); return withCache({ namespace: `datasource-${TerraformProviderDatasource.id}`, key: `getReleases:${url}/${repo}`, fallback: true }, () => this._getReleases(config)); } static getRepository({ packageName }) { return packageName.includes("/") ? packageName : `hashicorp/${packageName}`; } /** * Query the Terraform Registry using the undocumented v2 JSON:API. * * Returns release timestamps for all versions, unlike the v1 API * which only exposed the timestamp for the latest version. */ async queryTerraformRegistryV2(registryUrl, packageName) { const repository = TerraformProviderDatasource.getRepository({ packageName }); const providerUrl = `${joinUrlParts(registryUrl, "v2/providers", repository)}?${getQueryString({ include: "provider-versions" })}`; const { body: res } = await this.http.getJson(providerUrl, TerraformProviderV2Response); res.homepage = `${registryUrl}/providers/${repository}`; return res; } /** * Query the OpenTofu registry docs API. * https://api.opentofu.org/ * * Used when the registry URL is `registry.opentofu.org`. * Queries `api.opentofu.org` for provider versions with release timestamps. */ async queryOpenTofuRegistry(packageName) { const repository = TerraformProviderDatasource.getRepository({ packageName }); const docsUrl = joinUrlParts(TerraformProviderDatasource.openTofuApiUrl, "registry/docs/providers", repository, "index.json"); const { body: res } = await this.http.getJson(docsUrl, OpenTofuProviderDocsResponse); res.homepage = `https://search.opentofu.org/provider/${repository}`; const [namespace, name] = repository.split("/"); res.sourceUrl = `https://github.com/${namespace}/terraform-provider-${name}`; return res; } /** * Query a registry using the Provider Registry Protocol that all registries * are required to implement. * https://www.terraform.io/internals/provider-registry-protocol */ async queryProviderRegistry(registryUrl, packageName) { const repository = TerraformProviderDatasource.getRepository({ packageName }); const backendURL = createSDBackendURL(registryUrl, "providers.v1", await this.getTerraformServiceDiscoveryResult(registryUrl), `${repository}/versions`); return { releases: (await this.http.getJson(backendURL, TerraformProviderVersions)).body.versions.map(({ version }) => ({ version })) }; } async queryReleaseBackend(packageName, registryURL) { const backendLookUpName = `terraform-provider-${packageName.replace("hashicorp/", "")}`; const backendURL = joinUrlParts(registryURL, backendLookUpName, `index.json`); const res = (await this.http.getJson(backendURL, TerraformProviderReleaseBackend)).body; return { releases: Object.keys(res.versions).map((version) => ({ version })), sourceUrl: joinUrlParts("https://github.com/terraform-providers", backendLookUpName) }; } async _getBuilds(registryURL, repository, version) { if (registryURL === TerraformProviderDatasource.hashicorpReleaseUrl) { const repositoryRegexResult = TerraformProviderDatasource.repositoryRegex.exec(repository)?.groups; if (!repositoryRegexResult) return null; const backendLookUpName = `terraform-provider-${repositoryRegexResult.packageName}`; let versionReleaseBackend; try { versionReleaseBackend = await this.getReleaseBackendIndex(backendLookUpName, version); } catch (err) { if (err instanceof ExternalHostError) throw err; logger.debug({ err, backendLookUpName, version }, `Failed to retrieve builds for ${backendLookUpName} ${version}`); throw new ExternalHostError(err); } return versionReleaseBackend.builds; } const serviceDiscovery = await this.getTerraformServiceDiscoveryResult(registryURL); if (!serviceDiscovery) throw new ExternalHostError(/* @__PURE__ */ new Error(`Service discovery not found for ${registryURL}`)); const backendURL = createSDBackendURL(registryURL, "providers.v1", serviceDiscovery, repository); const versionsResponse = (await this.http.getJson(`${backendURL}/versions`, TerraformRegistryVersions)).body; if (!versionsResponse.versions) throw new ExternalHostError(/* @__PURE__ */ new Error(`Failed to retrieve version list for ${backendURL}`)); const builds = versionsResponse.versions.find((value) => value.version === version); if (!builds) throw new ExternalHostError(/* @__PURE__ */ new Error(`No builds found for ${repository}:${version} on ${registryURL}`)); return await map(builds.platforms, async (platform) => { const buildURL = `${backendURL}/${version}/download/${platform.os}/${platform.arch}`; try { const res = (await this.http.getJson(buildURL, TerraformRegistryBuildResponse)).body; return { name: repository, url: res.download_url, version, ...res }; } catch (err) { /* v8 ignore next 3 -- hard to test */ if (err instanceof ExternalHostError) throw err; logger.debug({ err, url: buildURL }, "Failed to retrieve build"); throw new ExternalHostError(err); } }, { concurrency: 4 }); } getBuilds(registryURL, repository, version) { return withCache({ namespace: `datasource-${TerraformProviderDatasource.id}`, key: `getBuilds:${registryURL}/${repository}/${version}` }, () => this._getBuilds(registryURL, repository, version)); } async _getZipHashes(zipHashUrl) { let rawHashData; try { rawHashData = (await this.http.getText(zipHashUrl)).body; } catch (err) { /* v8 ignore next 3 -- hard to test */ if (err instanceof ExternalHostError) throw err; logger.debug({ err, zipHashUrl }, `Failed to retrieve zip hashes from ${zipHashUrl}`); return; } return rawHashData.trimEnd().split("\n").map((line) => line.split(/\s/)[0]); } getZipHashes(zipHashUrl) { return withCache({ namespace: `datasource-${TerraformProviderDatasource.id}`, key: `getZipHashes:${zipHashUrl}` }, () => this._getZipHashes(zipHashUrl)); } async _getReleaseBackendIndex(backendLookUpName, version) { return (await this.http.getJson(`${TerraformProviderDatasource.hashicorpReleaseUrl}/${backendLookUpName}/${version}/index.json`, VersionDetailResponse)).body; } getReleaseBackendIndex(backendLookUpName, version) { return withCache({ namespace: `datasource-${TerraformProviderDatasource.id}`, key: `getReleaseBackendIndex:${backendLookUpName}/${version}` }, () => this._getReleaseBackendIndex(backendLookUpName, version)); } }; //#endregion export { TerraformProviderDatasource }; //# sourceMappingURL=index.js.map