collect all pg config interpretation in one function (#32465)

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2026-01-22 11:19:21 +01:00
committed by GitHub
parent 157470504c
commit 44f5d049ca
2 changed files with 174 additions and 194 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': patch
---
Minor internal restructure of the postgres config loading code
@@ -15,7 +15,7 @@
*/
import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';
import { Config, ConfigReader } from '@backstage/config';
import { Config } from '@backstage/config';
import { ForwardedError } from '@backstage/errors';
import { JsonObject } from '@backstage/types';
import knexFactory, { Knex } from 'knex';
@@ -23,8 +23,6 @@ import { merge, omit } from 'lodash';
import limiterFactory from 'p-limit';
import { Client } from 'pg';
import { Connector } from '../types';
import defaultNameOverride from './defaultNameOverride';
import defaultSchemaOverride from './defaultSchemaOverride';
import { mergeDatabaseConfig } from './mergeDatabaseConfig';
import format from 'pg-format';
@@ -293,6 +291,159 @@ function normalizeConnection(
: connection;
}
/**
* The computed configuration for a plugin's postgres database connection.
*/
export interface PgPluginDatabaseConfig {
/** The database client type (e.g. 'pg') */
client: string;
/** Whether the client type was overridden at the plugin level */
clientOverridden: boolean;
/** The optional role to set on connections */
role: string | undefined;
/** Additional knex configuration merged from base and plugin config */
additionalKnexConfig: JsonObject | undefined;
/** Whether to ensure the database exists */
ensureExists: boolean;
/** Whether to ensure the schema exists */
ensureSchemaExists: boolean;
/** The plugin division mode ('database' or 'schema') */
pluginDivisionMode: string;
/** The connection configuration */
connection: Knex.StaticConnectionConfig;
/** The database name, if any */
databaseName: string | undefined;
/** Database client overrides including schema overrides if applicable */
databaseClientOverrides: Knex.Config;
/** The full knex config for the plugin */
knexConfig: Knex.Config;
}
/**
* Computes all postgres database configuration for a plugin from the provided config.
*
* @param config - The database config object
* @param pluginId - The plugin ID to compute config for
* @param prefix - The database name prefix (e.g. 'backstage_plugin_')
* @returns All computed configuration values for the plugin
*/
export function computePgPluginConfig(
config: Config,
pluginId: string,
prefix: string,
): PgPluginDatabaseConfig {
// Client type
const pluginClient = config.getOptionalString(
`${pluginPath(pluginId)}.client`,
);
const baseClient = config.getString('client');
const client = pluginClient ?? baseClient;
const clientOverridden = client !== baseClient;
// Role
const role =
config.getOptionalString(`${pluginPath(pluginId)}.role`) ??
config.getOptionalString('role');
// Additional knex config
const pluginKnexConfig = config
.getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)
?.get<JsonObject>();
const baseKnexConfig = config
.getOptionalConfig('knexConfig')
?.get<JsonObject>();
const additionalKnexConfig = merge(baseKnexConfig, pluginKnexConfig);
// Ensure exists flags
const baseEnsureExists = config.getOptionalBoolean('ensureExists') ?? true;
const ensureExists =
config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??
baseEnsureExists;
const baseEnsureSchemaExists =
config.getOptionalBoolean('ensureSchemaExists') ?? false;
const ensureSchemaExists =
config.getOptionalBoolean(
`${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`,
) ?? baseEnsureSchemaExists;
// Plugin division mode
const pluginDivisionMode =
config.getOptionalString('pluginDivisionMode') ?? 'database';
// Connection config
let baseConnection = normalizeConnection(config.get('connection'));
// Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`.
// The `database` property from the base connection is omitted unless
// `pluginDivisionMode` is set to `schema`.
if (pluginDivisionMode !== 'schema') {
baseConnection = omit(baseConnection, 'database');
}
// Get and normalize optional plugin specific database connection
const pluginConnection = normalizeConnection(
config.getOptional(`${pluginPath(pluginId)}.connection`),
);
(
baseConnection as Knex.PgConnectionConfig
).application_name ||= `backstage_plugin_${pluginId}`;
const connection = {
// Include base connection if client type has not been overridden
...(clientOverridden ? {} : baseConnection),
...pluginConnection,
} as Knex.StaticConnectionConfig;
// Database name
const connectionDatabaseName = (connection as Knex.ConnectionConfig)
?.database;
let databaseName: string | undefined;
if (pluginDivisionMode === 'schema') {
// `pluginDivisionMode` as `schema` should use overridden databaseName if supplied
// or fallback to default knex database
databaseName = connectionDatabaseName;
} else {
// All other supported databases should fallback to an auto-prefixed name
databaseName = connectionDatabaseName ?? `${prefix}${pluginId}`;
}
// Database client overrides
let databaseClientOverrides: Knex.Config = {};
if (databaseName) {
databaseClientOverrides = { connection: { database: databaseName } };
}
if (pluginDivisionMode === 'schema') {
databaseClientOverrides = mergeDatabaseConfig({}, databaseClientOverrides, {
searchPath: [pluginId],
});
}
// Full knex config for plugin
const knexConfig: Knex.Config = {
...additionalKnexConfig,
client,
connection,
...(role && { role }),
};
return {
client,
clientOverridden,
role,
additionalKnexConfig,
ensureExists,
ensureSchemaExists,
pluginDivisionMode,
connection,
databaseName,
databaseClientOverrides,
knexConfig,
};
}
export class PgConnector implements Connector {
private readonly config: Config;
private readonly prefix: string;
@@ -309,30 +460,26 @@ export class PgConnector implements Connector {
lifecycle: LifecycleService;
},
): Promise<Knex> {
const pluginConfig = new ConfigReader(
this.getConfigForPlugin(pluginId) as JsonObject,
const pluginDbConfig = computePgPluginConfig(
this.config,
pluginId,
this.prefix,
);
const databaseName = this.getDatabaseName(pluginId);
if (databaseName && this.getEnsureExistsConfig(pluginId)) {
if (pluginDbConfig.databaseName && pluginDbConfig.ensureExists) {
try {
await ensurePgDatabaseExists(pluginConfig, databaseName);
await ensurePgDatabaseExists(this.config, pluginDbConfig.databaseName);
} catch (error) {
throw new Error(
`Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,
`Failed to connect to the database to make sure that '${pluginDbConfig.databaseName}' exists, ${error}`,
);
}
}
let schemaOverrides;
if (this.getPluginDivisionModeConfig() === 'schema') {
schemaOverrides = defaultSchemaOverride(pluginId);
if (
this.getEnsureSchemaExistsConfig(pluginId) ||
this.getEnsureExistsConfig(pluginId)
) {
if (pluginDbConfig.pluginDivisionMode === 'schema') {
if (pluginDbConfig.ensureSchemaExists || pluginDbConfig.ensureExists) {
try {
await ensurePgSchemaExists(pluginConfig, pluginId);
await ensurePgSchemaExists(this.config, pluginId);
} catch (error) {
throw new Error(
`Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,
@@ -341,186 +488,14 @@ export class PgConnector implements Connector {
}
}
const databaseClientOverrides = mergeDatabaseConfig(
{},
this.getDatabaseOverrides(pluginId),
schemaOverrides,
);
const client = createPgDatabaseClient(
pluginConfig,
databaseClientOverrides,
this.config,
mergeDatabaseConfig(
pluginDbConfig.knexConfig,
pluginDbConfig.databaseClientOverrides,
),
);
return client;
}
/**
* Provides the canonical database name for a given plugin.
*
* This method provides the effective database name which is determined using global
* and plugin specific database config. If no explicit database name is configured
* and `pluginDivisionMode` is not `schema`, this method will provide a generated name
* which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is
* `schema`, it will fallback to using the default database for the knex instance.
*
* @param pluginId - Lookup the database name for given plugin
* @returns String representing the plugin's database name
*/
private getDatabaseName(pluginId: string): string | undefined {
const connection = this.getConnectionConfig(pluginId);
const databaseName = (connection as Knex.ConnectionConfig)?.database;
// `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database
if (this.getPluginDivisionModeConfig() === 'schema') {
return databaseName;
}
// all other supported databases should fallback to an auto-prefixed name
return databaseName ?? `${this.prefix}${pluginId}`;
}
/**
* Provides the client type which should be used for a given plugin.
*
* The client type is determined by plugin specific config if present.
* Otherwise the base client is used as the fallback.
*
* @param pluginId - Plugin to get the client type for
* @returns Object with client type returned as `client` and boolean
* representing whether or not the client was overridden as
* `overridden`
*/
private getClientType(pluginId: string): {
client: string;
overridden: boolean;
} {
const pluginClient = this.config.getOptionalString(
`${pluginPath(pluginId)}.client`,
);
const baseClient = this.config.getString('client');
const client = pluginClient ?? baseClient;
return {
client,
overridden: client !== baseClient,
};
}
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.
*
* @param pluginId - Plugin to get the knexConfig for
* @returns The merged knexConfig value or undefined if it isn't specified
*/
private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {
const pluginConfig = this.config
.getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)
?.get<JsonObject>();
const baseConfig = this.config
.getOptionalConfig('knexConfig')
?.get<JsonObject>();
return merge(baseConfig, pluginConfig);
}
private getEnsureExistsConfig(pluginId: string): boolean {
const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true;
return (
this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??
baseConfig
);
}
private getEnsureSchemaExistsConfig(pluginId: string): boolean {
const baseConfig =
this.config.getOptionalBoolean('ensureSchemaExists') ?? false;
return (
this.config.getOptionalBoolean(
`${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`,
) ?? baseConfig
);
}
private getPluginDivisionModeConfig(): string {
return this.config.getOptionalString('pluginDivisionMode') ?? 'database';
}
/**
* Provides a Knex connection plugin config by combining base and plugin
* config.
*
* This method provides a baseConfig for a plugin database connector. If the
* client type has not been overridden, the global connection config will be
* included with plugin specific config as the base. Values from the plugin
* connection take precedence over the base. Base database name is omitted
* unless `pluginDivisionMode` is set to `schema`.
*/
private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {
const { overridden } = this.getClientType(pluginId);
let baseConnection = normalizeConnection(this.config.get('connection'));
// Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The
// `database` property from the base connection is omitted unless `pluginDivisionMode`
// is set to `schema`.
if (this.getPluginDivisionModeConfig() !== 'schema') {
baseConnection = omit(baseConnection, 'database');
}
// get and normalize optional plugin specific database connection
const connection = normalizeConnection(
this.config.getOptional(`${pluginPath(pluginId)}.connection`),
);
(
baseConnection as Knex.PgConnectionConfig
).application_name ||= `backstage_plugin_${pluginId}`;
return {
// include base connection if client type has not been overridden
...(overridden ? {} : baseConnection),
...connection,
} as Knex.StaticConnectionConfig;
}
/**
* Provides a Knex database config for a given plugin.
*
* This method provides a Knex configuration object along with the plugin's
* client type.
*
* @param pluginId - The plugin that the database config should correspond with
*/
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 }),
};
}
/**
* Provides a partial `Knex.Config`• database name override for a given plugin.
*
* @param pluginId - Target plugin to get database name override
* @returns Partial `Knex.Config` with database name override
*/
private getDatabaseOverrides(pluginId: string): Knex.Config {
const databaseName = this.getDatabaseName(pluginId);
return databaseName ? defaultNameOverride(databaseName) : {};
}
}