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:
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user