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