From 40f615e8ea80a007bc6471a839965dcc4af6adbe Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Thu, 10 Feb 2022 23:07:16 +0300 Subject: [PATCH] refactor(datasource/pod): Convert to class (#14133) * refactor(datasource/pod): Convert to class * Fix lint * Fix --- lib/constants/platform.spec.ts | 4 +- lib/datasource/api.ts | 4 +- lib/datasource/pod/index.spec.ts | 6 +- lib/datasource/pod/index.ts | 289 +++++++++++++++---------------- lib/manager/cocoapods/extract.ts | 4 +- lib/manager/cocoapods/index.ts | 4 +- 6 files changed, 153 insertions(+), 158 deletions(-) diff --git a/lib/constants/platform.spec.ts b/lib/constants/platform.spec.ts index 30c8572b84..1e1f407f9c 100644 --- a/lib/constants/platform.spec.ts +++ b/lib/constants/platform.spec.ts @@ -4,7 +4,7 @@ import { id as GH_TAGS_DS } from '../datasource/github-tags'; import { GitlabPackagesDatasource } from '../datasource/gitlab-packages'; import { GitlabReleasesDatasource } from '../datasource/gitlab-releases'; import { id as GL_TAGS_DS } from '../datasource/gitlab-tags'; -import { id as POD_DS } from '../datasource/pod'; +import { PodDatasource } from '../datasource/pod'; import { id as GITHUB_CHANGELOG_ID } from '../workers/pr/changelog/github'; import { id as GITLAB_CHANGELOG_ID } from '../workers/pr/changelog/gitlab'; import { @@ -36,7 +36,7 @@ describe('constants/platform', () => { it('should be part of the GITHUB_API_USING_HOST_TYPES ', () => { expect(GITHUB_API_USING_HOST_TYPES.includes(GH_TAGS_DS)).toBeTrue(); expect(GITHUB_API_USING_HOST_TYPES.includes(GH_RELEASES_DS)).toBeTrue(); - expect(GITHUB_API_USING_HOST_TYPES.includes(POD_DS)).toBeTrue(); + expect(GITHUB_API_USING_HOST_TYPES.includes(PodDatasource.id)).toBeTrue(); expect( GITHUB_API_USING_HOST_TYPES.includes(GITHUB_CHANGELOG_ID) ).toBeTrue(); diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts index 573a834f1d..43de52583e 100644 --- a/lib/datasource/api.ts +++ b/lib/datasource/api.ts @@ -28,7 +28,7 @@ import * as npm from './npm'; import * as nuget from './nuget'; import { OrbDatasource } from './orb'; import * as packagist from './packagist'; -import * as pod from './pod'; +import { PodDatasource } from './pod'; import { PypiDatasource } from './pypi'; import * as repology from './repology'; import { RubyVersionDatasource } from './ruby-version'; @@ -72,7 +72,7 @@ api.set('npm', npm); api.set('nuget', nuget); api.set('orb', new OrbDatasource()); api.set('packagist', packagist); -api.set('pod', pod); +api.set(PodDatasource.id, new PodDatasource()); api.set('pypi', new PypiDatasource()); api.set('repology', repology); api.set('ruby-version', new RubyVersionDatasource()); diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts index e2aef58e02..f08703b49a 100644 --- a/lib/datasource/pod/index.spec.ts +++ b/lib/datasource/pod/index.spec.ts @@ -2,11 +2,11 @@ import { getPkgReleases } from '..'; import * as httpMock from '../../../test/http-mock'; import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as rubyVersioning from '../../versioning/ruby'; -import * as pod from '.'; +import { PodDatasource } from '.'; const config = { versioning: rubyVersioning.id, - datasource: pod.id, + datasource: PodDatasource.id, depName: 'foo', registryUrls: [], }; @@ -30,7 +30,7 @@ describe('datasource/pod/index', () => { .reply(404); expect( await getPkgReleases({ - datasource: pod.id, + datasource: PodDatasource.id, depName: 'foobar', registryUrls: [], }) diff --git a/lib/datasource/pod/index.ts b/lib/datasource/pod/index.ts index 61fcd4b5de..ee82add16b 100644 --- a/lib/datasource/pod/index.ts +++ b/lib/datasource/pod/index.ts @@ -2,26 +2,14 @@ import crypto from 'crypto'; import { HOST_DISABLED } from '../../constants/error-messages'; 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 { GithubHttp } from '../../util/http/github'; import type { HttpError } from '../../util/http/types'; import { newlineRegex, regEx } from '../../util/regex'; +import { Datasource } from '../datasource'; import { massageGithubUrl } from '../metadata'; import type { GetReleasesConfig, ReleaseResult } from '../types'; -export const id = 'pod'; - -export const customRegistrySupport = true; -export const defaultRegistryUrls = ['https://cdn.cocoapods.org']; -export const registryStrategy = 'hunt'; - -const cacheNamespace = `datasource-${id}`; -const cacheMinutes = 30; - -const githubHttp = new GithubHttp(id); -const http = new Http(id); - // eslint-disable-next-line typescript-enum/no-enum, typescript-enum/no-const-enum const enum URLFormatOptions { WithShardWithSpec, @@ -39,6 +27,10 @@ function shardParts(lookupName: string): string[] { .split(''); } +const githubRegex = regEx( + /(?<hostURL>(^https:\/\/[a-zA-z0-9-.]+))\/(?<account>[^/]+)\/(?<repo>[^/]+?)(\.git|\/.*)?$/ +); + function releasesGithubUrl( lookupName: string, opts: { @@ -85,159 +77,162 @@ function handleError(lookupName: string, err: HttpError): void { } } -async function requestCDN( - url: string, - lookupName: string -): Promise<string | null> { - try { - const resp = await http.get(url); - if (resp?.body) { - return resp.body; - } - } catch (err) { - handleError(lookupName, err); +function isDefaultRepo(url: string): boolean { + const match = githubRegex.exec(url); + if (match) { + const { account, repo } = match.groups || {}; + return ( + account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs' + ); // https://github.com/CocoaPods/Specs.git } + return false; +} - return null; +function releasesCDNUrl(lookupName: string, registryUrl: string): string { + const shard = shardParts(lookupName).join('_'); + return `${registryUrl}/all_pods_versions_${shard}.txt`; } -async function requestGithub<T = unknown>( - url: string, - lookupName: string -): Promise<T | null> { - try { - const resp = await githubHttp.getJson<T>(url); - if (resp?.body) { - return resp.body; - } - } catch (err) { - handleError(lookupName, err); - } +export class PodDatasource extends Datasource { + static readonly id = 'pod'; - return null; -} + override readonly defaultRegistryUrls = ['https://cdn.cocoapods.org']; -const githubRegex = regEx( - /(?<hostURL>(^https:\/\/[a-zA-z0-9-.]+))\/(?<account>[^/]+)\/(?<repo>[^/]+?)(\.git|\/.*)?$/ -); + override readonly registryStrategy = 'hunt'; -async function getReleasesFromGithub( - lookupName: string, - opts: { hostURL: string; account: string; repo: string }, - useShard = true, - useSpecs = true, - urlFormatOptions = URLFormatOptions.WithShardWithSpec -): Promise<ReleaseResult | null> { - const url = releasesGithubUrl(lookupName, { ...opts, useShard, useSpecs }); - const resp = await requestGithub<{ name: string }[]>(url, lookupName); - if (resp) { - const releases = resp.map(({ name }) => ({ version: name })); - return { releases }; - } + githubHttp: GithubHttp; - // iterating through enum to support different url formats - switch (urlFormatOptions) { - case URLFormatOptions.WithShardWithSpec: - return getReleasesFromGithub( - lookupName, - opts, - true, - false, - URLFormatOptions.WithShardWithoutSpec - ); - case URLFormatOptions.WithShardWithoutSpec: - return getReleasesFromGithub( - lookupName, - opts, - false, - true, - URLFormatOptions.WithSpecsWithoutShard - ); - case URLFormatOptions.WithSpecsWithoutShard: - return getReleasesFromGithub( - lookupName, - opts, - false, - false, - URLFormatOptions.WithoutSpecsWithoutShard - ); - case URLFormatOptions.WithoutSpecsWithoutShard: - default: - return null; + constructor() { + super(PodDatasource.id); + this.githubHttp = new GithubHttp(PodDatasource.id); } -} -function releasesCDNUrl(lookupName: string, registryUrl: string): string { - const shard = shardParts(lookupName).join('_'); - return `${registryUrl}/all_pods_versions_${shard}.txt`; -} - -async function getReleasesFromCDN( - lookupName: string, - registryUrl: string -): Promise<ReleaseResult | null> { - const url = releasesCDNUrl(lookupName, registryUrl); - const resp = await requestCDN(url, lookupName); - if (resp) { - const lines = resp.split(newlineRegex); - for (let idx = 0; idx < lines.length; idx += 1) { - const line = lines[idx]; - const [name, ...versions] = line.split('/'); - if (name === lookupName.replace(regEx(/\/.*$/), '')) { - const releases = versions.map((version) => ({ version })); - return { releases }; + private async requestCDN( + url: string, + lookupName: string + ): Promise<string | null> { + try { + const resp = await this.http.get(url); + if (resp?.body) { + return resp.body; } + } catch (err) { + handleError(lookupName, err); } - } - return null; -} -function isDefaultRepo(url: string): boolean { - const match = githubRegex.exec(url); - if (match) { - const { account, repo } = match.groups || {}; - return ( - account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs' - ); // https://github.com/CocoaPods/Specs.git + return null; } - return false; -} -export async function getReleases({ - lookupName, - registryUrl, -}: GetReleasesConfig): Promise<ReleaseResult | null> { - const podName = lookupName.replace(regEx(/\/.*$/), ''); - - const cachedResult = await packageCache.get<ReleaseResult>( - cacheNamespace, - registryUrl + podName - ); - - // istanbul ignore if - if (cachedResult !== undefined) { - logger.trace(`CocoaPods: Return cached result for ${podName}`); - return cachedResult; + private async requestGithub<T = unknown>( + url: string, + lookupName: string + ): Promise<T | null> { + try { + const resp = await this.githubHttp.getJson<T>(url); + if (resp?.body) { + return resp.body; + } + } catch (err) { + handleError(lookupName, err); + } + + return null; } - let baseUrl = registryUrl.replace(regEx(/\/+$/), ''); - baseUrl = massageGithubUrl(baseUrl); - // In order to not abuse github API limits, query CDN instead - if (isDefaultRepo(baseUrl)) { - [baseUrl] = defaultRegistryUrls; + private async getReleasesFromGithub( + lookupName: string, + opts: { hostURL: string; account: string; repo: string }, + useShard = true, + useSpecs = true, + urlFormatOptions = URLFormatOptions.WithShardWithSpec + ): Promise<ReleaseResult | null> { + const url = releasesGithubUrl(lookupName, { ...opts, useShard, useSpecs }); + const resp = await this.requestGithub<{ name: string }[]>(url, lookupName); + if (resp) { + const releases = resp.map(({ name }) => ({ version: name })); + return { releases }; + } + + // iterating through enum to support different url formats + switch (urlFormatOptions) { + case URLFormatOptions.WithShardWithSpec: + return this.getReleasesFromGithub( + lookupName, + opts, + true, + false, + URLFormatOptions.WithShardWithoutSpec + ); + case URLFormatOptions.WithShardWithoutSpec: + return this.getReleasesFromGithub( + lookupName, + opts, + false, + true, + URLFormatOptions.WithSpecsWithoutShard + ); + case URLFormatOptions.WithSpecsWithoutShard: + return this.getReleasesFromGithub( + lookupName, + opts, + false, + false, + URLFormatOptions.WithoutSpecsWithoutShard + ); + case URLFormatOptions.WithoutSpecsWithoutShard: + default: + return null; + } } - let result: ReleaseResult | null = null; - const match = githubRegex.exec(baseUrl); - if (match) { - const { hostURL, account, repo } = match?.groups || {}; - const opts = { hostURL, account, repo }; - result = await getReleasesFromGithub(podName, opts); - } else { - result = await getReleasesFromCDN(podName, baseUrl); + private async getReleasesFromCDN( + lookupName: string, + registryUrl: string + ): Promise<ReleaseResult | null> { + const url = releasesCDNUrl(lookupName, registryUrl); + const resp = await this.requestCDN(url, lookupName); + if (resp) { + const lines = resp.split(newlineRegex); + for (let idx = 0; idx < lines.length; idx += 1) { + const line = lines[idx]; + const [name, ...versions] = line.split('/'); + if (name === lookupName.replace(regEx(/\/.*$/), '')) { + const releases = versions.map((version) => ({ version })); + return { releases }; + } + } + } + return null; } - await packageCache.set(cacheNamespace, podName, result, cacheMinutes); + @cache({ + ttlMinutes: 30, + namespace: `datasource-${PodDatasource.id}`, + key: ({ lookupName, registryUrl }: GetReleasesConfig) => + `${registryUrl}:${lookupName}`, + }) + async getReleases({ + lookupName, + registryUrl, + }: GetReleasesConfig): Promise<ReleaseResult | null> { + const podName = lookupName.replace(regEx(/\/.*$/), ''); + let baseUrl = registryUrl.replace(regEx(/\/+$/), ''); + baseUrl = massageGithubUrl(baseUrl); + // In order to not abuse github API limits, query CDN instead + if (isDefaultRepo(baseUrl)) { + [baseUrl] = this.defaultRegistryUrls; + } - return result; + let result: ReleaseResult | null = null; + const match = githubRegex.exec(baseUrl); + if (match) { + const { hostURL, account, repo } = match?.groups || {}; + const opts = { hostURL, account, repo }; + result = await this.getReleasesFromGithub(podName, opts); + } else { + result = await this.getReleasesFromCDN(podName, baseUrl); + } + + return result; + } } diff --git a/lib/manager/cocoapods/extract.ts b/lib/manager/cocoapods/extract.ts index 5c4ad4d668..f979086478 100644 --- a/lib/manager/cocoapods/extract.ts +++ b/lib/manager/cocoapods/extract.ts @@ -1,7 +1,7 @@ import { GitTagsDatasource } from '../../datasource/git-tags'; import * as datasourceGithubTags from '../../datasource/github-tags'; import * as datasourceGitlabTags from '../../datasource/gitlab-tags'; -import * as datasourcePod from '../../datasource/pod'; +import { PodDatasource } from '../../datasource/pod'; import { logger } from '../../logger'; import { getSiblingFileName, localPathExists } from '../../util/fs'; import { newlineRegex, regEx } from '../../util/regex'; @@ -119,7 +119,7 @@ export async function extractPackageFile( dep = { depName, groupName, - datasource: datasourcePod.id, + datasource: PodDatasource.id, currentValue, managerData, registryUrls, diff --git a/lib/manager/cocoapods/index.ts b/lib/manager/cocoapods/index.ts index 48284b915f..13c6be03d9 100644 --- a/lib/manager/cocoapods/index.ts +++ b/lib/manager/cocoapods/index.ts @@ -1,7 +1,7 @@ import { GitTagsDatasource } from '../../datasource/git-tags'; import * as datasourceGithubTags from '../../datasource/github-tags'; import * as datasourceGitlabTags from '../../datasource/gitlab-tags'; -import * as datasourcePod from '../../datasource/pod'; +import { PodDatasource } from '../../datasource/pod'; import * as rubyVersioning from '../../versioning/ruby'; export { extractPackageFile } from './extract'; @@ -16,5 +16,5 @@ export const supportedDatasources = [ GitTagsDatasource.id, datasourceGithubTags.id, datasourceGitlabTags.id, - datasourcePod.id, + PodDatasource.id, ]; -- GitLab