backend-common: port GitlabUrlReader
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Options>();
|
||||
|
||||
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<boolean> {
|
||||
if (location.type !== 'gitlab/api') {
|
||||
return false;
|
||||
async read(url: string): Promise<Buffer> {
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -19,3 +19,4 @@ export { UrlReaders } from './UrlReaders';
|
||||
export { AzureUrlReader } from './AzureUrlReader';
|
||||
export { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
export { GithubUrlReader } from './GithubUrlReader';
|
||||
export { GitlabUrlReader } from './GitlabUrlReader';
|
||||
|
||||
Reference in New Issue
Block a user