Merge remote-tracking branch 'upstream/master' into mcalus3/add-catalog-import-plugin
This commit is contained in:
@@ -1,5 +1,23 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f3bb55ee3: APIs now have real entity pages that are customizable in the app.
|
||||
Therefore the old entity page from this plugin is removed.
|
||||
See the `packages/app` on how to create and customize the API entity page.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6f70ed7a9: Replace usage of implementsApis with relations
|
||||
- Updated dependencies [6f70ed7a9]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- @backstage/plugin-catalog@0.2.4
|
||||
- @backstage/catalog-model@0.3.1
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -21,7 +21,7 @@ Right now, the following API formats are supported:
|
||||
Other formats are displayed as plain text, but this can easily be extended.
|
||||
|
||||
To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api).
|
||||
To link that an component implements an API, see [`implementsApis` property on components](https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional).
|
||||
To link that a component provides or consumes an API, see the [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) properties on the Component kind.
|
||||
|
||||
## Links
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.0",
|
||||
"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.3.0",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/core": "^0.3.2",
|
||||
"@backstage/plugin-catalog": "^0.2.3",
|
||||
"@backstage/plugin-catalog": "^0.2.4",
|
||||
"@backstage/theme": "^0.2.1",
|
||||
"@kyma-project/asyncapi-react": "^0.14.2",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
@@ -40,7 +40,7 @@
|
||||
"swagger-ui-react": "^3.31.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
+5
-6
@@ -28,7 +28,7 @@ spec:
|
||||
type: service
|
||||
lifecycle: production
|
||||
owner: guest
|
||||
implementsApis:
|
||||
providesApis:
|
||||
- example-api
|
||||
`;
|
||||
|
||||
@@ -47,11 +47,10 @@ export const MissingImplementsApisEmptyState = () => {
|
||||
missing="field"
|
||||
title="No APIs implemented by this entity"
|
||||
description={
|
||||
<Typography>
|
||||
<>
|
||||
Components can implement APIs that are displayed on this page. You
|
||||
need to fill the <code>implementsApis</code> field to enable this
|
||||
tool.
|
||||
</Typography>
|
||||
need to fill the <code>providesApis</code> field to enable this tool.
|
||||
</>
|
||||
}
|
||||
action={
|
||||
<>
|
||||
@@ -71,7 +70,7 @@ export const MissingImplementsApisEmptyState = () => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
href="https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional"
|
||||
href="https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional"
|
||||
>
|
||||
Read more
|
||||
</Button>
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { catalogRoute } from '../routes';
|
||||
import { EntityPageApi } from './EntityPageApi';
|
||||
import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState';
|
||||
|
||||
const isPluginApplicableToEntity = (entity: Entity) => {
|
||||
return ((entity.spec?.implementsApis as string[]) || []).length > 0;
|
||||
// TODO: Also support RELATION_CONSUMES_API
|
||||
return entity.relations?.some(r => r.type === RELATION_PROVIDES_API);
|
||||
};
|
||||
|
||||
export const Router = ({ entity }: { entity: Entity }) =>
|
||||
|
||||
@@ -22,7 +22,7 @@ import * as React from 'react';
|
||||
import { apiDocsConfigRef } from '../../config';
|
||||
import { ApiExplorerTable } from './ApiExplorerTable';
|
||||
|
||||
const entites: Entity[] = [
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'API',
|
||||
@@ -70,7 +70,7 @@ describe('ApiCatalogTable component', () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<ApiExplorerTable entities={entites} loading={false} />
|
||||
<ApiExplorerTable entities={entities} loading={false} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -14,8 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentEntity } from '@backstage/catalog-model';
|
||||
import {
|
||||
ComponentEntity,
|
||||
RELATION_PROVIDES_API,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
export const useComponentApiNames = (entity: ComponentEntity) => {
|
||||
return (entity.spec?.implementsApis as string[]) || [];
|
||||
// TODO: This code doesn't handle namespaces and kinds correctly, but will be removed soon
|
||||
return (
|
||||
entity.relations
|
||||
?.filter(r => r.type === RELATION_PROVIDES_API)
|
||||
?.map(r => r.target.name) || []
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-app-backend
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ff1301d28: Warn if the app-backend can't start-up because the static directory that should be served is unavailable.
|
||||
- Updated dependencies [3aa7efb3f]
|
||||
- Updated dependencies [b3d4e4e57]
|
||||
- @backstage/backend-common@0.3.2
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-app-backend",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"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.3.0",
|
||||
"@backstage/backend-common": "^0.3.2",
|
||||
"@backstage/config-loader": "^0.3.0",
|
||||
"@backstage/config": "^0.1.1",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -31,7 +31,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.0",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.20.5",
|
||||
"supertest": "^4.0.2"
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 50eff1d00: Allow the backend to register custom AuthProviderFactories
|
||||
- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure
|
||||
- Updated dependencies [3aa7efb3f]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- Updated dependencies [b3d4e4e57]
|
||||
- @backstage/backend-common@0.3.2
|
||||
- @backstage/catalog-model@0.3.1
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"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.3.1",
|
||||
"@backstage/backend-common": "^0.3.2",
|
||||
"@backstage/catalog-client": "^0.3.1",
|
||||
"@backstage/catalog-model": "^0.3.0",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/config": "^0.1.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
@@ -55,7 +55,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
|
||||
@@ -15,3 +15,11 @@
|
||||
*/
|
||||
|
||||
export * from './service/router';
|
||||
export * from './providers';
|
||||
|
||||
// flow package provides 2 functions
|
||||
// ensuresXRequestedWith and postMessageResponse to safely handle CORS requests for login. The WebMessageResponse type in flow is used to type the response from the login-popup
|
||||
export * from './lib/flow';
|
||||
|
||||
// OAuth wrapper over a passport or a custom `startegy`.
|
||||
export * from './lib/oauth';
|
||||
|
||||
@@ -81,6 +81,52 @@ describe('oauth helpers', () => {
|
||||
expect(mockResponse.end).toBeCalledWith(expect.stringContaining(encoded));
|
||||
});
|
||||
|
||||
it('should call postMessage twice but only one of them with target *', () => {
|
||||
let responseBody = '';
|
||||
|
||||
const mockResponse = ({
|
||||
end: jest.fn(body => {
|
||||
responseBody = body;
|
||||
return this;
|
||||
}),
|
||||
setHeader: jest.fn().mockReturnThis(),
|
||||
} as unknown) as express.Response;
|
||||
|
||||
const data: WebMessageResponse = {
|
||||
type: 'authorization_response',
|
||||
response: {
|
||||
providerInfo: {
|
||||
accessToken: 'ACCESS_TOKEN',
|
||||
idToken: 'ID_TOKEN',
|
||||
expiresInSeconds: 10,
|
||||
scope: 'email',
|
||||
},
|
||||
profile: {
|
||||
email: 'foo@bar.com',
|
||||
},
|
||||
backstageIdentity: {
|
||||
id: 'a',
|
||||
idToken: 'a.b.c',
|
||||
},
|
||||
},
|
||||
};
|
||||
postMessageResponse(mockResponse, appOrigin, data);
|
||||
expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2);
|
||||
expect(
|
||||
responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g),
|
||||
).toHaveLength(1);
|
||||
|
||||
const errData: WebMessageResponse = {
|
||||
type: 'authorization_response',
|
||||
error: new Error('Unknown error occurred'),
|
||||
};
|
||||
postMessageResponse(mockResponse, appOrigin, errData);
|
||||
expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2);
|
||||
expect(
|
||||
responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('handles single quotes and unicode chars safely', () => {
|
||||
const mockResponse = ({
|
||||
end: jest.fn().mockReturnThis(),
|
||||
|
||||
@@ -38,10 +38,24 @@ export const postMessageResponse = (
|
||||
// data.
|
||||
|
||||
// TODO: Make target app origin configurable globally
|
||||
|
||||
//
|
||||
// postMessage fails silently if the targetOrigin is disallowed.
|
||||
// So 2 postMessages are sent from the popup to the parent window.
|
||||
// First, the origin being used to post the actual authorization response is
|
||||
// shared with the parent window with a postMessage with targetOrigin '*'.
|
||||
// Second, the actual authorization response is sent with the app origin
|
||||
// as the targetOrigin.
|
||||
// If the first message was received but the actual auth response was
|
||||
// never received, the event listener can conclude that targetOrigin
|
||||
// was disallowed, indicating potential misconfiguration.
|
||||
//
|
||||
const script = `
|
||||
var json = decodeURIComponent('${base64Data}');
|
||||
var authResponse = decodeURIComponent('${base64Data}');
|
||||
var origin = decodeURIComponent('${base64Origin}');
|
||||
(window.opener || window.parent).postMessage(JSON.parse(json), origin);
|
||||
var originInfo = {'type': 'config_info', 'targetOrigin': origin};
|
||||
(window.opener || window.parent).postMessage(originInfo, '*');
|
||||
(window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);
|
||||
window.close();
|
||||
`;
|
||||
const hash = crypto.createHash('sha256').update(script).digest('base64');
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
|
||||
export { ensuresXRequestedWith, postMessageResponse } from './authFlowHelpers';
|
||||
|
||||
export type { WebMessageResponse } from './types';
|
||||
|
||||
@@ -149,12 +149,12 @@ export class Auth0AuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createAuth0Provider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'auth0';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const domain = envConfig.getString('domain');
|
||||
|
||||
@@ -24,9 +24,9 @@ import { createSamlProvider } from './saml';
|
||||
import { createAuth0Provider } from './auth0';
|
||||
import { createMicrosoftProvider } from './microsoft';
|
||||
import { createOneLoginProvider } from './onelogin';
|
||||
import { AuthProviderFactory, AuthProviderFactoryOptions } from './types';
|
||||
import { AuthProviderFactory } from './types';
|
||||
|
||||
const factories: { [providerId: string]: AuthProviderFactory } = {
|
||||
export const factories: { [providerId: string]: AuthProviderFactory } = {
|
||||
google: createGoogleProvider,
|
||||
github: createGithubProvider,
|
||||
gitlab: createGitlabProvider,
|
||||
@@ -38,15 +38,3 @@ const factories: { [providerId: string]: AuthProviderFactory } = {
|
||||
oidc: createOidcProvider,
|
||||
onelogin: createOneLoginProvider,
|
||||
};
|
||||
|
||||
export function createAuthProvider(
|
||||
providerId: string,
|
||||
options: AuthProviderFactoryOptions,
|
||||
) {
|
||||
const factory = factories[providerId];
|
||||
if (!factory) {
|
||||
throw Error(`No auth provider available for '${providerId}'`);
|
||||
}
|
||||
|
||||
return factory(options);
|
||||
}
|
||||
|
||||
@@ -137,12 +137,12 @@ export class GithubAuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createGithubProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'github';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const enterpriseInstanceUrl = envConfig.getOptionalString(
|
||||
|
||||
@@ -140,12 +140,12 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createGitlabProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'gitlab';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const audience = envConfig.getString('audience');
|
||||
|
||||
@@ -175,6 +175,7 @@ export class GoogleAuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createGoogleProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
logger,
|
||||
@@ -182,7 +183,6 @@ export const createGoogleProvider: AuthProviderFactory = ({
|
||||
catalogApi,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'google';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
||||
|
||||
@@ -14,4 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createAuthProvider } from './factories';
|
||||
export { factories as defaultAuthProviderFactories } from './factories';
|
||||
|
||||
// Export the minimal interface required for implementing a
|
||||
// custom Authorization Handler
|
||||
export type {
|
||||
AuthProviderRouteHandlers,
|
||||
AuthProviderFactoryOptions,
|
||||
AuthProviderFactory,
|
||||
} from './types';
|
||||
|
||||
// These types are needed for a postMessage from the login pop-up
|
||||
// to the frontend
|
||||
export type { AuthResponse, BackstageIdentity, ProfileInfo } from './types';
|
||||
|
||||
@@ -206,13 +206,12 @@ export class MicrosoftAuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createMicrosoftProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'microsoft';
|
||||
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const tenantID = envConfig.getString('tenantId');
|
||||
|
||||
@@ -157,12 +157,12 @@ export class OAuth2AuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createOAuth2Provider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'oauth2';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
||||
|
||||
@@ -171,12 +171,12 @@ export class OidcAuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createOidcProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'oidc';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
||||
|
||||
@@ -168,12 +168,12 @@ export class OktaAuthProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createOktaProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'okta';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const audience = envConfig.getString('audience');
|
||||
|
||||
@@ -147,12 +147,12 @@ export class OneLoginProvider implements OAuthHandlers {
|
||||
}
|
||||
|
||||
export const createOneLoginProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const providerId = 'onelogin';
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const issuer = envConfig.getString('issuer');
|
||||
|
||||
@@ -121,12 +121,12 @@ type SAMLProviderOptions = {
|
||||
};
|
||||
|
||||
export const createSamlProvider: AuthProviderFactory = ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) => {
|
||||
const url = new URL(globalConfig.baseUrl);
|
||||
const providerId = 'saml';
|
||||
const entryPoint = config.getString('entryPoint');
|
||||
const issuer = config.getString('issuer');
|
||||
const opts = {
|
||||
|
||||
@@ -113,6 +113,7 @@ export interface AuthProviderRouteHandlers {
|
||||
}
|
||||
|
||||
export type AuthProviderFactoryOptions = {
|
||||
providerId: string;
|
||||
globalConfig: AuthProviderConfig;
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
defaultAuthProviderFactories,
|
||||
AuthProviderFactory,
|
||||
} from '../providers';
|
||||
import {
|
||||
NotFoundError,
|
||||
PluginDatabaseManager,
|
||||
@@ -21,20 +29,18 @@ import {
|
||||
} from '@backstage/backend-common';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity';
|
||||
import { createAuthProvider } from '../providers';
|
||||
import session from 'express-session';
|
||||
import passport from 'passport';
|
||||
|
||||
type ProviderFactories = { [s: string]: AuthProviderFactory };
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
database: PluginDatabaseManager;
|
||||
config: Config;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
providerFactories?: ProviderFactories;
|
||||
}
|
||||
|
||||
export async function createRouter({
|
||||
@@ -42,6 +48,7 @@ export async function createRouter({
|
||||
config,
|
||||
discovery,
|
||||
database,
|
||||
providerFactories,
|
||||
}: RouterOptions): Promise<express.Router> {
|
||||
const router = Router();
|
||||
|
||||
@@ -74,13 +81,23 @@ export async function createRouter({
|
||||
router.use(express.urlencoded({ extended: false }));
|
||||
router.use(express.json());
|
||||
|
||||
const allProviderFactories = {
|
||||
...defaultAuthProviderFactories,
|
||||
...providerFactories,
|
||||
};
|
||||
const providersConfig = config.getConfig('auth.providers');
|
||||
const providers = providersConfig.keys();
|
||||
|
||||
for (const providerId of providers) {
|
||||
logger.info(`Configuring provider, ${providerId}`);
|
||||
try {
|
||||
const provider = createAuthProvider(providerId, {
|
||||
const providerFactory = allProviderFactories[providerId];
|
||||
if (!providerFactory) {
|
||||
throw Error(`No auth provider available for '${providerId}'`);
|
||||
}
|
||||
|
||||
const provider = providerFactory({
|
||||
providerId,
|
||||
globalConfig: { baseUrl: authUrl, appUrl },
|
||||
config: providersConfig.getConfig(providerId),
|
||||
logger,
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1ec19a3f4: Ignore empty YAML documents. Having a YAML file like this is now ingested without an error:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: web
|
||||
spec:
|
||||
type: website
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
This behaves now the same way as Kubernetes handles multiple documents in a single YAML file.
|
||||
|
||||
- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec.
|
||||
- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data.
|
||||
- Updated dependencies [3aa7efb3f]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- Updated dependencies [b3d4e4e57]
|
||||
- @backstage/backend-common@0.3.2
|
||||
- @backstage/catalog-model@0.3.1
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,8 +21,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-node": "^1.0.0-alpha.8",
|
||||
"@backstage/backend-common": "^0.3.1",
|
||||
"@backstage/catalog-model": "^0.3.0",
|
||||
"@backstage/backend-common": "^0.3.2",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/config": "^0.1.1",
|
||||
"@octokit/graphql": "^4.5.6",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -48,7 +48,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@types/core-js": "^2.5.4",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
|
||||
+35
-1
@@ -68,12 +68,14 @@ describe('BuiltinKindsEntityProcessor', () => {
|
||||
owner: 'o',
|
||||
lifecycle: 'l',
|
||||
implementsApis: ['a'],
|
||||
providesApis: ['b'],
|
||||
consumesApis: ['c'],
|
||||
},
|
||||
};
|
||||
|
||||
await processor.postProcessEntity(entity, location, emit);
|
||||
|
||||
expect(emit).toBeCalledTimes(4);
|
||||
expect(emit).toBeCalledTimes(8);
|
||||
expect(emit).toBeCalledWith({
|
||||
type: 'relation',
|
||||
relation: {
|
||||
@@ -106,6 +108,38 @@ describe('BuiltinKindsEntityProcessor', () => {
|
||||
target: { kind: 'API', namespace: 'default', name: 'a' },
|
||||
},
|
||||
});
|
||||
expect(emit).toBeCalledWith({
|
||||
type: 'relation',
|
||||
relation: {
|
||||
source: { kind: 'API', namespace: 'default', name: 'b' },
|
||||
type: 'apiProvidedBy',
|
||||
target: { kind: 'Component', namespace: 'default', name: 'n' },
|
||||
},
|
||||
});
|
||||
expect(emit).toBeCalledWith({
|
||||
type: 'relation',
|
||||
relation: {
|
||||
source: { kind: 'Component', namespace: 'default', name: 'n' },
|
||||
type: 'providesApi',
|
||||
target: { kind: 'API', namespace: 'default', name: 'b' },
|
||||
},
|
||||
});
|
||||
expect(emit).toBeCalledWith({
|
||||
type: 'relation',
|
||||
relation: {
|
||||
source: { kind: 'API', namespace: 'default', name: 'c' },
|
||||
type: 'apiConsumedBy',
|
||||
target: { kind: 'Component', namespace: 'default', name: 'n' },
|
||||
},
|
||||
});
|
||||
expect(emit).toBeCalledWith({
|
||||
type: 'relation',
|
||||
relation: {
|
||||
source: { kind: 'Component', namespace: 'default', name: 'n' },
|
||||
type: 'consumesApi',
|
||||
target: { kind: 'API', namespace: 'default', name: 'c' },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('generates relations for api entities', async () => {
|
||||
|
||||
@@ -26,8 +26,10 @@ import {
|
||||
locationEntityV1alpha1Validator,
|
||||
LocationSpec,
|
||||
parseEntityRef,
|
||||
RELATION_API_CONSUMED_BY,
|
||||
RELATION_API_PROVIDED_BY,
|
||||
RELATION_CHILD_OF,
|
||||
RELATION_CONSUMES_API,
|
||||
RELATION_HAS_MEMBER,
|
||||
RELATION_MEMBER_OF,
|
||||
RELATION_OWNED_BY,
|
||||
@@ -138,6 +140,18 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
|
||||
RELATION_PROVIDES_API,
|
||||
RELATION_API_PROVIDED_BY,
|
||||
);
|
||||
doEmit(
|
||||
component.spec.providesApis,
|
||||
{ defaultKind: 'API', defaultNamespace: selfRef.namespace },
|
||||
RELATION_PROVIDES_API,
|
||||
RELATION_API_PROVIDED_BY,
|
||||
);
|
||||
doEmit(
|
||||
component.spec.consumesApis,
|
||||
{ defaultKind: 'API', defaultNamespace: selfRef.namespace },
|
||||
RELATION_CONSUMES_API,
|
||||
RELATION_API_CONSUMED_BY,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -115,6 +115,41 @@ describe('parseEntityYaml', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty yaml documents', () => {
|
||||
// This happens if the user accidentially adds a "---"
|
||||
// at the end of a file
|
||||
const results = Array.from(
|
||||
parseEntityYaml(
|
||||
Buffer.from(
|
||||
`
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: web
|
||||
spec:
|
||||
type: website
|
||||
---
|
||||
`,
|
||||
'utf8',
|
||||
),
|
||||
testLoc,
|
||||
),
|
||||
);
|
||||
|
||||
expect(results).toEqual([
|
||||
result.entity(testLoc, {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'web',
|
||||
},
|
||||
spec: {
|
||||
type: 'website',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should emit parsing errors', () => {
|
||||
const results = Array.from(
|
||||
parseEntityYaml(Buffer.from('`', 'utf8'), testLoc),
|
||||
|
||||
@@ -40,6 +40,9 @@ export function* parseEntityYaml(
|
||||
const json = document.toJSON();
|
||||
if (lodash.isPlainObject(json)) {
|
||||
yield result.entity(location, json as Entity);
|
||||
} else if (json === null) {
|
||||
// Ignore null values, these happen if there is an empty document in the
|
||||
// YAML file, for example if --- is added to the end of the file.
|
||||
} else {
|
||||
const message = `Expected object at root, got ${typeof json}`;
|
||||
yield result.generalError(location, message);
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6f70ed7a9: Replace usage of implementsApis with relations
|
||||
- Updated dependencies [4b53294a6]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- @backstage/plugin-techdocs@0.3.0
|
||||
- @backstage/catalog-model@0.3.1
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -22,10 +22,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.1",
|
||||
"@backstage/catalog-model": "^0.3.0",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/core": "^0.3.2",
|
||||
"@backstage/plugin-scaffolder": "^0.3.1",
|
||||
"@backstage/plugin-techdocs": "^0.2.3",
|
||||
"@backstage/plugin-techdocs": "^0.3.0",
|
||||
"@backstage/theme": "^0.2.1",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -43,7 +43,7 @@
|
||||
"swr": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@microsoft/microsoft-graph-types": "^1.25.0",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Entity,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
RELATION_OWNED_BY,
|
||||
RELATION_PROVIDES_API,
|
||||
serializeEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
@@ -111,6 +112,8 @@ type AboutCardProps = {
|
||||
export function AboutCard({ entity, variant }: AboutCardProps) {
|
||||
const classes = useStyles();
|
||||
const codeLink = getCodeLinkInfo(entity);
|
||||
// TODO: Also support RELATION_CONSUMES_API here
|
||||
const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API);
|
||||
|
||||
return (
|
||||
<Card className={variant === 'gridItem' ? classes.gridItemCard : ''}>
|
||||
@@ -146,9 +149,9 @@ export function AboutCard({ entity, variant }: AboutCardProps) {
|
||||
}/${entity.kind}/${entity.metadata.name}`}
|
||||
/>
|
||||
<IconLinkVertical
|
||||
disabled={!entity.spec?.implementsApis}
|
||||
disabled={!hasApis}
|
||||
label="View API"
|
||||
title={!entity.spec?.implementsApis ? 'No APIs available' : ''}
|
||||
title={hasApis ? '' : 'No APIs available'}
|
||||
icon={<ExtensionIcon />}
|
||||
href="api"
|
||||
/>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-rollbar-backend
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3a201c5d5: Add config schema for the rollbar & rollbar-backend plugins
|
||||
- Updated dependencies [3aa7efb3f]
|
||||
- Updated dependencies [b3d4e4e57]
|
||||
- @backstage/backend-common@0.3.2
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,6 @@ The following values are read from the configuration file.
|
||||
|
||||
```yaml
|
||||
rollbar:
|
||||
organization: organization-name
|
||||
accountToken:
|
||||
$env: ROLLBAR_ACCOUNT_TOKEN
|
||||
```
|
||||
|
||||
+9
-24
@@ -14,28 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fetch from 'cross-fetch';
|
||||
|
||||
export class TechDocsMetadata {
|
||||
private async getMetadataFile(docsUrl: String) {
|
||||
const metadataURL = `${docsUrl}/techdocs_metadata.json`;
|
||||
|
||||
try {
|
||||
const req = await fetch(metadataURL);
|
||||
|
||||
return await req.json();
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getMkDocsMetaData(docsUrl: any) {
|
||||
const mkDocsMetadata = await this.getMetadataFile(docsUrl);
|
||||
|
||||
if (!mkDocsMetadata) return null;
|
||||
|
||||
return {
|
||||
...mkDocsMetadata,
|
||||
};
|
||||
}
|
||||
export interface Config {
|
||||
/** Configuration options for the rollbar-backend plugin */
|
||||
rollbar?: {
|
||||
/**
|
||||
* The autentication token for accessing the Rollbar API
|
||||
* @see https://explorer.docs.rollbar.com/#section/Authentication
|
||||
*/
|
||||
accountToken: string;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-rollbar-backend",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"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.3.0",
|
||||
"@backstage/backend-common": "^0.3.2",
|
||||
"@backstage/config": "^0.1.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"axios": "^0.20.0",
|
||||
@@ -37,11 +37,13 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.0",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/plugin-rollbar
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3a201c5d5: Add config schema for the rollbar & rollbar-backend plugins
|
||||
- Updated dependencies [6f70ed7a9]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- @backstage/plugin-catalog@0.2.4
|
||||
- @backstage/catalog-model@0.3.1
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -54,6 +54,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
# app.config.yaml
|
||||
rollbar:
|
||||
organization: organization-name
|
||||
# used by rollbar-backend
|
||||
accountToken:
|
||||
$env: ROLLBAR_ACCOUNT_TOKEN
|
||||
```
|
||||
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
/** Configuration options for the rollbar plugin */
|
||||
rollbar?: {
|
||||
/**
|
||||
* The Rollbar organization name. This can be omitted by using the `rollbar.com/project-slug` annotation.
|
||||
* @see https://backstage.io/docs/features/software-catalog/well-known-annotations#rollbarcomproject-slug
|
||||
* @visibility frontend
|
||||
*/
|
||||
organization?: string;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-rollbar",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"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.3.0",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/core": "^0.3.2",
|
||||
"@backstage/plugin-catalog": "^0.2.3",
|
||||
"@backstage/plugin-catalog": "^0.2.4",
|
||||
"@backstage/theme": "^0.2.1",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -37,7 +37,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
@@ -51,22 +51,8 @@
|
||||
"msw": "^0.21.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": {
|
||||
"$schema": "https://backstage.io/schema/config-v1",
|
||||
"title": "@backstage/rollbar",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rollbar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"organization": {
|
||||
"type": "string",
|
||||
"visibility": "frontend"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"swr": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"timeago.js": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @backstage/plugin-techdocs-backend
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages.
|
||||
- techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*`
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3aa7efb3f]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- Updated dependencies [b3d4e4e57]
|
||||
- @backstage/backend-common@0.3.2
|
||||
- @backstage/catalog-model@0.3.1
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-techdocs-backend",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.0",
|
||||
"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.3.1",
|
||||
"@backstage/catalog-model": "^0.3.0",
|
||||
"@backstage/backend-common": "^0.3.2",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/config": "^0.1.1",
|
||||
"@types/dockerode": "^2.5.34",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -32,12 +32,14 @@
|
||||
"express-promise-router": "^3.0.3",
|
||||
"fs-extra": "^9.0.1",
|
||||
"git-url-parse": "^11.4.0",
|
||||
"js-yaml": "^3.14.0",
|
||||
"knex": "^0.21.6",
|
||||
"mock-fs": "^4.13.0",
|
||||
"nodegit": "^0.27.0",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"supertest": "^4.0.2"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -69,10 +69,13 @@ export class DocsBuilder {
|
||||
this.logger.info(`Running preparer on entity ${getEntityId(this.entity)}`);
|
||||
const preparedDir = await this.preparer.prepare(this.entity);
|
||||
|
||||
const parsedLocationAnnotation = getLocationForEntity(this.entity);
|
||||
|
||||
this.logger.info(`Running generator on entity ${getEntityId(this.entity)}`);
|
||||
const { resultDir } = await this.generator.run({
|
||||
directory: preparedDir,
|
||||
dockerClient: this.dockerClient,
|
||||
parsedLocationAnnotation,
|
||||
});
|
||||
|
||||
this.logger.info(`Running publisher on entity ${getEntityId(this.entity)}`);
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function createRouter({
|
||||
}: RouterOptions): Promise<express.Router> {
|
||||
const router = Router();
|
||||
|
||||
router.get('/metadata/mkdocs/*', async (req, res) => {
|
||||
router.get('/metadata/techdocs/*', async (req, res) => {
|
||||
let storageUrl = config.getString('techdocs.storageUrl');
|
||||
if (publisher instanceof LocalPublish) {
|
||||
storageUrl = new URL(
|
||||
@@ -74,8 +74,8 @@ export async function createRouter({
|
||||
const metadataURL = `${storageUrl}/${path}/techdocs_metadata.json`;
|
||||
|
||||
try {
|
||||
const mkDocsMetadata = await (await fetch(metadataURL)).json();
|
||||
res.send(mkDocsMetadata);
|
||||
const techdocsMetadata = await (await fetch(metadataURL)).json();
|
||||
res.send(techdocsMetadata);
|
||||
} catch (err) {
|
||||
logger.info(`Unable to get metadata for ${path} with error ${err}`);
|
||||
throw new Error(`Unable to get metadata for ${path} with error ${err}`);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
site_name: Test site name
|
||||
site_description: Test site description
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
site_name: Test site name
|
||||
site_description: Test site description
|
||||
|
||||
repo_url: https://github.com/backstage/backstage
|
||||
@@ -13,10 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Stream, { PassThrough } from 'stream';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import Stream, { PassThrough } from 'stream';
|
||||
import Docker from 'dockerode';
|
||||
import { runDockerContainer, getGeneratorKey } from './helpers';
|
||||
import mockFs from 'mock-fs';
|
||||
import * as winston from 'winston';
|
||||
import {
|
||||
runDockerContainer,
|
||||
getGeneratorKey,
|
||||
isValidRepoUrlForMkdocs,
|
||||
getRepoUrlFromLocationAnnotation,
|
||||
patchMkdocsYmlPreBuild,
|
||||
} from './helpers';
|
||||
import { RemoteProtocol } from '../prepare/types';
|
||||
import { ParsedLocationAnnotation } from '../../../helpers';
|
||||
|
||||
const mockEntity = {
|
||||
apiVersion: 'version',
|
||||
@@ -28,6 +40,14 @@ const mockEntity = {
|
||||
|
||||
const mockDocker = new Docker() as jest.Mocked<Docker>;
|
||||
|
||||
const mkdocsYml = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs.yml'),
|
||||
);
|
||||
const mkdocsYmlWithRepoUrl = fs.readFileSync(
|
||||
resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'),
|
||||
);
|
||||
const mockLogger = winston.createLogger();
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('getGeneratorKey', () => {
|
||||
it('should return techdocs as the only generator key', () => {
|
||||
@@ -138,4 +158,175 @@ describe('helpers', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidRepoUrlForMkdocs', () => {
|
||||
it('should return true for valid repo_url values for mkdocs', () => {
|
||||
const validRepoUrls = [
|
||||
'https://github.com/org/repo',
|
||||
'https://github.com/backstage/backstage/',
|
||||
'https://github.com/org123/repo1-2-3/',
|
||||
'http://github.com/insecureOrg/insecureRepo',
|
||||
'https://gitlab.com/org/repo',
|
||||
'https://gitlab.com/backstage/backstage/',
|
||||
'https://gitlab.com/org123/repo1-2-3/',
|
||||
'http://gitlab.com/insecureOrg/insecureRepo',
|
||||
];
|
||||
|
||||
const validRemoteProtocols = ['github', 'gitlab'];
|
||||
|
||||
validRepoUrls.forEach(url => {
|
||||
validRemoteProtocols.forEach(targetType => {
|
||||
expect(
|
||||
isValidRepoUrlForMkdocs(url, targetType as RemoteProtocol),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false for invalid repo_urls values for mkdocs', () => {
|
||||
const invalidRepoUrls = [
|
||||
'git@github.com:org/repo',
|
||||
'https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend',
|
||||
];
|
||||
|
||||
invalidRepoUrls.forEach(url => {
|
||||
expect(isValidRepoUrlForMkdocs(url, 'github')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false for unsupported remote protocols', () => {
|
||||
const validRepoUrl = 'https://github.com/backstage/backstage';
|
||||
|
||||
const unsupportedRemoteProtocols = ['dir', 'file', 'url'];
|
||||
|
||||
unsupportedRemoteProtocols.forEach(targetType => {
|
||||
expect(
|
||||
isValidRepoUrlForMkdocs(validRepoUrl, targetType as RemoteProtocol),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRepoUrlFromLocationAnnotation', () => {
|
||||
it('should return undefined for unsupported location type', () => {
|
||||
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
|
||||
type: 'dir',
|
||||
target: '/home/user/workspace/docs-repository',
|
||||
};
|
||||
|
||||
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
|
||||
type: 'file',
|
||||
target: '/home/user/workspace/docs-repository/catalog-info.yaml',
|
||||
};
|
||||
|
||||
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
|
||||
type: 'url',
|
||||
target: 'https://my-website.com/storage/this/docs/repository',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe(
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe(
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct target url for supported hosts', () => {
|
||||
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
target: 'https://github.com/backstage/backstage.git',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe(
|
||||
'https://github.com/backstage/backstage',
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
target: 'https://github.com/org/repo',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe(
|
||||
'https://github.com/org/repo',
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
|
||||
type: 'gitlab',
|
||||
target: 'https://gitlab.com/org/repo',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe(
|
||||
'https://gitlab.com/org/repo',
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation4: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
target:
|
||||
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
|
||||
};
|
||||
|
||||
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation4)).toBe(
|
||||
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pathMkdocsPreBuild', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/mkdocs.yml': mkdocsYml,
|
||||
'/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should add repo_url to mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
target: 'https://github.com/backstage/backstage',
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs.yml',
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs.yml');
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
"repo_url: 'https://github.com/backstage/backstage'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should not override existing repo_url in mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
target: 'https://github.com/neworg/newrepo',
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs_with_repo_url.yml',
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_repo_url.yml');
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
"repo_url: 'https://github.com/backstage/backstage'",
|
||||
);
|
||||
expect(updatedMkdocsYml.toString()).not.toContain(
|
||||
"repo_url: 'https://github.com/neworg/newrepo'",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,11 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import fs from 'fs-extra';
|
||||
import { spawn } from 'child_process';
|
||||
import { Writable, PassThrough } from 'stream';
|
||||
import Docker from 'dockerode';
|
||||
import yaml from 'js-yaml';
|
||||
import { Logger } from 'winston';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { SupportedGeneratorKey } from './types';
|
||||
import { spawn } from 'child_process';
|
||||
import { ParsedLocationAnnotation } from '../../../helpers';
|
||||
import { RemoteProtocol } from '../prepare/types';
|
||||
|
||||
// TODO: Implement proper support for more generators.
|
||||
export function getGeneratorKey(entity: Entity): SupportedGeneratorKey {
|
||||
@@ -142,3 +147,131 @@ export const runCommand = async ({
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if mkdocs can compile docs with provided repo_url
|
||||
*
|
||||
* Valid repo_url examples in mkdocs.yml
|
||||
* - https://github.com/backstage/backstage
|
||||
* - https://gitlab.com/org/repo/
|
||||
* - http://github.com/backstage/backstage
|
||||
* - A http(s) protocol URL to the root of the repository
|
||||
*
|
||||
* Invalid repo_url examples in mkdocs.yml
|
||||
* - https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component
|
||||
* - (anything that is not valid as described above)
|
||||
*
|
||||
* @param {string} repoUrl URL supposed to be used as repo_url in mkdocs.yml
|
||||
* @param {RemoteProtocol} locationType Type of source code host - github, gitlab, dir, url, etc.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isValidRepoUrlForMkdocs = (
|
||||
repoUrl: string,
|
||||
locationType: RemoteProtocol,
|
||||
): boolean => {
|
||||
// Trim trailing slash
|
||||
const cleanRepoUrl = repoUrl.replace(/\/$/, '');
|
||||
|
||||
if (locationType === 'github' || locationType === 'gitlab') {
|
||||
// A valid repoUrl to the root of the repository will be split into 5 strings if split using the / delimiter.
|
||||
// We do not want URLs which have more than that number of forward slashes since they will signify a non-root location
|
||||
// Note: This is not the best possible implementation but will work most of the times.. Feel free to improve or
|
||||
// highlight edge cases.
|
||||
return cleanRepoUrl.split('/').length === 5;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a valid URL of the repository used in backstage.io/techdocs-ref annotation.
|
||||
* Return undefined if the `target` is not valid in context of repo_url in mkdocs.yml
|
||||
* Alter URL so that it is a valid repo_url config in mkdocs.yml
|
||||
*
|
||||
* @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export const getRepoUrlFromLocationAnnotation = (
|
||||
parsedLocationAnnotation: ParsedLocationAnnotation,
|
||||
): string | undefined => {
|
||||
const { type: locationType, target } = parsedLocationAnnotation;
|
||||
|
||||
// Add more options from the RemoteProtocol type of parsedLocationAnnotation.type here
|
||||
// when TechDocs supports more hosts and if mkdocs can generated an Edit URL for them.
|
||||
const supportedHosts = ['github', 'gitlab'];
|
||||
|
||||
if (supportedHosts.includes(locationType)) {
|
||||
// Trim .git or .git/ from the end of repository url
|
||||
return target.replace(/.git\/*$/, '');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the mkdocs.yml file before TechDocs generator uses it to build docs site.
|
||||
*
|
||||
* List of tasks:
|
||||
* - Add repo_url if it does not exists
|
||||
* If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default.
|
||||
* If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get
|
||||
* the repository URL.
|
||||
*
|
||||
* This function will not throw an error since this is not critical to the whole TechDocs pipeline.
|
||||
* Instead it will log warnings if there are any errors in reading, parsing or writing YAML.
|
||||
*
|
||||
* @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site
|
||||
* @param {Logger} logger
|
||||
* @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type
|
||||
*/
|
||||
export const patchMkdocsYmlPreBuild = async (
|
||||
mkdocsYmlPath: string,
|
||||
logger: Logger,
|
||||
parsedLocationAnnotation: ParsedLocationAnnotation,
|
||||
) => {
|
||||
let mkdocsYmlFileString;
|
||||
try {
|
||||
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Could not read file ${mkdocsYmlPath} before running the generator. ${error.message}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let mkdocsYml: any;
|
||||
try {
|
||||
mkdocsYml = yaml.safeLoad(mkdocsYmlFileString);
|
||||
|
||||
// mkdocsYml should be an object type after successful parsing.
|
||||
// But based on its type definition, it can also be a string or undefined, which we don't want.
|
||||
if (typeof mkdocsYml === 'string' || typeof mkdocsYml === 'undefined') {
|
||||
throw new Error('Bad YAML format.');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${error.message}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add repo_url to mkdocs.yml if it is missing. This will enable the Page edit button generated by MkDocs.
|
||||
if (!('repo_url' in mkdocsYml)) {
|
||||
const repoUrl = getRepoUrlFromLocationAnnotation(parsedLocationAnnotation);
|
||||
if (repoUrl !== undefined) {
|
||||
// mkdocs.yml will not build with invalid repo_url. So, make sure it is valid.
|
||||
if (isValidRepoUrlForMkdocs(repoUrl, parsedLocationAnnotation.type)) {
|
||||
mkdocsYml.repo_url = repoUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.writeFile(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8');
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,7 +26,11 @@ import {
|
||||
GeneratorRunOptions,
|
||||
GeneratorRunResult,
|
||||
} from './types';
|
||||
import { runDockerContainer, runCommand } from './helpers';
|
||||
import {
|
||||
runDockerContainer,
|
||||
runCommand,
|
||||
patchMkdocsYmlPreBuild,
|
||||
} from './helpers';
|
||||
|
||||
type TechdocsGeneratorOptions = {
|
||||
// This option enables users to configure if they want to use TechDocs container
|
||||
@@ -62,6 +66,7 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
public async run({
|
||||
directory,
|
||||
dockerClient,
|
||||
parsedLocationAnnotation,
|
||||
}: GeneratorRunOptions): Promise<GeneratorRunResult> {
|
||||
const tmpdirPath = os.tmpdir();
|
||||
// Fixes a problem with macOS returning a path that is a symlink
|
||||
@@ -71,6 +76,15 @@ export class TechdocsGenerator implements GeneratorBase {
|
||||
);
|
||||
const [log, logStream] = createStream();
|
||||
|
||||
// TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out
|
||||
// the correct file name.
|
||||
// Do some updates to mkdocs.yml before generating docs e.g. adding repo_url
|
||||
await patchMkdocsYmlPreBuild(
|
||||
path.join(directory, 'mkdocs.yml'),
|
||||
this.logger,
|
||||
parsedLocationAnnotation,
|
||||
);
|
||||
|
||||
try {
|
||||
switch (this.options.runGeneratorIn) {
|
||||
case 'local':
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import type { Writable } from 'stream';
|
||||
import { Writable } from 'stream';
|
||||
import Docker from 'dockerode';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ParsedLocationAnnotation } from '../../../helpers';
|
||||
|
||||
/**
|
||||
* The returned directory from the generator which is ready
|
||||
@@ -26,14 +27,18 @@ export type GeneratorRunResult = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The values that the generator will receive. The directory of the
|
||||
* uncompiled documentation, with the values from the frontend. A dedicated log stream and a docker
|
||||
* client to run any generator on top of your directory.
|
||||
* The values that the generator will receive.
|
||||
*
|
||||
* @param {string} directory The directory of the uncompiled documentation, with the values from the frontend
|
||||
* @param {Docker} dockerClient A docker client to run any generator on top of your directory
|
||||
* @param {ParsedLocationAnnotation} parsedLocationAnnotation backstage.io/techdocs-ref annotation of an entity
|
||||
* @param {Writable} [logStream] A dedicated log stream
|
||||
*/
|
||||
export type GeneratorRunOptions = {
|
||||
directory: string;
|
||||
logStream?: Writable;
|
||||
dockerClient: Docker;
|
||||
parsedLocationAnnotation: ParsedLocationAnnotation;
|
||||
logStream?: Writable;
|
||||
};
|
||||
|
||||
export type GeneratorBase = {
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @backstage/plugin-techdocs
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages.
|
||||
- techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*`
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6f70ed7a9]
|
||||
- Updated dependencies [ab94c9542]
|
||||
- Updated dependencies [2daf18e80]
|
||||
- Updated dependencies [069cda35f]
|
||||
- Updated dependencies [700a212b4]
|
||||
- @backstage/plugin-catalog@0.2.4
|
||||
- @backstage/catalog-model@0.3.1
|
||||
- @backstage/core-api@0.2.3
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-techdocs",
|
||||
"version": "0.2.3",
|
||||
"version": "0.3.0",
|
||||
"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.3.0",
|
||||
"@backstage/catalog-model": "^0.3.1",
|
||||
"@backstage/core": "^0.3.2",
|
||||
"@backstage/core-api": "^0.2.1",
|
||||
"@backstage/plugin-catalog": "^0.2.3",
|
||||
"@backstage/core-api": "^0.2.3",
|
||||
"@backstage/plugin-catalog": "^0.2.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.1",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -39,7 +39,7 @@
|
||||
"sanitize-html": "^1.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core';
|
||||
|
||||
import { ParsedEntityId } from './types';
|
||||
|
||||
export const techdocsStorageApiRef = createApiRef<TechDocsStorageApi>({
|
||||
@@ -38,7 +37,8 @@ export interface TechDocsStorage {
|
||||
}
|
||||
|
||||
export interface TechDocs {
|
||||
getMetadata(metadataType: string, entityId: ParsedEntityId): Promise<string>;
|
||||
getTechDocsMetadata(entityId: ParsedEntityId): Promise<string>;
|
||||
getEntityMetadata(entityId: ParsedEntityId): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,10 +53,38 @@ export class TechDocsApi implements TechDocs {
|
||||
this.apiOrigin = apiOrigin;
|
||||
}
|
||||
|
||||
async getMetadata(metadataType: string, entityId: ParsedEntityId) {
|
||||
/**
|
||||
* Retrieve TechDocs metadata.
|
||||
*
|
||||
* When docs are built, we generate a techdocs_metadata.json and store it along with the generated
|
||||
* static files. It includes necessary data about the docs site. This method requests techdocs-backend
|
||||
* which retrieves the TechDocs metadata.
|
||||
*
|
||||
* @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc.
|
||||
*/
|
||||
async getTechDocsMetadata(entityId: ParsedEntityId) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const requestUrl = `${this.apiOrigin}/metadata/${metadataType}/${namespace}/${kind}/${name}`;
|
||||
const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
|
||||
|
||||
const request = await fetch(`${requestUrl}`);
|
||||
const res = await request.json();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve metadata about an entity.
|
||||
*
|
||||
* This method requests techdocs-backend which uses the catalog APIs to respond with filtered
|
||||
* information required here.
|
||||
*
|
||||
* @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc.
|
||||
*/
|
||||
async getEntityMetadata(entityId: ParsedEntityId) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
|
||||
|
||||
const request = await fetch(`${requestUrl}`);
|
||||
const res = await request.json();
|
||||
|
||||
@@ -50,17 +50,18 @@ describe('<TechDocsPage />', () => {
|
||||
entityId: 'Component::backstage',
|
||||
});
|
||||
|
||||
const techDocsApi: Partial<TechDocsApi> = {
|
||||
getMetadata: () => Promise.resolve([]),
|
||||
const techdocsApi: Partial<TechDocsApi> = {
|
||||
getEntityMetadata: () => Promise.resolve([]),
|
||||
getTechDocsMetadata: () => Promise.resolve([]),
|
||||
};
|
||||
const techDocsStorageApi: Partial<TechDocsStorageApi> = {
|
||||
const techdocsStorageApi: Partial<TechDocsStorageApi> = {
|
||||
getEntityDocs: (): Promise<string> => Promise.resolve('String'),
|
||||
getBaseUrl: (): string => '',
|
||||
};
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[techdocsApiRef, techDocsApi],
|
||||
[techdocsStorageApiRef, techDocsStorageApi],
|
||||
[techdocsApiRef, techdocsApi],
|
||||
[techdocsStorageApiRef, techdocsStorageApi],
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -26,19 +26,19 @@ export const TechDocsPage = () => {
|
||||
const [documentReady, setDocumentReady] = useState<boolean>(false);
|
||||
const { namespace, kind, name } = useParams();
|
||||
|
||||
const techDocsApi = useApi(techdocsApiRef);
|
||||
const techdocsApi = useApi(techdocsApiRef);
|
||||
|
||||
const mkdocsMetadataRequest = useAsync(() => {
|
||||
const techdocsMetadataRequest = useAsync(() => {
|
||||
if (documentReady) {
|
||||
return techDocsApi.getMetadata('mkdocs', { kind, namespace, name });
|
||||
return techdocsApi.getTechDocsMetadata({ kind, namespace, name });
|
||||
}
|
||||
|
||||
return Promise.resolve({ loading: true });
|
||||
}, [kind, namespace, name, techDocsApi, documentReady]);
|
||||
}, [kind, namespace, name, techdocsApi, documentReady]);
|
||||
|
||||
const entityMetadataRequest = useAsync(() => {
|
||||
return techDocsApi.getMetadata('entity', { kind, namespace, name });
|
||||
}, [kind, namespace, name, techDocsApi]);
|
||||
return techdocsApi.getEntityMetadata({ kind, namespace, name });
|
||||
}, [kind, namespace, name, techdocsApi]);
|
||||
|
||||
const onReady = () => {
|
||||
setDocumentReady(true);
|
||||
@@ -48,7 +48,7 @@ export const TechDocsPage = () => {
|
||||
<Page themeId="documentation">
|
||||
<TechDocsPageHeader
|
||||
metadataRequest={{
|
||||
mkdocs: mkdocsMetadataRequest,
|
||||
techdocs: techdocsMetadataRequest,
|
||||
entity: entityMetadataRequest,
|
||||
}}
|
||||
entityId={{
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('<TechDocsPageHeader />', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
mkdocs: {
|
||||
techdocs: {
|
||||
loading: false,
|
||||
value: {
|
||||
site_name: 'test-site-name',
|
||||
@@ -73,7 +73,7 @@ describe('<TechDocsPageHeader />', () => {
|
||||
entity: {
|
||||
loading: false,
|
||||
},
|
||||
mkdocs: {
|
||||
techdocs: {
|
||||
loading: false,
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -25,7 +25,7 @@ type TechDocsPageHeaderProps = {
|
||||
entityId: ParsedEntityId;
|
||||
metadataRequest: {
|
||||
entity: AsyncState<any>;
|
||||
mkdocs: AsyncState<any>;
|
||||
techdocs: AsyncState<any>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,15 +33,18 @@ export const TechDocsPageHeader = ({
|
||||
entityId,
|
||||
metadataRequest,
|
||||
}: TechDocsPageHeaderProps) => {
|
||||
const { mkdocs: mkdocsMetadata, entity: entityMetadata } = metadataRequest;
|
||||
const {
|
||||
techdocs: techdocsMetadata,
|
||||
entity: entityMetadata,
|
||||
} = metadataRequest;
|
||||
|
||||
const { value: mkDocsMetadataValues } = mkdocsMetadata;
|
||||
const { value: techdocsMetadataValues } = techdocsMetadata;
|
||||
const { value: entityMetadataValues } = entityMetadata;
|
||||
|
||||
const { kind, name } = entityId;
|
||||
|
||||
const { site_name: siteName, site_description: siteDescription } =
|
||||
mkDocsMetadataValues || {};
|
||||
techdocsMetadataValues || {};
|
||||
|
||||
const {
|
||||
locationMetadata,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.3.1",
|
||||
"@backstage/cli": "^0.3.2",
|
||||
"@backstage/dev-utils": "^0.1.4",
|
||||
"@backstage/test-utils": "^0.1.3",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
Reference in New Issue
Block a user