From 880b7fb67a4356fbac70c48788cc32386c2e460c Mon Sep 17 00:00:00 2001
From: Jamie Magee <jamie.magee@gmail.com>
Date: Sun, 13 Jun 2021 23:13:10 -0700
Subject: [PATCH] refactor(helm): convert to class-based datasource (#10425)

---
 lib/datasource/api.ts                    |   4 +-
 lib/datasource/helm/index.spec.ts        |  26 ++--
 lib/datasource/helm/index.ts             | 156 +++++++++++------------
 lib/manager/argocd/extract.ts            |   4 +-
 lib/manager/helm-requirements/extract.ts |   4 +-
 lib/manager/helmfile/extract.ts          |   4 +-
 lib/manager/helmv3/extract.ts            |   4 +-
 lib/manager/terraform/resources.ts       |   4 +-
 8 files changed, 96 insertions(+), 110 deletions(-)

diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts
index 8cae308617..7cc5dd3484 100644
--- a/lib/datasource/api.ts
+++ b/lib/datasource/api.ts
@@ -13,7 +13,7 @@ import * as githubTags from './github-tags';
 import * as gitlabTags from './gitlab-tags';
 import * as go from './go';
 import * as gradleVersion from './gradle-version';
-import * as helm from './helm';
+import { HelmDatasource } from './helm';
 import * as hex from './hex';
 import * as jenkinsPlugins from './jenkins-plugins';
 import * as maven from './maven';
@@ -50,7 +50,7 @@ api.set('github-tags', githubTags);
 api.set('gitlab-tags', gitlabTags);
 api.set('go', go);
 api.set('gradle-version', gradleVersion);
-api.set('helm', helm);
+api.set('helm', new HelmDatasource());
 api.set('hex', hex);
 api.set('jenkins-plugins', jenkinsPlugins);
 api.set('maven', maven);
diff --git a/lib/datasource/helm/index.spec.ts b/lib/datasource/helm/index.spec.ts
index d4ed9bb425..83ce27950c 100644
--- a/lib/datasource/helm/index.spec.ts
+++ b/lib/datasource/helm/index.spec.ts
@@ -1,7 +1,7 @@
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/http-mock';
 import { getName, loadFixture } from '../../../test/util';
-import { id as datasource } from '.';
+import { HelmDatasource } from '.';
 
 // Truncated index.yaml file
 const indexYaml = loadFixture('index.yaml');
@@ -15,7 +15,7 @@ describe(getName(), () => {
     it('returns null if lookupName was not provided', async () => {
       expect(
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: undefined,
           registryUrls: ['https://example-repository.com'],
         })
@@ -24,7 +24,7 @@ describe(getName(), () => {
     it('returns null if repository was not provided', async () => {
       expect(
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: 'some_chart',
           registryUrls: [],
         })
@@ -37,7 +37,7 @@ describe(getName(), () => {
         .reply(200, null);
       expect(
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: 'non_existent_chart',
           registryUrls: ['https://example-repository.com'],
         })
@@ -51,7 +51,7 @@ describe(getName(), () => {
         .reply(200, undefined);
       expect(
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: 'non_existent_chart',
           registryUrls: ['https://example-repository.com'],
         })
@@ -65,7 +65,7 @@ describe(getName(), () => {
         .reply(404);
       expect(
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: 'some_chart',
           registryUrls: ['https://example-repository.com'],
         })
@@ -80,7 +80,7 @@ describe(getName(), () => {
       let e;
       try {
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: 'some_chart',
           registryUrls: ['https://example-repository.com'],
         });
@@ -98,7 +98,7 @@ describe(getName(), () => {
         .replyWithError('');
       expect(
         await getPkgReleases({
-          datasource,
+          datasource: HelmDatasource.id,
           depName: 'some_chart',
           registryUrls: ['https://example-repository.com'],
         })
@@ -111,7 +111,7 @@ describe(getName(), () => {
         .get('/index.yaml')
         .reply(200, '# A comment');
       const releases = await getPkgReleases({
-        datasource,
+        datasource: HelmDatasource.id,
         depName: 'non_existent_chart',
         registryUrls: ['https://example-repository.com'],
       });
@@ -130,7 +130,7 @@ describe(getName(), () => {
         .get('/index.yaml')
         .reply(200, res);
       const releases = await getPkgReleases({
-        datasource,
+        datasource: HelmDatasource.id,
         depName: 'non_existent_chart',
         registryUrls: ['https://example-repository.com'],
       });
@@ -143,7 +143,7 @@ describe(getName(), () => {
         .get('/index.yaml')
         .reply(200, indexYaml);
       const releases = await getPkgReleases({
-        datasource,
+        datasource: HelmDatasource.id,
         depName: 'non_existent_chart',
         registryUrls: ['https://example-repository.com'],
       });
@@ -156,7 +156,7 @@ describe(getName(), () => {
         .get('/index.yaml')
         .reply(200, indexYaml);
       const releases = await getPkgReleases({
-        datasource,
+        datasource: HelmDatasource.id,
         depName: 'ambassador',
         registryUrls: ['https://example-repository.com'],
       });
@@ -170,7 +170,7 @@ describe(getName(), () => {
         .get('/subdir/index.yaml')
         .reply(200, indexYaml);
       await getPkgReleases({
-        datasource,
+        datasource: HelmDatasource.id,
         depName: 'ambassador',
         registryUrls: ['https://example-repository.com/subdir'],
       });
diff --git a/lib/datasource/helm/index.ts b/lib/datasource/helm/index.ts
index 3090fe3013..6cf0e412cb 100644
--- a/lib/datasource/helm/index.ts
+++ b/lib/datasource/helm/index.ts
@@ -1,104 +1,90 @@
 import is from '@sindresorhus/is';
 import { load } from 'js-yaml';
 import { logger } from '../../logger';
-import { ExternalHostError } from '../../types/errors/external-host-error';
-import * as packageCache from '../../util/cache/package';
-import { Http } from '../../util/http';
+import { cache } from '../../util/cache/package/decorator';
 import { ensureTrailingSlash } from '../../util/url';
+import { Datasource } from '../datasource';
 import type { GetReleasesConfig, ReleaseResult } from '../types';
 import type { HelmRepository, RepositoryData } from './types';
 
-export const id = 'helm';
+export class HelmDatasource extends Datasource {
+  static readonly id = 'helm';
 
-const http = new Http(id);
+  constructor() {
+    super(HelmDatasource.id);
+  }
 
-export const customRegistrySupport = true;
-export const defaultRegistryUrls = ['https://charts.helm.sh/stable'];
-export const registryStrategy = 'first';
+  readonly defaultRegistryUrls = ['https://charts.helm.sh/stable'];
 
-export const defaultConfig = {
-  commitMessageTopic: 'Helm release {{depName}}',
-  group: {
-    commitMessageTopic: '{{{groupName}}} Helm releases',
-  },
-};
+  readonly defaultConfig = {
+    commitMessageTopic: 'Helm release {{depName}}',
+    group: {
+      commitMessageTopic: '{{{groupName}}} Helm releases',
+    },
+  };
 
-export async function getRepositoryData(
-  repository: string
-): Promise<RepositoryData> {
-  const cacheNamespace = 'datasource-helm';
-  const cacheKey = repository;
-  const cachedIndex = await packageCache.get<RepositoryData>(
-    cacheNamespace,
-    cacheKey
-  );
-  // istanbul ignore if
-  if (cachedIndex) {
-    return cachedIndex;
-  }
-  let res: any;
-  try {
-    res = await http.get('index.yaml', {
-      baseUrl: ensureTrailingSlash(repository),
-    });
-    if (!res || !res.body) {
-      logger.warn(`Received invalid response from ${repository}`);
-      return null;
-    }
-  } catch (err) {
-    if (
-      err.statusCode === 429 ||
-      (err.statusCode >= 500 && err.statusCode < 600)
-    ) {
-      throw new ExternalHostError(err);
+  @cache({
+    namespace: `datasource-${HelmDatasource.id}`,
+    key: (repository: string) => repository,
+  })
+  async getRepositoryData(repository: string): Promise<RepositoryData | null> {
+    let res: any;
+    try {
+      res = await this.http.get('index.yaml', {
+        baseUrl: ensureTrailingSlash(repository),
+      });
+      if (!res || !res.body) {
+        logger.warn(`Received invalid response from ${repository}`);
+        return null;
+      }
+    } catch (err) {
+      this.handleGenericErrors(err);
     }
-    throw err;
-  }
-  try {
-    const doc = load(res.body, {
-      json: true,
-    }) as HelmRepository;
-    if (!is.plainObject<HelmRepository>(doc)) {
+    try {
+      const doc = load(res.body, {
+        json: true,
+      }) as HelmRepository;
+      if (!is.plainObject<HelmRepository>(doc)) {
+        logger.warn(`Failed to parse index.yaml from ${repository}`);
+        return null;
+      }
+      const result: RepositoryData = {};
+      for (const [name, releases] of Object.entries(doc.entries)) {
+        result[name] = {
+          homepage: releases[0].home,
+          sourceUrl: releases[0].sources ? releases[0].sources[0] : undefined,
+          releases: releases.map((release) => ({
+            version: release.version,
+            releaseTimestamp: release.created ? release.created : null,
+          })),
+        };
+      }
+
+      return result;
+    } catch (err) {
       logger.warn(`Failed to parse index.yaml from ${repository}`);
+      logger.debug(err);
       return null;
     }
-    const result: RepositoryData = {};
-    for (const [name, releases] of Object.entries(doc.entries)) {
-      result[name] = {
-        homepage: releases[0].home,
-        sourceUrl: releases[0].sources ? releases[0].sources[0] : undefined,
-        releases: releases.map((release) => ({
-          version: release.version,
-          releaseTimestamp: release.created ? release.created : null,
-        })),
-      };
-    }
-    const cacheMinutes = 20;
-    await packageCache.set(cacheNamespace, cacheKey, result, cacheMinutes);
-    return result;
-  } catch (err) {
-    logger.warn(`Failed to parse index.yaml from ${repository}`);
-    logger.debug(err);
-    return null;
   }
-}
 
-export async function getReleases({
-  lookupName,
-  registryUrl: helmRepository,
-}: GetReleasesConfig): Promise<ReleaseResult | null> {
-  const repositoryData = await getRepositoryData(helmRepository);
-  if (!repositoryData) {
-    logger.debug(`Couldn't get index.yaml file from ${helmRepository}`);
-    return null;
-  }
-  const releases = repositoryData[lookupName];
-  if (!releases) {
-    logger.debug(
-      { dependency: lookupName },
-      `Entry ${lookupName} doesn't exist in index.yaml from ${helmRepository}`
-    );
-    return null;
+  async getReleases({
+    lookupName,
+    registryUrl: helmRepository,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const repositoryData = await this.getRepositoryData(helmRepository);
+    if (!repositoryData) {
+      logger.debug(`Couldn't get index.yaml file from ${helmRepository}`);
+      return null;
+    }
+    const releases = repositoryData[lookupName];
+    if (!releases) {
+      logger.debug(
+        { dependency: lookupName },
+        `Entry ${lookupName} doesn't exist in index.yaml from ${helmRepository}`
+      );
+      return null;
+    }
+    return releases;
   }
-  return releases;
 }
diff --git a/lib/manager/argocd/extract.ts b/lib/manager/argocd/extract.ts
index 40083281be..7de4ca1ece 100644
--- a/lib/manager/argocd/extract.ts
+++ b/lib/manager/argocd/extract.ts
@@ -1,6 +1,6 @@
 import { loadAll } from 'js-yaml';
 import * as gitTags from '../../datasource/git-tags';
-import * as helm from '../../datasource/helm';
+import { HelmDatasource } from '../../datasource/helm';
 import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
 import type { ApplicationDefinition } from './types';
 import { fileTestRegex } from './util';
@@ -20,7 +20,7 @@ function createDependency(
       depName: source.chart,
       registryUrls: [source.repoURL],
       currentValue: source.targetRevision,
-      datasource: helm.id,
+      datasource: HelmDatasource.id,
     };
   }
   return {
diff --git a/lib/manager/helm-requirements/extract.ts b/lib/manager/helm-requirements/extract.ts
index 01bd698f36..bea85701c7 100644
--- a/lib/manager/helm-requirements/extract.ts
+++ b/lib/manager/helm-requirements/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import { load } from 'js-yaml';
-import * as datasourceHelm from '../../datasource/helm';
+import { HelmDatasource } from '../../datasource/helm';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
 import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
@@ -71,7 +71,7 @@ export function extractPackageFile(
   });
   const res = {
     deps,
-    datasource: datasourceHelm.id,
+    datasource: HelmDatasource.id,
   };
   return res;
 }
diff --git a/lib/manager/helmfile/extract.ts b/lib/manager/helmfile/extract.ts
index 66d47da65c..f15090b181 100644
--- a/lib/manager/helmfile/extract.ts
+++ b/lib/manager/helmfile/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import { loadAll } from 'js-yaml';
-import * as datasourceHelm from '../../datasource/helm';
+import { HelmDatasource } from '../../datasource/helm';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
 import type { ExtractConfig, PackageDependency, PackageFile } from '../types';
@@ -88,5 +88,5 @@ export function extractPackageFile(
     return null;
   }
 
-  return { deps, datasource: datasourceHelm.id } as PackageFile;
+  return { deps, datasource: HelmDatasource.id } as PackageFile;
 }
diff --git a/lib/manager/helmv3/extract.ts b/lib/manager/helmv3/extract.ts
index b19cb3d79a..d589d3403c 100644
--- a/lib/manager/helmv3/extract.ts
+++ b/lib/manager/helmv3/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
 import { load } from 'js-yaml';
-import * as datasourceHelm from '../../datasource/helm';
+import { HelmDatasource } from '../../datasource/helm';
 import { logger } from '../../logger';
 import { SkipReason } from '../../types';
 import { getSiblingFileName, localPathExists } from '../../util/fs';
@@ -90,7 +90,7 @@ export async function extractPackageFile(
   });
   const res: PackageFile = {
     deps,
-    datasource: datasourceHelm.id,
+    datasource: HelmDatasource.id,
     packageFileVersion,
   };
   const lockFileName = getSiblingFileName(fileName, 'Chart.lock');
diff --git a/lib/manager/terraform/resources.ts b/lib/manager/terraform/resources.ts
index 7cf7799639..075a1b63b3 100644
--- a/lib/manager/terraform/resources.ts
+++ b/lib/manager/terraform/resources.ts
@@ -1,4 +1,4 @@
-import * as datasourceHelm from '../../datasource/helm';
+import { HelmDatasource } from '../../datasource/helm';
 import { SkipReason } from '../../types';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency } from '../types';
@@ -104,7 +104,7 @@ export function analyseTerraformResource(
       dep.depType = 'helm_release';
       dep.registryUrls = [dep.managerData.repository];
       dep.depName = dep.managerData.chart;
-      dep.datasource = datasourceHelm.id;
+      dep.datasource = HelmDatasource.id;
       break;
 
     default:
-- 
GitLab