From e2fe5b748c3077078fa43e4bfa427fef603656a9 Mon Sep 17 00:00:00 2001 From: alkatrivedi <58396306+alkatrivedi@users.noreply.github.com> Date: Wed, 28 Feb 2024 05:48:17 +0000 Subject: [PATCH] feat(spanner): add emulator support for the admin client autogenerated API samples (#1994) This PR introduces emulator support for the API samples, which are using admin client autogenerated methods. **Sample usage** For Instance samples: ``` const {Spanner} = require('@google-cloud/spanner'); const spanner = new Spanner({ projectId: projectId, }); const instanceAdminClient = spanner.get_instance_admin_client(); ``` For Database samples: ``` const {Spanner} = require('@google-cloud/spanner'); const spanner = new Spanner({ projectId: projectID, }); const databaseAdminClient = spanner.get_database_admin_client(); ``` --- .gitignore | 1 + src/index.ts | 58 +++++++++++++++++++++- system-test/spanner.ts | 109 +++++++++++++++++++++++++++++++++++++++++ test/index.ts | 2 + 4 files changed, 169 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 604f64ee0..126f263f1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ system-test/*key.json package-lock.json __pycache__ .idea +.vscode \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2f2b1db3e..eaa44f408 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,13 @@ import { CreateInstanceConfigCallback, CreateInstanceConfigResponse, } from './instance-config'; -import {grpc, GrpcClientOptions, CallOptions, GoogleError} from 'google-gax'; +import { + grpc, + GrpcClientOptions, + CallOptions, + GoogleError, + ClientOptions, +} from 'google-gax'; import {google, google as instanceAdmin} from '../protos/protos'; import { PagedOptions, @@ -347,6 +353,54 @@ class Spanner extends GrpcService { this.directedReadOptions = directedReadOptions; } + /** + * Gets the InstanceAdminClient object. + * The returned InstanceAdminClient object is a shared, managed instance and should not be manually closed. + * @returns {v1.InstanceAdminClient} The InstanceAdminClient object + * @example + * ``` + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner({ + * projectId: projectId, + * }); + * const instanceAdminClient = spanner.getInstanceAdminClient(); + * ``` + */ + getInstanceAdminClient(): v1.InstanceAdminClient { + const clientName = 'InstanceAdminClient'; + if (!this.clients_.has(clientName)) { + this.clients_.set( + clientName, + new v1[clientName](this.options as ClientOptions) + ); + } + return this.clients_.get(clientName)! as v1.InstanceAdminClient; + } + + /** + * Gets the DatabaseAdminClient object. + * The returned DatabaseAdminClient object is a managed, shared instance and should not be manually closed. + * @returns {v1.DatabaseAdminClient} The DatabaseAdminClient object. + * @example + * ``` + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner({ + * projectId: projectId, + * }); + * const databaseAdminClient = spanner.getDatabaseAdminClient(); + * ``` + */ + getDatabaseAdminClient(): v1.DatabaseAdminClient { + const clientName = 'DatabaseAdminClient'; + if (!this.clients_.has(clientName)) { + this.clients_.set( + clientName, + new v1[clientName](this.options as ClientOptions) + ); + } + return this.clients_.get(clientName)! as v1.DatabaseAdminClient; + } + /** Closes this Spanner client and cleans up all resources used by it. */ close(): void { this.clients_.forEach(c => { @@ -1741,6 +1795,8 @@ promisifyAll(Spanner, { 'pgJsonb', 'operation', 'timestamp', + 'getInstanceAdminClient', + 'getDatabaseAdminClient', ], }); diff --git a/system-test/spanner.ts b/system-test/spanner.ts index 011cba1f5..a31b89f01 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -231,6 +231,115 @@ describe('Spanner', () => { ); }); + describe('Autogenerated Admin Client', async () => { + const projectId = process.env.GCLOUD_PROJECT; + const instanceId = envInstanceName + ? envInstanceName + : generateName('instance'); + const DATABASE = generateName('database'); + const instanceAdminClient = spanner.getInstanceAdminClient(); + const databaseAdminClient = spanner.getDatabaseAdminClient(); + + before(async () => { + assert(projectId); + if (generateInstanceForTest) { + const [operation] = await instanceAdminClient.createInstance({ + parent: instanceAdminClient.projectPath(projectId), + instanceId: instanceId, + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-us-central1' + ), + nodeCount: 1, + displayName: instanceId, + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }, + }); + const [instance] = await operation.promise(); + RESOURCES_TO_CLEAN.push(instance as Instance); + } else { + console.log( + `Not creating temp instance, using + ${instanceAdminClient.instancePath( + projectId, + envInstanceName + )}...` + ); + } + const [operation] = await databaseAdminClient.createDatabase({ + createStatement: 'CREATE DATABASE `' + DATABASE + '`', + extraStatements: [ + `CREATE TABLE ${TABLE_NAME} ( + SingerId STRING(1024) NOT NULL, + Name STRING(1024), + ) PRIMARY KEY(SingerId)`, + ], + parent: databaseAdminClient.instancePath(projectId, instanceId), + }); + await operation.promise(); + }); + + describe('Instances', () => { + it('should have created the instance', async () => { + assert(projectId); + try { + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + assert.strictEqual( + metadata!.name, + instanceAdminClient.instancePath(projectId, instanceId) + ); + } catch (err) { + if (!err) { + assert.ifError(err); + } + } + }); + + it('should list the instances', async () => { + assert(projectId); + const [instances] = await instanceAdminClient.listInstances({ + parent: instanceAdminClient.projectPath(projectId), + }); + assert(instances!.length > 0); + }); + }); + + describe('Databases', () => { + async function createDatabase(database, dialect) { + assert(projectId); + const [metadata] = await databaseAdminClient.getDatabase({ + name: databaseAdminClient.databasePath( + projectId, + instanceId, + database + ), + }); + assert.strictEqual( + metadata!.name, + databaseAdminClient.databasePath(projectId, instanceId, database) + ); + assert.strictEqual(metadata!.state, 'READY'); + if (IS_EMULATOR_ENABLED) { + assert.strictEqual( + metadata!.databaseDialect, + 'DATABASE_DIALECT_UNSPECIFIED' + ); + } else { + assert.strictEqual(metadata!.databaseDialect, dialect); + } + } + + it('GOOGLE_STANDARD_SQL should have created the database', async () => { + createDatabase(DATABASE, 'GOOGLE_STANDARD_SQL'); + }); + }); + }); + describe('types', () => { const TABLE_NAME = 'TypeCheck'; const googleSqlTable = DATABASE.table(TABLE_NAME); diff --git a/test/index.ts b/test/index.ts index 70f68857e..cd60bbbcd 100644 --- a/test/index.ts +++ b/test/index.ts @@ -91,6 +91,8 @@ const fakePfy = extend({}, pfy, { 'pgJsonb', 'operation', 'timestamp', + 'getInstanceAdminClient', + 'getDatabaseAdminClient', ]); }, });