catalog(github): merge github v3 to github
This commit is contained in:
@@ -58,7 +58,6 @@ export class LocationReaders implements LocationReader {
|
||||
return [
|
||||
new FileReaderProcessor(),
|
||||
new GithubReaderProcessor(),
|
||||
new GithubV3ReaderProcessor(),
|
||||
new GitlabReaderProcessor(),
|
||||
new YamlProcessor(),
|
||||
new EntityPolicyProcessor(entityPolicy),
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { GithubReaderProcessor } from './GithubReaderProcessor';
|
||||
|
||||
describe('GithubReaderProcessor', () => {
|
||||
it('should build raw api', () => {
|
||||
const processor = new GithubReaderProcessor();
|
||||
|
||||
const tests = [
|
||||
{
|
||||
target: 'https://github.com/a/b/blob/master/path/to/c.yaml',
|
||||
url: new URL(
|
||||
'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=master',
|
||||
),
|
||||
err: undefined,
|
||||
},
|
||||
{
|
||||
target: 'https://api.com/a/b/blob/master/path/to/c.yaml',
|
||||
url: null,
|
||||
err:
|
||||
'Incorrect url: https://api.com/a/b/blob/master/path/to/c.yaml, Error: Wrong GitHub URL',
|
||||
},
|
||||
{
|
||||
target: 'com/a/b/blob/master/path/to/c.yaml',
|
||||
url: null,
|
||||
err:
|
||||
'Incorrect url: com/a/b/blob/master/path/to/c.yaml, TypeError: Invalid URL: com/a/b/blob/master/path/to/c.yaml',
|
||||
},
|
||||
{
|
||||
target:
|
||||
'https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-order-component.yaml',
|
||||
url: new URL(
|
||||
'https://api.github.com/repos/spotify/backstage/contents/packages/catalog-model/examples/playback-order-component.yaml?ref=master',
|
||||
),
|
||||
err: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
if (test.err) {
|
||||
expect(() => processor.buildRawUrl(test.target)).toThrowError(test.err);
|
||||
} else {
|
||||
expect(processor.buildRawUrl(test.target)).toEqual(test.url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should return request options', () => {
|
||||
// todo
|
||||
});
|
||||
});
|
||||
@@ -15,11 +15,28 @@
|
||||
*/
|
||||
|
||||
import { LocationSpec } from '@backstage/catalog-model';
|
||||
import fetch from 'node-fetch';
|
||||
import fetch, { RequestInit, HeadersInit } from 'node-fetch';
|
||||
import * as result from './results';
|
||||
import { LocationProcessor, LocationProcessorEmit } from './types';
|
||||
|
||||
const privateToken: string = process.env.GITHUB_PRIVATE_TOKEN || '';
|
||||
|
||||
export class GithubReaderProcessor implements LocationProcessor {
|
||||
getRequestOptions(): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
Accept: 'application/vnd.github.v3.raw',
|
||||
};
|
||||
if (privateToken) {
|
||||
headers.Authorization = `token ${privateToken}`;
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
headers,
|
||||
};
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
optional: boolean,
|
||||
@@ -34,7 +51,7 @@ export class GithubReaderProcessor implements LocationProcessor {
|
||||
|
||||
// TODO(freben): Should "hard" errors thrown by this line be treated as
|
||||
// notFound instead of fatal?
|
||||
const response = await fetch(url.toString());
|
||||
const response = await fetch(url.toString(), this.getRequestOptions());
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.buffer();
|
||||
@@ -58,9 +75,9 @@ export class GithubReaderProcessor implements LocationProcessor {
|
||||
}
|
||||
|
||||
// Converts
|
||||
// from: https://github.com/a/b/blob/master/c.yaml
|
||||
// to: https://raw.githubusercontent.com/a/b/master/c.yaml
|
||||
private buildRawUrl(target: string): URL {
|
||||
// from: https://github.com/a/b/blob/master/path/to/c.yaml
|
||||
// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=master
|
||||
buildRawUrl(target: string): URL {
|
||||
try {
|
||||
const url = new URL(target);
|
||||
|
||||
@@ -69,6 +86,7 @@ export class GithubReaderProcessor implements LocationProcessor {
|
||||
userOrOrg,
|
||||
repoName,
|
||||
blobKeyword,
|
||||
ref,
|
||||
...restOfPath
|
||||
] = url.pathname.split('/');
|
||||
|
||||
@@ -80,13 +98,21 @@ export class GithubReaderProcessor implements LocationProcessor {
|
||||
blobKeyword !== 'blob' ||
|
||||
!restOfPath.join('/').match(/\.yaml$/)
|
||||
) {
|
||||
throw new Error('Wrong GitHub URL');
|
||||
throw new Error('Wrong GitHub URL or Invalid file path');
|
||||
}
|
||||
|
||||
// Removing the "blob" part
|
||||
url.pathname = [empty, userOrOrg, repoName, ...restOfPath].join('/');
|
||||
url.hostname = 'raw.githubusercontent.com';
|
||||
// transform to api
|
||||
url.pathname = [
|
||||
empty,
|
||||
'repos',
|
||||
userOrOrg,
|
||||
repoName,
|
||||
'contents',
|
||||
...restOfPath,
|
||||
].join('/');
|
||||
url.hostname = 'api.github.com';
|
||||
url.protocol = 'https';
|
||||
url.search = `ref=${ref}`;
|
||||
|
||||
return url;
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { GithubV3ReaderProcessor } from './GithubV3ReaderProcessor';
|
||||
|
||||
describe('GithubV3ReaderProcessor', () => {
|
||||
// it('should build raw url')
|
||||
});
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LocationSpec } from '@backstage/catalog-model';
|
||||
import fetch from 'node-fetch';
|
||||
import * as result from './results';
|
||||
import { LocationProcessor, LocationProcessorEmit } from './types';
|
||||
|
||||
const privateToken = process.env.GITHUB_PRIVATE_TOKEN;
|
||||
|
||||
export class GithubV3ReaderProcessor implements LocationProcessor {
|
||||
getRequestOptions(): RequestInit {
|
||||
const requestOptions: RequestInit = {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3.raw',
|
||||
},
|
||||
};
|
||||
|
||||
if (privateToken) {
|
||||
requestOptions.headers.Authorization = `token ${privateToken}`;
|
||||
}
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
optional: boolean,
|
||||
emit: LocationProcessorEmit,
|
||||
): Promise<boolean> {
|
||||
if (location.type !== 'github/v3') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = this.buildRawUrl(location.target);
|
||||
|
||||
const response = await fetch(url.toString(), this.getRequestOptions());
|
||||
|
||||
if (response.ok) {
|
||||
const buffer = await response.buffer();
|
||||
|
||||
emit(result.data(location, buffer));
|
||||
} 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));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Converts
|
||||
// from: https://github.com/a/b/blob/master/path/to/c.yaml
|
||||
// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=master
|
||||
buildRawUrl(target: string): URL {
|
||||
try {
|
||||
const url = new URL(target);
|
||||
|
||||
const [
|
||||
empty,
|
||||
userOrOrg,
|
||||
repoName,
|
||||
blobKeyword,
|
||||
ref,
|
||||
...restOfPath
|
||||
] = url.pathname.split('/');
|
||||
|
||||
if (
|
||||
url.hostname !== 'github.com' ||
|
||||
empty !== '' ||
|
||||
userOrOrg === '' ||
|
||||
repoName === '' ||
|
||||
blobKeyword !== 'blob' ||
|
||||
!restOfPath.join('/').match(/\.yaml$/)
|
||||
) {
|
||||
throw new Error('Wrong GitHub URL');
|
||||
}
|
||||
|
||||
// transform to api
|
||||
url.pathname = [
|
||||
empty,
|
||||
'repos',
|
||||
userOrOrg,
|
||||
repoName,
|
||||
'contents',
|
||||
...restOfPath,
|
||||
].join('/');
|
||||
url.hostname = 'api.github.com';
|
||||
url.protocol = 'https';
|
||||
url.search = `ref=${ref}`;
|
||||
|
||||
return url;
|
||||
} catch (e) {
|
||||
throw new Error(`Incorrect url: ${target}, ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user