From 915fbc7b772b3690bc58b5238be6c66b02303dda Mon Sep 17 00:00:00 2001 From: Jamie Magee <jamie.magee@gmail.com> Date: Mon, 14 Jun 2021 12:14:32 -0700 Subject: [PATCH] refactor(terraform-provider): convert to class-based datasource (#10362) --- lib/datasource/api.ts | 4 +- .../terraform-provider/index.spec.ts | 23 ++- lib/datasource/terraform-provider/index.ts | 194 +++++++++--------- lib/manager/terraform/providers.ts | 4 +- 4 files changed, 112 insertions(+), 113 deletions(-) diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts index 7cc5dd3484..4f9fee1d1e 100644 --- a/lib/datasource/api.ts +++ b/lib/datasource/api.ts @@ -29,7 +29,7 @@ import * as rubygems from './rubygems'; import * as sbtPackage from './sbt-package'; import * as sbtPlugin from './sbt-plugin'; import * as terraformModule from './terraform-module'; -import * as terraformProvider from './terraform-provider'; +import { TerraformProviderDatasource } from './terraform-provider'; import type { DatasourceApi } from './types'; const api = new Map<string, DatasourceApi>(); @@ -66,4 +66,4 @@ api.set('rubygems', rubygems); api.set('sbt-package', sbtPackage); api.set('sbt-plugin', sbtPlugin); api.set('terraform-module', terraformModule); -api.set('terraform-provider', terraformProvider); +api.set('terraform-provider', new TerraformProviderDatasource()); diff --git a/lib/datasource/terraform-provider/index.spec.ts b/lib/datasource/terraform-provider/index.spec.ts index 10751814ae..afde1cffb3 100644 --- a/lib/datasource/terraform-provider/index.spec.ts +++ b/lib/datasource/terraform-provider/index.spec.ts @@ -1,14 +1,15 @@ import { getPkgReleases } from '..'; import * as httpMock from '../../../test/http-mock'; import { getName, loadFixture } from '../../../test/util'; -import { id as datasource, defaultRegistryUrls } from '.'; +import { TerraformProviderDatasource } from '.'; const consulData: any = loadFixture('azurerm-provider.json'); const hashicorpReleases: any = loadFixture('releaseBackendIndex.json'); const serviceDiscoveryResult: any = loadFixture('service-discovery.json'); -const primaryUrl = defaultRegistryUrls[0]; -const secondaryUrl = defaultRegistryUrls[1]; +const terraformProviderDatasource = new TerraformProviderDatasource(); +const primaryUrl = terraformProviderDatasource.defaultRegistryUrls[0]; +const secondaryUrl = terraformProviderDatasource.defaultRegistryUrls[1]; describe(getName(), () => { describe('getReleases', () => { @@ -26,7 +27,7 @@ describe(getName(), () => { httpMock.scope(secondaryUrl).get('/index.json').reply(200, {}); expect( await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'azurerm', }) ).toBeNull(); @@ -42,7 +43,7 @@ describe(getName(), () => { httpMock.scope(secondaryUrl).get('/index.json').reply(404); expect( await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'azurerm', }) ).toBeNull(); @@ -58,7 +59,7 @@ describe(getName(), () => { httpMock.scope(secondaryUrl).get('/index.json').replyWithError(''); expect( await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'azurerm', }) ).toBeNull(); @@ -72,7 +73,7 @@ describe(getName(), () => { .get('/.well-known/terraform.json') .reply(200, serviceDiscoveryResult); const res = await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'azurerm', }); expect(res).toMatchSnapshot(); @@ -88,7 +89,7 @@ describe(getName(), () => { .get('/.well-known/terraform.json') .reply(200, serviceDiscoveryResult); const res = await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'azure', lookupName: 'hashicorp/azurerm', registryUrls: ['https://registry.company.com'], @@ -112,7 +113,7 @@ describe(getName(), () => { .reply(200, JSON.parse(hashicorpReleases)); const res = await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'google-beta', }); expect(res).toMatchSnapshot(); @@ -131,7 +132,7 @@ describe(getName(), () => { httpMock.scope(secondaryUrl).get('/index.json').reply(404); const res = await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'datadog', }); expect(res).toMatchSnapshot(); @@ -143,7 +144,7 @@ describe(getName(), () => { httpMock.scope(secondaryUrl).get('/index.json').replyWithError(''); expect( await getPkgReleases({ - datasource, + datasource: TerraformProviderDatasource.id, depName: 'azurerm', }) ).toBeNull(); diff --git a/lib/datasource/terraform-provider/index.ts b/lib/datasource/terraform-provider/index.ts index 10986fedbc..d40ce9fb42 100644 --- a/lib/datasource/terraform-provider/index.ts +++ b/lib/datasource/terraform-provider/index.ts @@ -1,8 +1,8 @@ -import URL from 'url'; import { logger } from '../../logger'; -import * as packageCache from '../../util/cache/package'; -import { Http } from '../../util/http'; +import { cache } from '../../util/cache/package/decorator'; +import { parseUrl } from '../../util/url'; import * as hashicorpVersioning from '../../versioning/hashicorp'; +import { Datasource } from '../datasource'; import { getTerraformServiceDiscoveryResult } from '../terraform-module'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import type { @@ -10,110 +10,108 @@ import type { TerraformProviderReleaseBackend, } from './types'; -export const id = 'terraform-provider'; -export const customRegistrySupport = true; -export const defaultRegistryUrls = [ - 'https://registry.terraform.io', - 'https://releases.hashicorp.com', -]; -export const defaultVersioning = hashicorpVersioning.id; -export const registryStrategy = 'hunt'; +export class TerraformProviderDatasource extends Datasource { + static readonly id = 'terraform-provider'; -const http = new Http(id); - -async function queryRegistry( - lookupName: string, - registryURL: string, - repository: string -): Promise<ReleaseResult> { - const serviceDiscovery = await getTerraformServiceDiscoveryResult( - registryURL - ); - const backendURL = `${registryURL}${serviceDiscovery['providers.v1']}${repository}`; - const res = (await http.getJson<TerraformProvider>(backendURL)).body; - const dep: ReleaseResult = { - releases: null, - }; - if (res.source) { - dep.sourceUrl = res.source; - } - dep.releases = res.versions.map((version) => ({ - version, - })); - // set published date for latest release - const latestVersion = dep.releases.find( - (release) => res.version === release.version - ); - // istanbul ignore else - if (latestVersion) { - latestVersion.releaseTimestamp = res.published_at; + constructor() { + super(TerraformProviderDatasource.id); } - dep.homepage = `${registryURL}/providers/${repository}`; - logger.trace({ dep }, 'dep'); - return dep; -} -// TODO: add long term cache (#9590) -async function queryReleaseBackend( - lookupName: string, - registryURL: string, - repository: string -): Promise<ReleaseResult> { - const backendLookUpName = `terraform-provider-${lookupName}`; - const backendURL = registryURL + `/index.json`; - const res = (await http.getJson<TerraformProviderReleaseBackend>(backendURL)) - .body; + readonly defaultRegistryUrls = [ + 'https://registry.terraform.io', + 'https://releases.hashicorp.com', + ]; + + readonly defaultVersioning = hashicorpVersioning.id; + + readonly registryStrategy = 'hunt'; + + @cache({ + namespace: `datasource-${TerraformProviderDatasource.id}`, + key: (getReleasesConfig: GetReleasesConfig) => + `${ + getReleasesConfig.registryUrl + }/${TerraformProviderDatasource.getRepository(getReleasesConfig)}`, + }) + async getReleases({ + lookupName, + registryUrl, + }: GetReleasesConfig): Promise<ReleaseResult | null> { + logger.debug({ lookupName }, 'terraform-provider.getDependencies()'); + let dep: ReleaseResult = null; + const registryHost = parseUrl(registryUrl).host; + if (registryHost === 'releases.hashicorp.com') { + dep = await this.queryReleaseBackend(lookupName, registryUrl); + } else { + const repository = TerraformProviderDatasource.getRepository({ + lookupName, + }); + dep = await this.queryRegistry(registryUrl, repository); + } - if (!res[backendLookUpName]) { - return null; + return dep; } - const dep: ReleaseResult = { - releases: null, - sourceUrl: `https://github.com/terraform-providers/${backendLookUpName}`, - }; - dep.releases = Object.keys(res[backendLookUpName].versions).map( - (version) => ({ + private static getRepository({ lookupName }: GetReleasesConfig): string { + return lookupName.includes('/') ? lookupName : `hashicorp/${lookupName}`; + } + + private async queryRegistry( + registryURL: string, + repository: string + ): Promise<ReleaseResult> { + const serviceDiscovery = await getTerraformServiceDiscoveryResult( + registryURL + ); + const backendURL = `${registryURL}${serviceDiscovery['providers.v1']}${repository}`; + const res = (await this.http.getJson<TerraformProvider>(backendURL)).body; + const dep: ReleaseResult = { + releases: null, + }; + if (res.source) { + dep.sourceUrl = res.source; + } + dep.releases = res.versions.map((version) => ({ version, - }) - ); - logger.trace({ dep }, 'dep'); - return dep; -} + })); + // set published date for latest release + const latestVersion = dep.releases.find( + (release) => res.version === release.version + ); + // istanbul ignore else + if (latestVersion) { + latestVersion.releaseTimestamp = res.published_at; + } + dep.homepage = `${registryURL}/providers/${repository}`; + logger.trace({ dep }, 'dep'); + return dep; + } -/** - * terraform-provider.getReleases - * - * This function will fetch a provider from the public Terraform registry and return all semver versions. - */ -export async function getReleases({ - lookupName, - registryUrl, -}: GetReleasesConfig): Promise<ReleaseResult | null> { - const repository = lookupName.includes('/') - ? lookupName - : `hashicorp/${lookupName}`; + // TODO: add long term cache (#9590) + private async queryReleaseBackend( + lookupName: string, + registryURL: string + ): Promise<ReleaseResult> { + const backendLookUpName = `terraform-provider-${lookupName}`; + const backendURL = registryURL + `/index.json`; + const res = ( + await this.http.getJson<TerraformProviderReleaseBackend>(backendURL) + ).body; - const cacheNamespace = 'terraform-provider'; - const pkgUrl = `${registryUrl}/${repository}`; - const cachedResult = await packageCache.get<ReleaseResult>( - cacheNamespace, - pkgUrl - ); - // istanbul ignore if - if (cachedResult) { - return cachedResult; - } + if (!res[backendLookUpName]) { + return null; + } - logger.debug({ lookupName }, 'terraform-provider.getDependencies()'); - let dep: ReleaseResult = null; - const registryHost = URL.parse(registryUrl).host; - if (registryHost === 'releases.hashicorp.com') { - dep = await queryReleaseBackend(lookupName, registryUrl, repository); - } else { - dep = await queryRegistry(lookupName, registryUrl, repository); + const dep: ReleaseResult = { + releases: null, + sourceUrl: `https://github.com/terraform-providers/${backendLookUpName}`, + }; + dep.releases = Object.keys(res[backendLookUpName].versions).map( + (version) => ({ + version, + }) + ); + logger.trace({ dep }, 'dep'); + return dep; } - const cacheMinutes = 30; - await packageCache.set(cacheNamespace, pkgUrl, dep, cacheMinutes); - return dep; } diff --git a/lib/manager/terraform/providers.ts b/lib/manager/terraform/providers.ts index ffef811c08..6feb0f9209 100644 --- a/lib/manager/terraform/providers.ts +++ b/lib/manager/terraform/providers.ts @@ -1,5 +1,5 @@ import is from '@sindresorhus/is'; -import * as datasourceTerraformProvider from '../../datasource/terraform-provider'; +import { TerraformProviderDatasource } from '../../datasource/terraform-provider'; import { logger } from '../../logger'; import { SkipReason } from '../../types'; import type { PackageDependency } from '../types'; @@ -62,7 +62,7 @@ export function analyzeTerraformProvider(dep: PackageDependency): void { /* eslint-disable no-param-reassign */ dep.depType = 'provider'; dep.depName = dep.managerData.moduleName; - dep.datasource = datasourceTerraformProvider.id; + dep.datasource = TerraformProviderDatasource.id; if (is.nonEmptyString(dep.managerData.source)) { const source = sourceExtractionRegex.exec(dep.managerData.source); -- GitLab