backend-common: port GithubUrlReader

This commit is contained in:
Patrik Oldsberg
2020-09-29 18:46:25 +02:00
parent 8a3e63e590
commit b461b367d7
3 changed files with 43 additions and 85 deletions
+3
View File
@@ -39,11 +39,13 @@
"express": "^4.17.1",
"express-prom-bundle": "^6.1.0",
"express-promise-router": "^3.0.3",
"git-url-parse": "^11.2.0",
"helmet": "^4.0.0",
"knex": "^0.21.1",
"lodash": "^4.17.15",
"logform": "^2.1.1",
"morgan": "^1.10.0",
"node-fetch": "^2.6.0",
"prom-client": "^12.0.0",
"selfsigned": "^1.10.7",
"stoppable": "^1.1.0",
@@ -62,6 +64,7 @@
"@types/compression": "^1.7.0",
"@types/http-errors": "^1.6.3",
"@types/morgan": "^1.9.0",
"@types/node-fetch": "^2.5.7",
"@types/stoppable": "^1.1.0",
"@types/supertest": "^2.0.8",
"@types/webpack-env": "^1.15.2",
@@ -14,20 +14,19 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { LocationSpec } from '@backstage/catalog-model';
import { ConfigReader } from '@backstage/config';
import { getVoidLogger } from '../logging';
import {
getApiRequestOptions,
getApiUrl,
getRawRequestOptions,
getRawUrl,
GithubReaderProcessor,
GithubUrlReader,
ProviderConfig,
readConfig,
} from './GithubReaderProcessor';
} from './GithubUrlReader';
describe('GithubReaderProcessor', () => {
describe('GithubUrlReader', () => {
describe('getApiRequestOptions', () => {
it('sets the correct API version', () => {
const config: ProviderConfig = { target: '', apiBaseUrl: '' };
@@ -238,31 +237,15 @@ describe('GithubReaderProcessor', () => {
});
describe('implementation', () => {
it('rejects unknown types', async () => {
const processor = new GithubReaderProcessor([
{ target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
]);
const location: LocationSpec = {
type: 'not-github',
target: 'https://github.com',
};
await expect(
processor.readLocation(location, false, () => {}),
).resolves.toBeFalsy();
});
it('rejects unknown targets', async () => {
const processor = new GithubReaderProcessor([
{ target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
]);
const location: LocationSpec = {
type: 'github',
target: 'https://not.github.com/apa',
};
const processor = new GithubUrlReader({
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
});
await expect(
processor.readLocation(location, false, () => {}),
processor.read('https://not.github.com/apa'),
).rejects.toThrow(
/There is no GitHub provider that matches https:\/\/not.github.com\/apa/,
'Incorrect URL: https://not.github.com/apa, Error: Invalid GitHub URL or file path',
);
});
});
@@ -14,13 +14,12 @@
* limitations under the License.
*/
import { LocationSpec } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import parseGitUri from 'git-url-parse';
import fetch, { HeadersInit, RequestInit } from 'node-fetch';
import fetch, { HeadersInit, RequestInit, Response } from 'node-fetch';
import { Logger } from 'winston';
import * as result from './results';
import { LocationProcessor, LocationProcessorEmit } from './types';
import { NotFoundError } from '../errors';
import { UrlReader } from './types';
/**
* The configuration parameters for a single GitHub API provider.
@@ -101,7 +100,7 @@ export function getApiUrl(target: string, provider: ProviderConfig): URL {
!ref ||
(filepathtype !== 'blob' && filepathtype !== 'raw')
) {
throw new Error('Wrong URL or invalid file path');
throw new Error('Invalid GitHub URL or file path');
}
const pathWithoutSlash = filepath.replace(/^\//, '');
@@ -126,7 +125,7 @@ export function getRawUrl(target: string, provider: ProviderConfig): URL {
!ref ||
(filepathtype !== 'blob' && filepathtype !== 'raw')
) {
throw new Error('Wrong URL or invalid file path');
throw new Error('Invalid GitHub URL or file path');
}
const pathWithoutSlash = filepath.replace(/^\//, '');
@@ -205,65 +204,38 @@ export function readConfig(config: Config, logger: Logger): ProviderConfig[] {
* A processor that adds the ability to read files from GitHub v3 APIs, such as
* the one exposed by GitHub itself.
*/
export class GithubReaderProcessor implements LocationProcessor {
private providers: ProviderConfig[];
export class GithubUrlReader implements UrlReader {
private config: ProviderConfig;
static fromConfig(config: Config, logger: Logger) {
return new GithubReaderProcessor(readConfig(config, logger));
constructor(config: ProviderConfig) {
this.config = config;
}
constructor(providers: ProviderConfig[]) {
this.providers = providers;
}
async readLocation(
location: LocationSpec,
optional: boolean,
emit: LocationProcessorEmit,
): Promise<boolean> {
// The github/api type is for backward compatibility
if (location.type !== 'github' && location.type !== 'github/api') {
return false;
}
const provider = this.providers.find(p =>
location.target.startsWith(`${p.target}/`),
);
if (!provider) {
throw new Error(
`There is no GitHub provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.github.providers.`,
);
}
async read(url: string): Promise<Buffer> {
const useApi =
this.config.apiBaseUrl && (this.config.token || !this.config.rawBaseUrl);
const ghUrl = useApi
? getApiUrl(url, this.config)
: getRawUrl(url, this.config);
const options = useApi
? getApiRequestOptions(this.config)
: getRawRequestOptions(this.config);
let response: Response;
try {
const useApi =
provider.apiBaseUrl && (provider.token || !provider.rawBaseUrl);
const url = useApi
? getApiUrl(location.target, provider)
: getRawUrl(location.target, provider);
const options = useApi
? getApiRequestOptions(provider)
: getRawRequestOptions(provider);
const response = await fetch(url.toString(), options);
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));
}
}
response = await fetch(ghUrl.toString(), options);
} catch (e) {
const message = `Unable to read ${location.type} ${location.target}, ${e}`;
emit(result.generalError(location, message));
throw new Error(`Unable to read ${url}, ${e}`);
}
return true;
if (response.ok) {
return response.buffer();
}
const message = `${url} could not be read as ${ghUrl}, ${response.status} ${response.statusText}`;
if (response.status === 404) {
throw new NotFoundError(message);
}
throw new Error(message);
}
}