Merge branch 'master' of github.com:spotify/backstage into mob/union-types

* 'master' of github.com:spotify/backstage:
  Update quickstart-app-auth.md
  Update changelog
  v0.1.1-alpha.22
  v0.1.1-alpha.21
  TechDocs: Use a flag to determine if we should use a local mkdocs or techdocs-container (#2503)
  feat(catalog-backend): delete log entries older than a cutoff
  fix(workflows): do not run e2e when contrib or docs change
  chore(contrib): form the basic contrib hierarchy
  fix(catalog-backend): address comments and add docs
  fix(catalog-backend): unify github and github/api, and use git-url-parse
This commit is contained in:
blam
2020-09-20 21:00:45 +02:00
83 changed files with 948 additions and 727 deletions
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,10 +20,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@kyma-project/asyncapi-react": "^0.11.0",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.11.0",
@@ -39,9 +39,9 @@
"swagger-ui-react": "^3.31.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/config-loader": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config-loader": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
@@ -30,7 +30,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/supertest": "^2.0.8",
"msw": "^0.19.5",
"supertest": "^4.0.2"
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
@@ -49,7 +49,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/jwt-decode": "2.2.1",
+7 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,13 +20,14 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"fs-extra": "^9.0.0",
"git-url-parse": "^11.2.0",
"knex": "^0.21.1",
"lodash": "^4.17.15",
"morgan": "^1.10.0",
@@ -39,7 +40,8 @@
"yup": "^0.29.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/git-url-parse": "^9.0.0",
"@types/lodash": "^4.14.151",
"@types/node-fetch": "^2.5.7",
"@types/supertest": "^2.0.8",
@@ -343,7 +343,14 @@ export class CommonDatabase implements Database {
entityName?: string,
message?: string,
): Promise<void> {
return this.database<DatabaseLocationUpdateLogEvent>(
// Remove log entries older than a day
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 1);
await this.database<DatabaseLocationUpdateLogEvent>('location_update_log')
.where('created_at', '<', cutoff.toISOString())
.del();
await this.database<DatabaseLocationUpdateLogEvent>(
'location_update_log',
).insert({
status,
@@ -27,7 +27,6 @@ import { AnnotateLocationEntityProcessor } from './processors/AnnotateLocationEn
import { EntityPolicyProcessor } from './processors/EntityPolicyProcessor';
import { FileReaderProcessor } from './processors/FileReaderProcessor';
import { GithubReaderProcessor } from './processors/GithubReaderProcessor';
import { GithubApiReaderProcessor } from './processors/GithubApiReaderProcessor';
import { GitlabApiReaderProcessor } from './processors/GitlabApiReaderProcessor';
import { GitlabReaderProcessor } from './processors/GitlabReaderProcessor';
import { BitbucketApiReaderProcessor } from './processors/BitbucketApiReaderProcessor';
@@ -67,18 +66,19 @@ export class LocationReaders implements LocationReader {
private readonly rulesEnforcer: CatalogRulesEnforcer;
static defaultProcessors(options: {
logger: Logger;
config?: Config;
entityPolicy?: EntityPolicy;
}): LocationProcessor[] {
const {
logger,
config = new ConfigReader({}, 'missing-config'),
entityPolicy = new EntityPolicies(),
} = options;
return [
StaticLocationProcessor.fromConfig(config),
new FileReaderProcessor(),
new GithubReaderProcessor(config),
GithubApiReaderProcessor.fromConfig(config),
GithubReaderProcessor.fromConfig(config, logger),
new GitlabApiReaderProcessor(config),
new GitlabReaderProcessor(),
new BitbucketApiReaderProcessor(config),
@@ -94,7 +94,7 @@ export class LocationReaders implements LocationReader {
constructor({
logger = getVoidLogger(),
config,
processors = LocationReaders.defaultProcessors({ config }),
processors = LocationReaders.defaultProcessors({ logger, config }),
}: Options) {
this.logger = logger;
this.processors = processors;
@@ -1,162 +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 { ConfigReader } from '@backstage/config';
import {
getRawUrl,
getRequestOptions,
GithubApiReaderProcessor,
ProviderConfig,
readConfig,
} from './GithubApiReaderProcessor';
describe('GithubApiReaderProcessor', () => {
describe('getRequestOptions', () => {
it('sets the correct API version', () => {
const config: ProviderConfig = { target: '', apiBaseUrl: '' };
expect((getRequestOptions(config).headers as any).Accept).toEqual(
'application/vnd.github.v3.raw',
);
});
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
target: '',
apiBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
target: '',
apiBaseUrl: '',
};
expect(
(getRequestOptions(withToken).headers as any).Authorization,
).toEqual('token A');
expect(
(getRequestOptions(withoutToken).headers as any).Authorization,
).toBeUndefined();
});
});
describe('getRawUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { target: '', apiBaseUrl: '' };
expect(() => getRawUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('passes through the happy path', () => {
const config: ProviderConfig = {
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
};
expect(
getRawUrl(
'https://github.com/a/b/blob/branchname/path/to/c.yaml',
config,
),
).toEqual(
new URL(
'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname',
),
);
});
});
describe('readConfig', () => {
function config(
providers: { target: string; apiBaseUrl?: string; token?: string }[],
) {
return ConfigReader.fromConfigs([
{
context: '',
data: {
catalog: { processors: { githubApi: { providers } } },
},
},
]);
}
it('adds a default GitHub entry when missing', () => {
const output = readConfig(config([]));
expect(output).toEqual([
{ target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
]);
});
it('injects the correct GitHub API base URL when missing', () => {
const output = readConfig(config([{ target: 'https://github.com' }]));
expect(output).toEqual([
{ target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
]);
});
it('rejects custom targets with no API base URL', () => {
expect(() =>
readConfig(config([{ target: 'https://ghe.company.com' }])),
).toThrow(
'Provider at https://ghe.company.com must configure an explicit apiBaseUrl',
);
});
it('rejects funky configs', () => {
expect(() => readConfig(config([{ target: 7 } as any]))).toThrow(
/target/,
);
expect(() => readConfig(config([{ noTarget: '7' } as any]))).toThrow(
/target/,
);
expect(() =>
readConfig(
config([{ target: 'https://github.com', apiBaseUrl: 7 } as any]),
),
).toThrow(/apiBaseUrl/);
expect(() =>
readConfig(config([{ target: 'https://github.com', token: 7 } as any])),
).toThrow(/token/);
});
});
describe('implementation', () => {
it('rejects unknown types', async () => {
const processor = new GithubApiReaderProcessor([
{ target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
]);
const location: LocationSpec = {
type: 'not-github/api',
target: 'https://github.com',
};
await expect(
processor.readLocation(location, false, () => {}),
).resolves.toBeFalsy();
});
it('rejects unknown targets', async () => {
const processor = new GithubApiReaderProcessor([
{ target: 'https://github.com', apiBaseUrl: 'https://api.github.com' },
]);
const location: LocationSpec = {
type: 'github/api',
target: 'https://not.github.com/apa',
};
await expect(
processor.readLocation(location, false, () => {}),
).rejects.toThrow(
/There is no GitHub provider that matches https:\/\/not.github.com\/apa/,
);
});
});
});
@@ -1,192 +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 { Config } from '@backstage/config';
import fetch, { HeadersInit, RequestInit } from 'node-fetch';
import * as result from './results';
import { LocationProcessor, LocationProcessorEmit } from './types';
/**
* The configuration parameters for a single GitHub API provider.
*/
export type ProviderConfig = {
/**
* The prefix of the target that this matches on, e.g. "https://github.com",
* with no trailing slash.
*/
target: string;
/**
* The base URL of the API of this provider, e.g. "https://api.github.com",
* with no trailing slash.
*/
apiBaseUrl: string;
/**
* The authorization token to use for requests to this provider.
*
* If no token is specified, anonymous API access is used.
*/
token?: string;
};
export function getRequestOptions(provider: ProviderConfig): RequestInit {
const headers: HeadersInit = {
Accept: 'application/vnd.github.v3.raw',
};
if (provider.token) {
headers.Authorization = `token ${provider.token}`;
}
return {
headers,
};
}
// Converts for example
// from: https://github.com/a/b/blob/branchname/path/to/c.yaml
// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname
export function getRawUrl(target: string, provider: ProviderConfig): URL {
try {
const oldPath = new URL(target).pathname.split('/');
const [, userOrOrg, repoName, blobOrRaw, ref, ...restOfPath] = oldPath;
if (
!userOrOrg ||
!repoName ||
(blobOrRaw !== 'blob' && blobOrRaw !== 'raw') ||
!restOfPath.join('/').match(/\.ya?ml$/)
) {
throw new Error('Wrong URL or Invalid file path');
}
// Transform to API path
const newPath = [
'repos',
userOrOrg,
repoName,
'contents',
...restOfPath,
].join('/');
return new URL(`${provider.apiBaseUrl}/${newPath}?ref=${ref}`);
} catch (e) {
throw new Error(`Incorrect URL: ${target}, ${e}`);
}
}
export function readConfig(configRoot: Config): ProviderConfig[] {
const providers: ProviderConfig[] = [];
// In a previous version of the configuration, we only supported github,
// and the "privateToken" key held the token to use for it. The new
// configuration method is to use the "providers" key instead.
const config = configRoot.getOptionalConfig('catalog.processors.githubApi');
const providerConfigs = config?.getOptionalConfigArray('providers') ?? [];
const legacyToken = config?.getOptionalString('privateToken');
// First read all the explicit providers
for (const providerConfig of providerConfigs) {
const target = providerConfig.getString('target').replace(/\/+$/, '');
let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
const token = providerConfig.getOptionalString('token');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (target === 'https://github.com') {
apiBaseUrl = 'https://api.github.com';
} else {
throw new Error(
`Provider at ${target} must configure an explicit apiBaseUrl`,
);
}
providers.push({ target, apiBaseUrl, token });
}
// If no explicit github.com provider was added, put one in the list as
// a convenience
if (!providers.some(p => p.target === 'https://github.com')) {
providers.push({
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
token: legacyToken,
});
}
return providers;
}
/**
* A processor that adds the ability to read files from GitHub v3 APIs, such as
* the one exposed by GitHub itself.
*/
export class GithubApiReaderProcessor implements LocationProcessor {
private providers: ProviderConfig[];
static fromConfig(config: Config) {
return new GithubApiReaderProcessor(readConfig(config));
}
constructor(providers: ProviderConfig[]) {
this.providers = providers;
}
async readLocation(
location: LocationSpec,
optional: boolean,
emit: LocationProcessorEmit,
): Promise<boolean> {
if (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.github.processors.githubApi.`,
);
}
try {
const url = getRawUrl(location.target, provider);
const options = getRequestOptions(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));
}
}
} catch (e) {
const message = `Unable to read ${location.type} ${location.target}, ${e}`;
emit(result.generalError(location, message));
}
return true;
}
}
@@ -0,0 +1,269 @@
/*
* 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 { getVoidLogger } from '@backstage/backend-common';
import { LocationSpec } from '@backstage/catalog-model';
import { ConfigReader } from '@backstage/config';
import {
getApiRequestOptions,
getApiUrl,
getRawRequestOptions,
getRawUrl,
GithubReaderProcessor,
ProviderConfig,
readConfig,
} from './GithubReaderProcessor';
describe('GithubReaderProcessor', () => {
describe('getApiRequestOptions', () => {
it('sets the correct API version', () => {
const config: ProviderConfig = { target: '', apiBaseUrl: '' };
expect((getApiRequestOptions(config).headers as any).Accept).toEqual(
'application/vnd.github.v3.raw',
);
});
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
target: '',
apiBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
target: '',
apiBaseUrl: '',
};
expect(
(getApiRequestOptions(withToken).headers as any).Authorization,
).toEqual('token A');
expect(
(getApiRequestOptions(withoutToken).headers as any).Authorization,
).toBeUndefined();
});
});
describe('getRawRequestOptions', () => {
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
target: '',
rawBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
target: '',
rawBaseUrl: '',
};
expect(
(getRawRequestOptions(withToken).headers as any).Authorization,
).toEqual('token A');
expect(
(getRawRequestOptions(withoutToken).headers as any).Authorization,
).toBeUndefined();
});
});
describe('getApiUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { target: '', apiBaseUrl: '' };
expect(() => getApiUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for github', () => {
const config: ProviderConfig = {
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
};
expect(
getApiUrl(
'https://github.com/a/b/blob/branchname/path/to/c.yaml',
config,
),
).toEqual(
new URL(
'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname',
),
);
expect(
getApiUrl(
'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml',
config,
),
).toEqual(
new URL(
'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname',
),
);
});
it('happy path for ghe', () => {
const config: ProviderConfig = {
target: 'https://ghe.mycompany.net',
apiBaseUrl: 'https://ghe.mycompany.net/api/v3',
};
expect(
getApiUrl(
'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml',
config,
),
).toEqual(
new URL(
'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname',
),
);
});
});
describe('getRawUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { target: '', apiBaseUrl: '' };
expect(() => getRawUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for github', () => {
const config: ProviderConfig = {
target: 'https://github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
};
expect(
getRawUrl(
'https://github.com/a/b/blob/branchname/path/to/c.yaml',
config,
),
).toEqual(
new URL(
'https://raw.githubusercontent.com/a/b/branchname/path/to/c.yaml',
),
);
});
it('happy path for ghe', () => {
const config: ProviderConfig = {
target: 'https://ghe.mycompany.net',
rawBaseUrl: 'https://ghe.mycompany.net/raw',
};
expect(
getRawUrl(
'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml',
config,
),
).toEqual(
new URL('https://ghe.mycompany.net/raw/a/b/branchname/path/to/c.yaml'),
);
});
});
describe('readConfig', () => {
function config(
providers: { target: string; apiBaseUrl?: string; token?: string }[],
) {
return ConfigReader.fromConfigs([
{
context: '',
data: {
catalog: { processors: { github: { providers } } },
},
},
]);
}
it('adds a default GitHub entry when missing', () => {
const output = readConfig(config([]), getVoidLogger());
expect(output).toEqual([
{
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
it('injects the correct GitHub API base URL when missing', () => {
const output = readConfig(
config([{ target: 'https://github.com' }]),
getVoidLogger(),
);
expect(output).toEqual([
{
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
it('rejects custom targets with no base URLs', () => {
expect(() =>
readConfig(
config([{ target: 'https://ghe.company.com' }]),
getVoidLogger(),
),
).toThrow(
'Provider at https://ghe.company.com must configure an explicit apiBaseUrl or rawBaseUrl',
);
});
it('rejects funky configs', () => {
expect(() =>
readConfig(config([{ target: 7 } as any]), getVoidLogger()),
).toThrow(/target/);
expect(() =>
readConfig(config([{ noTarget: '7' } as any]), getVoidLogger()),
).toThrow(/target/);
expect(() =>
readConfig(
config([{ target: 'https://github.com', apiBaseUrl: 7 } as any]),
getVoidLogger(),
),
).toThrow(/apiBaseUrl/);
expect(() =>
readConfig(
config([{ target: 'https://github.com', token: 7 } as any]),
getVoidLogger(),
),
).toThrow(/token/);
});
});
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/api',
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/api',
target: 'https://not.github.com/apa',
};
await expect(
processor.readLocation(location, false, () => {}),
).rejects.toThrow(
/There is no GitHub provider that matches https:\/\/not.github.com\/apa/,
);
});
});
});
@@ -15,33 +15,207 @@
*/
import { LocationSpec } from '@backstage/catalog-model';
import fetch, { RequestInit, HeadersInit } from 'node-fetch';
import { Config } from '@backstage/config';
import parseGitUri from 'git-url-parse';
import fetch, { HeadersInit, RequestInit } from 'node-fetch';
import { Logger } from 'winston';
import * as result from './results';
import { LocationProcessor, LocationProcessorEmit } from './types';
import { Config } from '@backstage/config';
export class GithubReaderProcessor implements LocationProcessor {
private privateToken: string;
/**
* The configuration parameters for a single GitHub API provider.
*/
export type ProviderConfig = {
/**
* The prefix of the target that this matches on, e.g. "https://github.com",
* with no trailing slash.
*/
target: string;
constructor(config?: Config) {
this.privateToken =
config?.getOptionalString('catalog.processors.github.privateToken') ?? '';
/**
* The base URL of the API of this provider, e.g. "https://api.github.com",
* with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The base URL of the raw fetch endpoint of this provider, e.g.
* "https://raw.githubusercontent.com", with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
rawBaseUrl?: string;
/**
* The authorization token to use for requests to this provider.
*
* If no token is specified, anonymous access is used.
*/
token?: string;
};
export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
const headers: HeadersInit = {
Accept: 'application/vnd.github.v3.raw',
};
if (provider.token) {
headers.Authorization = `token ${provider.token}`;
}
getRequestOptions(): RequestInit {
const headers: HeadersInit = {
Accept: 'application/vnd.github.v3.raw',
};
return {
headers,
};
}
if (this.privateToken !== '') {
headers.Authorization = `token ${this.privateToken}`;
export function getRawRequestOptions(provider: ProviderConfig): RequestInit {
const headers: HeadersInit = {};
if (provider.token) {
headers.Authorization = `token ${provider.token}`;
}
return {
headers,
};
}
// Converts for example
// from: https://github.com/a/b/blob/branchname/path/to/c.yaml
// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname
export function getApiUrl(target: string, provider: ProviderConfig): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
if (
!owner ||
!name ||
!ref ||
(filepathtype !== 'blob' && filepathtype !== 'raw') ||
!filepath?.match(/\.ya?ml$/)
) {
throw new Error('Wrong URL or invalid file path');
}
const requestOptions: RequestInit = {
headers,
};
const pathWithoutSlash = filepath.replace(/^\//, '');
return new URL(
`${provider.apiBaseUrl}/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`,
);
} catch (e) {
throw new Error(`Incorrect URL: ${target}, ${e}`);
}
}
return requestOptions;
// Converts for example
// from: https://github.com/a/b/blob/branchname/c.yaml
// to: https://raw.githubusercontent.com/a/b/branchname/c.yaml
export function getRawUrl(target: string, provider: ProviderConfig): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
if (
!owner ||
!name ||
!ref ||
(filepathtype !== 'blob' && filepathtype !== 'raw') ||
!filepath?.match(/\.ya?ml$/)
) {
throw new Error('Wrong URL or invalid file path');
}
const pathWithoutSlash = filepath.replace(/^\//, '');
return new URL(
`${provider.rawBaseUrl}/${owner}/${name}/${ref}/${pathWithoutSlash}`,
);
} catch (e) {
throw new Error(`Incorrect URL: ${target}, ${e}`);
}
}
export function readConfig(config: Config, logger: Logger): ProviderConfig[] {
const providers: ProviderConfig[] = [];
// TODO(freben): Deprecate the old config root entirely in a later release
if (config.has('catalog.processors.githubApi')) {
logger.warn(
'The catalog.processors.githubApi configuration key has been deprecated, please use catalog.processors.github instead',
);
}
// In a previous version of the configuration, we only supported github,
// and the "privateToken" key held the token to use for it. The new
// configuration method is to use the "providers" key instead.
const providerConfigs =
config.getOptionalConfigArray('catalog.processors.github.providers') ??
config.getOptionalConfigArray('catalog.processors.githubApi.providers') ??
[];
const legacyToken =
config.getOptionalString('catalog.processors.github.privateToken') ??
config.getOptionalString('catalog.processors.githubApi.privateToken');
// First read all the explicit providers
for (const providerConfig of providerConfigs) {
const target = providerConfig.getString('target').replace(/\/+$/, '');
let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
let rawBaseUrl = providerConfig.getOptionalString('rawBaseUrl');
const token = providerConfig.getOptionalString('token');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (target === 'https://github.com') {
apiBaseUrl = 'https://api.github.com';
}
if (rawBaseUrl) {
rawBaseUrl = rawBaseUrl.replace(/\/+$/, '');
} else if (target === 'https://github.com') {
rawBaseUrl = 'https://raw.githubusercontent.com';
}
if (!apiBaseUrl && !rawBaseUrl) {
throw new Error(
`Provider at ${target} must configure an explicit apiBaseUrl or rawBaseUrl`,
);
}
providers.push({ target, apiBaseUrl, rawBaseUrl, token });
}
// If no explicit github.com provider was added, put one in the list as
// a convenience
if (!providers.some(p => p.target === 'https://github.com')) {
providers.push({
target: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
token: legacyToken,
});
}
return providers;
}
/**
* 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[];
static fromConfig(config: Config, logger: Logger) {
return new GithubReaderProcessor(readConfig(config, logger));
}
constructor(providers: ProviderConfig[]) {
this.providers = providers;
}
async readLocation(
@@ -49,16 +223,30 @@ export class GithubReaderProcessor implements LocationProcessor {
optional: boolean,
emit: LocationProcessorEmit,
): Promise<boolean> {
if (location.type !== 'github') {
// The github/api type is for backward compatibility
if (location.type !== 'github' && location.type !== 'github/api') {
return false;
}
try {
const url = this.buildRawUrl(location.target);
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.`,
);
}
// TODO(freben): Should "hard" errors thrown by this line be treated as
// notFound instead of fatal?
const response = await fetch(url.toString(), this.getRequestOptions());
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();
@@ -80,41 +268,4 @@ export class GithubReaderProcessor implements LocationProcessor {
return true;
}
// 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 {
try {
const url = new URL(target);
const [
empty,
userOrOrg,
repoName,
blobKeyword,
...restOfPath
] = url.pathname.split('/');
if (
url.hostname !== 'github.com' ||
empty !== '' ||
userOrOrg === '' ||
repoName === '' ||
blobKeyword !== 'blob' ||
!restOfPath.join('/').match(/\.yaml$/)
) {
throw new Error('Wrong GitHub URL');
}
// Removing the "blob" part
url.pathname = [empty, userOrOrg, repoName, ...restOfPath].join('/');
url.hostname = 'raw.githubusercontent.com';
url.protocol = 'https';
return url;
} catch (e) {
throw new Error(`Incorrect url: ${target}, ${e}`);
}
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-graphql",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.18",
"@backstage/backend-common": "^0.1.1-alpha.18",
"@backstage/config": "^0.1.1-alpha.18",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@graphql-modules/core": "^0.7.17",
"apollo-server": "^2.16.1",
"graphql": "^15.3.0",
@@ -32,7 +32,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.18",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/express": "^4.17.7",
"@types/supertest": "^2.0.8",
"@graphql-codegen/cli": "^1.17.7",
+11 -11
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,14 +21,15 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.21",
"@backstage/plugin-techdocs": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.22",
"@backstage/plugin-techdocs": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
"classnames": "^2.2.6",
"moment": "^2.26.0",
"react": "^16.13.1",
@@ -37,13 +38,12 @@
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"swr": "^0.3.0",
"@types/react": "^16.9"
"swr": "^0.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/react-hooks": "^3.3.0",
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-circleci",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"postpack": "backstage-cli postpack"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -38,8 +38,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-explore",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,21 +21,21 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3",
"react-router": "6.0.0-beta.0"
"react-router": "6.0.0-beta.0",
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gcp-projects",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -32,8 +32,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-github-actions",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,11 +21,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/core-api": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/core-api": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -40,8 +40,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gitops-profiles",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -32,8 +32,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-graphiql",
"description": "Backstage plugin for browsing GraphQL APIs",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"private": false,
"publishConfig": {
"access": "public",
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -43,9 +43,9 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-graphql-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -19,10 +19,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/plugin-catalog-graphql": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/plugin-catalog-graphql": "^0.1.1-alpha.22",
"@graphql-modules/core": "^0.7.17",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"apollo-server": "^2.16.1",
"apollo-server-express": "^2.16.1",
@@ -36,7 +36,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/supertest": "^2.0.8",
"eslint-plugin-graphql": "^4.0.0",
"msw": "^0.20.5",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-identity-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
"cors": "^2.8.5",
@@ -33,7 +33,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"jest-fetch-mock": "^3.0.3"
},
"files": [
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-jenkins",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -36,8 +36,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-lighthouse",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,9 +21,9 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -34,9 +34,9 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-newrelic",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -31,8 +31,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-proxy-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -19,8 +19,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"@types/http-proxy-middleware": "^0.19.3",
"express": "^4.17.1",
@@ -35,7 +35,7 @@
"yup": "^0.29.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/node-fetch": "^2.5.7",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.0.0",
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-register-component",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -36,8 +36,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-rollbar-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"axios": "^0.20.0",
"camelcase-keys": "^6.2.2",
@@ -37,7 +37,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/supertest": "^2.0.8",
"jest-fetch-mock": "^3.0.3",
"supertest": "^4.0.2"
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-rollbar",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -37,9 +37,9 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/react-hooks": "^3.3.0",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@gitbeaker/core": "^23.5.0",
"@gitbeaker/node": "^23.5.0",
"@octokit/rest": "^18.0.0",
@@ -46,7 +46,7 @@
"yaml": "^1.10.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@octokit/types": "^5.4.1",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,9 +41,9 @@
"swr": "^0.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-sentry-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"axios": "^0.20.0",
"compression": "^1.7.4",
@@ -34,7 +34,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"jest-fetch-mock": "^3.0.3"
},
"files": [
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-sentry",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,9 +21,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -36,8 +36,8 @@
"timeago.js": "^4.0.2"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-tech-radar",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,9 +21,9 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -35,8 +35,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-techdocs-backend",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@types/dockerode": "^2.5.34",
"@types/express": "^4.17.6",
"command-exists-promise": "^2.0.2",
@@ -38,7 +38,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@types/node-fetch": "^2.5.7",
"supertest": "^4.0.2"
},
@@ -38,6 +38,7 @@ export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'techdocs-backend' });
const config = ConfigReader.fromConfigs([]);
logger.debug('Creating application...');
const preparers = new Preparers();
@@ -45,7 +46,7 @@ export async function startStandaloneServer(
preparers.register('dir', directoryPreparer);
const generators = new Generators();
const techdocsGenerator = new TechdocsGenerator(logger);
const techdocsGenerator = new TechdocsGenerator(logger, config);
generators.register('techdocs', techdocsGenerator);
const publisher = new LocalPublish(logger);
@@ -59,7 +60,7 @@ export async function startStandaloneServer(
logger,
publisher,
dockerClient,
config: ConfigReader.fromConfigs([]),
config,
});
const service = createServiceBuilder(module)
.enableCors({ origin: 'http://localhost:3000' })
@@ -16,6 +16,7 @@
import { Generators, TechdocsGenerator } from './';
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
const logger = getVoidLogger();
@@ -38,7 +39,10 @@ describe('generators', () => {
it('should return correct registered generator', async () => {
const generators = new Generators();
const techdocs = new TechdocsGenerator(logger);
const techdocs = new TechdocsGenerator(
logger,
ConfigReader.fromConfigs([]),
);
generators.register('techdocs', techdocs);
@@ -18,6 +18,8 @@ import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import { Logger } from 'winston';
import { PassThrough } from 'stream';
import { Config } from '@backstage/config';
import {
GeneratorBase,
@@ -26,18 +28,39 @@ import {
} from './types';
import { runDockerContainer, runCommand } from './helpers';
const commandExists = require('command-exists-promise');
type TechdocsGeneratorOptions = {
// This option enables users to configure if they want to use TechDocs container
// or generate without the container.
// This is used to avoid running into Docker in Docker environment.
runGeneratorIn: string;
};
const createStream = (): [string[], PassThrough] => {
const log = [] as Array<string>;
const stream = new PassThrough();
stream.on('data', chunk => {
const textValue = chunk.toString().trim();
if (textValue?.length > 1) log.push(textValue);
});
return [log, stream];
};
export class TechdocsGenerator implements GeneratorBase {
private readonly logger: Logger;
private readonly options: TechdocsGeneratorOptions;
constructor(logger: Logger) {
constructor(logger: Logger, config: Config) {
this.logger = logger;
this.options = {
runGeneratorIn:
config.getOptionalString('techdocs.generators.techdocs') ?? 'docker',
};
}
public async run({
directory,
logStream,
dockerClient,
}: GeneratorRunOptions): Promise<GeneratorRunResult> {
const tmpdirPath = os.tmpdir();
@@ -46,35 +69,46 @@ export class TechdocsGenerator implements GeneratorBase {
const resultDir = fs.mkdtempSync(
path.join(tmpdirResolvedPath, 'techdocs-tmp-'),
);
const [log, logStream] = createStream();
try {
const mkdocsInstalled = await commandExists('mkdocs');
if (mkdocsInstalled) {
await runCommand({
command: 'mkdocs',
args: ['build', '-d', resultDir, '-v'],
options: {
cwd: directory,
},
logStream,
});
} else {
await runDockerContainer({
imageName: 'spotify/techdocs',
args: ['build', '-d', '/result'],
logStream,
docsDir: directory,
resultDir,
dockerClient,
});
this.logger.info(
`[TechDocs]: Successfully generated docs from ${directory} into ${resultDir}`,
);
switch (this.options.runGeneratorIn) {
case 'local':
await runCommand({
command: 'mkdocs',
args: ['build', '-d', resultDir, '-v'],
options: {
cwd: directory,
},
logStream,
});
this.logger.info(
`[TechDocs]: Successfully generated docs from ${directory} into ${resultDir} using local mkdocs`,
);
break;
case 'docker':
await runDockerContainer({
imageName: 'spotify/techdocs',
args: ['build', '-d', '/result'],
logStream,
docsDir: directory,
resultDir,
dockerClient,
});
this.logger.info(
`[TechDocs]: Successfully generated docs from ${directory} into ${resultDir} using techdocs-container`,
);
break;
default:
throw new Error(
`Invalid config value "${this.options.runGeneratorIn}" provided in 'techdocs.generators.techdocs'.`,
);
}
} catch (error) {
this.logger.debug(
`[TechDocs]: Failed to generate docs from ${directory} into ${resultDir}`,
);
this.logger.debug(`[TechDocs]: Build failed with error: ${log}`);
throw new Error(
`Failed to generate docs from ${directory} into ${resultDir} with error ${error.message}`,
);
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-techdocs",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -22,12 +22,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/core-api": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/core-api": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -40,8 +40,8 @@
"sanitize-html": "^1.27.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-welcome",
"version": "0.1.1-alpha.21",
"version": "0.1.1-alpha.22",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -21,8 +21,8 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -32,8 +32,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",