chore: support cloudsql-connector

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-11-28 10:45:36 +01:00
parent e89242027d
commit 5424ff7742
5 changed files with 140 additions and 36 deletions
+8
View File
@@ -185,6 +185,14 @@
"yn": "^4.0.0",
"zod": "^3.22.4"
},
"peerDependencies": {
"@google-cloud/cloud-sql-connector": "^1.4.0"
},
"peerDependenciesMeta": {
"@google-cloud/cloud-sql-connector": {
"optional": true
}
},
"devDependencies": {
"@aws-sdk/util-stream-node": "^3.350.0",
"@backstage/backend-plugin-api": "workspace:^",
@@ -252,6 +252,7 @@ export class DatabaseManager {
databaseConfig,
{
pg: new PgConnector(databaseConfig, prefix),
'pg+cloudsql': new PgConnector(databaseConfig, prefix),
sqlite3: new Sqlite3Connector(databaseConfig),
'better-sqlite3': new Sqlite3Connector(databaseConfig),
mysql: new MysqlConnector(databaseConfig, prefix),
@@ -22,6 +22,8 @@ import {
parsePgConnectionString,
} from './postgres';
jest.mock('@google-cloud/cloud-sql-connector');
describe('postgres', () => {
const createMockConnection = () => ({
host: 'acme',
@@ -37,33 +39,35 @@ describe('postgres', () => {
new ConfigReader({ client: 'pg', connection });
describe('buildPgDatabaseConfig', () => {
it('builds a postgres config', () => {
it('builds a postgres config', async () => {
const mockConnection = createMockConnection();
expect(buildPgDatabaseConfig(createConfig(mockConnection))).toEqual({
client: 'pg',
connection: mockConnection,
useNullAsDefault: true,
});
});
it('builds a connection string config', () => {
const mockConnectionString = createMockConnectionString();
expect(buildPgDatabaseConfig(createConfig(mockConnectionString))).toEqual(
expect(await buildPgDatabaseConfig(createConfig(mockConnection))).toEqual(
{
client: 'pg',
connection: mockConnectionString,
connection: mockConnection,
useNullAsDefault: true,
},
);
});
it('overrides the database name', () => {
it('builds a connection string config', async () => {
const mockConnectionString = createMockConnectionString();
expect(
await buildPgDatabaseConfig(createConfig(mockConnectionString)),
).toEqual({
client: 'pg',
connection: mockConnectionString,
useNullAsDefault: true,
});
});
it('overrides the database name', async () => {
const mockConnection = createMockConnection();
expect(
buildPgDatabaseConfig(createConfig(mockConnection), {
await buildPgDatabaseConfig(createConfig(mockConnection), {
connection: { database: 'other_db' },
}),
).toEqual({
@@ -76,14 +80,14 @@ describe('postgres', () => {
});
});
it('overrides the schema name', () => {
it('overrides the schema name', async () => {
const mockConnection = {
...createMockConnection(),
schema: 'schemaName',
};
expect(
buildPgDatabaseConfig(createConfig(mockConnection), {
await buildPgDatabaseConfig(createConfig(mockConnection), {
searchPath: ['schemaName'],
}),
).toEqual({
@@ -94,11 +98,11 @@ describe('postgres', () => {
});
});
it('adds additional config settings', () => {
it('adds additional config settings', async () => {
const mockConnection = createMockConnection();
expect(
buildPgDatabaseConfig(createConfig(mockConnection), {
await buildPgDatabaseConfig(createConfig(mockConnection), {
connection: { database: 'other_db' },
pool: { min: 0, max: 7 },
debug: true,
@@ -115,12 +119,12 @@ describe('postgres', () => {
});
});
it('overrides the database from connection string', () => {
it('overrides the database from connection string', async () => {
const mockConnectionString = createMockConnectionString();
const mockConnection = createMockConnection();
expect(
buildPgDatabaseConfig(createConfig(mockConnectionString), {
await buildPgDatabaseConfig(createConfig(mockConnectionString), {
connection: { database: 'other_db' },
}),
).toEqual({
@@ -133,6 +137,64 @@ describe('postgres', () => {
useNullAsDefault: true,
});
});
it('uses the correct config when using pg+google-cloud-sql', async () => {
const mockConnectionString = createMockConnectionString();
const mockConnection = createMockConnection();
expect(
await buildPgDatabaseConfig(
new ConfigReader({
client: 'pg+google-cloud-sql',
connection: mockConnectionString,
instanceConnectionName: 'project:region:instance',
}),
{ connection: { database: 'other_db' } },
),
).toEqual({
client: 'pg',
connection: {
...mockConnection,
port: '5432',
database: 'other_db',
},
instanceConnectionName: 'project:region:instance',
useNullAsDefault: true,
});
});
it('adds the settings from cloud-sql-connector', async () => {
const { Connector } = jest.requireMock(
'@google-cloud/cloud-sql-connector',
) as jest.Mocked<typeof import('@google-cloud/cloud-sql-connector')>;
const mockStream = (): any => {};
Connector.prototype.getOptions.mockResolvedValue({ stream: mockStream });
const mockConnectionString = createMockConnectionString();
const mockConnection = createMockConnection();
expect(
await buildPgDatabaseConfig(
new ConfigReader({
client: 'pg+google-cloud-sql',
connection: mockConnectionString,
instanceConnectionName: 'project:region:instance',
}),
{ connection: { database: 'other_db' } },
),
).toEqual({
client: 'pg',
connection: {
...mockConnection,
port: '5432',
database: 'other_db',
stream: mockStream,
},
instanceConnectionName: 'project:region:instance',
useNullAsDefault: true,
});
});
});
describe('getPgConnectionConfig', () => {
@@ -174,9 +236,9 @@ describe('postgres', () => {
});
describe('createPgDatabaseClient', () => {
it('creates a postgres knex instance', () => {
it('creates a postgres knex instance', async () => {
expect(
createPgDatabaseClient(
await createPgDatabaseClient(
createConfig({
host: 'acme',
user: 'foo',
@@ -187,14 +249,14 @@ describe('postgres', () => {
).toBeTruthy();
});
it('attempts to read an ssl cert', () => {
expect(() =>
it('attempts to read an ssl cert', async () => {
await expect(() =>
createPgDatabaseClient(
createConfig(
'postgresql://postgres:pass@localhost:5432/dbname?sslrootcert=/path/to/file',
),
),
).toThrow(/no such file or directory/);
).rejects.toThrow(/no such file or directory/);
});
});
@@ -37,11 +37,11 @@ const ddlLimiter = limiterFactory(1);
* @param dbConfig - The database config
* @param overrides - Additional options to merge with the config
*/
export function createPgDatabaseClient(
export async function createPgDatabaseClient(
dbConfig: Config,
overrides?: Knex.Config,
) {
const knexConfig = buildPgDatabaseConfig(dbConfig, overrides);
const knexConfig = await buildPgDatabaseConfig(dbConfig, overrides);
const database = knexFactory(knexConfig);
const role = dbConfig.getOptionalString('role');
@@ -64,11 +64,11 @@ export function createPgDatabaseClient(
* @param dbConfig - The database config
* @param overrides - Additional options to merge with the config
*/
export function buildPgDatabaseConfig(
export async function buildPgDatabaseConfig(
dbConfig: Config,
overrides?: Knex.Config,
) {
return mergeDatabaseConfig(
const config = mergeDatabaseConfig(
dbConfig.get(),
{
connection: getPgConnectionConfig(dbConfig, !!overrides),
@@ -76,6 +76,33 @@ export function buildPgDatabaseConfig(
},
overrides,
);
if (config.client === 'pg+google-cloud-sql') {
const {
Connector: CloudSqlConnector,
IpAddressTypes,
AuthTypes,
} = await import('@google-cloud/cloud-sql-connector');
// override the config to be pg for backwards compat with other code
config.client = 'pg';
const connector = new CloudSqlConnector();
const clientOpts = await connector.getOptions({
instanceConnectionName: dbConfig.getString('instanceConnectionName'),
ipType: IpAddressTypes.PUBLIC,
authType: AuthTypes.IAM,
});
return {
...config,
connection: {
...config.connection,
...clientOpts,
},
};
}
return config;
}
/**
@@ -130,7 +157,7 @@ export async function ensurePgDatabaseExists(
dbConfig: Config,
...databases: Array<string>
) {
const admin = createPgDatabaseClient(dbConfig, {
const admin = await createPgDatabaseClient(dbConfig, {
connection: {
database: 'postgres',
},
@@ -186,7 +213,7 @@ export async function ensurePgSchemaExists(
dbConfig: Config,
...schemas: Array<string>
): Promise<void> {
const admin = createPgDatabaseClient(dbConfig);
const admin = await createPgDatabaseClient(dbConfig);
const role = dbConfig.getOptionalString('role');
try {
@@ -219,7 +246,7 @@ export async function dropPgDatabase(
dbConfig: Config,
...databases: Array<string>
) {
const admin = createPgDatabaseClient(dbConfig);
const admin = await createPgDatabaseClient(dbConfig);
try {
await Promise.all(
databases.map(async database => {
+9 -3
View File
@@ -3662,6 +3662,11 @@ __metadata:
yauzl: ^3.0.0
yn: ^4.0.0
zod: ^3.22.4
peerDependencies:
"@google-cloud/cloud-sql-connector": ^1.4.0
peerDependenciesMeta:
"@google-cloud/cloud-sql-connector":
optional: true
languageName: unknown
linkType: soft
@@ -30112,14 +30117,15 @@ __metadata:
linkType: hard
"gaxios@npm:^6.0.0, gaxios@npm:^6.0.2, gaxios@npm:^6.1.1":
version: 6.3.0
resolution: "gaxios@npm:6.3.0"
version: 6.7.1
resolution: "gaxios@npm:6.7.1"
dependencies:
extend: ^3.0.2
https-proxy-agent: ^7.0.1
is-stream: ^2.0.0
node-fetch: ^2.6.9
checksum: 4d4a8db32d833f8012435e2016cb0c919cac288e821bf81f877504e4284ef12b444cd903448e738c4031cd5219adf1e8d68e7df2b3dba774db9fde27f71109d4
uuid: ^9.0.1
checksum: ed5952655339918e0868c6f4e079d6e9e55b20a242ddb1be25076cdfb0dd1ca5a2cb233da7352baa972c19f898a78fa6ba67e7d848717c9ca9877c269b5b02f7
languageName: node
linkType: hard