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