@backstage/backend-defaults 0.18.0 → 0.18.1-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/CreateBackend.cjs.js.map +1 -1
- package/dist/PackageDiscoveryService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actions/DefaultActionsService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actions/actionsServiceFactory.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actionsRegistry/actionFilters.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/metrics/DefaultMetricsService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/metrics/metricsServiceFactory.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/tracing/DefaultTracingService.cjs.js.map +1 -1
- package/dist/alpha/entrypoints/tracing/tracingServiceFactory.cjs.js.map +1 -1
- package/dist/discoveryFeatureLoader.cjs.js.map +1 -1
- package/dist/entrypoints/auditor/DefaultAuditorService.cjs.js.map +1 -1
- package/dist/entrypoints/auditor/WinstonRootAuditorService.cjs.js.map +1 -1
- package/dist/entrypoints/auditor/auditorServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/auditor/utils.cjs.js.map +1 -1
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
- package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -1
- package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/helpers.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/jwks.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/legacy.cjs.js.map +1 -1
- package/dist/entrypoints/auth/external/static.cjs.js.map +1 -1
- package/dist/entrypoints/auth/helpers.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js.map +1 -1
- package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js.map +1 -1
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
- package/dist/entrypoints/cache/CacheClient.cjs.js.map +1 -1
- package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -1
- package/dist/entrypoints/cache/cacheServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/cache/providers/infinispan/InfinispanKeyvStore.cjs.js.map +1 -1
- package/dist/entrypoints/cache/providers/infinispan/InfinispanOptionsMapper.cjs.js.map +1 -1
- package/dist/entrypoints/cache/types.cjs.js.map +1 -1
- package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -1
- package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js.map +1 -1
- package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js.map +1 -1
- package/dist/entrypoints/database/connectors/mysql.cjs.js.map +1 -1
- package/dist/entrypoints/database/connectors/postgres.cjs.js +2 -0
- package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -1
- package/dist/entrypoints/database/connectors/sqlite3.cjs.js.map +1 -1
- package/dist/entrypoints/database/databaseServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -1
- package/dist/entrypoints/discovery/SrvResolvers.cjs.js.map +1 -1
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/discovery/parsing.cjs.js.map +1 -1
- package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/http/createAuthIntegrationRouter.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/http/createCookieAuthRefreshMiddleware.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/http/createCredentialsBarrier.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/http/createLifecycleMiddleware.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/http/createRateLimitMiddleware.cjs.js.map +1 -1
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/logger/loggerServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/permissionsRegistry/permissionsRegistryServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js.map +1 -1
- package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/config.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js.map +1 -1
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -1
- package/dist/entrypoints/rootLogger/config.cjs.js.map +1 -1
- package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/rootLogger/types.cjs.js.map +1 -1
- package/dist/entrypoints/rootLogger/utils.cjs.js.map +1 -1
- package/dist/entrypoints/rootSystemMetadata/lib/DefaultRootSystemMetadataService.cjs.js.map +1 -1
- package/dist/entrypoints/rootSystemMetadata/rootSystemMetadataServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/database/tables.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/TaskStatePoller.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/types.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/lib/util.cjs.js.map +1 -1
- package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/tree/util.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -1
- package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js.map +1 -1
- package/dist/entrypoints/userInfo/CachedUserInfoService.cjs.js.map +1 -1
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -1
- package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js.map +1 -1
- package/dist/lib/RateLimitStoreFactory.cjs.js.map +1 -1
- package/dist/lib/escapeRegExp.cjs.js.map +1 -1
- package/dist/lib/rateLimitMiddleware.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +1 -1
- package/dist/package.json.cjs.js.map +1 -1
- package/package.json +17 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mysql.cjs.js","sources":["../../../../src/entrypoints/database/connectors/mysql.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport limiterFactory from 'p-limit';\nimport yn from 'yn';\nimport { Connector } from '../types';\nimport defaultNameOverride from './defaultNameOverride';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\n\n// Limits the number of concurrent DDL operations to 1\nconst ddlLimiter = limiterFactory(1);\n\n/**\n * Creates a knex mysql database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createMysqlDatabaseClient(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const knexConfig = buildMysqlDatabaseConfig(dbConfig, overrides);\n const database = knexFactory(knexConfig);\n return database;\n}\n\n/**\n * Builds a knex mysql database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildMysqlDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n return mergeDatabaseConfig(\n dbConfig.get(),\n {\n connection: getMysqlConnectionConfig(dbConfig, !!overrides),\n useNullAsDefault: true,\n },\n overrides,\n );\n}\n\n/**\n * Gets the mysql connection config\n *\n * @param dbConfig - The database config\n * @param parseConnectionString - Flag to explicitly control connection string parsing\n */\nexport function getMysqlConnectionConfig(\n dbConfig: Config,\n parseConnectionString?: boolean,\n): Knex.MySqlConnectionConfig | string {\n const connection = dbConfig.get('connection') as any;\n const isConnectionString =\n typeof connection === 'string' || connection instanceof String;\n const autoParse = typeof parseConnectionString !== 'boolean';\n\n const shouldParseConnectionString = autoParse\n ? isConnectionString\n : parseConnectionString && isConnectionString;\n\n return shouldParseConnectionString\n ? parseMysqlConnectionString(connection as string)\n : connection;\n}\n\n/**\n * Parses a mysql connection string.\n *\n * e.g. mysql://examplename:somepassword@examplehost:3306/dbname\n * @param connectionString - The mysql connection string\n */\nexport function parseMysqlConnectionString(\n connectionString: string,\n): Knex.MySqlConnectionConfig {\n try {\n const {\n protocol,\n username,\n password,\n port,\n hostname,\n pathname,\n searchParams,\n } = new URL(connectionString);\n\n if (protocol !== 'mysql:') {\n throw new Error(`Unknown protocol ${protocol}`);\n } else if (!username || !password) {\n throw new Error(`Missing username/password`);\n } else if (!pathname.match(/^\\/[^/]+$/)) {\n throw new Error(`Expected single path segment`);\n }\n\n const result: Knex.MySqlConnectionConfig = {\n user: username,\n password,\n host: hostname,\n port: Number(port || 3306),\n database: decodeURIComponent(pathname.substring(1)),\n };\n\n const ssl = searchParams.get('ssl');\n if (ssl) {\n result.ssl = ssl;\n }\n\n const debug = searchParams.get('debug');\n if (debug) {\n result.debug = yn(debug);\n }\n\n return result;\n } catch (e) {\n throw new InputError(\n `Error while parsing MySQL connection string, ${e}`,\n e,\n );\n }\n}\n\n/**\n * Creates the missing mysql database if it does not exist\n *\n * @param dbConfig - The database config\n * @param databases - The names of the databases to create\n */\nexport async function ensureMysqlDatabaseExists(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = createMysqlDatabaseClient(dbConfig, {\n connection: {\n database: null as unknown as string,\n },\n pool: {\n min: 0,\n acquireTimeoutMillis: 10000,\n },\n });\n\n try {\n const ensureDatabase = async (database: string) => {\n await admin.raw(`CREATE DATABASE IF NOT EXISTS ??`, [database]);\n };\n await Promise.all(\n databases.map(async database => {\n // For initial setup we use a smaller timeout but several retries. Given that this\n // is a separate connection pool we should never really run into issues with connection\n // acquisition timeouts, but we do anyway. This might be a bug in knex or some other dependency.\n let lastErr: Error | undefined = undefined;\n for (let i = 0; i < 3; i++) {\n try {\n return await ddlLimiter(() => ensureDatabase(database));\n } catch (err) {\n lastErr = err;\n }\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n throw lastErr;\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Drops the given mysql databases.\n *\n * @param dbConfig - The database config\n * @param databases - The names of the databases to create\n */\nexport async function dropMysqlDatabase(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = createMysqlDatabaseClient(dbConfig, {\n connection: {\n database: null as unknown as string,\n },\n pool: {\n min: 0,\n acquireTimeoutMillis: 10000,\n },\n });\n\n try {\n const dropDatabase = async (database: string) => {\n await admin.raw(`DROP DATABASE ??`, [database]);\n };\n await Promise.all(\n databases.map(async database => {\n return await ddlLimiter(() => dropDatabase(database));\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parseMysqlConnectionString(connection as string)\n : connection;\n}\n\nexport class MysqlConnector implements Connector {\n private readonly config: Config;\n private readonly prefix: string;\n\n constructor(config: Config, prefix: string) {\n this.config = config;\n this.prefix = prefix;\n }\n\n async getClient(\n pluginId: string,\n _deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const databaseName = this.getDatabaseName(pluginId);\n if (databaseName && this.getEnsureExistsConfig(pluginId)) {\n try {\n await ensureMysqlDatabaseExists(pluginConfig, databaseName);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,\n );\n }\n }\n\n const pluginDivisionMode = this.getPluginDivisionModeConfig();\n if (pluginDivisionMode !== 'database') {\n throw new Error(\n `The MySQL driver does not support plugin division mode '${pluginDivisionMode}'`,\n );\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n );\n\n const client = createMysqlDatabaseClient(\n pluginConfig,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using\n * global and plugin specific database config. If no explicit database name,\n * this method will provide a generated name which is the pluginId prefixed\n * with 'backstage_plugin_'.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n const databaseName = (connection as Knex.ConnectionConfig)?.database;\n return databaseName ?? `${this.prefix}${pluginId}`;\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getEnsureExistsConfig(pluginId: string): boolean {\n const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true;\n return (\n this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??\n baseConfig\n );\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted\n * unless `pluginDivisionMode` is set to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? defaultNameOverride(databaseName) : {};\n }\n}\n"],"names":["limiterFactory","knexFactory","mergeDatabaseConfig","yn","InputError","ConfigReader","merge","omit","defaultNameOverride"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mysql.cjs.js","sources":["../../../../src/entrypoints/database/connectors/mysql.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport limiterFactory from 'p-limit';\nimport yn from 'yn';\nimport { Connector } from '../types';\nimport defaultNameOverride from './defaultNameOverride';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\n\n// Limits the number of concurrent DDL operations to 1\nconst ddlLimiter = limiterFactory(1);\n\n/**\n * Creates a knex mysql database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createMysqlDatabaseClient(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const knexConfig = buildMysqlDatabaseConfig(dbConfig, overrides);\n const database = knexFactory(knexConfig);\n return database;\n}\n\n/**\n * Builds a knex mysql database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildMysqlDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n return mergeDatabaseConfig(\n dbConfig.get(),\n {\n connection: getMysqlConnectionConfig(dbConfig, !!overrides),\n useNullAsDefault: true,\n },\n overrides,\n );\n}\n\n/**\n * Gets the mysql connection config\n *\n * @param dbConfig - The database config\n * @param parseConnectionString - Flag to explicitly control connection string parsing\n */\nexport function getMysqlConnectionConfig(\n dbConfig: Config,\n parseConnectionString?: boolean,\n): Knex.MySqlConnectionConfig | string {\n const connection = dbConfig.get('connection') as any;\n const isConnectionString =\n typeof connection === 'string' || connection instanceof String;\n const autoParse = typeof parseConnectionString !== 'boolean';\n\n const shouldParseConnectionString = autoParse\n ? isConnectionString\n : parseConnectionString && isConnectionString;\n\n return shouldParseConnectionString\n ? parseMysqlConnectionString(connection as string)\n : connection;\n}\n\n/**\n * Parses a mysql connection string.\n *\n * e.g. mysql://examplename:somepassword@examplehost:3306/dbname\n * @param connectionString - The mysql connection string\n */\nexport function parseMysqlConnectionString(\n connectionString: string,\n): Knex.MySqlConnectionConfig {\n try {\n const {\n protocol,\n username,\n password,\n port,\n hostname,\n pathname,\n searchParams,\n } = new URL(connectionString);\n\n if (protocol !== 'mysql:') {\n throw new Error(`Unknown protocol ${protocol}`);\n } else if (!username || !password) {\n throw new Error(`Missing username/password`);\n } else if (!pathname.match(/^\\/[^/]+$/)) {\n throw new Error(`Expected single path segment`);\n }\n\n const result: Knex.MySqlConnectionConfig = {\n user: username,\n password,\n host: hostname,\n port: Number(port || 3306),\n database: decodeURIComponent(pathname.substring(1)),\n };\n\n const ssl = searchParams.get('ssl');\n if (ssl) {\n result.ssl = ssl;\n }\n\n const debug = searchParams.get('debug');\n if (debug) {\n result.debug = yn(debug);\n }\n\n return result;\n } catch (e) {\n throw new InputError(\n `Error while parsing MySQL connection string, ${e}`,\n e,\n );\n }\n}\n\n/**\n * Creates the missing mysql database if it does not exist\n *\n * @param dbConfig - The database config\n * @param databases - The names of the databases to create\n */\nexport async function ensureMysqlDatabaseExists(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = createMysqlDatabaseClient(dbConfig, {\n connection: {\n database: null as unknown as string,\n },\n pool: {\n min: 0,\n acquireTimeoutMillis: 10000,\n },\n });\n\n try {\n const ensureDatabase = async (database: string) => {\n await admin.raw(`CREATE DATABASE IF NOT EXISTS ??`, [database]);\n };\n await Promise.all(\n databases.map(async database => {\n // For initial setup we use a smaller timeout but several retries. Given that this\n // is a separate connection pool we should never really run into issues with connection\n // acquisition timeouts, but we do anyway. This might be a bug in knex or some other dependency.\n let lastErr: Error | undefined = undefined;\n for (let i = 0; i < 3; i++) {\n try {\n return await ddlLimiter(() => ensureDatabase(database));\n } catch (err) {\n lastErr = err;\n }\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n throw lastErr;\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Drops the given mysql databases.\n *\n * @param dbConfig - The database config\n * @param databases - The names of the databases to create\n */\nexport async function dropMysqlDatabase(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = createMysqlDatabaseClient(dbConfig, {\n connection: {\n database: null as unknown as string,\n },\n pool: {\n min: 0,\n acquireTimeoutMillis: 10000,\n },\n });\n\n try {\n const dropDatabase = async (database: string) => {\n await admin.raw(`DROP DATABASE ??`, [database]);\n };\n await Promise.all(\n databases.map(async database => {\n return await ddlLimiter(() => dropDatabase(database));\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parseMysqlConnectionString(connection as string)\n : connection;\n}\n\nexport class MysqlConnector implements Connector {\n private readonly config: Config;\n private readonly prefix: string;\n\n constructor(config: Config, prefix: string) {\n this.config = config;\n this.prefix = prefix;\n }\n\n async getClient(\n pluginId: string,\n _deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const databaseName = this.getDatabaseName(pluginId);\n if (databaseName && this.getEnsureExistsConfig(pluginId)) {\n try {\n await ensureMysqlDatabaseExists(pluginConfig, databaseName);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,\n );\n }\n }\n\n const pluginDivisionMode = this.getPluginDivisionModeConfig();\n if (pluginDivisionMode !== 'database') {\n throw new Error(\n `The MySQL driver does not support plugin division mode '${pluginDivisionMode}'`,\n );\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n );\n\n const client = createMysqlDatabaseClient(\n pluginConfig,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using\n * global and plugin specific database config. If no explicit database name,\n * this method will provide a generated name which is the pluginId prefixed\n * with 'backstage_plugin_'.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n const databaseName = (connection as Knex.ConnectionConfig)?.database;\n return databaseName ?? `${this.prefix}${pluginId}`;\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getEnsureExistsConfig(pluginId: string): boolean {\n const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true;\n return (\n this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??\n baseConfig\n );\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted\n * unless `pluginDivisionMode` is set to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? defaultNameOverride(databaseName) : {};\n }\n}\n"],"names":["limiterFactory","knexFactory","mergeDatabaseConfig","yn","InputError","ConfigReader","merge","omit","defaultNameOverride"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres.cjs.js","sources":["../../../../src/entrypoints/database/connectors/postgres.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport {\n Config,\n ConfigReader,\n readDurationFromConfig,\n} from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n durationToMilliseconds,\n HumanDuration,\n JsonObject,\n} from '@backstage/types';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport limiterFactory from 'p-limit';\nimport { Client } from 'pg';\nimport { Connector } from '../types';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\nimport format from 'pg-format';\nimport { TokenCredential } from '@azure/identity';\n\n// Limits the number of concurrent DDL operations to 1\nconst ddlLimiter = limiterFactory(1);\n\nexport class PgAdminPool {\n private client?: Promise<Knex>;\n private destruction?: Promise<void>;\n private readonly destructionErrors: unknown[] = [];\n private idleTimer?: NodeJS.Timeout;\n private readonly operations = new Set<Promise<unknown>>();\n private isShutdown = false;\n\n constructor(\n private readonly createClient: () => Promise<Knex>,\n private readonly idleTimeoutMillis: number,\n ) {}\n\n async run<T>(operation: (client: Knex) => Promise<T> | T): Promise<T> {\n if (this.isShutdown) {\n throw new Error('PostgreSQL admin pool is shut down');\n }\n\n clearTimeout(this.idleTimer);\n this.idleTimer = undefined;\n\n const operationPromise = this.runOperation(operation);\n this.operations.add(operationPromise);\n try {\n return await operationPromise;\n } finally {\n this.operations.delete(operationPromise);\n this.scheduleIdleDestroy();\n }\n }\n\n async shutdown(): Promise<void> {\n this.isShutdown = true;\n clearTimeout(this.idleTimer);\n this.idleTimer = undefined;\n\n await Promise.allSettled(this.operations);\n await this.destroyClient().catch(() => {});\n if (this.destructionErrors.length > 0) {\n throw new AggregateError(\n this.destructionErrors,\n 'Failed to destroy PostgreSQL admin pool clients',\n );\n }\n }\n\n async invalidate(): Promise<void> {\n clearTimeout(this.idleTimer);\n this.idleTimer = undefined;\n\n await Promise.allSettled(this.operations);\n await this.destroyClient();\n }\n\n private async runOperation<T>(\n operation: (client: Knex) => Promise<T> | T,\n ): Promise<T> {\n if (this.destruction) {\n await this.destruction;\n }\n if (this.isShutdown) {\n throw new Error('PostgreSQL admin pool is shut down');\n }\n\n let clientPromise = this.client;\n if (!clientPromise) {\n clientPromise = this.createClient();\n this.client = clientPromise;\n void clientPromise.catch(() => {\n if (this.client === clientPromise) {\n this.client = undefined;\n }\n });\n }\n return operation(await clientPromise);\n }\n\n private scheduleIdleDestroy(): void {\n if (this.isShutdown || this.operations.size > 0) {\n return;\n }\n\n const timer = setTimeout(() => {\n if (this.idleTimer === timer) {\n this.idleTimer = undefined;\n void this.destroyClient().catch(() => {});\n }\n }, this.idleTimeoutMillis);\n timer.unref();\n this.idleTimer = timer;\n }\n\n private destroyClient(): Promise<void> {\n if (this.destruction) {\n return this.destruction;\n }\n\n const clientPromise = this.client;\n this.client = undefined;\n if (!clientPromise) {\n return Promise.resolve();\n }\n\n const destruction = clientPromise.then(\n client => client.destroy(),\n () => undefined,\n );\n this.destruction = destruction;\n void destruction.then(\n () => {\n if (this.destruction === destruction) {\n this.destruction = undefined;\n }\n },\n error => {\n if (this.destruction === destruction) {\n this.destruction = undefined;\n this.destructionErrors.push(error);\n }\n },\n );\n return destruction;\n }\n}\n\n/**\n * Creates a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport async function createPgDatabaseClient(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const knexConfig = await buildPgDatabaseConfig(dbConfig, overrides);\n const database = knexFactory(knexConfig);\n\n const role = dbConfig.getOptionalString('role');\n\n if (role) {\n database.client.pool.on(\n 'createSuccess',\n async (_event: number, pgClient: Client) => {\n const query = format('SET ROLE %I', role);\n await pgClient.query(query);\n },\n );\n }\n return database;\n}\n\n/**\n * Builds a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport async function buildPgDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const config = mergeDatabaseConfig(\n dbConfig.get(),\n {\n connection: getPgConnectionConfig(dbConfig, !!overrides),\n useNullAsDefault: true,\n },\n overrides,\n );\n const mergedConfigReader = new ConfigReader(config);\n\n if (config.connection.type === 'default' || !config.connection.type) {\n const connectionValue = config.connection;\n const sanitizedConnection =\n typeof connectionValue === 'string' || connectionValue instanceof String\n ? connectionValue\n : // connection is an object, omit config-only props\n omit(connectionValue as Record<string, unknown>, [\n 'type',\n 'instance',\n 'tokenCredential',\n ]);\n\n return {\n ...config,\n connection: sanitizedConnection,\n };\n }\n\n switch (config.connection.type) {\n case 'azure':\n return buildAzurePgConfig(mergedConfigReader);\n case 'cloudsql':\n return buildCloudSqlConfig(mergedConfigReader);\n case 'rds':\n return buildRdsPgConfig(mergedConfigReader);\n default:\n throw new Error(`Unknown connection type: ${config.connection.type}`);\n }\n}\n\n/* Note: the following type definition is intentionally duplicated in\n * /packages/backend-defaults/config.d.ts so the clientSecret property\n * can be annotated with \"@visibility secret\" there.\n */\nexport type AzureTokenCredentialConfig = {\n /**\n * How early before an access token expires to refresh it with a new one.\n * Defaults to 5 minutes\n * Supported formats:\n * - A string in the format of '1d', '2 seconds' etc. as supported by the `ms`\n * library.\n * - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.\n * - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.\n */\n tokenRenewableOffsetTime?: string | HumanDuration;\n /**\n * The client ID of a user-assigned managed identity.\n * If not provided, the system-assigned managed identity is used.\n */\n clientId?: string;\n clientSecret?: string;\n tenantId?: string;\n};\n\nexport async function buildAzurePgConfig(config: Config): Promise<Knex.Config> {\n const {\n DefaultAzureCredential,\n ManagedIdentityCredential,\n ClientSecretCredential,\n } = require('@azure/identity');\n\n const tokenConfig = config.getOptionalConfig('connection.tokenCredential');\n\n const tokenRenewableOffsetTime = durationToMilliseconds(\n tokenConfig?.has('tokenRenewableOffsetTime')\n ? readDurationFromConfig(tokenConfig, { key: 'tokenRenewableOffsetTime' })\n : { minutes: 5 },\n );\n\n const clientId = tokenConfig?.getOptionalString('clientId');\n const tenantId = tokenConfig?.getOptionalString('tenantId');\n const clientSecret = tokenConfig?.getOptionalString('clientSecret');\n let credential: TokenCredential;\n\n /**\n * Determine which TokenCredential to use based on provided config\n * 1. If clientId, tenantId and clientSecret are provided, use ClientSecretCredential\n * 2. If only clientId is provided, use ManagedIdentityCredential with user-assigned identity\n * 3. Otherwise, use DefaultAzureCredential (which may use system-assigned identity among other methods)\n */\n if (clientId && tenantId && clientSecret) {\n credential = new ClientSecretCredential(tenantId, clientId, clientSecret);\n } else if (clientId) {\n credential = new ManagedIdentityCredential(clientId);\n } else {\n credential = new DefaultAzureCredential();\n }\n\n const rawConfig = config.get() as Record<string, unknown>;\n\n const normalized = normalizeConnection(rawConfig.connection as any);\n const sanitizedConnection = omit(normalized, [\n 'type',\n 'instance',\n 'tokenCredential',\n ]) as Partial<Knex.StaticConnectionConfig>;\n\n async function getConnectionConfig() {\n const token = await credential.getToken(\n 'https://ossrdbms-aad.database.windows.net/.default',\n );\n\n if (!token) {\n throw new Error(\n 'Failed to acquire Azure access token for database authentication',\n );\n }\n\n const connectionConfig = {\n ...sanitizedConnection,\n password: token.token,\n expirationChecker: () =>\n /* return true if the token is within the renewable offset time */\n token.expiresOnTimestamp - tokenRenewableOffsetTime <= Date.now(),\n };\n\n return connectionConfig;\n }\n\n return {\n ...(rawConfig as Record<string, unknown>),\n connection: getConnectionConfig,\n };\n}\n\nexport async function buildCloudSqlConfig(\n config: Config,\n): Promise<Knex.Config> {\n const client = config.getOptionalString('client');\n\n if (client && client !== 'pg') {\n throw new Error('Cloud SQL only supports the pg client');\n }\n\n const instance = config.getOptionalString('connection.instance');\n if (!instance) {\n throw new Error('Missing instance connection name for Cloud SQL');\n }\n\n const {\n Connector: CloudSqlConnector,\n IpAddressTypes,\n AuthTypes,\n } = require('@google-cloud/cloud-sql-connector') as typeof import('@google-cloud/cloud-sql-connector');\n const connector = new CloudSqlConnector();\n\n type IpType = (typeof IpAddressTypes)[keyof typeof IpAddressTypes];\n const ipTypeRaw = config.getOptionalString('connection.ipAddressType');\n\n let ipType: IpType | undefined;\n if (ipTypeRaw !== undefined) {\n if (\n !(Object.values(IpAddressTypes) as Array<string | number>).includes(\n ipTypeRaw as any,\n )\n ) {\n throw new Error(\n `Invalid connection.ipAddressType: ${ipTypeRaw}; valid values: ${Object.values(\n IpAddressTypes,\n ).join(', ')}`,\n );\n }\n ipType = ipTypeRaw as unknown as IpType;\n }\n\n const clientOpts = await connector.getOptions({\n instanceConnectionName: instance,\n ipType: ipType ?? IpAddressTypes.PUBLIC,\n authType: AuthTypes.IAM,\n });\n\n const rawConfig = config.get() as Record<string, unknown>;\n const normalized = normalizeConnection(rawConfig.connection as any);\n const sanitizedConnection = omit(normalized, [\n 'type',\n 'instance',\n ]) as Partial<Knex.StaticConnectionConfig>;\n\n return {\n ...(rawConfig as Record<string, unknown>),\n client: 'pg',\n connection: {\n ...sanitizedConnection,\n ...clientOpts,\n },\n };\n}\n\nexport async function buildRdsPgConfig(config: Config): Promise<Knex.Config> {\n const { Signer } =\n require('@aws-sdk/rds-signer') as typeof import('@aws-sdk/rds-signer');\n\n let hostname: string;\n let port: number;\n let username: string;\n try {\n hostname = config.getString('connection.host');\n port = config.getNumber('connection.port');\n username = config.getString('connection.user');\n } catch (err) {\n throw new ForwardedError(\n 'AWS RDS IAM auth: missing required database connection config — make sure connection.host, connection.port, and connection.user are set and any environment variables they reference are set',\n err,\n );\n }\n const region =\n config.getOptionalString('connection.region') ??\n process.env.AWS_REGION ??\n process.env.AWS_DEFAULT_REGION;\n if (!region) {\n throw new Error(\n 'Missing region for AWS RDS IAM auth: set connection.region or the AWS_REGION environment variable',\n );\n }\n\n const rawConfig = config.get() as Record<string, unknown>;\n const sanitizedConnection = omit(\n config.get('connection') as Record<string, unknown>,\n ['type', 'region'],\n ) as Partial<Knex.StaticConnectionConfig>;\n\n const signer = new Signer({ hostname, port, username, region });\n\n // RDS IAM auth tokens are valid for 15 minutes. Renew 1 minute early so\n // that pooled connections are refreshed before the token actually expires.\n const tokenTtlMs = 15 * 60 * 1000;\n const renewalOffsetMs = 60 * 1000;\n\n async function getConnectionConfig() {\n try {\n const password = await signer.getAuthToken();\n const tokenExpiration = Date.now() + tokenTtlMs - renewalOffsetMs;\n return {\n ...sanitizedConnection,\n password,\n expirationChecker: () => tokenExpiration <= Date.now(),\n };\n } catch (err) {\n throw new ForwardedError(\n `AWS RDS IAM auth token acquisition failed for ${username}@${hostname}:${port}`,\n err,\n );\n }\n }\n\n return {\n ...(rawConfig as Record<string, unknown>),\n connection: getConnectionConfig,\n };\n}\n\n/**\n * Gets the postgres connection config\n *\n * @param dbConfig - The database config\n * @param parseConnectionString - Flag to explicitly control connection string parsing\n */\nexport function getPgConnectionConfig(\n dbConfig: Config,\n parseConnectionString?: boolean,\n): Knex.PgConnectionConfig | string {\n const connection = dbConfig.get('connection') as any;\n const isConnectionString =\n typeof connection === 'string' || connection instanceof String;\n const autoParse = typeof parseConnectionString !== 'boolean';\n\n const shouldParseConnectionString = autoParse\n ? isConnectionString\n : parseConnectionString && isConnectionString;\n\n return shouldParseConnectionString\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\n/**\n * Parses a connection string using pg-connection-string\n *\n * @param connectionString - The postgres connection string\n */\nexport function parsePgConnectionString(connectionString: string) {\n const parse = requirePgConnectionString();\n return parse(connectionString);\n}\n\nfunction requirePgConnectionString() {\n try {\n return require('pg-connection-string').parse;\n } catch (e) {\n throw new ForwardedError(\"Postgres: Install 'pg-connection-string'\", e);\n }\n}\n\nconst PG_DATABASE_ADMIN_OVERRIDES: Knex.Config = {\n connection: { database: 'postgres' },\n pool: { min: 0, max: 1, acquireTimeoutMillis: 10000 },\n};\n\nconst PG_SCHEMA_ADMIN_OVERRIDES: Knex.Config = {\n pool: { min: 0, max: 1, acquireTimeoutMillis: 10000 },\n};\n\n/**\n * Creates the missing Postgres database if it does not exist\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to create\n */\nexport async function ensurePgDatabaseExists(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n await Promise.all(\n databases.map(async database => {\n return retryPgAdminOperation(() =>\n ddlLimiter(async () => {\n const admin = await createPgDatabaseClient(\n dbConfig,\n PG_DATABASE_ADMIN_OVERRIDES,\n );\n try {\n await ensurePgDatabase(admin, database);\n } finally {\n await admin.destroy();\n }\n }),\n );\n }),\n );\n}\n\nasync function ensurePgDatabase(admin: Knex, database: string): Promise<void> {\n const result = await admin\n .from('pg_database')\n .where('datname', database)\n .count<Record<string, { count: string }>>();\n\n if (parseInt(result[0].count, 10) === 0) {\n await admin.raw(`CREATE DATABASE ??`, [database]);\n }\n}\n\nasync function retryPgAdminOperation(\n operation: () => Promise<void>,\n): Promise<void> {\n // For initial setup we use a smaller timeout but several retries. Given that this\n // is a separate connection pool we should never really run into issues with connection\n // acquisition timeouts, but we do anyway. This might be a bug in knex or some other dependency.\n const maxAttempts = 3;\n for (let attempt = 1; ; attempt++) {\n try {\n return await operation();\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error;\n }\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n }\n}\n\nasync function ensurePgSchema(\n admin: Knex,\n role: string | undefined,\n schema: string,\n): Promise<void> {\n if (role) {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ??`, [\n schema,\n role,\n ]);\n } else {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [schema]);\n }\n}\n\n/**\n * Creates the missing Postgres schema if it does not exist\n *\n * @param dbConfig - The database config\n * @param schemas - The name of the schemas to create\n */\nexport async function ensurePgSchemaExists(\n dbConfig: Config,\n ...schemas: Array<string>\n): Promise<void> {\n const admin = await createPgDatabaseClient(dbConfig);\n const role = dbConfig.getOptionalString('role');\n\n try {\n await Promise.all(\n schemas.map(schema =>\n ddlLimiter(() => ensurePgSchema(admin, role, schema)),\n ),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Drops the Postgres databases.\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to drop\n */\nexport async function dropPgDatabase(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = await createPgDatabaseClient(dbConfig);\n try {\n await Promise.all(\n databases.map(async database => {\n await ddlLimiter(() => admin.raw(`DROP DATABASE ??`, [database]));\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\n/**\n * The computed configuration for a plugin's postgres database connection.\n */\nexport interface PgPluginDatabaseConfig {\n /** The database client type (e.g. 'pg') */\n client: string;\n /** Whether the client type was overridden at the plugin level */\n clientOverridden: boolean;\n /** The optional role to set on connections */\n role: string | undefined;\n /** Additional knex configuration merged from base and plugin config */\n additionalKnexConfig: JsonObject | undefined;\n /** Whether to ensure the database exists */\n ensureExists: boolean;\n /** Whether to ensure the schema exists */\n ensureSchemaExists: boolean;\n /** The plugin division mode ('database' or 'schema') */\n pluginDivisionMode: string;\n /** The connection configuration */\n connection: Knex.PgConnectionConfig;\n /** The database name, if any */\n databaseName: string | undefined;\n /** The schema name, if using schema division mode */\n schemaName: string | undefined;\n /** Database client overrides including schema overrides if applicable */\n databaseClientOverrides: Knex.Config;\n /** The full knex config for the plugin */\n knexConfig: Knex.Config;\n}\n\n/**\n * Computes all postgres database configuration for a plugin from the provided config.\n *\n * @param config - The database config object\n * @param pluginId - The plugin ID to compute config for\n * @param prefix - The database name prefix (e.g. 'backstage_plugin_')\n * @returns All computed configuration values for the plugin\n */\nexport function computePgPluginConfig(\n config: Config,\n pluginId: string,\n prefix: string,\n schemaPrefix: string = '',\n): PgPluginDatabaseConfig {\n // Client type\n const pluginClient = config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n const baseClient = config.getString('client');\n const client = pluginClient ?? baseClient;\n const clientOverridden = client !== baseClient;\n\n // Role\n const role =\n config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n config.getOptionalString('role');\n\n // Additional knex config\n const pluginKnexConfig = config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n const baseKnexConfig = config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n const additionalKnexConfig = merge(baseKnexConfig, pluginKnexConfig);\n\n // Ensure exists flags\n const baseEnsureExists = config.getOptionalBoolean('ensureExists') ?? true;\n const ensureExists =\n config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??\n baseEnsureExists;\n\n const baseEnsureSchemaExists =\n config.getOptionalBoolean('ensureSchemaExists') ?? false;\n const ensureSchemaExists =\n config.getOptionalBoolean(\n `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`,\n ) ?? baseEnsureSchemaExists;\n\n // Plugin division mode\n const pluginDivisionMode =\n config.getOptionalString('pluginDivisionMode') ?? 'database';\n\n // Connection config\n let baseConnection = normalizeConnection(config.get('connection'));\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`.\n // The `database` property from the base connection is omitted unless\n // `pluginDivisionMode` is set to `schema`.\n if (pluginDivisionMode !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // Get and normalize optional plugin specific database connection\n const pluginConnection = normalizeConnection(\n config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n (\n baseConnection as Knex.PgConnectionConfig\n ).application_name ||= `backstage_plugin_${pluginId}`;\n\n const connection = {\n // Include base connection if client type has not been overridden\n ...(clientOverridden ? {} : baseConnection),\n ...pluginConnection,\n } as Knex.PgConnectionConfig;\n\n // Database name\n const connectionDatabaseName = (connection as Knex.ConnectionConfig)\n ?.database;\n let databaseName: string | undefined;\n\n if (pluginDivisionMode === 'schema') {\n // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied\n // or fallback to default knex database\n databaseName = connectionDatabaseName;\n } else {\n // All other supported databases should fallback to an auto-prefixed name\n databaseName = connectionDatabaseName ?? `${prefix}${pluginId}`;\n }\n\n // Database client overrides\n let databaseClientOverrides: Knex.Config = {};\n let schemaName: string | undefined;\n\n if (databaseName) {\n databaseClientOverrides = { connection: { database: databaseName } };\n }\n if (pluginDivisionMode === 'schema') {\n schemaName = `${schemaPrefix}${pluginId}`;\n\n if (Buffer.byteLength(schemaName, 'utf8') > 63) {\n throw new Error(\n `PostgreSQL schema name \"${schemaName}\" exceeds the 63-byte limit. ` +\n `Consider using a shorter schemaPrefix (current: \"${schemaPrefix}\") or ` +\n `plugin ID (current: \"${pluginId}\").`,\n );\n }\n\n databaseClientOverrides = mergeDatabaseConfig({}, databaseClientOverrides, {\n searchPath: [schemaName],\n });\n }\n\n // Full knex config for plugin\n const knexConfig: Knex.Config = {\n ...additionalKnexConfig,\n client,\n connection,\n ...(role && { role }),\n };\n\n return {\n client,\n clientOverridden,\n role,\n additionalKnexConfig,\n ensureExists,\n ensureSchemaExists,\n pluginDivisionMode,\n connection,\n databaseName,\n schemaName,\n databaseClientOverrides,\n knexConfig,\n };\n}\n\nexport class PgConnector implements Connector {\n private readonly config: Config;\n private readonly prefix: string;\n private readonly schemaPrefix: string;\n private readonly databaseEnsureCache = new Map<string, Promise<void>>();\n private readonly customEnsureDatabaseExists?: typeof ensurePgDatabaseExists;\n private readonly databaseAdminPool: PgAdminPool;\n private readonly schemaAdminPool: PgAdminPool;\n\n constructor(\n config: Config,\n prefix: string,\n schemaPrefix: string,\n options: {\n ensureDatabaseExists?: typeof ensurePgDatabaseExists;\n createAdminClient?: (overrides: Knex.Config) => Promise<Knex>;\n adminPoolIdleTimeoutMillis?: number;\n } = {},\n ) {\n this.config = config;\n this.prefix = prefix;\n this.schemaPrefix = schemaPrefix;\n this.customEnsureDatabaseExists = options.ensureDatabaseExists;\n\n const createAdminClient =\n options.createAdminClient ??\n ((overrides: Knex.Config) =>\n createPgDatabaseClient(this.config, overrides));\n const idleTimeoutMillis = options.adminPoolIdleTimeoutMillis ?? 10_000;\n this.databaseAdminPool = new PgAdminPool(\n () => createAdminClient(PG_DATABASE_ADMIN_OVERRIDES),\n idleTimeoutMillis,\n );\n this.schemaAdminPool = new PgAdminPool(\n () => createAdminClient(PG_SCHEMA_ADMIN_OVERRIDES),\n idleTimeoutMillis,\n );\n }\n\n private async ensureDatabase(databaseName: string): Promise<void> {\n let promise = this.databaseEnsureCache.get(databaseName);\n if (!promise) {\n promise = this.customEnsureDatabaseExists\n ? this.customEnsureDatabaseExists(this.config, databaseName)\n : retryPgAdminOperation(() =>\n ddlLimiter(() =>\n this.runAdminOperation(this.databaseAdminPool, admin =>\n ensurePgDatabase(admin, databaseName),\n ),\n ),\n );\n this.databaseEnsureCache.set(databaseName, promise);\n }\n\n try {\n await promise;\n } catch (error) {\n if (this.databaseEnsureCache.get(databaseName) === promise) {\n this.databaseEnsureCache.delete(databaseName);\n }\n throw error;\n }\n }\n\n private async runAdminOperation<T>(\n pool: PgAdminPool,\n operation: (client: Knex) => Promise<T>,\n ): Promise<T> {\n try {\n return await pool.run(operation);\n } catch (error) {\n await pool.invalidate().catch(() => {});\n throw error;\n }\n }\n\n async shutdown(): Promise<void> {\n const results = await Promise.allSettled([\n this.databaseAdminPool.shutdown(),\n this.schemaAdminPool.shutdown(),\n ]);\n const errors = results\n .filter(\n (result): result is PromiseRejectedResult =>\n result.status === 'rejected',\n )\n .map(result => result.reason);\n if (errors.length === 1) {\n throw errors[0];\n }\n if (errors.length > 1) {\n throw new AggregateError(\n errors,\n 'Failed to shut down PostgreSQL admin pools',\n );\n }\n }\n\n async getClient(\n pluginId: string,\n _deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginDbConfig = computePgPluginConfig(\n this.config,\n pluginId,\n this.prefix,\n this.schemaPrefix,\n );\n\n if (pluginDbConfig.databaseName && pluginDbConfig.ensureExists) {\n try {\n await this.ensureDatabase(pluginDbConfig.databaseName);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that '${pluginDbConfig.databaseName}' exists, ${error}`,\n );\n }\n }\n\n if (pluginDbConfig.pluginDivisionMode === 'schema') {\n if (pluginDbConfig.ensureSchemaExists || pluginDbConfig.ensureExists) {\n try {\n await ddlLimiter(() =>\n this.runAdminOperation(this.schemaAdminPool, admin =>\n ensurePgSchema(\n admin,\n this.config.getOptionalString('role'),\n pluginDbConfig.schemaName!,\n ),\n ),\n );\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,\n );\n }\n }\n }\n\n const client = createPgDatabaseClient(\n this.config,\n mergeDatabaseConfig(\n pluginDbConfig.knexConfig,\n pluginDbConfig.databaseClientOverrides,\n ),\n );\n\n return client;\n }\n}\n"],"names":["limiterFactory","knexFactory","format","config","mergeDatabaseConfig","ConfigReader","omit","durationToMilliseconds","readDurationFromConfig","ForwardedError","merge"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postgres.cjs.js","sources":["../../../../src/entrypoints/database/connectors/postgres.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport {\n Config,\n ConfigReader,\n readDurationFromConfig,\n} from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n durationToMilliseconds,\n HumanDuration,\n JsonObject,\n} from '@backstage/types';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport limiterFactory from 'p-limit';\nimport { Client } from 'pg';\nimport { Connector } from '../types';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\nimport format from 'pg-format';\nimport { TokenCredential } from '@azure/identity';\n\n// Limits the number of concurrent DDL operations to 1\nconst ddlLimiter = limiterFactory(1);\n\nexport class PgAdminPool {\n private client?: Promise<Knex>;\n private destruction?: Promise<void>;\n private readonly destructionErrors: unknown[] = [];\n private idleTimer?: NodeJS.Timeout;\n private readonly operations = new Set<Promise<unknown>>();\n private isShutdown = false;\n\n constructor(\n private readonly createClient: () => Promise<Knex>,\n private readonly idleTimeoutMillis: number,\n ) {}\n\n async run<T>(operation: (client: Knex) => Promise<T> | T): Promise<T> {\n if (this.isShutdown) {\n throw new Error('PostgreSQL admin pool is shut down');\n }\n\n clearTimeout(this.idleTimer);\n this.idleTimer = undefined;\n\n const operationPromise = this.runOperation(operation);\n this.operations.add(operationPromise);\n try {\n return await operationPromise;\n } finally {\n this.operations.delete(operationPromise);\n this.scheduleIdleDestroy();\n }\n }\n\n async shutdown(): Promise<void> {\n this.isShutdown = true;\n clearTimeout(this.idleTimer);\n this.idleTimer = undefined;\n\n await Promise.allSettled(this.operations);\n await this.destroyClient().catch(() => {});\n if (this.destructionErrors.length > 0) {\n throw new AggregateError(\n this.destructionErrors,\n 'Failed to destroy PostgreSQL admin pool clients',\n );\n }\n }\n\n async invalidate(): Promise<void> {\n clearTimeout(this.idleTimer);\n this.idleTimer = undefined;\n\n await Promise.allSettled(this.operations);\n await this.destroyClient();\n }\n\n private async runOperation<T>(\n operation: (client: Knex) => Promise<T> | T,\n ): Promise<T> {\n if (this.destruction) {\n await this.destruction;\n }\n if (this.isShutdown) {\n throw new Error('PostgreSQL admin pool is shut down');\n }\n\n let clientPromise = this.client;\n if (!clientPromise) {\n clientPromise = this.createClient();\n this.client = clientPromise;\n void clientPromise.catch(() => {\n if (this.client === clientPromise) {\n this.client = undefined;\n }\n });\n }\n return operation(await clientPromise);\n }\n\n private scheduleIdleDestroy(): void {\n if (this.isShutdown || this.operations.size > 0) {\n return;\n }\n\n const timer = setTimeout(() => {\n if (this.idleTimer === timer) {\n this.idleTimer = undefined;\n void this.destroyClient().catch(() => {});\n }\n }, this.idleTimeoutMillis);\n timer.unref();\n this.idleTimer = timer;\n }\n\n private destroyClient(): Promise<void> {\n if (this.destruction) {\n return this.destruction;\n }\n\n const clientPromise = this.client;\n this.client = undefined;\n if (!clientPromise) {\n return Promise.resolve();\n }\n\n const destruction = clientPromise.then(\n client => client.destroy(),\n () => undefined,\n );\n this.destruction = destruction;\n void destruction.then(\n () => {\n if (this.destruction === destruction) {\n this.destruction = undefined;\n }\n },\n error => {\n if (this.destruction === destruction) {\n this.destruction = undefined;\n this.destructionErrors.push(error);\n }\n },\n );\n return destruction;\n }\n}\n\n/**\n * Creates a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport async function createPgDatabaseClient(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const knexConfig = await buildPgDatabaseConfig(dbConfig, overrides);\n const database = knexFactory(knexConfig);\n\n const role = dbConfig.getOptionalString('role');\n\n if (role) {\n database.client.pool.on(\n 'createSuccess',\n async (_event: number, pgClient: Client) => {\n const query = format('SET ROLE %I', role);\n await pgClient.query(query);\n },\n );\n }\n return database;\n}\n\n/**\n * Builds a knex postgres database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport async function buildPgDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n) {\n const config = mergeDatabaseConfig(\n dbConfig.get(),\n {\n connection: getPgConnectionConfig(dbConfig, !!overrides),\n useNullAsDefault: true,\n },\n overrides,\n );\n const mergedConfigReader = new ConfigReader(config);\n\n if (config.connection.type === 'default' || !config.connection.type) {\n const connectionValue = config.connection;\n const sanitizedConnection =\n typeof connectionValue === 'string' || connectionValue instanceof String\n ? connectionValue\n : // connection is an object, omit config-only props\n omit(connectionValue as Record<string, unknown>, [\n 'type',\n 'instance',\n 'tokenCredential',\n ]);\n\n return {\n ...config,\n connection: sanitizedConnection,\n };\n }\n\n switch (config.connection.type) {\n case 'azure':\n return buildAzurePgConfig(mergedConfigReader);\n case 'cloudsql':\n return buildCloudSqlConfig(mergedConfigReader);\n case 'rds':\n return buildRdsPgConfig(mergedConfigReader);\n default:\n throw new Error(`Unknown connection type: ${config.connection.type}`);\n }\n}\n\n/* Note: the following type definition is intentionally duplicated in\n * /packages/backend-defaults/config.d.ts so the clientSecret property\n * can be annotated with \"@visibility secret\" there.\n */\nexport type AzureTokenCredentialConfig = {\n /**\n * How early before an access token expires to refresh it with a new one.\n * Defaults to 5 minutes\n * Supported formats:\n * - A string in the format of '1d', '2 seconds' etc. as supported by the `ms`\n * library.\n * - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.\n * - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.\n */\n tokenRenewableOffsetTime?: string | HumanDuration;\n /**\n * The client ID of a user-assigned managed identity.\n * If not provided, the system-assigned managed identity is used.\n */\n clientId?: string;\n clientSecret?: string;\n tenantId?: string;\n};\n\nexport async function buildAzurePgConfig(config: Config): Promise<Knex.Config> {\n const {\n DefaultAzureCredential,\n ManagedIdentityCredential,\n ClientSecretCredential,\n } = require('@azure/identity');\n\n const tokenConfig = config.getOptionalConfig('connection.tokenCredential');\n\n const tokenRenewableOffsetTime = durationToMilliseconds(\n tokenConfig?.has('tokenRenewableOffsetTime')\n ? readDurationFromConfig(tokenConfig, { key: 'tokenRenewableOffsetTime' })\n : { minutes: 5 },\n );\n\n const clientId = tokenConfig?.getOptionalString('clientId');\n const tenantId = tokenConfig?.getOptionalString('tenantId');\n const clientSecret = tokenConfig?.getOptionalString('clientSecret');\n let credential: TokenCredential;\n\n /**\n * Determine which TokenCredential to use based on provided config\n * 1. If clientId, tenantId and clientSecret are provided, use ClientSecretCredential\n * 2. If only clientId is provided, use ManagedIdentityCredential with user-assigned identity\n * 3. Otherwise, use DefaultAzureCredential (which may use system-assigned identity among other methods)\n */\n if (clientId && tenantId && clientSecret) {\n credential = new ClientSecretCredential(tenantId, clientId, clientSecret);\n } else if (clientId) {\n credential = new ManagedIdentityCredential(clientId);\n } else {\n credential = new DefaultAzureCredential();\n }\n\n const rawConfig = config.get() as Record<string, unknown>;\n\n const normalized = normalizeConnection(rawConfig.connection as any);\n const sanitizedConnection = omit(normalized, [\n 'type',\n 'instance',\n 'tokenCredential',\n ]) as Partial<Knex.StaticConnectionConfig>;\n\n async function getConnectionConfig() {\n const token = await credential.getToken(\n 'https://ossrdbms-aad.database.windows.net/.default',\n );\n\n if (!token) {\n throw new Error(\n 'Failed to acquire Azure access token for database authentication',\n );\n }\n\n const connectionConfig = {\n ...sanitizedConnection,\n password: token.token,\n expirationChecker: () =>\n /* return true if the token is within the renewable offset time */\n token.expiresOnTimestamp - tokenRenewableOffsetTime <= Date.now(),\n };\n\n return connectionConfig;\n }\n\n return {\n ...(rawConfig as Record<string, unknown>),\n connection: getConnectionConfig,\n };\n}\n\nexport async function buildCloudSqlConfig(\n config: Config,\n): Promise<Knex.Config> {\n const client = config.getOptionalString('client');\n\n if (client && client !== 'pg') {\n throw new Error('Cloud SQL only supports the pg client');\n }\n\n const instance = config.getOptionalString('connection.instance');\n if (!instance) {\n throw new Error('Missing instance connection name for Cloud SQL');\n }\n\n const {\n Connector: CloudSqlConnector,\n IpAddressTypes,\n AuthTypes,\n } = require('@google-cloud/cloud-sql-connector') as typeof import('@google-cloud/cloud-sql-connector');\n const connector = new CloudSqlConnector();\n\n type IpType = (typeof IpAddressTypes)[keyof typeof IpAddressTypes];\n const ipTypeRaw = config.getOptionalString('connection.ipAddressType');\n\n let ipType: IpType | undefined;\n if (ipTypeRaw !== undefined) {\n if (\n !(Object.values(IpAddressTypes) as Array<string | number>).includes(\n ipTypeRaw as any,\n )\n ) {\n throw new Error(\n `Invalid connection.ipAddressType: ${ipTypeRaw}; valid values: ${Object.values(\n IpAddressTypes,\n ).join(', ')}`,\n );\n }\n ipType = ipTypeRaw as unknown as IpType;\n }\n\n const clientOpts = await connector.getOptions({\n instanceConnectionName: instance,\n ipType: ipType ?? IpAddressTypes.PUBLIC,\n authType: AuthTypes.IAM,\n });\n\n const rawConfig = config.get() as Record<string, unknown>;\n const normalized = normalizeConnection(rawConfig.connection as any);\n const sanitizedConnection = omit(normalized, [\n 'type',\n 'instance',\n ]) as Partial<Knex.StaticConnectionConfig>;\n\n return {\n ...(rawConfig as Record<string, unknown>),\n client: 'pg',\n connection: {\n ...sanitizedConnection,\n ...clientOpts,\n },\n };\n}\n\nexport async function buildRdsPgConfig(config: Config): Promise<Knex.Config> {\n const { Signer } =\n require('@aws-sdk/rds-signer') as typeof import('@aws-sdk/rds-signer');\n\n let hostname: string;\n let port: number;\n let username: string;\n try {\n hostname = config.getString('connection.host');\n port = config.getNumber('connection.port');\n username = config.getString('connection.user');\n } catch (err) {\n throw new ForwardedError(\n 'AWS RDS IAM auth: missing required database connection config — make sure connection.host, connection.port, and connection.user are set and any environment variables they reference are set',\n err,\n );\n }\n const region =\n config.getOptionalString('connection.region') ??\n process.env.AWS_REGION ??\n process.env.AWS_DEFAULT_REGION;\n if (!region) {\n throw new Error(\n 'Missing region for AWS RDS IAM auth: set connection.region or the AWS_REGION environment variable',\n );\n }\n\n const rawConfig = config.get() as Record<string, unknown>;\n const sanitizedConnection = omit(\n config.get('connection') as Record<string, unknown>,\n ['type', 'region'],\n ) as Partial<Knex.StaticConnectionConfig>;\n\n const signer = new Signer({ hostname, port, username, region });\n\n // RDS IAM auth tokens are valid for 15 minutes. Renew 1 minute early so\n // that pooled connections are refreshed before the token actually expires.\n const tokenTtlMs = 15 * 60 * 1000;\n const renewalOffsetMs = 60 * 1000;\n\n async function getConnectionConfig() {\n try {\n const password = await signer.getAuthToken();\n const tokenExpiration = Date.now() + tokenTtlMs - renewalOffsetMs;\n return {\n ...sanitizedConnection,\n password,\n expirationChecker: () => tokenExpiration <= Date.now(),\n };\n } catch (err) {\n throw new ForwardedError(\n `AWS RDS IAM auth token acquisition failed for ${username}@${hostname}:${port}`,\n err,\n );\n }\n }\n\n return {\n ...(rawConfig as Record<string, unknown>),\n connection: getConnectionConfig,\n };\n}\n\n/**\n * Gets the postgres connection config\n *\n * @param dbConfig - The database config\n * @param parseConnectionString - Flag to explicitly control connection string parsing\n */\nexport function getPgConnectionConfig(\n dbConfig: Config,\n parseConnectionString?: boolean,\n): Knex.PgConnectionConfig | string {\n const connection = dbConfig.get('connection') as any;\n const isConnectionString =\n typeof connection === 'string' || connection instanceof String;\n const autoParse = typeof parseConnectionString !== 'boolean';\n\n const shouldParseConnectionString = autoParse\n ? isConnectionString\n : parseConnectionString && isConnectionString;\n\n return shouldParseConnectionString\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\n/**\n * Parses a connection string using pg-connection-string\n *\n * @param connectionString - The postgres connection string\n */\nexport function parsePgConnectionString(connectionString: string) {\n const parse = requirePgConnectionString();\n return parse(connectionString);\n}\n\nfunction requirePgConnectionString() {\n try {\n return require('pg-connection-string').parse;\n } catch (e) {\n throw new ForwardedError(\"Postgres: Install 'pg-connection-string'\", e);\n }\n}\n\nconst PG_DATABASE_ADMIN_OVERRIDES: Knex.Config = {\n connection: { database: 'postgres' },\n pool: { min: 0, max: 1, acquireTimeoutMillis: 10000 },\n};\n\nconst PG_SCHEMA_ADMIN_OVERRIDES: Knex.Config = {\n pool: { min: 0, max: 1, acquireTimeoutMillis: 10000 },\n};\n\n/**\n * Creates the missing Postgres database if it does not exist\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to create\n */\nexport async function ensurePgDatabaseExists(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n await Promise.all(\n databases.map(async database => {\n return retryPgAdminOperation(() =>\n ddlLimiter(async () => {\n const admin = await createPgDatabaseClient(\n dbConfig,\n PG_DATABASE_ADMIN_OVERRIDES,\n );\n try {\n await ensurePgDatabase(admin, database);\n } finally {\n await admin.destroy();\n }\n }),\n );\n }),\n );\n}\n\nasync function ensurePgDatabase(admin: Knex, database: string): Promise<void> {\n const result = await admin\n .from('pg_database')\n .where('datname', database)\n .count<Record<string, { count: string }>>();\n\n if (parseInt(result[0].count, 10) === 0) {\n await admin.raw(`CREATE DATABASE ??`, [database]);\n }\n}\n\nasync function retryPgAdminOperation(\n operation: () => Promise<void>,\n): Promise<void> {\n // For initial setup we use a smaller timeout but several retries. Given that this\n // is a separate connection pool we should never really run into issues with connection\n // acquisition timeouts, but we do anyway. This might be a bug in knex or some other dependency.\n const maxAttempts = 3;\n for (let attempt = 1; ; attempt++) {\n try {\n return await operation();\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error;\n }\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n }\n}\n\nasync function ensurePgSchema(\n admin: Knex,\n role: string | undefined,\n schema: string,\n): Promise<void> {\n if (role) {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ??`, [\n schema,\n role,\n ]);\n } else {\n await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [schema]);\n }\n}\n\n/**\n * Creates the missing Postgres schema if it does not exist\n *\n * @param dbConfig - The database config\n * @param schemas - The name of the schemas to create\n */\nexport async function ensurePgSchemaExists(\n dbConfig: Config,\n ...schemas: Array<string>\n): Promise<void> {\n const admin = await createPgDatabaseClient(dbConfig);\n const role = dbConfig.getOptionalString('role');\n\n try {\n await Promise.all(\n schemas.map(schema =>\n ddlLimiter(() => ensurePgSchema(admin, role, schema)),\n ),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Drops the Postgres databases.\n *\n * @param dbConfig - The database config\n * @param databases - The name of the databases to drop\n */\nexport async function dropPgDatabase(\n dbConfig: Config,\n ...databases: Array<string>\n) {\n const admin = await createPgDatabaseClient(dbConfig);\n try {\n await Promise.all(\n databases.map(async database => {\n await ddlLimiter(() => admin.raw(`DROP DATABASE ??`, [database]));\n }),\n );\n } finally {\n await admin.destroy();\n }\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parsePgConnectionString(connection as string)\n : connection;\n}\n\n/**\n * The computed configuration for a plugin's postgres database connection.\n */\nexport interface PgPluginDatabaseConfig {\n /** The database client type (e.g. 'pg') */\n client: string;\n /** Whether the client type was overridden at the plugin level */\n clientOverridden: boolean;\n /** The optional role to set on connections */\n role: string | undefined;\n /** Additional knex configuration merged from base and plugin config */\n additionalKnexConfig: JsonObject | undefined;\n /** Whether to ensure the database exists */\n ensureExists: boolean;\n /** Whether to ensure the schema exists */\n ensureSchemaExists: boolean;\n /** The plugin division mode ('database' or 'schema') */\n pluginDivisionMode: string;\n /** The connection configuration */\n connection: Knex.PgConnectionConfig;\n /** The database name, if any */\n databaseName: string | undefined;\n /** The schema name, if using schema division mode */\n schemaName: string | undefined;\n /** Database client overrides including schema overrides if applicable */\n databaseClientOverrides: Knex.Config;\n /** The full knex config for the plugin */\n knexConfig: Knex.Config;\n}\n\n/**\n * Computes all postgres database configuration for a plugin from the provided config.\n *\n * @param config - The database config object\n * @param pluginId - The plugin ID to compute config for\n * @param prefix - The database name prefix (e.g. 'backstage_plugin_')\n * @returns All computed configuration values for the plugin\n */\nexport function computePgPluginConfig(\n config: Config,\n pluginId: string,\n prefix: string,\n schemaPrefix: string = '',\n): PgPluginDatabaseConfig {\n // Client type\n const pluginClient = config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n const baseClient = config.getString('client');\n const client = pluginClient ?? baseClient;\n const clientOverridden = client !== baseClient;\n\n // Role\n const role =\n config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n config.getOptionalString('role');\n\n // Additional knex config\n const pluginKnexConfig = config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n const baseKnexConfig = config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n const additionalKnexConfig = merge(baseKnexConfig, pluginKnexConfig);\n\n // Ensure exists flags\n const baseEnsureExists = config.getOptionalBoolean('ensureExists') ?? true;\n const ensureExists =\n config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ??\n baseEnsureExists;\n\n const baseEnsureSchemaExists =\n config.getOptionalBoolean('ensureSchemaExists') ?? false;\n const ensureSchemaExists =\n config.getOptionalBoolean(\n `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`,\n ) ?? baseEnsureSchemaExists;\n\n // Plugin division mode\n const pluginDivisionMode =\n config.getOptionalString('pluginDivisionMode') ?? 'database';\n\n // Connection config\n let baseConnection = normalizeConnection(config.get('connection'));\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`.\n // The `database` property from the base connection is omitted unless\n // `pluginDivisionMode` is set to `schema`.\n if (pluginDivisionMode !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // Get and normalize optional plugin specific database connection\n const pluginConnection = normalizeConnection(\n config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n (\n baseConnection as Knex.PgConnectionConfig\n ).application_name ||= `backstage_plugin_${pluginId}`;\n\n const connection = {\n // Include base connection if client type has not been overridden\n ...(clientOverridden ? {} : baseConnection),\n ...pluginConnection,\n } as Knex.PgConnectionConfig;\n\n // Database name\n const connectionDatabaseName = (connection as Knex.ConnectionConfig)\n ?.database;\n let databaseName: string | undefined;\n\n if (pluginDivisionMode === 'schema') {\n // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied\n // or fallback to default knex database\n databaseName = connectionDatabaseName;\n } else {\n // All other supported databases should fallback to an auto-prefixed name\n databaseName = connectionDatabaseName ?? `${prefix}${pluginId}`;\n }\n\n // Database client overrides\n let databaseClientOverrides: Knex.Config = {};\n let schemaName: string | undefined;\n\n if (databaseName) {\n databaseClientOverrides = { connection: { database: databaseName } };\n }\n if (pluginDivisionMode === 'schema') {\n schemaName = `${schemaPrefix}${pluginId}`;\n\n if (Buffer.byteLength(schemaName, 'utf8') > 63) {\n throw new Error(\n `PostgreSQL schema name \"${schemaName}\" exceeds the 63-byte limit. ` +\n `Consider using a shorter schemaPrefix (current: \"${schemaPrefix}\") or ` +\n `plugin ID (current: \"${pluginId}\").`,\n );\n }\n\n databaseClientOverrides = mergeDatabaseConfig({}, databaseClientOverrides, {\n searchPath: [schemaName],\n });\n }\n\n // Full knex config for plugin\n const knexConfig: Knex.Config = {\n ...additionalKnexConfig,\n client,\n connection,\n ...(role && { role }),\n };\n\n return {\n client,\n clientOverridden,\n role,\n additionalKnexConfig,\n ensureExists,\n ensureSchemaExists,\n pluginDivisionMode,\n connection,\n databaseName,\n schemaName,\n databaseClientOverrides,\n knexConfig,\n };\n}\n\nexport class PgConnector implements Connector {\n private readonly config: Config;\n private readonly prefix: string;\n private readonly schemaPrefix: string;\n private readonly databaseEnsureCache = new Map<string, Promise<void>>();\n private readonly customEnsureDatabaseExists?: typeof ensurePgDatabaseExists;\n private readonly databaseAdminPool: PgAdminPool;\n private readonly schemaAdminPool: PgAdminPool;\n\n constructor(\n config: Config,\n prefix: string,\n schemaPrefix: string,\n options: {\n ensureDatabaseExists?: typeof ensurePgDatabaseExists;\n createAdminClient?: (overrides: Knex.Config) => Promise<Knex>;\n adminPoolIdleTimeoutMillis?: number;\n } = {},\n ) {\n this.config = config;\n this.prefix = prefix;\n this.schemaPrefix = schemaPrefix;\n this.customEnsureDatabaseExists = options.ensureDatabaseExists;\n\n const createAdminClient =\n options.createAdminClient ??\n ((overrides: Knex.Config) =>\n createPgDatabaseClient(this.config, overrides));\n const idleTimeoutMillis = options.adminPoolIdleTimeoutMillis ?? 10_000;\n this.databaseAdminPool = new PgAdminPool(\n () => createAdminClient(PG_DATABASE_ADMIN_OVERRIDES),\n idleTimeoutMillis,\n );\n this.schemaAdminPool = new PgAdminPool(\n () => createAdminClient(PG_SCHEMA_ADMIN_OVERRIDES),\n idleTimeoutMillis,\n );\n }\n\n private async ensureDatabase(databaseName: string): Promise<void> {\n let promise = this.databaseEnsureCache.get(databaseName);\n if (!promise) {\n promise = this.customEnsureDatabaseExists\n ? this.customEnsureDatabaseExists(this.config, databaseName)\n : retryPgAdminOperation(() =>\n ddlLimiter(() =>\n this.runAdminOperation(this.databaseAdminPool, admin =>\n ensurePgDatabase(admin, databaseName),\n ),\n ),\n );\n this.databaseEnsureCache.set(databaseName, promise);\n }\n\n try {\n await promise;\n } catch (error) {\n if (this.databaseEnsureCache.get(databaseName) === promise) {\n this.databaseEnsureCache.delete(databaseName);\n }\n throw error;\n }\n }\n\n private async runAdminOperation<T>(\n pool: PgAdminPool,\n operation: (client: Knex) => Promise<T>,\n ): Promise<T> {\n try {\n return await pool.run(operation);\n } catch (error) {\n await pool.invalidate().catch(() => {});\n throw error;\n }\n }\n\n async shutdown(): Promise<void> {\n const results = await Promise.allSettled([\n this.databaseAdminPool.shutdown(),\n this.schemaAdminPool.shutdown(),\n ]);\n const errors = results\n .filter(\n (result): result is PromiseRejectedResult =>\n result.status === 'rejected',\n )\n .map(result => result.reason);\n if (errors.length === 1) {\n throw errors[0];\n }\n if (errors.length > 1) {\n throw new AggregateError(\n errors,\n 'Failed to shut down PostgreSQL admin pools',\n );\n }\n }\n\n async getClient(\n pluginId: string,\n _deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginDbConfig = computePgPluginConfig(\n this.config,\n pluginId,\n this.prefix,\n this.schemaPrefix,\n );\n\n if (pluginDbConfig.databaseName && pluginDbConfig.ensureExists) {\n try {\n await this.ensureDatabase(pluginDbConfig.databaseName);\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that '${pluginDbConfig.databaseName}' exists, ${error}`,\n );\n }\n }\n\n if (pluginDbConfig.pluginDivisionMode === 'schema') {\n if (pluginDbConfig.ensureSchemaExists || pluginDbConfig.ensureExists) {\n try {\n await ddlLimiter(() =>\n this.runAdminOperation(this.schemaAdminPool, admin =>\n ensurePgSchema(\n admin,\n this.config.getOptionalString('role'),\n pluginDbConfig.schemaName!,\n ),\n ),\n );\n } catch (error) {\n throw new Error(\n `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`,\n );\n }\n }\n }\n\n const client = createPgDatabaseClient(\n this.config,\n mergeDatabaseConfig(\n pluginDbConfig.knexConfig,\n pluginDbConfig.databaseClientOverrides,\n ),\n );\n\n return client;\n }\n}\n"],"names":["limiterFactory","knexFactory","format","config","mergeDatabaseConfig","ConfigReader","omit","durationToMilliseconds","readDurationFromConfig","ForwardedError","merge"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite3.cjs.js","sources":["../../../../src/entrypoints/database/connectors/sqlite3.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DevDataStore } from '@backstage/backend-dev-utils';\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { ensureDirSync } from 'fs-extra';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport path from 'node:path';\nimport { Connector } from '../types';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\n\n/**\n * Creates a knex SQLite3 database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createSqliteDatabaseClient(\n pluginId: string,\n dbConfig: Config,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n overrides?: Knex.Config,\n) {\n const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);\n const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig;\n\n const filename = connConfig.filename ?? ':memory:';\n\n // If storage on disk is used, ensure that the directory exists\n if (filename !== ':memory:') {\n const directory = path.dirname(filename);\n ensureDirSync(directory);\n }\n\n let database: Knex;\n\n if (deps && filename === ':memory:') {\n // The dev store is used during watch mode to store and restore the database\n // across reloads. It is only available when running the backend through\n // `backstage-cli package start`.\n const devStore = DevDataStore.get();\n\n if (devStore) {\n const dataKey = `sqlite3-db-${pluginId}`;\n\n const connectionLoader = async () => {\n // If seed data is available, use it tconnectionLoader restore the database\n const { data: seedData } = await devStore.load(dataKey);\n\n return {\n ...(knexConfig.connection as Knex.Sqlite3ConnectionConfig),\n filename: seedData ?? ':memory:',\n };\n };\n\n database = knexFactory({\n ...knexConfig,\n connection: Object.assign(connectionLoader, {\n // This is a workaround for the knex SQLite driver always warning when using a config loader\n filename: ':memory:',\n }),\n });\n\n // If the dev store is available we save the database state on shutdown\n deps.lifecycle.addShutdownHook(async () => {\n const connection = await database.client.acquireConnection();\n const data = connection.serialize();\n await devStore.save(dataKey, data);\n });\n } else {\n database = knexFactory(knexConfig);\n }\n } else {\n database = knexFactory(knexConfig);\n }\n\n database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {\n resource.run('PRAGMA foreign_keys = ON', () => {});\n });\n\n return database;\n}\n\n/**\n * Builds a knex SQLite3 connection config\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildSqliteDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n): Knex.Config {\n const baseConfig = dbConfig.get<Knex.Config>();\n\n // Normalize config to always contain a connection object\n if (typeof baseConfig.connection === 'string') {\n baseConfig.connection = { filename: baseConfig.connection };\n }\n if (overrides && typeof overrides.connection === 'string') {\n overrides.connection = { filename: overrides.connection };\n }\n\n const config: Knex.Config = mergeDatabaseConfig(\n {\n connection: {},\n },\n baseConfig,\n {\n useNullAsDefault: true,\n },\n overrides,\n );\n\n return config;\n}\n\n/**\n * Provides a partial knex SQLite3 config to override database name.\n */\nexport function createSqliteNameOverride(name: string): Partial<Knex.Config> {\n return {\n connection: parseSqliteConnectionString(name),\n };\n}\n\n/**\n * Produces a partial knex SQLite3 connection config with database name.\n */\nexport function parseSqliteConnectionString(\n name: string,\n): Knex.Sqlite3ConnectionConfig {\n return {\n filename: name,\n };\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parseSqliteConnectionString(connection as string)\n : connection;\n}\n\nexport class Sqlite3Connector implements Connector {\n private readonly config: Config;\n\n constructor(config: Config) {\n this.config = config;\n }\n\n async getClient(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const pluginDivisionMode = this.getPluginDivisionModeConfig();\n if (pluginDivisionMode !== 'database') {\n throw new Error(\n `The SQLite driver does not support plugin division mode '${pluginDivisionMode}'`,\n );\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n );\n\n const client = createSqliteDatabaseClient(\n pluginId,\n pluginConfig,\n deps,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using global\n * and plugin specific database config. If no explicit database name is configured\n * and `pluginDivisionMode` is not `schema`, this method will provide a generated name\n * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is\n * `schema`, it will fallback to using the default database for the knex instance.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n\n const sqliteFilename: string | undefined = (\n connection as Knex.Sqlite3ConnectionConfig\n ).filename;\n\n if (sqliteFilename === ':memory:') {\n return sqliteFilename;\n }\n\n const sqliteDirectory =\n (connection as { directory?: string }).directory ?? '.';\n\n return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`);\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted for\n * all supported databases excluding SQLite unless `pluginDivisionMode` is set\n * to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { client, overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n if (\n client.includes('sqlite3') &&\n 'filename' in baseConnection &&\n baseConnection.filename !== ':memory:'\n ) {\n throw new Error(\n '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.',\n );\n }\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a\n // directory elsewhere so we preserve `filename`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? createSqliteNameOverride(databaseName) : {};\n }\n}\n"],"names":["path","ensureDirSync","DevDataStore","knexFactory","mergeDatabaseConfig","ConfigReader","merge","omit"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite3.cjs.js","sources":["../../../../src/entrypoints/database/connectors/sqlite3.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DevDataStore } from '@backstage/backend-dev-utils';\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { ensureDirSync } from 'fs-extra';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport path from 'node:path';\nimport { Connector } from '../types';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\n\n/**\n * Creates a knex SQLite3 database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createSqliteDatabaseClient(\n pluginId: string,\n dbConfig: Config,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n overrides?: Knex.Config,\n) {\n const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);\n const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig;\n\n const filename = connConfig.filename ?? ':memory:';\n\n // If storage on disk is used, ensure that the directory exists\n if (filename !== ':memory:') {\n const directory = path.dirname(filename);\n ensureDirSync(directory);\n }\n\n let database: Knex;\n\n if (deps && filename === ':memory:') {\n // The dev store is used during watch mode to store and restore the database\n // across reloads. It is only available when running the backend through\n // `backstage-cli package start`.\n const devStore = DevDataStore.get();\n\n if (devStore) {\n const dataKey = `sqlite3-db-${pluginId}`;\n\n const connectionLoader = async () => {\n // If seed data is available, use it tconnectionLoader restore the database\n const { data: seedData } = await devStore.load(dataKey);\n\n return {\n ...(knexConfig.connection as Knex.Sqlite3ConnectionConfig),\n filename: seedData ?? ':memory:',\n };\n };\n\n database = knexFactory({\n ...knexConfig,\n connection: Object.assign(connectionLoader, {\n // This is a workaround for the knex SQLite driver always warning when using a config loader\n filename: ':memory:',\n }),\n });\n\n // If the dev store is available we save the database state on shutdown\n deps.lifecycle.addShutdownHook(async () => {\n const connection = await database.client.acquireConnection();\n const data = connection.serialize();\n await devStore.save(dataKey, data);\n });\n } else {\n database = knexFactory(knexConfig);\n }\n } else {\n database = knexFactory(knexConfig);\n }\n\n database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {\n resource.run('PRAGMA foreign_keys = ON', () => {});\n });\n\n return database;\n}\n\n/**\n * Builds a knex SQLite3 connection config\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildSqliteDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n): Knex.Config {\n const baseConfig = dbConfig.get<Knex.Config>();\n\n // Normalize config to always contain a connection object\n if (typeof baseConfig.connection === 'string') {\n baseConfig.connection = { filename: baseConfig.connection };\n }\n if (overrides && typeof overrides.connection === 'string') {\n overrides.connection = { filename: overrides.connection };\n }\n\n const config: Knex.Config = mergeDatabaseConfig(\n {\n connection: {},\n },\n baseConfig,\n {\n useNullAsDefault: true,\n },\n overrides,\n );\n\n return config;\n}\n\n/**\n * Provides a partial knex SQLite3 config to override database name.\n */\nexport function createSqliteNameOverride(name: string): Partial<Knex.Config> {\n return {\n connection: parseSqliteConnectionString(name),\n };\n}\n\n/**\n * Produces a partial knex SQLite3 connection config with database name.\n */\nexport function parseSqliteConnectionString(\n name: string,\n): Knex.Sqlite3ConnectionConfig {\n return {\n filename: name,\n };\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parseSqliteConnectionString(connection as string)\n : connection;\n}\n\nexport class Sqlite3Connector implements Connector {\n private readonly config: Config;\n\n constructor(config: Config) {\n this.config = config;\n }\n\n async getClient(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const pluginDivisionMode = this.getPluginDivisionModeConfig();\n if (pluginDivisionMode !== 'database') {\n throw new Error(\n `The SQLite driver does not support plugin division mode '${pluginDivisionMode}'`,\n );\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n );\n\n const client = createSqliteDatabaseClient(\n pluginId,\n pluginConfig,\n deps,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using global\n * and plugin specific database config. If no explicit database name is configured\n * and `pluginDivisionMode` is not `schema`, this method will provide a generated name\n * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is\n * `schema`, it will fallback to using the default database for the knex instance.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n\n const sqliteFilename: string | undefined = (\n connection as Knex.Sqlite3ConnectionConfig\n ).filename;\n\n if (sqliteFilename === ':memory:') {\n return sqliteFilename;\n }\n\n const sqliteDirectory =\n (connection as { directory?: string }).directory ?? '.';\n\n return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`);\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted for\n * all supported databases excluding SQLite unless `pluginDivisionMode` is set\n * to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { client, overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n if (\n client.includes('sqlite3') &&\n 'filename' in baseConnection &&\n baseConnection.filename !== ':memory:'\n ) {\n throw new Error(\n '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.',\n );\n }\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a\n // directory elsewhere so we preserve `filename`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? createSqliteNameOverride(databaseName) : {};\n }\n}\n"],"names":["path","ensureDirSync","DevDataStore","knexFactory","mergeDatabaseConfig","ConfigReader","merge","omit"],"mappings":";;;;;;;;;;;;;;;AAiCO,SAAS,0BAAA,CACd,QAAA,EACA,QAAA,EACA,IAAA,EAIA,SAAA,EACA;AACA,EAAA,MAAM,UAAA,GAAa,yBAAA,CAA0B,QAAA,EAAU,SAAS,CAAA;AAChE,EAAA,MAAM,aAAa,UAAA,CAAW,UAAA;AAE9B,EAAA,MAAM,QAAA,GAAW,WAAW,QAAA,IAAY,UAAA;AAGxC,EAAA,IAAI,aAAa,UAAA,EAAY;AAC3B,IAAA,MAAM,SAAA,GAAYA,6BAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AACvC,IAAAC,gBAAA,CAAc,SAAS,CAAA;AAAA,EACzB;AAEA,EAAA,IAAI,QAAA;AAEJ,EAAA,IAAI,IAAA,IAAQ,aAAa,UAAA,EAAY;AAInC,IAAA,MAAM,QAAA,GAAWC,6BAAa,GAAA,EAAI;AAElC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,OAAA,GAAU,cAAc,QAAQ,CAAA,CAAA;AAEtC,MAAA,MAAM,mBAAmB,YAAY;AAEnC,QAAA,MAAM,EAAE,IAAA,EAAM,QAAA,KAAa,MAAM,QAAA,CAAS,KAAK,OAAO,CAAA;AAEtD,QAAA,OAAO;AAAA,UACL,GAAI,UAAA,CAAW,UAAA;AAAA,UACf,UAAU,QAAA,IAAY;AAAA,SACxB;AAAA,MACF,CAAA;AAEA,MAAA,QAAA,GAAWC,4BAAA,CAAY;AAAA,QACrB,GAAG,UAAA;AAAA,QACH,UAAA,EAAY,MAAA,CAAO,MAAA,CAAO,gBAAA,EAAkB;AAAA;AAAA,UAE1C,QAAA,EAAU;AAAA,SACX;AAAA,OACF,CAAA;AAGD,MAAA,IAAA,CAAK,SAAA,CAAU,gBAAgB,YAAY;AACzC,QAAA,MAAM,UAAA,GAAa,MAAM,QAAA,CAAS,MAAA,CAAO,iBAAA,EAAkB;AAC3D,QAAA,MAAM,IAAA,GAAO,WAAW,SAAA,EAAU;AAClC,QAAA,MAAM,QAAA,CAAS,IAAA,CAAK,OAAA,EAAS,IAAI,CAAA;AAAA,MACnC,CAAC,CAAA;AAAA,IACH,CAAA,MAAO;AACL,MAAA,QAAA,GAAWA,6BAAY,UAAU,CAAA;AAAA,IACnC;AAAA,EACF,CAAA,MAAO;AACL,IAAA,QAAA,GAAWA,6BAAY,UAAU,CAAA;AAAA,EACnC;AAEA,EAAA,QAAA,CAAS,OAAO,IAAA,CAAK,EAAA,CAAG,eAAA,EAAiB,CAAC,UAAe,QAAA,KAAkB;AACzE,IAAA,QAAA,CAAS,GAAA,CAAI,4BAA4B,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EACnD,CAAC,CAAA;AAED,EAAA,OAAO,QAAA;AACT;AAQO,SAAS,yBAAA,CACd,UACA,SAAA,EACa;AACb,EAAA,MAAM,UAAA,GAAa,SAAS,GAAA,EAAiB;AAG7C,EAAA,IAAI,OAAO,UAAA,CAAW,UAAA,KAAe,QAAA,EAAU;AAC7C,IAAA,UAAA,CAAW,UAAA,GAAa,EAAE,QAAA,EAAU,UAAA,CAAW,UAAA,EAAW;AAAA,EAC5D;AACA,EAAA,IAAI,SAAA,IAAa,OAAO,SAAA,CAAU,UAAA,KAAe,QAAA,EAAU;AACzD,IAAA,SAAA,CAAU,UAAA,GAAa,EAAE,QAAA,EAAU,SAAA,CAAU,UAAA,EAAW;AAAA,EAC1D;AAEA,EAAA,MAAM,MAAA,GAAsBC,uCAAA;AAAA,IAC1B;AAAA,MACE,YAAY;AAAC,KACf;AAAA,IACA,UAAA;AAAA,IACA;AAAA,MACE,gBAAA,EAAkB;AAAA,KACpB;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,yBAAyB,IAAA,EAAoC;AAC3E,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,4BAA4B,IAAI;AAAA,GAC9C;AACF;AAKO,SAAS,4BACd,IAAA,EAC8B;AAC9B,EAAA,OAAO;AAAA,IACL,QAAA,EAAU;AAAA,GACZ;AACF;AAKA,SAAS,WAAW,QAAA,EAA0B;AAC5C,EAAA,OAAO,UAAU,QAAQ,CAAA,CAAA;AAC3B;AAEA,SAAS,oBACP,UAAA,EACsC;AACtC,EAAA,IAAI,OAAO,UAAA,KAAe,WAAA,IAAe,UAAA,KAAe,IAAA,EAAM;AAC5D,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,OAAO,OAAO,UAAA,KAAe,QAAA,IAAY,sBAAsB,MAAA,GAC3D,2BAAA,CAA4B,UAAoB,CAAA,GAChD,UAAA;AACN;AAEO,MAAM,gBAAA,CAAsC;AAAA,EAChC,MAAA;AAAA,EAEjB,YAAY,MAAA,EAAgB;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,MAAM,SAAA,CACJ,QAAA,EACA,IAAA,EAIe;AACf,IAAA,MAAM,eAAe,IAAIC,mBAAA;AAAA,MACvB,IAAA,CAAK,mBAAmB,QAAQ;AAAA,KAClC;AAEA,IAAA,MAAM,kBAAA,GAAqB,KAAK,2BAAA,EAA4B;AAC5D,IAAA,IAAI,uBAAuB,UAAA,EAAY;AACrC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,4DAA4D,kBAAkB,CAAA,CAAA;AAAA,OAChF;AAAA,IACF;AAEA,IAAA,MAAM,uBAAA,GAA0BD,uCAAA;AAAA,MAC9B,EAAC;AAAA,MACD,IAAA,CAAK,qBAAqB,QAAQ;AAAA,KACpC;AAEA,IAAA,MAAM,MAAA,GAAS,0BAAA;AAAA,MACb,QAAA;AAAA,MACA,YAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,gBAAgB,QAAA,EAAsC;AAC5D,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,mBAAA,CAAoB,QAAQ,CAAA;AAEpD,IAAA,MAAM,iBACJ,UAAA,CACA,QAAA;AAEF,IAAA,IAAI,mBAAmB,UAAA,EAAY;AACjC,MAAA,OAAO,cAAA;AAAA,IACT;AAEA,IAAA,MAAM,eAAA,GACH,WAAsC,SAAA,IAAa,GAAA;AAEtD,IAAA,OAAOJ,8BAAK,IAAA,CAAK,eAAA,EAAiB,cAAA,IAAkB,CAAA,EAAG,QAAQ,CAAA,OAAA,CAAS,CAAA;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,cAAc,QAAA,EAGpB;AACA,IAAA,MAAM,YAAA,GAAe,KAAK,MAAA,CAAO,iBAAA;AAAA,MAC/B,CAAA,EAAG,UAAA,CAAW,QAAQ,CAAC,CAAA,OAAA;AAAA,KACzB;AAEA,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,QAAQ,CAAA;AACjD,IAAA,MAAM,SAAS,YAAA,IAAgB,UAAA;AAC/B,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA,YAAY,MAAA,KAAW;AAAA,KACzB;AAAA,EACF;AAAA,EAEQ,cAAc,QAAA,EAAsC;AAC1D,IAAA,OACE,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,CAAA,EAAG,UAAA,CAAW,QAAQ,CAAC,CAAA,KAAA,CAAO,CAAA,IAC5D,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,MAAM,CAAA;AAAA,EAExC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,wBAAwB,QAAA,EAA0C;AACxE,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,MAAA,CACvB,iBAAA,CAAkB,CAAA,EAAG,WAAW,QAAQ,CAAC,CAAA,WAAA,CAAa,CAAA,EACrD,GAAA,EAAgB;AAEpB,IAAA,MAAM,aAAa,IAAA,CAAK,MAAA,CACrB,iBAAA,CAAkB,YAAY,GAC7B,GAAA,EAAgB;AAEpB,IAAA,OAAOM,YAAA,CAAM,YAAY,YAAY,CAAA;AAAA,EACvC;AAAA,EAEQ,2BAAA,GAAsC;AAC5C,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,oBAAoB,CAAA,IAAK,UAAA;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,oBAAoB,QAAA,EAA+C;AACzE,IAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAW,GAAI,IAAA,CAAK,cAAc,QAAQ,CAAA;AAE1D,IAAA,IAAI,iBAAiB,mBAAA,CAAoB,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,YAAY,CAAC,CAAA;AAEtE,IAAA,IACE,MAAA,CAAO,SAAS,SAAS,CAAA,IACzB,cAAc,cAAA,IACd,cAAA,CAAe,aAAa,UAAA,EAC5B;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAMA,IAAA,IAAI,IAAA,CAAK,2BAAA,EAA4B,KAAM,QAAA,EAAU;AACnD,MAAA,cAAA,GAAiBC,WAAA,CAAK,gBAAgB,UAAU,CAAA;AAAA,IAClD;AAGA,IAAA,MAAM,UAAA,GAAa,mBAAA;AAAA,MACjB,KAAK,MAAA,CAAO,WAAA,CAAY,GAAG,UAAA,CAAW,QAAQ,CAAC,CAAA,WAAA,CAAa;AAAA,KAC9D;AAEA,IAAA,OAAO;AAAA;AAAA,MAEL,GAAI,UAAA,GAAa,EAAC,GAAI,cAAA;AAAA,MACtB,GAAG;AAAA,KACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,mBAAmB,QAAA,EAA+B;AACxD,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,IAAA,CAAK,cAAc,QAAQ,CAAA;AAC9C,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,aAAA,CAAc,QAAQ,CAAA;AAExC,IAAA,OAAO;AAAA,MACL,GAAG,IAAA,CAAK,uBAAA,CAAwB,QAAQ,CAAA;AAAA,MACxC,MAAA;AAAA,MACA,UAAA,EAAY,IAAA,CAAK,mBAAA,CAAoB,QAAQ,CAAA;AAAA,MAC7C,GAAI,IAAA,IAAQ,EAAE,IAAA;AAAK,KACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,qBAAqB,QAAA,EAA+B;AAC1D,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,eAAA,CAAgB,QAAQ,CAAA;AAClD,IAAA,OAAO,YAAA,GAAe,wBAAA,CAAyB,YAAY,CAAA,GAAI,EAAC;AAAA,EAClE;AACF;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"databaseServiceFactory.cjs.js","sources":["../../../src/entrypoints/database/databaseServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { ConfigReader } from '@backstage/config';\nimport { DatabaseManager } from './DatabaseManager';\n\n/**\n * Database access and management via `knex`.\n *\n * See {@link @backstage/code-plugin-api#DatabaseService}\n * and {@link https://backstage.io/docs/backend-system/core-services/database | the service docs}\n * for more information.\n *\n * @public\n */\nexport const databaseServiceFactory = createServiceFactory({\n service: coreServices.database,\n deps: {\n config: coreServices.rootConfig,\n lifecycle: coreServices.lifecycle,\n logger: coreServices.logger,\n pluginMetadata: coreServices.pluginMetadata,\n rootLifecycle: coreServices.rootLifecycle,\n rootLogger: coreServices.rootLogger,\n },\n async createRootContext({ config, rootLifecycle, rootLogger }) {\n return config.getOptional('backend.database')\n ? DatabaseManager.fromConfig(config, { rootLifecycle, rootLogger })\n : DatabaseManager.fromConfig(\n new ConfigReader({\n backend: {\n database: { client: 'better-sqlite3', connection: ':memory:' },\n },\n }),\n { rootLifecycle, rootLogger },\n );\n },\n async factory({ pluginMetadata, lifecycle, logger }, databaseManager) {\n return databaseManager.forPlugin(pluginMetadata.getId(), {\n lifecycle,\n logger,\n });\n },\n});\n"],"names":["createServiceFactory","coreServices","config","DatabaseManager","ConfigReader"],"mappings":";;;;;;AAgCO,MAAM,yBAAyBA,qCAAA,CAAqB;AAAA,EACzD,SAASC,6BAAA,CAAa,QAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,QAAQA,6BAAA,CAAa,UAAA;AAAA,IACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,IACxB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,gBAAgBA,6BAAA,CAAa,cAAA;AAAA,IAC7B,eAAeA,6BAAA,CAAa,aAAA;AAAA,IAC5B,YAAYA,6BAAA,CAAa;AAAA,GAC3B;AAAA,EACA,MAAM,iBAAA,CAAkB,UAAEC,QAAA,EAAQ,aAAA,EAAe,YAAW,EAAG;AAC7D,IAAA,OAAOA,QAAA,CAAO,WAAA,CAAY,kBAAkB,CAAA,GACxCC,+BAAA,CAAgB,UAAA,CAAWD,QAAA,EAAQ,EAAE,aAAA,EAAe,UAAA,EAAY,CAAA,GAChEC,+BAAA,CAAgB,UAAA;AAAA,MACd,IAAIC,mBAAA,CAAa;AAAA,QACf,OAAA,EAAS;AAAA,UACP,QAAA,EAAU,EAAE,MAAA,EAAQ,gBAAA,EAAkB,YAAY,UAAA;AAAW;AAC/D,OACD,CAAA;AAAA,MACD,EAAE,eAAe,UAAA;AAAW,KAC9B;AAAA,EACN,CAAA;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,gBAAgB,SAAA,EAAW,MAAA,IAAU,eAAA,EAAiB;AACpE,IAAA,OAAO,eAAA,CAAgB,SAAA,CAAU,cAAA,CAAe,KAAA,EAAM,EAAG;AAAA,MACvD,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC
|
|
1
|
+
{"version":3,"file":"databaseServiceFactory.cjs.js","sources":["../../../src/entrypoints/database/databaseServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { ConfigReader } from '@backstage/config';\nimport { DatabaseManager } from './DatabaseManager';\n\n/**\n * Database access and management via `knex`.\n *\n * See {@link @backstage/code-plugin-api#DatabaseService}\n * and {@link https://backstage.io/docs/backend-system/core-services/database | the service docs}\n * for more information.\n *\n * @public\n */\nexport const databaseServiceFactory = createServiceFactory({\n service: coreServices.database,\n deps: {\n config: coreServices.rootConfig,\n lifecycle: coreServices.lifecycle,\n logger: coreServices.logger,\n pluginMetadata: coreServices.pluginMetadata,\n rootLifecycle: coreServices.rootLifecycle,\n rootLogger: coreServices.rootLogger,\n },\n async createRootContext({ config, rootLifecycle, rootLogger }) {\n return config.getOptional('backend.database')\n ? DatabaseManager.fromConfig(config, { rootLifecycle, rootLogger })\n : DatabaseManager.fromConfig(\n new ConfigReader({\n backend: {\n database: { client: 'better-sqlite3', connection: ':memory:' },\n },\n }),\n { rootLifecycle, rootLogger },\n );\n },\n async factory({ pluginMetadata, lifecycle, logger }, databaseManager) {\n return databaseManager.forPlugin(pluginMetadata.getId(), {\n lifecycle,\n logger,\n });\n },\n});\n"],"names":["createServiceFactory","coreServices","config","DatabaseManager","ConfigReader"],"mappings":";;;;;;AAgCO,MAAM,yBAAyBA,qCAAA,CAAqB;AAAA,EACzD,SAASC,6BAAA,CAAa,QAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,QAAQA,6BAAA,CAAa,UAAA;AAAA,IACrB,WAAWA,6BAAA,CAAa,SAAA;AAAA,IACxB,QAAQA,6BAAA,CAAa,MAAA;AAAA,IACrB,gBAAgBA,6BAAA,CAAa,cAAA;AAAA,IAC7B,eAAeA,6BAAA,CAAa,aAAA;AAAA,IAC5B,YAAYA,6BAAA,CAAa;AAAA,GAC3B;AAAA,EACA,MAAM,iBAAA,CAAkB,UAAEC,QAAA,EAAQ,aAAA,EAAe,YAAW,EAAG;AAC7D,IAAA,OAAOA,QAAA,CAAO,WAAA,CAAY,kBAAkB,CAAA,GACxCC,+BAAA,CAAgB,UAAA,CAAWD,QAAA,EAAQ,EAAE,aAAA,EAAe,UAAA,EAAY,CAAA,GAChEC,+BAAA,CAAgB,UAAA;AAAA,MACd,IAAIC,mBAAA,CAAa;AAAA,QACf,OAAA,EAAS;AAAA,UACP,QAAA,EAAU,EAAE,MAAA,EAAQ,gBAAA,EAAkB,YAAY,UAAA;AAAW;AAC/D,OACD,CAAA;AAAA,MACD,EAAE,eAAe,UAAA;AAAW,KAC9B;AAAA,EACN,CAAA;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,gBAAgB,SAAA,EAAW,MAAA,IAAU,eAAA,EAAiB;AACpE,IAAA,OAAO,eAAA,CAAgB,SAAA,CAAU,cAAA,CAAe,KAAA,EAAM,EAAG;AAAA,MACvD,SAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\nimport { getEndpoints } from './parsing';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is useful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n // The getExternalBaseUrl implementation relies on the backend base URL\n // being a valid, non-local URL that others will be able to route to.\n const baseUrl = config.getString('backend.baseUrl');\n try {\n const { hostname } = new URL(baseUrl);\n const isLocalhost =\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '::1' ||\n hostname === '::';\n if (isLocalhost && process.env.NODE_ENV === 'production') {\n options?.logger?.warn(\n `backend.baseUrl is set to a localhost URL and NODE_ENV is '${process.env.NODE_ENV}'. This is likely a misconfiguration — localhost URLs are not reachable by other services in a deployed environment. Prefer setting it to a routable URL that can be resolved and reached both by your app and by other plugin deployments / services.`,\n );\n }\n } catch {\n options?.logger?.warn(\n `backend.baseUrl config value '${baseUrl}' does not appear to be a valid URL.`,\n );\n }\n\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n endpoints.push(...getEndpoints(config));\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions","getEndpoints"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HostDiscovery.cjs.js","sources":["../../../src/entrypoints/discovery/HostDiscovery.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DiscoveryService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { readHttpServerOptions } from '../rootHttpRouter/http/config';\nimport { SrvResolvers } from './SrvResolvers';\nimport { trimEnd } from 'lodash';\nimport { getEndpoints } from './parsing';\n\ntype Resolver = (pluginId: string) => Promise<string>;\n\n/**\n * A list of target base URLs and their associated plugins.\n *\n * @public\n */\nexport interface HostDiscoveryEndpoint {\n /**\n * The target base URL to use for the given set of plugins. Note that this\n * needs to be a full URL _including_ the protocol and path parts that fully\n * address the root of a plugin's API endpoints.\n *\n * @remarks\n *\n * Can be either a single URL or an object where you can explicitly give a\n * dedicated URL for internal (as seen from the backend) and/or external (as\n * seen from the frontend) lookups.\n *\n * The default behavior is to use the backend base URL for external lookups,\n * and a URL formed from the `.listen` and `.https` configs for internal\n * lookups. Adding discovery endpoints as described here overrides one or both\n * of those behaviors for a given set of plugins.\n *\n * URLs can be in the form of a regular HTTP or HTTPS URL if you are using\n * A/AAAA/CNAME records or IP addresses. Specifically for internal URLs, if\n * you add `+src` to the protocol part then the hostname is treated as an SRV\n * record name and resolved. For example, if you pass in\n * `http+srv://<record>/path` then the record part is resolved into an\n * actual host and port (with random weighted choice as usual when there is\n * more than one match).\n *\n * Any strings with `{{pluginId}}` or `{{ pluginId }}` placeholders in them\n * will have them replaced with the plugin ID.\n *\n * Example URLs:\n *\n * - `https://internal.example.com/secure/api/{{ pluginId }}`\n * - `http+srv://backstage-plugin-{{pluginId}}.http.services.company.net/api/{{pluginId}}`\n * (can only be used in the `internal` key)\n */\n target:\n | string\n | {\n internal?: string;\n external?: string;\n };\n\n /**\n * Array of plugins which use that target base URL.\n *\n * The special value `*` can be used to match all plugins.\n */\n plugins: string[];\n}\n\n/**\n * Options for the {@link HostDiscovery} class.\n *\n * @public\n */\nexport interface HostDiscoveryOptions {\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * A default set of endpoints to use.\n *\n * @remarks\n *\n * These endpoints have lower priority than any that are defined in\n * app-config, but higher priority than the fallback ones.\n *\n * This parameter is useful for example if you want to provide a shared\n * library of core services to your plugin developers, which is set up for the\n * default behaviors in your org. This alleviates the need for replicating any\n * given set of endpoint config in every backend that you deploy.\n */\n defaultEndpoints?: HostDiscoveryEndpoint[];\n}\n\n/**\n * A basic {@link @backstage/backend-plugin-api#DiscoveryService} implementation\n * that can handle plugins that are hosted in a single or multiple deployments.\n *\n * @public\n * @remarks\n *\n * Configuration is read from the `backend` config section, specifically the\n * `.baseUrl` for discovering the external URL, and the `.listen` and `.https`\n * config for the internal one. The fixed base path for these is `/api`, meaning\n * for example the default full internal path for the `catalog` plugin typically\n * will be `http://localhost:7007/api/catalog`.\n *\n * Those defaults can be overridden by providing a target and corresponding\n * plugins in `discovery.endpoints`, e.g.:\n *\n * ```yaml\n * discovery:\n * endpoints:\n * # Set a static internal and external base URL for a plugin\n * - target: https://internal.example.com/internal-catalog\n * plugins: [catalog]\n * # Sets a dynamic internal and external base URL pattern for two plugins\n * - target: https://internal.example.com/secure/api/{{pluginId}}\n * plugins: [auth, permission]\n * # Sets a dynamic base URL pattern for only the internal resolution for all\n * # other plugins, while leaving the external resolution unaffected\n * - target:\n * internal: http+srv://backstage-plugin-{{pluginId}}.http.${SERVICE_DOMAIN}/api/{{pluginId}}\n * plugins: [*]\n * ```\n */\nexport class HostDiscovery implements DiscoveryService {\n #srvResolver: SrvResolvers;\n #internalResolvers: Map<string, Resolver> = new Map();\n #externalResolvers: Map<string, Resolver> = new Map();\n #internalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n #externalFallbackResolver: Resolver = async () => {\n throw new Error('Not initialized');\n };\n\n static fromConfig(config: RootConfigService, options?: HostDiscoveryOptions) {\n // The getExternalBaseUrl implementation relies on the backend base URL\n // being a valid, non-local URL that others will be able to route to.\n const baseUrl = config.getString('backend.baseUrl');\n try {\n const { hostname } = new URL(baseUrl);\n const isLocalhost =\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '::1' ||\n hostname === '::';\n if (isLocalhost && process.env.NODE_ENV === 'production') {\n options?.logger?.warn(\n `backend.baseUrl is set to a localhost URL and NODE_ENV is '${process.env.NODE_ENV}'. This is likely a misconfiguration — localhost URLs are not reachable by other services in a deployed environment. Prefer setting it to a routable URL that can be resolved and reached both by your app and by other plugin deployments / services.`,\n );\n }\n } catch {\n options?.logger?.warn(\n `backend.baseUrl config value '${baseUrl}' does not appear to be a valid URL.`,\n );\n }\n\n const discovery = new HostDiscovery(new SrvResolvers());\n\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n config.subscribe?.(() => {\n try {\n discovery.#updateResolvers(config, options?.defaultEndpoints);\n } catch (e) {\n options?.logger.error(`Failed to update discovery service: ${e}`);\n }\n });\n\n return discovery;\n }\n\n private constructor(srvResolver: SrvResolvers) {\n this.#srvResolver = srvResolver;\n this.#internalResolvers = new Map();\n this.#externalResolvers = new Map();\n this.#internalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n this.#externalFallbackResolver = () => {\n throw new Error('Not initialized');\n };\n }\n\n async getBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#internalResolvers.get(pluginId) ??\n this.#internalResolvers.get('*') ??\n this.#internalFallbackResolver;\n return await resolver(pluginId);\n }\n\n async getExternalBaseUrl(pluginId: string): Promise<string> {\n const resolver =\n this.#externalResolvers.get(pluginId) ??\n this.#externalResolvers.get('*') ??\n this.#externalFallbackResolver;\n return await resolver(pluginId);\n }\n\n #updateResolvers(config: Config, defaultEndpoints?: HostDiscoveryEndpoint[]) {\n this.#updateFallbackResolvers(config);\n this.#updatePluginResolvers(config, defaultEndpoints);\n }\n\n #updateFallbackResolvers(config: Config) {\n const backendBaseUrl = trimEnd(config.getString('backend.baseUrl'), '/');\n\n const {\n listen: { host: listenHost = '::', port: listenPort },\n } = readHttpServerOptions(config.getConfig('backend'));\n const protocol = config.has('backend.https') ? 'https' : 'http';\n\n // Translate bind-all to localhost, and support IPv6\n let host = listenHost;\n if (host === '::' || host === '') {\n // We use localhost instead of ::1, since IPv6-compatible systems should default\n // to using IPv6 when they see localhost, but if the system doesn't support IPv6\n // things will still work.\n host = 'localhost';\n } else if (host === '0.0.0.0') {\n host = '127.0.0.1';\n }\n if (host.includes(':')) {\n host = `[${host}]`;\n }\n\n this.#internalFallbackResolver = this.#makeResolver(\n `${protocol}://${host}:${listenPort}/api/{{pluginId}}`,\n false,\n );\n this.#externalFallbackResolver = this.#makeResolver(\n `${backendBaseUrl}/api/{{pluginId}}`,\n false,\n );\n }\n\n #updatePluginResolvers(\n config: Config,\n defaultEndpoints?: HostDiscoveryEndpoint[],\n ) {\n // Start out with the default endpoints, if any\n const endpoints = defaultEndpoints?.slice() ?? [];\n\n // Allow config to override the default endpoints\n endpoints.push(...getEndpoints(config));\n\n // Build up a new set of resolvers\n const internalResolvers: Map<string, Resolver> = new Map();\n const externalResolvers: Map<string, Resolver> = new Map();\n for (const { target, plugins } of endpoints) {\n let internalResolver: Resolver | undefined;\n let externalResolver: Resolver | undefined;\n\n if (typeof target === 'string') {\n internalResolver = externalResolver = this.#makeResolver(target, false);\n } else {\n if (target.internal) {\n internalResolver = this.#makeResolver(target.internal, true);\n }\n if (target.external) {\n externalResolver = this.#makeResolver(target.external, false);\n }\n }\n\n if (internalResolver) {\n for (const pluginId of plugins) {\n internalResolvers.set(pluginId, internalResolver);\n }\n }\n if (externalResolver) {\n for (const pluginId of plugins) {\n externalResolvers.set(pluginId, externalResolver);\n }\n }\n }\n\n // Only persist if no errors were thrown above\n this.#internalResolvers = internalResolvers;\n this.#externalResolvers = externalResolvers;\n }\n\n #makeResolver(urlPattern: string, allowSrv: boolean): Resolver {\n const withPluginId = (pluginId: string, url: string) => {\n return url.replace(\n /\\{\\{\\s*pluginId\\s*\\}\\}/g,\n encodeURIComponent(pluginId),\n );\n };\n\n if (!this.#srvResolver.isSrvUrl(urlPattern)) {\n return async pluginId => withPluginId(pluginId, urlPattern);\n }\n\n if (!allowSrv) {\n throw new Error(\n `SRV resolver URLs cannot be used in the target for external endpoints`,\n );\n }\n\n const lazyResolvers = new Map<string, () => Promise<string>>();\n return async pluginId => {\n let lazyResolver = lazyResolvers.get(pluginId);\n if (!lazyResolver) {\n lazyResolver = this.#srvResolver.getResolver(\n withPluginId(pluginId, urlPattern),\n );\n lazyResolvers.set(pluginId, lazyResolver);\n }\n return await lazyResolver();\n };\n }\n}\n"],"names":["SrvResolvers","config","trimEnd","readHttpServerOptions","getEndpoints"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SrvResolvers.cjs.js","sources":["../../../src/entrypoints/discovery/SrvResolvers.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ForwardedError, InputError, NotFoundError } from '@backstage/errors';\nimport { resolveSrv, SrvRecord } from 'node:dns';\n\nconst PROTOCOL_SUFFIX = '+srv:';\n\n/**\n * Helps with resolution and caching of SRV lookups.\n *\n * Supports URLs on the form `http+srv://myplugin.services.region.example.net/api/myplugin`\n */\nexport class SrvResolvers {\n readonly #cache: Map<string, Promise<SrvRecord[]>>;\n readonly #cacheTtlMillis: number;\n readonly #resolveSrv: (host: string) => Promise<SrvRecord[]>;\n\n constructor(options?: {\n resolveSrv?: (host: string) => Promise<SrvRecord[]>;\n cacheTtlMillis?: number;\n }) {\n this.#cache = new Map();\n this.#cacheTtlMillis = options?.cacheTtlMillis ?? 1000;\n this.#resolveSrv =\n options?.resolveSrv ??\n (host =>\n new Promise((resolve, reject) => {\n resolveSrv(host, (err, result) => {\n if (err) {\n reject(err);\n } else {\n resolve(result);\n }\n });\n }));\n }\n\n isSrvUrl(url: string): boolean {\n try {\n this.#parseSrvUrl(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get a resolver function for a given SRV form URL.\n *\n * @param url An SRV form URL, e.g. `http+srv://myplugin.services.region.example.net/api/myplugin`\n * @returns A function that returns resolved URLs, e.g. `http://1234abcd.region.example.net:8080/api/myplugin`\n */\n getResolver(url: string): () => Promise<string> {\n const { protocol, host, path } = this.#parseSrvUrl(url);\n return () =>\n this.#resolveHost(host).then(\n resolved => `${protocol}://${resolved}${path}`,\n );\n }\n\n /**\n * Attempts to parse out the relevant parts of an SRV URL.\n */\n #parseSrvUrl(url: string): { protocol: string; host: string; path: string } {\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new InputError(\n `SRV resolver expected a valid URL starting with http(s)+srv:// but got '${url}'`,\n );\n }\n if (!parsedUrl.protocol?.endsWith(PROTOCOL_SUFFIX) || !parsedUrl.hostname) {\n throw new InputError(\n `SRV resolver expected a URL with protocol http(s)+srv:// but got '${url}'`,\n );\n }\n if (parsedUrl.port) {\n throw new InputError(\n `SRV resolver URLs cannot contain a port but got '${url}'`,\n );\n }\n if (parsedUrl.username || parsedUrl.password) {\n throw new InputError(\n `SRV resolver URLs cannot contain username or password but got '${url}'`,\n );\n }\n if (parsedUrl.search || parsedUrl.hash) {\n throw new InputError(\n `SRV resolver URLs cannot contain search params or a hash but got '${url}'`,\n );\n }\n\n const protocol = parsedUrl.protocol.substring(\n 0,\n parsedUrl.protocol.length - PROTOCOL_SUFFIX.length,\n );\n const host = parsedUrl.hostname;\n const path = parsedUrl.pathname.replace(/\\/+$/, '');\n\n if (!['http', 'https'].includes(protocol)) {\n throw new InputError(\n `SRV URLs must be based on http or https but got '${url}'`,\n );\n }\n\n return { protocol, host, path };\n }\n\n /**\n * Resolves a single SRV record name to a host:port string.\n */\n #resolveHost(host: string): Promise<string> {\n let records = this.#cache.get(host);\n if (!records) {\n records = this.#resolveSrv(host).then(\n result => {\n if (!result.length) {\n throw new NotFoundError(`No SRV records found for ${host}`);\n }\n return result;\n },\n err => {\n throw new ForwardedError(`Failed SRV resolution for ${host}`, err);\n },\n );\n this.#cache.set(host, records);\n setTimeout(() => {\n this.#cache.delete(host);\n }, this.#cacheTtlMillis);\n }\n\n return records.then(rs => {\n const r = this.#pickRandomRecord(rs);\n return `${r.name}:${r.port}`;\n });\n }\n\n /**\n * Among a set of records, pick one at random.\n *\n * This assumes that the set is not empty.\n *\n * Since this contract only ever returns a single record, the best it can do\n * is to pick weighted-randomly among the highest-priority records. In order\n * to be smarter than that, the caller would have to be able to make decisions\n * on the whole set of records.\n */\n #pickRandomRecord(allRecords: SrvRecord[]): SrvRecord {\n // Lowest priority number means highest priority\n const lowestPriority = allRecords.reduce(\n (acc, r) => Math.min(acc, r.priority),\n Number.MAX_SAFE_INTEGER,\n );\n const records = allRecords.filter(r => r.priority === lowestPriority);\n\n const totalWeight = records.reduce((acc, r) => acc + r.weight, 0);\n const targetWeight = Math.random() * totalWeight;\n\n // Just as a fallback, we expect the loop below to always find a result\n let result = records[0];\n let currentWeight = 0;\n\n for (const record of records) {\n currentWeight += record.weight;\n if (targetWeight <= currentWeight) {\n result = record;\n break;\n }\n }\n\n return result;\n }\n}\n"],"names":["resolveSrv","InputError","NotFoundError","ForwardedError"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"SrvResolvers.cjs.js","sources":["../../../src/entrypoints/discovery/SrvResolvers.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ForwardedError, InputError, NotFoundError } from '@backstage/errors';\nimport { resolveSrv, SrvRecord } from 'node:dns';\n\nconst PROTOCOL_SUFFIX = '+srv:';\n\n/**\n * Helps with resolution and caching of SRV lookups.\n *\n * Supports URLs on the form `http+srv://myplugin.services.region.example.net/api/myplugin`\n */\nexport class SrvResolvers {\n readonly #cache: Map<string, Promise<SrvRecord[]>>;\n readonly #cacheTtlMillis: number;\n readonly #resolveSrv: (host: string) => Promise<SrvRecord[]>;\n\n constructor(options?: {\n resolveSrv?: (host: string) => Promise<SrvRecord[]>;\n cacheTtlMillis?: number;\n }) {\n this.#cache = new Map();\n this.#cacheTtlMillis = options?.cacheTtlMillis ?? 1000;\n this.#resolveSrv =\n options?.resolveSrv ??\n (host =>\n new Promise((resolve, reject) => {\n resolveSrv(host, (err, result) => {\n if (err) {\n reject(err);\n } else {\n resolve(result);\n }\n });\n }));\n }\n\n isSrvUrl(url: string): boolean {\n try {\n this.#parseSrvUrl(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get a resolver function for a given SRV form URL.\n *\n * @param url An SRV form URL, e.g. `http+srv://myplugin.services.region.example.net/api/myplugin`\n * @returns A function that returns resolved URLs, e.g. `http://1234abcd.region.example.net:8080/api/myplugin`\n */\n getResolver(url: string): () => Promise<string> {\n const { protocol, host, path } = this.#parseSrvUrl(url);\n return () =>\n this.#resolveHost(host).then(\n resolved => `${protocol}://${resolved}${path}`,\n );\n }\n\n /**\n * Attempts to parse out the relevant parts of an SRV URL.\n */\n #parseSrvUrl(url: string): { protocol: string; host: string; path: string } {\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new InputError(\n `SRV resolver expected a valid URL starting with http(s)+srv:// but got '${url}'`,\n );\n }\n if (!parsedUrl.protocol?.endsWith(PROTOCOL_SUFFIX) || !parsedUrl.hostname) {\n throw new InputError(\n `SRV resolver expected a URL with protocol http(s)+srv:// but got '${url}'`,\n );\n }\n if (parsedUrl.port) {\n throw new InputError(\n `SRV resolver URLs cannot contain a port but got '${url}'`,\n );\n }\n if (parsedUrl.username || parsedUrl.password) {\n throw new InputError(\n `SRV resolver URLs cannot contain username or password but got '${url}'`,\n );\n }\n if (parsedUrl.search || parsedUrl.hash) {\n throw new InputError(\n `SRV resolver URLs cannot contain search params or a hash but got '${url}'`,\n );\n }\n\n const protocol = parsedUrl.protocol.substring(\n 0,\n parsedUrl.protocol.length - PROTOCOL_SUFFIX.length,\n );\n const host = parsedUrl.hostname;\n const path = parsedUrl.pathname.replace(/\\/+$/, '');\n\n if (!['http', 'https'].includes(protocol)) {\n throw new InputError(\n `SRV URLs must be based on http or https but got '${url}'`,\n );\n }\n\n return { protocol, host, path };\n }\n\n /**\n * Resolves a single SRV record name to a host:port string.\n */\n #resolveHost(host: string): Promise<string> {\n let records = this.#cache.get(host);\n if (!records) {\n records = this.#resolveSrv(host).then(\n result => {\n if (!result.length) {\n throw new NotFoundError(`No SRV records found for ${host}`);\n }\n return result;\n },\n err => {\n throw new ForwardedError(`Failed SRV resolution for ${host}`, err);\n },\n );\n this.#cache.set(host, records);\n setTimeout(() => {\n this.#cache.delete(host);\n }, this.#cacheTtlMillis);\n }\n\n return records.then(rs => {\n const r = this.#pickRandomRecord(rs);\n return `${r.name}:${r.port}`;\n });\n }\n\n /**\n * Among a set of records, pick one at random.\n *\n * This assumes that the set is not empty.\n *\n * Since this contract only ever returns a single record, the best it can do\n * is to pick weighted-randomly among the highest-priority records. In order\n * to be smarter than that, the caller would have to be able to make decisions\n * on the whole set of records.\n */\n #pickRandomRecord(allRecords: SrvRecord[]): SrvRecord {\n // Lowest priority number means highest priority\n const lowestPriority = allRecords.reduce(\n (acc, r) => Math.min(acc, r.priority),\n Number.MAX_SAFE_INTEGER,\n );\n const records = allRecords.filter(r => r.priority === lowestPriority);\n\n const totalWeight = records.reduce((acc, r) => acc + r.weight, 0);\n const targetWeight = Math.random() * totalWeight;\n\n // Just as a fallback, we expect the loop below to always find a result\n let result = records[0];\n let currentWeight = 0;\n\n for (const record of records) {\n currentWeight += record.weight;\n if (targetWeight <= currentWeight) {\n result = record;\n break;\n }\n }\n\n return result;\n }\n}\n"],"names":["resolveSrv","InputError","NotFoundError","ForwardedError"],"mappings":"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"}
|