Merge branch 'master' of github.com:spotify/backstage into shmidt-i/app-catalog-tabs-routes-everything-is-connected

This commit is contained in:
Ivan Shmidt
2020-09-02 15:59:09 +02:00
59 changed files with 1037 additions and 617 deletions
+4 -9
View File
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import { resolve as resolvePath, dirname } from 'path';
import { notFoundHandler } from '@backstage/backend-common';
import { resolve as resolvePath } from 'path';
import { notFoundHandler, resolvePackagePath } from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
@@ -23,19 +23,14 @@ import { injectEnvConfig } from '../lib/config';
export interface RouterOptions {
logger: Logger;
appPackageName?: string;
appPackageName: string;
staticFallbackHandler?: express.Handler;
}
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const appDistDir = resolvePath(
dirname(
__non_webpack_require__.resolve(`${options.appPackageName}/package.json`),
),
'dist',
);
const appDistDir = resolvePackagePath(options.appPackageName, 'dist');
options.logger.info(`Serving static app content from ${appDistDir}`);
await injectEnvConfig({
-1
View File
@@ -23,7 +23,6 @@
"@backstage/backend-common": "^0.1.1-alpha.20",
"@backstage/config": "^0.1.1-alpha.20",
"@types/express": "^4.17.6",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
@@ -15,13 +15,13 @@
*/
import Knex from 'knex';
import path from 'path';
import { utc } from 'moment';
import { resolvePackagePath } from '@backstage/backend-common';
import { AnyJWK, KeyStore, StoredKey } from './types';
const migrationsDir = path.resolve(
require.resolve('@backstage/plugin-auth-backend/package.json'),
'../migrations',
const migrationsDir = resolvePackagePath(
'@backstage/plugin-auth-backend',
'migrations',
);
const TABLE = 'signing_keys';
+1 -3
View File
@@ -18,8 +18,6 @@ import express from 'express';
import { Logger } from 'winston';
import { TokenIssuer } from '../identity';
import { Config } from '@backstage/config';
import { OAuthProvider } from '../lib/OAuthProvider';
import { SamlAuthProvider } from './saml/provider';
export type OAuthProviderOptions = {
/**
@@ -174,7 +172,7 @@ export type AuthProviderFactory = (
envConfig: Config,
logger: Logger,
issuer: TokenIssuer,
) => OAuthProvider | SamlAuthProvider | undefined;
) => AuthProviderRouteHandlers | undefined;
export type AuthResponse<ProviderInfo> = {
providerInfo: ProviderInfo;
+2 -3
View File
@@ -17,7 +17,6 @@
import express from 'express';
import Router from 'express-promise-router';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import Knex from 'knex';
import { Logger } from 'winston';
import { createAuthProviderRouter } from '../providers';
@@ -53,8 +52,8 @@ export async function createRouter(
});
router.use(cookieParser());
router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json());
router.use(express.urlencoded({ extended: false }));
router.use(express.json());
const providersConfig = options.config.getConfig('auth.providers');
const providers = providersConfig.keys();
@@ -14,17 +14,16 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { getVoidLogger, resolvePackagePath } from '@backstage/backend-common';
import { makeValidator } from '@backstage/catalog-model';
import Knex from 'knex';
import path from 'path';
import { Logger } from 'winston';
import { CommonDatabase } from './CommonDatabase';
import { Database } from './types';
const migrationsDir = path.resolve(
require.resolve('@backstage/plugin-catalog-backend/package.json'),
'../migrations',
const migrationsDir = resolvePackagePath(
'@backstage/plugin-catalog-backend',
'migrations',
);
export type CreateDatabaseOptions = {
@@ -28,6 +28,9 @@ const entity = {
component: {
kind: 'component',
} as Entity,
location: {
kind: 'Location',
} as Entity,
};
const location: Record<string, LocationSpec> = {
@@ -51,6 +54,7 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.user, location.x)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
});
it('should deny all', () => {
@@ -58,15 +62,21 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.user, location.x)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
});
it('should allow all', () => {
const enforcer = new CatalogRulesEnforcer([
{ allow: [{ kind: 'User' }, { kind: 'Group' }, { kind: 'Component' }] },
{
allow: ['User', 'Group', 'Component', 'Location'].map(kind => ({
kind,
})),
},
]);
expect(enforcer.isAllowed(entity.user, location.x)).toBe(true);
expect(enforcer.isAllowed(entity.group, location.y)).toBe(true);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(true);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(true);
});
it('should deny groups', () => {
@@ -121,6 +131,7 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.user, location.x)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(true);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(true);
});
it('should deny all', () => {
@@ -130,6 +141,7 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.user, location.x)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
});
it('should allow all', () => {
@@ -156,6 +168,7 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(true);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
});
it('should allow groups from a specific github location', () => {
@@ -167,7 +180,11 @@ describe('CatalogRulesEnforcer', () => {
{
type: 'github',
target: 'https://github.com/a/b/blob/master/x.yaml',
allow: ['Group'],
rules: [
{
allow: ['Group'],
},
],
},
],
},
@@ -178,6 +195,7 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.group, location.y)).toBe(false);
expect(enforcer.isAllowed(entity.group, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
});
it('should not care about location configuration in catalog.rules', () => {
@@ -193,6 +211,7 @@ describe('CatalogRulesEnforcer', () => {
expect(enforcer.isAllowed(entity.group, location.y)).toBe(true);
expect(enforcer.isAllowed(entity.group, location.z)).toBe(true);
expect(enforcer.isAllowed(entity.component, location.z)).toBe(false);
expect(enforcer.isAllowed(entity.location, location.z)).toBe(false);
});
});
});
@@ -50,7 +50,7 @@ export class CatalogRulesEnforcer {
*/
static readonly defaultRules: CatalogRule[] = [
{
allow: [{ kind: 'Component' }, { kind: 'API' }],
allow: ['Component', 'API', 'Location'].map(kind => ({ kind })),
},
];
@@ -64,7 +64,7 @@ export class CatalogRulesEnforcer {
* If there is no matching rule to allow an ingested entity, it will be rejected by the catalog.
*
* It also reads in rules from `catalog.locations`, where each location can have a list
* of allowed entity for the location, specified in an `allow` field.
* of rules for that specific location, specified in a `rules` field.
*
* For example:
*
@@ -76,10 +76,12 @@ export class CatalogRulesEnforcer {
* locations:
* - type: github
* target: https://github.com/org/repo/blob/master/users.yaml
* allow: [User, Group]
* rules:
* - allow: [User, Group]
* - type: github
* target: https://github.com/org/repo/blob/master/systems.yaml
* allow: [System]
* rules:
* - allow: [System]
* ```
*/
static fromConfig(config: Config) {
@@ -97,22 +99,17 @@ export class CatalogRulesEnforcer {
if (config.has('catalog.locations')) {
const locationRules = config
.getConfigArray('catalog.locations')
.flatMap(sub => {
if (!sub.has('allow')) {
.flatMap(locConf => {
if (!locConf.has('rules')) {
return [];
}
const type = locConf.getString('type');
const target = locConf.getString('target');
return [
{
allow: sub.getStringArray('allow').map(kind => ({ kind })),
locations: [
{
type: sub.getString('type'),
target: sub.getString('target'),
},
],
},
];
return locConf.getConfigArray('rules').map(ruleConf => ({
allow: ruleConf.getStringArray('allow').map(kind => ({ kind })),
locations: [{ type, target }],
}));
});
rules.push(...locationRules);
+6 -7
View File
@@ -14,17 +14,16 @@
* limitations under the License.
*/
import { errorHandler } from '@backstage/backend-common';
import { errorHandler, resolvePackagePath } from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import fs from 'fs';
import path from 'path';
import { ApolloServer } from 'apollo-server-express';
const schemaPath = path.resolve(
require.resolve('@backstage/plugin-graphql-backend/package.json'),
'../schema.gql',
const schemaPath = resolvePackagePath(
'@backstage/plugin-graphql-backend',
'schema.gql',
);
export interface RouterOptions {
@@ -39,8 +38,8 @@ export async function createRouter(
const server = new ApolloServer({ typeDefs, logger: options.logger });
const router = Router();
const apolloMiddlware = server.getMiddleware({ path: '/' });
router.use(apolloMiddlware);
const apolloMiddleware = server.getMiddleware({ path: '/' });
router.use(apolloMiddleware);
router.get('/health', (_, response) => {
response.send({ status: 'ok' });
@@ -27,13 +27,15 @@ export interface RouterOptions {
const makeRouter = (adapter: IdentityApi): express.Router => {
const router = Router();
router.use(express.json());
router.get('/users/:user/groups', async (req, res) => {
const user = req.params.user;
const type = req.query.type?.toString() ?? '';
const response = await adapter.getUserGroups({ user, type });
res.send(response);
});
return router;
};
+10 -1
View File
@@ -34,6 +34,7 @@ export interface RouterOptions {
// given config.
function buildMiddleware(
pathPrefix: string,
logger: Logger,
route: string,
config: string | ProxyConfig,
): Proxy {
@@ -54,6 +55,9 @@ function buildMiddleware(
fullConfig.changeOrigin = true;
}
// Attach the logger to the proxy config
fullConfig.logProvider = () => logger;
return createProxyMiddleware(fullConfig);
}
@@ -66,7 +70,12 @@ export async function createRouter(
Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => {
router.use(
route,
buildMiddleware(options.pathPrefix, route, proxyRouteConfig),
buildMiddleware(
options.pathPrefix,
options.logger,
route,
proxyRouteConfig,
),
);
});
@@ -31,6 +31,7 @@ export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const router = Router();
const logger = options.logger.child({ plugin: 'rollbar' });
const config = options.config.getConfig('rollbar');
const accessToken = !options.rollbarApi
@@ -42,6 +42,7 @@ export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const router = Router();
router.use(express.json());
const {
preparers,
@@ -20,6 +20,8 @@ import { getSentryApiForwarder } from './sentry-api';
export async function createRouter(logger: Logger): Promise<express.Router> {
const router = Router();
router.use(express.json());
const SENTRY_TOKEN = process.env.SENTRY_TOKEN;
if (!SENTRY_TOKEN) {
if (process.env.NODE_ENV !== 'development') {
@@ -19,7 +19,6 @@ import express from 'express';
import Knex from 'knex';
import fetch from 'node-fetch';
import { Config } from '@backstage/config';
import path from 'path';
import Docker from 'dockerode';
import {
GeneratorBuilder,
@@ -27,6 +26,7 @@ import {
PublisherBase,
LocalPublish,
} from '../techdocs';
import { resolvePackagePath } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
type RouterOptions = {
@@ -39,6 +39,11 @@ type RouterOptions = {
dockerClient: Docker;
};
const staticDocsDir = resolvePackagePath(
'@backstage/plugin-techdocs-backend',
'static/docs',
);
export async function createRouter({
preparers,
generators,
@@ -102,10 +107,7 @@ export async function createRouter({
});
if (publisher instanceof LocalPublish) {
router.use(
'/static/docs/',
express.static(path.resolve(__dirname, `../../static/docs`)),
);
router.use('/static/docs/', express.static(staticDocsDir));
router.use(
'/static/docs/:kind/:namespace/:name',
async (req, res, next) => {
@@ -79,9 +79,10 @@ export const checkoutGitRepository = async (
if (fs.existsSync(repositoryTmpPath)) {
const repository = await Repository.open(repositoryTmpPath);
const currentBranchName = (await repository.getCurrentBranch()).shorthand();
await repository.mergeBranches(
parsedGitLocation.ref,
`origin/${parsedGitLocation.ref}`,
currentBranchName,
`origin/${currentBranchName}`,
);
return repositoryTmpPath;
}
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import path from 'path';
import { Logger } from 'winston';
import { Entity } from '@backstage/catalog-model';
import { PublisherBase } from './types';
import { resolvePackagePath } from '@backstage/backend-common';
export class LocalPublish implements PublisherBase {
private readonly logger: Logger;
@@ -39,9 +39,9 @@ export class LocalPublish implements PublisherBase {
| { remoteUrl: string } {
const entityNamespace = entity.metadata.namespace ?? 'default';
const publishDir = path.resolve(
__dirname,
'../../../../static/docs/',
const publishDir = resolvePackagePath(
'@backstage/plugin-techdocs-backend',
'static/docs',
entity.kind,
entityNamespace,
entity.metadata.name,