Skip to content

Commit

Permalink
feat: support custom SQL Admin API endpoint (#210)
Browse files Browse the repository at this point in the history
Allows library users to optionally pass custom
SQL Admin API endpoint in the Connector constructor.

Fixes: #64
  • Loading branch information
edosrecki committed Sep 28, 2023
1 parent 319823a commit 290d741
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 12 deletions.
8 changes: 6 additions & 2 deletions src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,19 @@ class CloudSQLInstanceMap extends Map {
}
}

interface ConnectorOptions {
sqlAdminAPIEndpoint?: string;
}

// The Connector class is the main public API to interact
// with the Cloud SQL Node.js Connector.
export class Connector {
private readonly instances: CloudSQLInstanceMap;
private readonly sqlAdminFetcher: SQLAdminFetcher;

constructor() {
constructor({sqlAdminAPIEndpoint}: ConnectorOptions = {}) {
this.instances = new CloudSQLInstanceMap();
this.sqlAdminFetcher = new SQLAdminFetcher();
this.sqlAdminFetcher = new SQLAdminFetcher({sqlAdminAPIEndpoint});
}

// Connector.getOptions is a method that accepts a Cloud SQL instance
Expand Down
8 changes: 7 additions & 1 deletion src/sqladmin-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,21 @@ function cleanGaxiosConfig() {
gaxios.defaults = defaultGaxiosConfig;
}

export interface SQLAdminFetcherOptions {
loginAuth?: GoogleAuth;
sqlAdminAPIEndpoint?: string;
}

export class SQLAdminFetcher {
private readonly client: sqladmin_v1beta4.Sqladmin;
private readonly auth: GoogleAuth;

constructor(loginAuth?: GoogleAuth) {
constructor({loginAuth, sqlAdminAPIEndpoint}: SQLAdminFetcherOptions = {}) {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/sqlservice.admin'],
});
this.client = new Sqladmin({
rootUrl: sqlAdminAPIEndpoint,
auth,
userAgentDirectives: [
{
Expand Down
21 changes: 21 additions & 0 deletions test/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {setupCredentials} from './fixtures/setup-credentials';
import {IpAddressTypes} from '../src/ip-addresses';
import {CA_CERT, CLIENT_CERT, CLIENT_KEY} from './fixtures/certs';
import {AuthTypes} from '../src/auth-types';
import {SQLAdminFetcherOptions} from '../src/sqladmin-fetcher';

t.test('Connector', async t => {
setupCredentials(t); // setup google-auth credentials mocks
Expand Down Expand Up @@ -625,3 +626,23 @@ t.test('Connector force refresh on socket connection error', async t => {
});
connector.close();
});

t.test('Connector, custom sqlAdminAPIEndpoint', async t => {
const expectedsqlAdminAPIEndpoint = 'https://sqladmin.mydomain.com';
let actualsqlAdminAPIEndpoint: string | undefined;
// mocks sql admin fetcher to check that the custom
// sqlAdminAPIEndpoint is correctly passed into it
const {Connector} = t.mock('../src/connector', {
'../src/sqladmin-fetcher': {
SQLAdminFetcher: class {
constructor({sqlAdminAPIEndpoint}: SQLAdminFetcherOptions) {
actualsqlAdminAPIEndpoint = sqlAdminAPIEndpoint;
}
},
},
});

new Connector({sqlAdminAPIEndpoint: expectedsqlAdminAPIEndpoint});

t.same(actualsqlAdminAPIEndpoint, expectedsqlAdminAPIEndpoint);
});
83 changes: 74 additions & 9 deletions test/sqladmin-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ const ephCertResponse = {

const mockRequest = (
instanceInfo: InstanceConnectionInfo,
overrides?: sqladmin_v1beta4.Schema$ConnectSettings
overrides?: sqladmin_v1beta4.Schema$ConnectSettings,
sqlAdminAPIEndpoint?: string
): void => {
const {projectId, regionId, instanceId} = instanceInfo;

nock('https://sqladmin.googleapis.com/sql/v1beta4/projects')
.get(`/${projectId}/instances/${instanceId}/connectSettings`)
nock(sqlAdminAPIEndpoint ?? 'https://sqladmin.googleapis.com')
.get(
`/sql/v1beta4/projects/${projectId}/instances/${instanceId}/connectSettings`
)
.reply(200, {
kind: 'sql#connectSettings',
serverCaCert: serverCaCertResponse(instanceId),
Expand Down Expand Up @@ -96,6 +99,35 @@ t.test('getInstanceMetadata', async t => {
);
});

t.test('getInstanceMetadata custom SQL Admin API endpoint', async t => {
setupCredentials(t);
const sqlAdminAPIEndpoint = 'https://sqladmin.mydomain.com';
const instanceConnectionInfo: InstanceConnectionInfo = {
projectId: 'my-project',
regionId: 'us-east1',
instanceId: 'my-instance',
};
mockRequest(instanceConnectionInfo, {}, sqlAdminAPIEndpoint);

const fetcher = new SQLAdminFetcher({sqlAdminAPIEndpoint});
const instanceMetadata = await fetcher.getInstanceMetadata(
instanceConnectionInfo
);
t.same(
instanceMetadata,
{
ipAddresses: {
public: '0.0.0.0',
},
serverCaCert: {
cert: '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----',
expirationTime: '2033-01-06T10:00:00.232Z',
},
},
'should return expected instance metadata object'
);
});

t.test('getInstanceMetadata private ip', async t => {
setupCredentials(t);
const instanceConnectionInfo: InstanceConnectionInfo = {
Expand Down Expand Up @@ -219,12 +251,15 @@ t.test('getInstanceMetadata invalid region', async t => {

const mockGenerateEphemeralCertRequest = (
instanceInfo: InstanceConnectionInfo,
overrides?: sqladmin_v1beta4.Schema$GenerateEphemeralCertResponse
overrides?: sqladmin_v1beta4.Schema$GenerateEphemeralCertResponse,
sqlAdminAPIEndpoint?: string
): void => {
const {projectId, instanceId} = instanceInfo;

nock('https://sqladmin.googleapis.com/sql/v1beta4/projects')
.post(`/${projectId}/instances/${instanceId}:generateEphemeralCert`)
nock(sqlAdminAPIEndpoint ?? 'https://sqladmin.googleapis.com')
.post(
`/sql/v1beta4/projects/${projectId}/instances/${instanceId}:generateEphemeralCert`
)
.reply(200, {
ephemeralCert: ephCertResponse,
// overrides any properties from the base mock
Expand Down Expand Up @@ -257,6 +292,36 @@ t.test('getEphemeralCertificate', async t => {
);
});

t.test('getEphemeralCertificate custom SQL Admin API endpoint', async t => {
setupCredentials(t);
const sqlAdminAPIEndpoint = 'https://sqladmin.mydomain.com';
const instanceConnectionInfo: InstanceConnectionInfo = {
projectId: 'my-project',
regionId: 'us-east1',
instanceId: 'my-instance',
};
mockGenerateEphemeralCertRequest(
instanceConnectionInfo,
{},
sqlAdminAPIEndpoint
);

const fetcher = new SQLAdminFetcher({sqlAdminAPIEndpoint});
const ephemeralCert = await fetcher.getEphemeralCertificate(
instanceConnectionInfo,
'key',
AuthTypes.PASSWORD
);
t.same(
ephemeralCert,
{
cert: CLIENT_CERT,
expirationTime: '3022-07-22T17:53:09.000Z',
},
'should return expected ssl cert'
);
});

t.test('getEphemeralCertificate no certificate', async t => {
setupCredentials(t);
const instanceConnectionInfo: InstanceConnectionInfo = {
Expand Down Expand Up @@ -320,12 +385,12 @@ t.test('getEphemeralCertificate no access token', async t => {
};
mockGenerateEphemeralCertRequest(instanceConnectionInfo);

const auth = new GoogleAuth({
const loginAuth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/sqlservice.login'],
});
auth.getAccessToken = async () => null;
loginAuth.getAccessToken = async () => null;

const fetcher = new SQLAdminFetcher(auth);
const fetcher = new SQLAdminFetcher({loginAuth});

t.rejects(
fetcher.getEphemeralCertificate(
Expand Down

0 comments on commit 290d741

Please sign in to comment.