Merge pull request #16424 from joeppeeters/16355-add-pg-schema-ownership-config
Add `role` config to control postgres ownership of schemas and tables
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Adds config option `backend.database.role` to set ownership for newly created schemas and tables in Postgres
|
||||
|
||||
The example config below connects to the database as user `v-backstage-123` but sets the ownership of
|
||||
the create schemas and tables to `backstage`
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: pg
|
||||
pluginDivisionMode: schema
|
||||
role: backstage
|
||||
connection:
|
||||
user: v-backstage-123
|
||||
...
|
||||
```
|
||||
Vendored
+4
@@ -96,6 +96,8 @@ export interface Config {
|
||||
* @default database
|
||||
*/
|
||||
pluginDivisionMode?: 'database' | 'schema';
|
||||
/** Configures the ownership of newly created schemas in pg databases. */
|
||||
role?: string;
|
||||
/**
|
||||
* Arbitrary config object to pass to knex when initializing
|
||||
* (https://knexjs.org/#Installation-client). Most notable is the debug
|
||||
@@ -125,6 +127,8 @@ export interface Config {
|
||||
* This is merged recursively into the base knexConfig
|
||||
*/
|
||||
knexConfig?: object;
|
||||
/** Configures the ownership of newly created schemas in pg databases. */
|
||||
role?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"morgan": "^1.10.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-forge": "^1.3.1",
|
||||
"pg": "^8.3.0",
|
||||
"raw-body": "^2.4.1",
|
||||
"request": "^2.88.2",
|
||||
"selfsigned": "^2.0.0",
|
||||
@@ -122,6 +123,7 @@
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/morgan": "^1.9.0",
|
||||
"@types/node-forge": "^1.3.0",
|
||||
"@types/pg": "^8.6.6",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/stoppable": "^1.1.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
|
||||
@@ -687,5 +687,59 @@ describe('DatabaseManager', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('sets the owner config for plugin using default config', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
database: 'foodb',
|
||||
},
|
||||
role: 'backstage',
|
||||
plugin: {
|
||||
testowner: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
await testManager.forPlugin('testowner').getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig] = mockCalls[0];
|
||||
|
||||
expect(baseConfig.data.role).toEqual('backstage');
|
||||
});
|
||||
|
||||
it('sets the owner config for plugin using plugin config', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
database: 'foodb',
|
||||
},
|
||||
role: 'backstage',
|
||||
plugin: {
|
||||
testowner: {
|
||||
role: 'backstage-plugin',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
await testManager.forPlugin('testowner').getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig] = mockCalls[0];
|
||||
|
||||
expect(baseConfig.data.role).toEqual('backstage-plugin');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,6 +186,13 @@ export class DatabaseManager {
|
||||
};
|
||||
}
|
||||
|
||||
private getRoleConfig(pluginId: string): string | undefined {
|
||||
return (
|
||||
this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??
|
||||
this.config.getOptionalString('role')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the knexConfig which should be used for a given plugin.
|
||||
*
|
||||
@@ -278,11 +285,13 @@ export class DatabaseManager {
|
||||
*/
|
||||
private getConfigForPlugin(pluginId: string): Knex.Config {
|
||||
const { client } = this.getClientType(pluginId);
|
||||
const role = this.getRoleConfig(pluginId);
|
||||
|
||||
return {
|
||||
...this.getAdditionalKnexConfig(pluginId),
|
||||
client,
|
||||
connection: this.getConnectionConfig(pluginId),
|
||||
...(role && { role }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { mergeDatabaseConfig } from '../config';
|
||||
import { DatabaseConnector } from '../types';
|
||||
import defaultNameOverride from './defaultNameOverride';
|
||||
import defaultSchemaOverride from './defaultSchemaOverride';
|
||||
import { Client } from 'pg';
|
||||
|
||||
/**
|
||||
* Creates a knex postgres database connection
|
||||
@@ -35,6 +36,17 @@ export function createPgDatabaseClient(
|
||||
) {
|
||||
const knexConfig = buildPgDatabaseConfig(dbConfig, overrides);
|
||||
const database = knexFactory(knexConfig);
|
||||
|
||||
const role = dbConfig.getOptionalString('role');
|
||||
|
||||
if (role) {
|
||||
database.client.pool.on(
|
||||
'createSuccess',
|
||||
async (_event: number, pgClient: Client) => {
|
||||
await pgClient.query(`SET ROLE ${role}`);
|
||||
},
|
||||
);
|
||||
}
|
||||
return database;
|
||||
}
|
||||
|
||||
@@ -147,10 +159,18 @@ export async function ensurePgSchemaExists(
|
||||
...schemas: Array<string>
|
||||
): Promise<void> {
|
||||
const admin = createPgDatabaseClient(dbConfig);
|
||||
const role = dbConfig.getOptionalString('role');
|
||||
|
||||
try {
|
||||
const ensureSchema = async (database: string) => {
|
||||
await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [database]);
|
||||
if (role) {
|
||||
await admin.raw(`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ??`, [
|
||||
database,
|
||||
role,
|
||||
]);
|
||||
} else {
|
||||
await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [database]);
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all(schemas.map(ensureSchema));
|
||||
|
||||
@@ -3453,6 +3453,7 @@ __metadata:
|
||||
"@types/mock-fs": ^4.13.0
|
||||
"@types/morgan": ^1.9.0
|
||||
"@types/node-forge": ^1.3.0
|
||||
"@types/pg": ^8.6.6
|
||||
"@types/recursive-readdir": ^2.2.0
|
||||
"@types/stoppable": ^1.1.0
|
||||
"@types/supertest": ^2.0.8
|
||||
@@ -3489,6 +3490,7 @@ __metadata:
|
||||
mysql2: ^2.2.5
|
||||
node-fetch: ^2.6.7
|
||||
node-forge: ^1.3.1
|
||||
pg: ^8.3.0
|
||||
raw-body: ^2.4.1
|
||||
recursive-readdir: ^2.2.2
|
||||
request: ^2.88.2
|
||||
@@ -15508,6 +15510,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/pg@npm:^8.6.6":
|
||||
version: 8.6.6
|
||||
resolution: "@types/pg@npm:8.6.6"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
pg-protocol: "*"
|
||||
pg-types: ^2.2.0
|
||||
checksum: ac145553a8ad2f357feacad1bceaf5d6ce904eb9d66233b84c469a2b4fa3738d4ebdf29b7ea45387be2d07f915fd873a229f90a2f766d7c377afa7c41fbcf8d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/pluralize@npm:^0.0.29":
|
||||
version: 0.0.29
|
||||
resolution: "@types/pluralize@npm:0.0.29"
|
||||
@@ -32034,14 +32047,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-protocol@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "pg-protocol@npm:1.5.0"
|
||||
checksum: b839d12cafe942ef9cbc5b13c174eb2356804fb4fe8ead8279f46a36be90722d19a91409955beb8a3d5301639c44854e49749de4aef02dc361fee3e2a61fb1e4
|
||||
"pg-protocol@npm:*, pg-protocol@npm:^1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "pg-protocol@npm:1.6.0"
|
||||
checksum: e12662d2de2011e0c3a03f6a09f435beb1025acdc860f181f18a600a5495dc38a69d753bbde1ace279c8c442536af9c1a7c11e1d0fe3fad3aa1348b28d9d2683
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-types@npm:^2.1.0":
|
||||
"pg-types@npm:^2.1.0, pg-types@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "pg-types@npm:2.2.0"
|
||||
dependencies:
|
||||
@@ -32055,14 +32068,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"pg@npm:^8.3.0, pg@npm:^8.4.0":
|
||||
version: 8.8.0
|
||||
resolution: "pg@npm:8.8.0"
|
||||
version: 8.9.0
|
||||
resolution: "pg@npm:8.9.0"
|
||||
dependencies:
|
||||
buffer-writer: 2.0.0
|
||||
packet-reader: 1.0.0
|
||||
pg-connection-string: ^2.5.0
|
||||
pg-pool: ^3.5.2
|
||||
pg-protocol: ^1.5.0
|
||||
pg-protocol: ^1.6.0
|
||||
pg-types: ^2.1.0
|
||||
pgpass: 1.x
|
||||
peerDependencies:
|
||||
@@ -32070,7 +32083,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
checksum: fa30a85814dd7238b582c3bc6c0b9e2b0ae38dd0a6bb485ef480e64bb5ce589de6cb873ce4d3cd10c37a3e0a1e1281ba75dc7d80b1a68bae91999cd5b70d398b
|
||||
checksum: dfd158955318f9ffb9428eaada29f3ee98b9eb07e87ed4b56589a19984d109f23bb8f88db78b7d7f870553e5b75ca0d58d0ed55755a8c6aed5df44e038c1d529
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user