backend-common: port AzureUrlReader
This commit is contained in:
@@ -14,28 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AzureApiReaderProcessor } from './AzureApiReaderProcessor';
|
||||
import { AzureUrlReader, readConfig } from './AzureUrlReader';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('AzureApiReaderProcessor', () => {
|
||||
const host = 'dev.azure.com';
|
||||
|
||||
describe('AzureUrlReader', () => {
|
||||
const createConfig = (token: string | undefined) =>
|
||||
ConfigReader.fromConfigs([
|
||||
{
|
||||
context: '',
|
||||
data: {
|
||||
catalog: {
|
||||
processors: {
|
||||
azureApi: {
|
||||
privateToken: token,
|
||||
integrations: {
|
||||
azure: [
|
||||
{
|
||||
host,
|
||||
token,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
it('should build raw api', () => {
|
||||
const processor = new AzureApiReaderProcessor(createConfig(undefined));
|
||||
const processor = new AzureUrlReader(
|
||||
readConfig(createConfig(undefined))[0],
|
||||
);
|
||||
const tests = [
|
||||
{
|
||||
target:
|
||||
@@ -98,7 +103,7 @@ describe('AzureApiReaderProcessor', () => {
|
||||
headers: {},
|
||||
},
|
||||
err:
|
||||
"Invalid type in config for key 'catalog.processors.azureApi.privateToken' in '', got empty-string, wanted string",
|
||||
"Invalid type in config for key 'integrations.azure[0].token' in '', got empty-string, wanted string",
|
||||
},
|
||||
{
|
||||
token: undefined,
|
||||
@@ -111,10 +116,12 @@ describe('AzureApiReaderProcessor', () => {
|
||||
for (const test of tests) {
|
||||
if (test.err) {
|
||||
expect(
|
||||
() => new AzureApiReaderProcessor(createConfig(test.token)),
|
||||
() => new AzureUrlReader(readConfig(createConfig(test.token))[0]),
|
||||
).toThrowError(test.err);
|
||||
} else {
|
||||
const processor = new AzureApiReaderProcessor(createConfig(test.token));
|
||||
const processor = new AzureUrlReader(
|
||||
readConfig(createConfig(test.token))[0],
|
||||
);
|
||||
expect(processor.getRequestOptions()).toEqual(test.expect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,27 +14,63 @@
|
||||
* 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, HeadersInit, Response } from 'node-fetch';
|
||||
import { Config } from '@backstage/config';
|
||||
import { NotFoundError } from '../errors';
|
||||
import { UrlReader } from './types';
|
||||
import { ReaderFactory } from './UrlReaders';
|
||||
|
||||
export class AzureApiReaderProcessor implements LocationProcessor {
|
||||
private privateToken: string;
|
||||
type Options = {
|
||||
// TODO: added here for future support, but we only allow dev.azure.com for now
|
||||
host: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
constructor(config: Config) {
|
||||
this.privateToken =
|
||||
config.getOptionalString('catalog.processors.azureApi.privateToken') ??
|
||||
'';
|
||||
export function readConfig(config: Config): Options[] {
|
||||
const optionsArr = Array<Options>();
|
||||
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [];
|
||||
|
||||
for (const providerConfig of providerConfigs) {
|
||||
const host = providerConfig.getOptionalString('host') ?? 'dev.azure.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 azure repos.
|
||||
if (!optionsArr.some(p => p.host === 'dev.azure.com')) {
|
||||
optionsArr.push({ host: 'dev.azure.com' });
|
||||
}
|
||||
|
||||
return optionsArr;
|
||||
}
|
||||
|
||||
export class AzureUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config }) => {
|
||||
return readConfig(config).map(options => {
|
||||
const reader = new AzureUrlReader(options);
|
||||
const predicate = (url: URL) => url.host === options.host;
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(private readonly options: Options) {
|
||||
if (options.host !== 'dev.azure.com') {
|
||||
throw Error(
|
||||
`Azure integration currently only supports 'dev.azure.com', tried to use host '${options.host}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getRequestOptions(): RequestInit {
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
if (this.privateToken !== '') {
|
||||
if (this.options.token) {
|
||||
headers.Authorization = `Basic ${Buffer.from(
|
||||
`:${this.privateToken}`,
|
||||
`:${this.options.token}`,
|
||||
'utf8',
|
||||
).toString('base64')}`;
|
||||
}
|
||||
@@ -46,39 +82,26 @@ export class AzureApiReaderProcessor implements LocationProcessor {
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
optional: boolean,
|
||||
emit: LocationProcessorEmit,
|
||||
): Promise<boolean> {
|
||||
if (location.type !== 'azure/api') {
|
||||
return false;
|
||||
}
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const builtUrl = this.buildRawUrl(url);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
const url = this.buildRawUrl(location.target);
|
||||
|
||||
const response = await fetch(url.toString(), this.getRequestOptions());
|
||||
|
||||
// for private repos when PAT is not valid, Azure API returns a http status code 203 with sign in page html
|
||||
if (response.ok && response.status !== 203) {
|
||||
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(builtUrl.toString(), this.getRequestOptions());
|
||||
} 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;
|
||||
|
||||
// for private repos when PAT is not valid, Azure API returns a http status code 203 with sign in page html
|
||||
if (response.ok && response.status !== 203) {
|
||||
return response.buffer();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Converts
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { GithubUrlReader } from './GithubUrlReader';
|
||||
import { UrlReader } from './types';
|
||||
import {
|
||||
UrlReaderPredicateMux,
|
||||
UrlReaderPredicateTuple,
|
||||
} from './UrlReaderPredicateMux';
|
||||
import { AzureUrlReader } from './AzureUrlReader';
|
||||
import { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
import { GithubUrlReader } from './GithubUrlReader';
|
||||
|
||||
export type ReaderFactoryOptions = {
|
||||
config: Config;
|
||||
@@ -53,7 +54,11 @@ export class UrlReaders {
|
||||
*/
|
||||
static default({ logger }: { logger: Logger }) {
|
||||
return new UrlReaders(
|
||||
[GithubUrlReader.factory, BitbucketUrlReader.factory],
|
||||
[
|
||||
GithubUrlReader.factory,
|
||||
BitbucketUrlReader.factory,
|
||||
AzureUrlReader.factory,
|
||||
],
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
|
||||
export type { UrlReader } from './types';
|
||||
export { UrlReaders } from './UrlReaders';
|
||||
export { GithubUrlReader } from './GithubUrlReader';
|
||||
export { AzureUrlReader } from './AzureUrlReader';
|
||||
export { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
export { GithubUrlReader } from './GithubUrlReader';
|
||||
|
||||
Reference in New Issue
Block a user