Skip to content

Commit 28905c9

Browse files
fix: only add PSC ipType if PSC is enabled (#388)
With CAS-based instances being supported soon and also using dnsName, we can no longer rely on the presence of dnsName in the connectSettings API as identifying that PSC is enabled on an instance. Instead we should check pscEnabled from the response as source of truth. Check if pscEnabled is True before setting PSC ip type. Moving parseIPAddresses into private method on SQLAdminFetcher
1 parent 680c2e9 commit 28905c9

File tree

4 files changed

+59
-184
lines changed

4 files changed

+59
-184
lines changed

src/ip-addresses.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import {sqladmin_v1beta4} from '@googleapis/sqladmin';
1615
import {CloudSQLConnectorError} from './errors';
1716

1817
export enum IpAddressTypes {
@@ -57,36 +56,6 @@ const getPSCIpAddress = (ipAddresses: IpAddresses) => {
5756
return ipAddresses.psc;
5857
};
5958

60-
export function parseIpAddresses(
61-
ipResponse: sqladmin_v1beta4.Schema$IpMapping[] | undefined,
62-
dnsName: string | null | undefined
63-
): IpAddresses {
64-
const ipAddresses: IpAddresses = {};
65-
if (ipResponse) {
66-
for (const ip of ipResponse) {
67-
if (ip.type === 'PRIMARY' && ip.ipAddress) {
68-
ipAddresses.public = ip.ipAddress;
69-
}
70-
if (ip.type === 'PRIVATE' && ip.ipAddress) {
71-
ipAddresses.private = ip.ipAddress;
72-
}
73-
}
74-
}
75-
76-
if (dnsName) {
77-
ipAddresses.psc = dnsName;
78-
}
79-
80-
if (!ipAddresses.public && !ipAddresses.private && !ipAddresses.psc) {
81-
throw new CloudSQLConnectorError({
82-
message: 'Cannot connect to instance, it has no supported IP addresses',
83-
code: 'ENOSQLADMINIPADDRESS',
84-
});
85-
}
86-
87-
return ipAddresses;
88-
}
89-
9059
export function selectIpAddress(
9160
ipAddresses: IpAddresses,
9261
type: IpAddressTypes | unknown

src/sqladmin-fetcher.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const {Sqladmin} = sqladmin_v1beta4;
1919
import {InstanceConnectionInfo} from './instance-connection-info';
2020
import {SslCert} from './ssl-cert';
2121
import {parseCert} from './crypto';
22-
import {IpAddresses, parseIpAddresses} from './ip-addresses';
22+
import {IpAddresses} from './ip-addresses';
2323
import {CloudSQLConnectorError} from './errors';
2424
import {getNearestExpiration} from './time';
2525
import {AuthTypes} from './auth-types';
@@ -124,6 +124,40 @@ export class SQLAdminFetcher {
124124
}
125125
}
126126

127+
private parseIpAddresses(
128+
ipResponse: sqladmin_v1beta4.Schema$IpMapping[] | undefined,
129+
dnsName: string | null | undefined,
130+
pscEnabled: boolean | null | undefined
131+
): IpAddresses {
132+
const ipAddresses: IpAddresses = {};
133+
if (ipResponse) {
134+
for (const ip of ipResponse) {
135+
if (ip.type === 'PRIMARY' && ip.ipAddress) {
136+
ipAddresses.public = ip.ipAddress;
137+
}
138+
if (ip.type === 'PRIVATE' && ip.ipAddress) {
139+
ipAddresses.private = ip.ipAddress;
140+
}
141+
}
142+
}
143+
144+
// Resolve dnsName into IP address for PSC enabled instances.
145+
// Note that we have to check for PSC enablement because CAS instances
146+
// also set the dnsName field.
147+
if (dnsName && pscEnabled) {
148+
ipAddresses.psc = dnsName;
149+
}
150+
151+
if (!ipAddresses.public && !ipAddresses.private && !ipAddresses.psc) {
152+
throw new CloudSQLConnectorError({
153+
message: 'Cannot connect to instance, it has no supported IP addresses',
154+
code: 'ENOSQLADMINIPADDRESS',
155+
});
156+
}
157+
158+
return ipAddresses;
159+
}
160+
127161
async getInstanceMetadata({
128162
projectId,
129163
regionId,
@@ -146,9 +180,10 @@ export class SQLAdminFetcher {
146180
});
147181
}
148182

149-
const ipAddresses = parseIpAddresses(
183+
const ipAddresses = this.parseIpAddresses(
150184
res.data.ipAddresses,
151-
res.data.dnsName
185+
res.data.dnsName,
186+
res.data.pscEnabled
152187
);
153188

154189
const {serverCaCert} = res.data;

test/ip-addresses.ts

Lines changed: 1 addition & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -13,138 +13,7 @@
1313
// limitations under the License.
1414

1515
import t from 'tap';
16-
import {
17-
IpAddressTypes,
18-
parseIpAddresses,
19-
selectIpAddress,
20-
} from '../src/ip-addresses';
21-
22-
t.throws(
23-
() => parseIpAddresses(undefined, undefined),
24-
{code: 'ENOSQLADMINIPADDRESS'},
25-
'should throw if no argument'
26-
);
27-
28-
t.throws(
29-
() => parseIpAddresses([], undefined),
30-
{code: 'ENOSQLADMINIPADDRESS'},
31-
'should throw if no ip is found'
32-
);
33-
34-
t.same(
35-
parseIpAddresses(
36-
[
37-
{
38-
ipAddress: '0.0.0.0',
39-
type: 'PRIMARY',
40-
},
41-
],
42-
undefined
43-
),
44-
{
45-
public: '0.0.0.0',
46-
},
47-
'should return a public ip successfully'
48-
);
49-
50-
t.same(
51-
parseIpAddresses(
52-
[
53-
{
54-
ipAddress: '0.0.0.0',
55-
type: 'PRIMARY',
56-
},
57-
{
58-
ipAddress: '0.0.0.1',
59-
type: 'OUTGOING',
60-
},
61-
],
62-
undefined
63-
),
64-
{
65-
public: '0.0.0.0',
66-
},
67-
'should return a public ip from a list'
68-
);
69-
70-
t.same(
71-
parseIpAddresses(
72-
[
73-
{
74-
ipAddress: '0.0.0.2',
75-
type: 'PRIVATE',
76-
},
77-
{
78-
ipAddress: '0.0.0.1',
79-
type: 'OUTGOING',
80-
},
81-
],
82-
undefined
83-
),
84-
{
85-
private: '0.0.0.2',
86-
},
87-
'should return a private ip from a list'
88-
);
89-
90-
t.same(
91-
parseIpAddresses(
92-
[
93-
{
94-
ipAddress: '0.0.0.0',
95-
type: 'PRIMARY',
96-
},
97-
{
98-
ipAddress: '0.0.0.2',
99-
type: 'PRIVATE',
100-
},
101-
{
102-
ipAddress: '0.0.0.1',
103-
type: 'OUTGOING',
104-
},
105-
],
106-
undefined
107-
),
108-
{
109-
private: '0.0.0.2',
110-
public: '0.0.0.0',
111-
},
112-
'should return a both public and private ips if available'
113-
);
114-
115-
t.same(
116-
parseIpAddresses([], 'abcde.12345.us-central1.sql.goog'),
117-
{
118-
psc: 'abcde.12345.us-central1.sql.goog',
119-
},
120-
'should return a psc ip from a defined dnsName'
121-
);
122-
123-
t.same(
124-
parseIpAddresses(
125-
[
126-
{
127-
ipAddress: '0.0.0.0',
128-
type: 'PRIMARY',
129-
},
130-
{
131-
ipAddress: '0.0.0.2',
132-
type: 'PRIVATE',
133-
},
134-
{
135-
ipAddress: '0.0.0.1',
136-
type: 'OUTGOING',
137-
},
138-
],
139-
'abcde.12345.us-central1.sql.goog'
140-
),
141-
{
142-
private: '0.0.0.2',
143-
public: '0.0.0.0',
144-
psc: 'abcde.12345.us-central1.sql.goog',
145-
},
146-
'should return a public, private and psc ips if available'
147-
);
16+
import {IpAddressTypes, selectIpAddress} from '../src/ip-addresses';
14817

14918
t.throws(
15019
() => selectIpAddress({}, IpAddressTypes.PUBLIC),

test/sqladmin-fetcher.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface IpAddress {
2828
interface SQLAdminClientGetResponse {
2929
dnsName?: string;
3030
ipAddresses?: IpAddress[];
31+
pscEnabled?: boolean;
3132
region?: string;
3233
serverCaCert?: {} | ReturnType<typeof serverCaCertResponse>;
3334
}
@@ -97,16 +98,22 @@ const mockSQLAdminGetInstanceMetadata = (
9798

9899
sqlAdminClient.get = () => ({
99100
data: {
101+
dnsName: 'abcde.12345.us-central1.sql.goog',
100102
ipAddresses: [
101103
{
102104
type: 'PRIMARY',
103105
ipAddress: '0.0.0.0',
104106
},
107+
{
108+
type: 'PRIVATE',
109+
ipAddress: '10.0.0.1',
110+
},
105111
{
106112
type: 'OUTGOING',
107113
ipAddress: '0.0.0.1',
108114
},
109115
],
116+
pscEnabled: true,
110117
region: regionId,
111118
serverCaCert: serverCaCertResponse(instanceId),
112119
...overrides,
@@ -175,6 +182,8 @@ t.test('getInstanceMetadata', async t => {
175182
{
176183
ipAddresses: {
177184
public: '0.0.0.0',
185+
private: '10.0.0.1',
186+
psc: 'abcde.12345.us-central1.sql.goog',
178187
},
179188
serverCaCert: {
180189
cert: '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----',
@@ -195,33 +204,26 @@ t.test('getInstanceMetadata custom SQL Admin API endpoint', async t => {
195204
);
196205
});
197206

198-
t.test('getInstanceMetadata private ip', async t => {
207+
// dnsName without PSC enabled should result in no PSC ip type
208+
t.test('getInstanceMetadata no ip', async t => {
199209
const instanceConnectionInfo: InstanceConnectionInfo = {
200-
projectId: 'private-ip-project',
210+
projectId: 'no-ip-project',
201211
regionId: 'us-east1',
202-
instanceId: 'private-ip-instance',
212+
instanceId: 'no-ip-instance',
203213
};
204214
mockSQLAdminGetInstanceMetadata(instanceConnectionInfo, {
205-
ipAddresses: [
206-
{
207-
type: 'PRIVATE',
208-
ipAddress: '0.0.0.0',
209-
},
210-
],
215+
dnsName: 'abcde.12345.us-central1.sql.goog',
216+
ipAddresses: [],
217+
pscEnabled: false,
211218
});
212219

213220
const fetcher = new SQLAdminFetcher();
214-
const instanceMetadata = await fetcher.getInstanceMetadata(
215-
instanceConnectionInfo
216-
);
217-
t.match(
218-
instanceMetadata,
221+
t.rejects(
222+
fetcher.getInstanceMetadata(instanceConnectionInfo),
219223
{
220-
ipAddresses: {
221-
private: '0.0.0.0',
222-
},
224+
code: 'ENOSQLADMINIPADDRESS',
223225
},
224-
'should return expected instance metadata containing private ip'
226+
'should throw no ip type found'
225227
);
226228
});
227229

0 commit comments

Comments
 (0)