Merge branch 'master' of github.com:spotify/backstage into migrate-to-msw

* 'master' of github.com:spotify/backstage: (110 commits)
  chore(catalog-backend): removing redudant classes and some functions
  chore(deps-dev): bump @types/webpack from 4.41.21 to 4.41.22 (#2765)
  move codecov.yml to .github
  feat(catalog-backend): add batch concurrency
  create-app: remove build step
  cli: simplify jest transform ignore regex
  feat(catalog-backend): introduce batching, speed up reading and writing of large datasets
  Techdocs: add Azure DevOps prepare support (#2748)
  feat(techdocs-header): Show breadcrumbs on docs page (#2786)
  changesets: add entry for create-app template location fix
  create-app: revert to github location type for example templates
  fix: make catalog filter work again
  Use new url scheme for techdocs
  feat: remove LocationProcessor.processEntity
  Add Dockerfile for helm chart
  feat: use the new UrlReader in the CodeOwnersProcessor
  feat: use new UrlReader in PlaceholderProcessor
  feat: remove the backstage.io/definition-at-location annotation
  Update loud-lamps-visit.md
  feat(proxy-backend): limit the forwarded http headers to a safe set
  ...
This commit is contained in:
blam
2020-10-09 14:48:32 +02:00
251 changed files with 6161 additions and 1918 deletions
+1 -1
View File
@@ -41,7 +41,7 @@
"express": "^4.17.1",
"express-prom-bundle": "^6.1.0",
"express-promise-router": "^3.0.3",
"git-url-parse": "^11.2.0",
"git-url-parse": "^11.3.0",
"helmet": "^4.0.0",
"knex": "^0.21.1",
"lodash": "^4.17.15",
@@ -0,0 +1,97 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { createDatabaseClient } from './connection';
import { SingleConnectionDatabaseManager } from './SingleConnection';
jest.mock('./connection');
describe('SingleConnectionDatabaseManager', () => {
const createConfig = (data: any) =>
ConfigReader.fromConfigs([
{
context: '',
data,
},
]);
const defaultConfigOptions = {
backend: {
database: {
client: 'pg',
connection: {
host: 'localhost',
user: 'foo',
password: 'bar',
database: 'foodb',
},
},
},
};
const defaultConfig = () => createConfig(defaultConfigOptions);
// This is similar to the ts-jest `mocked` helper.
const mocked = (f: Function) => f as jest.Mock;
afterEach(() => jest.resetAllMocks());
describe('SingleConnectionDatabaseManager.fromConfig', () => {
it('accesses the backend.database key', () => {
const getConfig = jest.fn();
const config = defaultConfig();
config.getConfig = getConfig;
SingleConnectionDatabaseManager.fromConfig(config);
expect(getConfig.mock.calls[0][0]).toEqual('backend.database');
});
});
describe('SingleConnectionDatabaseManager.forPlugin', () => {
const manager = SingleConnectionDatabaseManager.fromConfig(defaultConfig());
it('connects to a database scoped to the plugin', async () => {
const pluginId = 'test1';
await manager.forPlugin(pluginId).getClient();
expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1);
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
const callArgs = mockCalls[0];
expect(callArgs[0].get()).toEqual(defaultConfigOptions.backend.database);
expect(callArgs[1].connection.database).toEqual(
`backstage_plugin_${pluginId}`,
);
});
it('provides different plugins different databases', async () => {
const plugin1Id = 'test1';
const plugin2Id = 'test2';
await manager.forPlugin(plugin1Id).getClient();
await manager.forPlugin(plugin2Id).getClient();
expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(2);
const mockCalls = mocked(createDatabaseClient).mock.calls;
const plugin1CallArgs = mockCalls[0];
const plugin2CallArgs = mockCalls[1];
expect(plugin1CallArgs[1].connection.database).not.toEqual(
plugin2CallArgs[1].connection.database,
);
});
});
});
@@ -0,0 +1,82 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Knex from 'knex';
import { Config } from '@backstage/config';
import { createDatabaseClient, ensureDatabaseExists } from './connection';
import { PluginDatabaseManager } from './types';
/**
* Implements a Database Manager which will automatically create new databases
* for plugins when requested. All requested databases are created with the
* credentials provided; if the database already exists no attempt to create
* the database will be made.
*/
export class SingleConnectionDatabaseManager {
/**
* Creates a new SingleConnectionDatabaseManager instance by reading from the `backend`
* config section, specifically the `.database` key for discovering the management
* database configuration.
*
* @param config The loaded application configuration.
*/
static fromConfig(config: Config): SingleConnectionDatabaseManager {
return new SingleConnectionDatabaseManager(
config.getConfig('backend.database'),
);
}
private constructor(private readonly config: Config) {}
/**
* Generates a PluginDatabaseManager for consumption by plugins.
*
* @param pluginId The plugin that the database manager should be created for. Plugin names should be unique.
*/
forPlugin(pluginId: string): PluginDatabaseManager {
const _this = this;
return {
getClient(): Promise<Knex> {
return _this.getDatabase(pluginId);
},
};
}
private async getDatabase(pluginId: string): Promise<Knex> {
const config = this.config;
const overrides = SingleConnectionDatabaseManager.getDatabaseOverrides(
pluginId,
);
const overrideConfig = overrides.connection as Knex.ConnectionConfig;
await this.ensureDatabase(overrideConfig.database);
return createDatabaseClient(config, overrides);
}
private static getDatabaseOverrides(pluginId: string): Knex.Config {
return {
connection: {
database: `backstage_plugin_${pluginId}`,
},
};
}
private async ensureDatabase(database: string) {
const config = this.config;
await ensureDatabaseExists(config, database);
}
}
@@ -18,6 +18,18 @@ import { mergeDatabaseConfig } from './config';
describe('config', () => {
describe(mergeDatabaseConfig, () => {
it('does not mutate the input object', () => {
const input = {
original: 'key',
};
const override = {
added: 'value',
};
mergeDatabaseConfig(input, override);
expect(input).not.toHaveProperty('added');
});
it('does not require overrides', () => {
expect(
mergeDatabaseConfig({
@@ -19,9 +19,9 @@ import { merge } from 'lodash';
/**
* Merges database objects together
*
* @param config The base config
* @param config The base config. The input is not modified
* @param overrides Any additional overrides
*/
export function mergeDatabaseConfig(config: any, ...overrides: any[]) {
return merge(config, ...overrides);
return merge({}, config, ...overrides);
}
@@ -15,3 +15,5 @@
*/
export * from './connection';
export * from './types';
export * from './SingleConnection';
@@ -0,0 +1,30 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import knex from 'knex';
/**
* The PluginDatabaseManager manages access to databases that Plugins get.
*/
export interface PluginDatabaseManager {
/**
* getClient provides backend plugins database connections for itself.
*
* The purpose of this method is to allow plugins to get isolated data
* stores so that plugins are discouraged from database integration.
*/
getClient(): Promise<knex>;
}