Merge branch 'master' of github.com:spotify/backstage into shmidt-i/app-catalog-tabs-routes-everything-is-connected
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user