From f50a233a4fcb89a0754ca7d17c37a338cd1722d2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 2 Oct 2020 15:37:29 +0200 Subject: [PATCH] backend-common: port GitlabUrlReader --- .../src/reading/GitlabUrlReader.test.ts | 27 +++-- .../src/reading/GitlabUrlReader.ts | 108 ++++++++++-------- .../backend-common/src/reading/UrlReaders.ts | 6 +- packages/backend-common/src/reading/index.ts | 1 + 4 files changed, 81 insertions(+), 61 deletions(-) diff --git a/packages/backend-common/src/reading/GitlabUrlReader.test.ts b/packages/backend-common/src/reading/GitlabUrlReader.test.ts index 7e55fece1b..17023d9120 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.test.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.test.ts @@ -14,28 +14,31 @@ * limitations under the License. */ -import { GitlabApiReaderProcessor } from './GitlabApiReaderProcessor'; +import { GitlabUrlReader, readConfig } from './GitlabUrlReader'; import { ConfigReader } from '@backstage/config'; -describe('GitlabApiReaderProcessor', () => { +describe('GitlabUrlReader', () => { const createConfig = (token: string | undefined) => ConfigReader.fromConfigs([ { context: '', data: { - catalog: { - processors: { - gitlabApi: { - privateToken: token, + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: token, }, - }, + ], }, }, }, ]); it('should build raw api', () => { - const processor = new GitlabApiReaderProcessor(createConfig(undefined)); + const processor = new GitlabUrlReader( + readConfig(createConfig(undefined))[0], + ); const tests = [ { @@ -90,7 +93,7 @@ describe('GitlabApiReaderProcessor', () => { { token: '', err: - "Invalid type in config for key 'catalog.processors.gitlabApi.privateToken' in '', got empty-string, wanted string", + "Invalid type in config for key 'integrations.gitlab[0].token' in '', got empty-string, wanted string", expect: { headers: { 'PRIVATE-TOKEN': '', @@ -110,11 +113,11 @@ describe('GitlabApiReaderProcessor', () => { for (const test of tests) { if (test.err) { expect( - () => new GitlabApiReaderProcessor(createConfig(test.token)), + () => new GitlabUrlReader(readConfig(createConfig(test.token))[0]), ).toThrowError(test.err); } else { - const processor = new GitlabApiReaderProcessor( - createConfig(test.token), + const processor = new GitlabUrlReader( + readConfig(createConfig(test.token))[0], ); expect(processor.getRequestOptions()).toEqual(test.expect); } diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index a51c306777..56ae91bb4b 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -14,65 +14,79 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; -import fetch, { RequestInit, HeadersInit } from 'node-fetch'; -import * as result from './results'; -import { LocationProcessor, LocationProcessorEmit } from './types'; +import fetch, { RequestInit, Response } from 'node-fetch'; import { Config } from '@backstage/config'; +import { NotFoundError } from '../errors'; +import { UrlReader } from './types'; +import { ReaderFactory } from './UrlReaders'; -export class GitlabApiReaderProcessor implements LocationProcessor { - private privateToken: string; +type Options = { + // TODO: added here for future support, but we only allow bitbucket.org for now + host: string; + token?: string; +}; - constructor(config: Config) { - this.privateToken = - config.getOptionalString('catalog.processors.gitlabApi.privateToken') ?? - ''; +export function readConfig(config: Config): Options[] { + const optionsArr = Array(); + + const providerConfigs = + config.getOptionalConfigArray('integrations.gitlab') ?? []; + + for (const providerConfig of providerConfigs) { + const host = providerConfig.getOptionalString('host') ?? 'gitlab.com'; + const token = providerConfig.getOptionalString('token'); + + optionsArr.push({ host, token }); } + // As a convenience we always make sure there's at least an unauthenticated + // reader for public gitlab repos. + if (!optionsArr.some(p => p.host === 'gitlab.com')) { + optionsArr.push({ host: 'gitlab.com' }); + } + + return optionsArr; +} + +export class GitlabUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config }) => { + return readConfig(config).map(options => { + const reader = new GitlabUrlReader(options); + const predicate = (url: URL) => url.host === options.host; + return { reader, predicate }; + }); + }; + + constructor(private readonly options: Options) {} + getRequestOptions(): RequestInit { - const headers: HeadersInit = { 'PRIVATE-TOKEN': '' }; - if (this.privateToken !== '') { - headers['PRIVATE-TOKEN'] = this.privateToken; - } - - const requestOptions: RequestInit = { - headers, + return { + headers: { + ['PRIVATE-TOKEN']: this.options.token ?? '', + }, }; - - return requestOptions; } - async readLocation( - location: LocationSpec, - optional: boolean, - emit: LocationProcessorEmit, - ): Promise { - if (location.type !== 'gitlab/api') { - return false; + async read(url: string): Promise { + const projectID = await this.getProjectID(url); + const builtUrl = this.buildRawUrl(url, projectID); + + let response: Response; + try { + response = await fetch(builtUrl.toString(), this.getRequestOptions()); + } catch (e) { + throw new Error(`Unable to read ${url}, ${e}`); } - try { - const projectID = await this.getProjectID(location.target); - const url = this.buildRawUrl(location.target, projectID); - const response = await fetch(url.toString(), this.getRequestOptions()); - if (response.ok) { - const data = await response.buffer(); - emit(result.data(location, data)); - } else { - const message = `${location.target} could not be read as ${url}, ${response.status} ${response.statusText}`; - if (response.status === 404) { - if (!optional) { - emit(result.notFoundError(location, message)); - } - } else { - emit(result.generalError(location, message)); - } - } - } catch (e) { - const message = `Unable to read ${location.type} ${location.target}, ${e}`; - emit(result.generalError(location, message)); + if (response.ok) { + return response.buffer(); } - return true; + + const message = `${url} could not be read as ${builtUrl}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + throw new Error(message); } // convert https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index f88c3bfacc..2535c953e9 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -24,6 +24,7 @@ import { import { AzureUrlReader } from './AzureUrlReader'; import { BitbucketUrlReader } from './BitbucketUrlReader'; import { GithubUrlReader } from './GithubUrlReader'; +import { GitlabUrlReader } from './GitlabUrlReader'; export type ReaderFactoryOptions = { config: Config; @@ -55,9 +56,10 @@ export class UrlReaders { static default({ logger }: { logger: Logger }) { return new UrlReaders( [ - GithubUrlReader.factory, - BitbucketUrlReader.factory, AzureUrlReader.factory, + BitbucketUrlReader.factory, + GithubUrlReader.factory, + GitlabUrlReader.factory, ], logger, ); diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index d1dc1e7952..8ecc08ebca 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -19,3 +19,4 @@ export { UrlReaders } from './UrlReaders'; export { AzureUrlReader } from './AzureUrlReader'; export { BitbucketUrlReader } from './BitbucketUrlReader'; export { GithubUrlReader } from './GithubUrlReader'; +export { GitlabUrlReader } from './GitlabUrlReader';