@axinom/mosaic-cli 0.62.0-rc.1 → 0.62.0-rc.11

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.
Files changed (21) hide show
  1. package/dist/esm/commands/hosting/database/database-commands.d.ts +2 -0
  2. package/dist/esm/commands/hosting/database/database-commands.js +13 -0
  3. package/dist/esm/commands/hosting/database/database-commands.js.map +1 -0
  4. package/dist/esm/commands/hosting/database/proxy/database-proxy-command.d.ts +3 -0
  5. package/dist/esm/commands/hosting/database/proxy/database-proxy-command.js +60 -0
  6. package/dist/esm/commands/hosting/database/proxy/database-proxy-command.js.map +1 -0
  7. package/dist/esm/commands/hosting/database/proxy/database-proxy-options.d.ts +13 -0
  8. package/dist/esm/commands/hosting/database/proxy/database-proxy-options.js +2 -0
  9. package/dist/esm/commands/hosting/database/proxy/database-proxy-options.js.map +1 -0
  10. package/dist/esm/commands/hosting/database/proxy/database-proxy.d.ts +27 -0
  11. package/dist/esm/commands/hosting/database/proxy/database-proxy.js +382 -0
  12. package/dist/esm/commands/hosting/database/proxy/database-proxy.js.map +1 -0
  13. package/dist/esm/commands/hosting/index.js +2 -0
  14. package/dist/esm/commands/hosting/index.js.map +1 -1
  15. package/package.json +7 -5
  16. package/src/commands/hosting/database/database-commands.ts +21 -0
  17. package/src/commands/hosting/database/proxy/database-proxy-command.ts +78 -0
  18. package/src/commands/hosting/database/proxy/database-proxy-options.ts +13 -0
  19. package/src/commands/hosting/database/proxy/database-proxy.spec.ts +178 -0
  20. package/src/commands/hosting/database/proxy/database-proxy.ts +548 -0
  21. package/src/commands/hosting/index.ts +2 -0
@@ -0,0 +1,2 @@
1
+ import type { CommandModule } from 'yargs';
2
+ export declare const databaseCommands: CommandModule<unknown, unknown>;
@@ -0,0 +1,13 @@
1
+ import { databaseProxy } from './proxy/database-proxy-command.js';
2
+ export const databaseCommands = {
3
+ command: 'database',
4
+ aliases: ['db'],
5
+ describe: 'Database access related actions',
6
+ builder: (yargs) => yargs
7
+ .command('proxy', 'Open a database access session and start a local database proxy.', databaseProxy)
8
+ .demandCommand(),
9
+ handler: () => {
10
+ console.log('Please pick an action related to the hosting database command.');
11
+ },
12
+ };
13
+ //# sourceMappingURL=database-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-commands.js","sourceRoot":"","sources":["../../../../../src/commands/hosting/database/database-commands.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,MAAM,CAAC,MAAM,gBAAgB,GAAoC;IAC/D,OAAO,EAAE,UAAU;IACnB,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,QAAQ,EAAE,iCAAiC;IAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,KAAK;SACF,OAAO,CACN,OAAO,EACP,kEAAkE,EAClE,aAAa,CACd;SACA,aAAa,EAAE;IACpB,OAAO,EAAE,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CACT,gEAAgE,CACjE,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import type { DatabaseProxyOptions } from './database-proxy-options.js';
3
+ export declare const databaseProxy: CommandModule<unknown, DatabaseProxyOptions>;
@@ -0,0 +1,60 @@
1
+ import chalk from 'chalk';
2
+ import { DEFAULT_LOCAL_PROXY_PORT, proxyDatabase, validateDatabaseProxyArgs, } from './database-proxy.js';
3
+ const { red, yellow } = chalk;
4
+ export const databaseProxy = {
5
+ builder: (yargs) => {
6
+ return yargs
7
+ .option('serviceId', {
8
+ describe: 'Service ID to open a database access session for',
9
+ alias: 'i',
10
+ string: true,
11
+ })
12
+ .option('ttlMinutes', {
13
+ describe: 'How long the session should last. A request, not a guarantee: a shorter lifetime is granted when the service allows less. Defaults to whatever the service allows.',
14
+ number: true,
15
+ })
16
+ .option('localProxyPort', {
17
+ describe: 'Local loopback port the database client connects to. Not 5432, which a local PostgreSQL usually holds. Deliberately not named `port`: a solution sets PORT for its own service, and that would override this.',
18
+ number: true,
19
+ default: DEFAULT_LOCAL_PROXY_PORT,
20
+ })
21
+ .option('json', {
22
+ describe: 'Emit the connection details as one JSON object on stdout, status messages on stderr. Contains the password, so it is as sensitive as the normal output.',
23
+ boolean: true,
24
+ })
25
+ .option('mosaicHostingClientId', {
26
+ describe: "The service account client id.\nRequired permissions:\nHOSTING SERVICE -> 'Database Access: Write', 'Database Access Sessions: View', 'Service Definitions: View'",
27
+ alias: 'c',
28
+ string: true,
29
+ })
30
+ .option('mosaicHostingClientSecret', {
31
+ describe: 'Service Account Client Secret to authenticate',
32
+ alias: 's',
33
+ string: true,
34
+ })
35
+ .option('idServiceAuthBaseURL', {
36
+ describe: 'ID Service Authentication Endpoint Base URL',
37
+ alias: 'a',
38
+ string: true,
39
+ })
40
+ .option('hostingServiceBaseURL', {
41
+ describe: 'Hosting Service Base URL',
42
+ alias: 'h',
43
+ string: true,
44
+ })
45
+ .example('$0 hosting database proxy -i channel-service', 'Open a session and listen on 127.0.0.1:5433')
46
+ .example('$0 hosting database proxy -i channel-service --ttl-minutes 30', 'Open a session for half an hour');
47
+ },
48
+ handler: async (args) => {
49
+ const [validatedArgs, errorMessages] = validateDatabaseProxyArgs(args);
50
+ if (errorMessages.length > 0) {
51
+ process.stderr.write(red('Some required arguments are missing.\n'));
52
+ process.stderr.write(yellow(errorMessages.join('\n') + '\n'));
53
+ process.exit(1);
54
+ }
55
+ else {
56
+ await proxyDatabase(validatedArgs);
57
+ }
58
+ },
59
+ };
60
+ //# sourceMappingURL=database-proxy-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-proxy-command.js","sourceRoot":"","sources":["../../../../../../src/commands/hosting/database/proxy/database-proxy-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EACL,wBAAwB,EACxB,aAAa,EACb,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;AAE9B,MAAM,CAAC,MAAM,aAAa,GAAiD;IACzE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjB,OAAO,KAAK;aACT,MAAM,CAAC,WAAW,EAAE;YACnB,QAAQ,EAAE,kDAAkD;YAC5D,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,IAAI;SACb,CAAC;aACD,MAAM,CAAC,YAAY,EAAE;YACpB,QAAQ,EACN,oKAAoK;YACtK,MAAM,EAAE,IAAI;SACb,CAAC;aACD,MAAM,CAAC,gBAAgB,EAAE;YACxB,QAAQ,EACN,+MAA+M;YACjN,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,wBAAwB;SAClC,CAAC;aACD,MAAM,CAAC,MAAM,EAAE;YACd,QAAQ,EACN,yJAAyJ;YAC3J,OAAO,EAAE,IAAI;SACd,CAAC;aACD,MAAM,CAAC,uBAAuB,EAAE;YAC/B,QAAQ,EACN,mKAAmK;YACrK,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,IAAI;SACb,CAAC;aACD,MAAM,CAAC,2BAA2B,EAAE;YACnC,QAAQ,EAAE,+CAA+C;YACzD,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,IAAI;SACb,CAAC;aACD,MAAM,CAAC,sBAAsB,EAAE;YAC9B,QAAQ,EAAE,6CAA6C;YACvD,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,IAAI;SACb,CAAC;aACD,MAAM,CAAC,uBAAuB,EAAE;YAC/B,QAAQ,EAAE,0BAA0B;YACpC,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,IAAI;SACb,CAAC;aACD,OAAO,CACN,8CAA8C,EAC9C,6CAA6C,CAC9C;aACA,OAAO,CACN,+DAA+D,EAC/D,iCAAiC,CAClC,CAAC;IACN,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC;QACvE,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC,CAAC;YACpE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,MAAM,aAAa,CACjB,aAA6D,CAC9D,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Options for proxying a direct database connection to a customizable service.
3
+ */
4
+ export interface DatabaseProxyOptions {
5
+ serviceId?: string;
6
+ ttlMinutes?: number;
7
+ localProxyPort?: number;
8
+ json?: boolean;
9
+ mosaicHostingClientId?: string;
10
+ mosaicHostingClientSecret?: string;
11
+ idServiceAuthBaseURL?: string;
12
+ hostingServiceBaseURL?: string;
13
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=database-proxy-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-proxy-options.js","sourceRoot":"","sources":["../../../../../../src/commands/hosting/database/proxy/database-proxy-options.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import type { DatabaseProxyOptions } from './database-proxy-options.js';
2
+ export declare const DEFAULT_LOCAL_PROXY_PORT = 5433;
3
+ /** Declared here rather than imported: the Hosting schema types are private. */
4
+ export interface DatabaseAccessSession {
5
+ databaseAccessSessionId: string;
6
+ username: string;
7
+ password: string;
8
+ databaseName: string;
9
+ expiresAt: string;
10
+ databaseProxyUrl: string;
11
+ }
12
+ export declare const minutesUntil: (expiresAt: string, now?: number) => number;
13
+ /**
14
+ * Hosting may grant less than was asked for. The minute of tolerance absorbs
15
+ * the round trip.
16
+ */
17
+ export declare const formatClampedTtlWarning: (requestedMinutes: number | undefined, expiresAt: string, now?: number) => string | undefined;
18
+ export declare const WARN_BEFORE_EXPIRY_MS: number;
19
+ /** Undefined when the session is too short for the warning to precede it. */
20
+ export declare const toExpiryWarningDelay: (expiresAt: string, now?: number) => number | undefined;
21
+ export declare const toConnectionJson: (session: DatabaseAccessSession, port: number) => Record<string, string | number>;
22
+ export declare const formatConnectionDetails: (session: DatabaseAccessSession, port: number, now?: number) => string;
23
+ export declare const proxyDatabase: (args: Required<Pick<DatabaseProxyOptions, "serviceId">> & DatabaseProxyOptions) => Promise<void>;
24
+ /**
25
+ * Validate arguments for proxying a direct database connection.
26
+ */
27
+ export declare const validateDatabaseProxyArgs: (args: DatabaseProxyOptions) => [DatabaseProxyOptions, string[]];
@@ -0,0 +1,382 @@
1
+ import { getServiceAccountToken } from '@axinom/mosaic-id-link-be';
2
+ import { isNullOrWhitespace } from '@axinom/mosaic-service-common';
3
+ import axios, { AxiosError } from 'axios';
4
+ import chalk from 'chalk';
5
+ import net from 'net';
6
+ import ora from 'ora';
7
+ import WebSocket, { createWebSocketStream } from 'ws';
8
+ import { exitCode } from '../../../../common/index.js';
9
+ const { red, yellow, green, dim, bold } = chalk;
10
+ export const DEFAULT_LOCAL_PROXY_PORT = 5433;
11
+ /** Only loopback is ever bound: the credential is not meant to leave the machine. */
12
+ const LOOPBACK = '127.0.0.1';
13
+ const OPEN_SESSION = `mutation OpenDatabaseAccessSession($input: OpenDatabaseAccessSessionInput!) {
14
+ openDatabaseAccessSession(input: $input) {
15
+ databaseAccessSessionId
16
+ username
17
+ password
18
+ databaseName
19
+ expiresAt
20
+ databaseProxyUrl
21
+ }
22
+ }`;
23
+ const CLOSE_SESSION = `mutation CloseDatabaseAccessSession($input: CloseDatabaseAccessSessionInput!) {
24
+ closeDatabaseAccessSession(input: $input) {
25
+ status
26
+ terminatedBackends
27
+ }
28
+ }`;
29
+ /** Connection details go to stdout and nothing else does, so `--json` pipes. */
30
+ const writeOut = (text) => {
31
+ process.stdout.write(text + '\n');
32
+ };
33
+ const writeStatus = (text) => {
34
+ process.stderr.write(text + '\n');
35
+ };
36
+ export const minutesUntil = (expiresAt, now = Date.now()) => Math.max(0, Math.floor((new Date(expiresAt).getTime() - now) / 60_000));
37
+ /**
38
+ * Hosting may grant less than was asked for. The minute of tolerance absorbs
39
+ * the round trip.
40
+ */
41
+ export const formatClampedTtlWarning = (requestedMinutes, expiresAt, now = Date.now()) => {
42
+ if (requestedMinutes === undefined) {
43
+ return undefined;
44
+ }
45
+ const granted = minutesUntil(expiresAt, now);
46
+ if (granted >= requestedMinutes - 1) {
47
+ return undefined;
48
+ }
49
+ return `Requested ${requestedMinutes} minutes, granted ${granted} - database access for this service expires then.`;
50
+ };
51
+ export const WARN_BEFORE_EXPIRY_MS = 5 * 60_000;
52
+ /** The byte a terminal delivers for Ctrl+C once it stops raising SIGINT. */
53
+ const ETX = 0x03;
54
+ /** Undefined when the session is too short for the warning to precede it. */
55
+ export const toExpiryWarningDelay = (expiresAt, now = Date.now()) => {
56
+ const remaining = new Date(expiresAt).getTime() - now;
57
+ return remaining > WARN_BEFORE_EXPIRY_MS
58
+ ? remaining - WARN_BEFORE_EXPIRY_MS
59
+ : undefined;
60
+ };
61
+ export const toConnectionJson = (session, port) => ({
62
+ databaseAccessSessionId: session.databaseAccessSessionId,
63
+ host: LOOPBACK,
64
+ port,
65
+ database: session.databaseName,
66
+ user: session.username,
67
+ password: session.password,
68
+ expiresAt: session.expiresAt,
69
+ });
70
+ export const formatConnectionDetails = (session, port, now = Date.now()) => {
71
+ const rows = [
72
+ ['Host', LOOPBACK],
73
+ ['Port', String(port)],
74
+ ['Database', session.databaseName],
75
+ ['User', session.username],
76
+ ['Password', session.password],
77
+ [
78
+ 'Expires',
79
+ `${session.expiresAt} (${minutesUntil(session.expiresAt, now)} minutes)`,
80
+ ],
81
+ ];
82
+ return rows
83
+ .map(([label, value]) => ` ${label.padEnd(9)} ${value}`)
84
+ .join('\n');
85
+ };
86
+ const getAxiosInstance = (hostingServiceBaseUrl, serviceAccountToken) => {
87
+ return axios.create({
88
+ baseURL: new URL(hostingServiceBaseUrl).toString(),
89
+ headers: {
90
+ Authorization: `Bearer ${serviceAccountToken}`,
91
+ 'content-type': 'application/json',
92
+ },
93
+ });
94
+ };
95
+ /** Built per call rather than once: a long session outlives its startup token. */
96
+ const callHosting = async (hostingServiceBaseUrl, serviceAccountToken, query, variables) => {
97
+ const axiosInstance = getAxiosInstance(hostingServiceBaseUrl, serviceAccountToken);
98
+ const payload = JSON.stringify({ query, variables });
99
+ const response = await axiosInstance.post('graphql', payload);
100
+ const errors = response.data.errors ?? [];
101
+ if (errors.length > 0) {
102
+ const { message, code } = errors[0];
103
+ throw new Error(code ? `${code}: ${message}` : message);
104
+ }
105
+ return response.data.data;
106
+ };
107
+ /** Caches the token and renews it a minute before it lapses. */
108
+ const createTokenProvider = (authEndpoint, clientId, clientSecret) => {
109
+ let token = '';
110
+ let renewAt = 0;
111
+ return async () => {
112
+ if (token === '' || Date.now() >= renewAt) {
113
+ const result = await getServiceAccountToken(authEndpoint, clientId, clientSecret);
114
+ token = result.accessToken;
115
+ renewAt = Date.now() + (result.expiresInSeconds - 60) * 1_000;
116
+ }
117
+ return token;
118
+ };
119
+ };
120
+ const describeError = (error) => {
121
+ const axiosError = error;
122
+ if (axiosError.response) {
123
+ return `${axiosError.message} (HTTP ${axiosError.response.status}: ${JSON.stringify(axiosError.response.data)})`;
124
+ }
125
+ return error instanceof Error ? error.message : String(error);
126
+ };
127
+ /**
128
+ * Pipes one accepted TCP connection through one WebSocket, never multiplexed.
129
+ * The bytes are the TLS session the client negotiated with Postgres directly.
130
+ */
131
+ const databaseProxyConnection = (client, session, getToken) => {
132
+ // Paused until the carrier is open, so nothing the client sends is dropped.
133
+ client.pause();
134
+ void (async () => {
135
+ // Everything up to the carrier existing is guarded: an escape here is an
136
+ // unhandled rejection, which kills the process before the session is
137
+ // closed and strands the database role until it expires.
138
+ let ws;
139
+ try {
140
+ const token = await getToken();
141
+ ws = new WebSocket(session.databaseProxyUrl, {
142
+ headers: {
143
+ authorization: `Bearer ${token}`,
144
+ 'x-mosaic-database-access-session': session.databaseAccessSessionId,
145
+ },
146
+ });
147
+ }
148
+ catch (error) {
149
+ writeStatus(red(`Could not reach the database proxy: ${describeError(error)}`));
150
+ client.destroy();
151
+ return;
152
+ }
153
+ // end(), not destroy(): destroy drops bytes still buffered for the client.
154
+ const stop = () => {
155
+ client.end();
156
+ if (ws.readyState === WebSocket.CONNECTING ||
157
+ ws.readyState === WebSocket.OPEN) {
158
+ ws.close();
159
+ }
160
+ };
161
+ // Piped rather than forwarded by hand: a slow reader on either end pauses
162
+ // the other instead of queueing without limit.
163
+ ws.on('open', () => {
164
+ const carrier = createWebSocketStream(ws);
165
+ carrier.on('error', stop);
166
+ client.pipe(carrier);
167
+ carrier.pipe(client);
168
+ client.resume();
169
+ });
170
+ ws.on('unexpected-response', (_request, response) => {
171
+ writeStatus(red(`The database proxy refused the connection (HTTP ${response.statusCode}).`));
172
+ client.destroy();
173
+ });
174
+ ws.on('error', (error) => {
175
+ writeStatus(red(`Database proxy error: ${error.message}`));
176
+ stop();
177
+ });
178
+ ws.on('close', stop);
179
+ client.on('error', stop);
180
+ client.on('close', () => {
181
+ if (ws.readyState === WebSocket.CONNECTING ||
182
+ ws.readyState === WebSocket.OPEN) {
183
+ ws.close();
184
+ }
185
+ });
186
+ })();
187
+ };
188
+ const listen = (server, port) => new Promise((resolve, reject) => {
189
+ server.once('error', reject);
190
+ server.listen(port, LOOPBACK, () => {
191
+ server.removeListener('error', reject);
192
+ resolve();
193
+ });
194
+ });
195
+ /** Hands the terminal back; the mode belongs to it, not to this process. */
196
+ const restoreStdinMode = () => {
197
+ if (process.stdin.isTTY && process.stdin.isRaw) {
198
+ process.stdin.setRawMode(false);
199
+ process.stdin.pause();
200
+ }
201
+ };
202
+ /**
203
+ * Calls `onShutdown` once, on Ctrl+C or a termination signal.
204
+ *
205
+ * Ctrl+C is read from stdin rather than taken as a signal: in raw mode the
206
+ * terminal stops raising one, which keeps it from also killing the `yarn` and
207
+ * `env-cmd` wrappers a solution runs this through. They would exit at once and
208
+ * hand the shell prompt back while the session was still closing.
209
+ */
210
+ const onShutdownRequested = (onShutdown) => {
211
+ let requested = false;
212
+ const request = () => {
213
+ if (requested) {
214
+ return;
215
+ }
216
+ requested = true;
217
+ onShutdown();
218
+ };
219
+ process.on('exit', restoreStdinMode);
220
+ // The baseline everywhere: `kill`, a shutting-down machine, Ctrl+Break, and
221
+ // any stdin that is not a terminal. SIGKILL cannot be caught; that falls to
222
+ // the session's expiry.
223
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK']) {
224
+ process.on(signal, request);
225
+ }
226
+ // On a terminal, take the key before it becomes a SIGINT that would kill the
227
+ // wrappers too. Raw mode suppresses that signal on every platform.
228
+ if (process.stdin.isTTY) {
229
+ process.stdin.setRawMode(true);
230
+ process.stdin.resume();
231
+ process.stdin.on('data', (chunk) => {
232
+ if (chunk.includes(ETX)) {
233
+ request();
234
+ }
235
+ });
236
+ }
237
+ };
238
+ const openSession = async (args, hostingServiceBaseUrl, getToken) => {
239
+ // Creating the role is a remote call that regularly takes half a minute.
240
+ const spinner = ora({
241
+ text: `Opening a database access session for [${args.serviceId}]...`,
242
+ // Off: it would put stdin in raw mode, which is this command's to manage.
243
+ discardStdin: false,
244
+ }).start();
245
+ try {
246
+ const data = await callHosting(hostingServiceBaseUrl, await getToken(), OPEN_SESSION, {
247
+ input: {
248
+ serviceId: args.serviceId,
249
+ // Left undefined when not asked for, so the service applies its own
250
+ // default rather than this one.
251
+ ttlMinutes: args.ttlMinutes,
252
+ },
253
+ });
254
+ spinner.stop();
255
+ return data.openDatabaseAccessSession;
256
+ }
257
+ catch (error) {
258
+ spinner.fail(`Could not open a session for service [${args.serviceId}].`);
259
+ writeStatus(red(describeError(error)));
260
+ process.exit(exitCode);
261
+ }
262
+ };
263
+ /** Closes the session and reports the outcome. Resolves to the exit code. */
264
+ const closeSession = async (hostingServiceBaseUrl, getToken, session) => {
265
+ try {
266
+ // Fresh token: the startup one may have lapsed, and a 401 strands the role.
267
+ const data = await callHosting(hostingServiceBaseUrl, await getToken(), CLOSE_SESSION, {
268
+ input: { databaseAccessSessionId: session.databaseAccessSessionId },
269
+ });
270
+ if (data.closeDatabaseAccessSession.status === 'CLOSED') {
271
+ writeStatus(green('Session closed and the database role dropped.'));
272
+ }
273
+ else {
274
+ // CLOSING: the role outlives the command by at most one reconciliation
275
+ // pass, so there is nothing useful to wait here for.
276
+ writeStatus(yellow('Local proxy closed. Dropping the database role is still pending and will be completed by the service.'));
277
+ }
278
+ return 0;
279
+ }
280
+ catch (error) {
281
+ writeStatus(yellow(`The session could not be closed and will expire at ${session.expiresAt}: ${describeError(error)}`));
282
+ return exitCode;
283
+ }
284
+ };
285
+ const printSession = (args, session, port) => {
286
+ const clampWarning = formatClampedTtlWarning(args.ttlMinutes, session.expiresAt);
287
+ if (clampWarning) {
288
+ writeStatus(yellow(clampWarning));
289
+ }
290
+ if (args.json === true) {
291
+ writeOut(JSON.stringify(toConnectionJson(session, port)));
292
+ return;
293
+ }
294
+ writeStatus(bold(`\nDatabase access to [${args.serviceId}] is open.\n`));
295
+ writeOut(formatConnectionDetails(session, port));
296
+ writeStatus(dim('\nThe password is shown once and cannot be retrieved again.'));
297
+ writeStatus(dim('Press Ctrl+C to close the session.'));
298
+ };
299
+ const warnBeforeExpiry = (session) => {
300
+ const delay = toExpiryWarningDelay(session.expiresAt);
301
+ if (delay === undefined) {
302
+ return;
303
+ }
304
+ setTimeout(() => {
305
+ writeStatus(yellow(`\nThis session ends at ${session.expiresAt}. Open connections will be dropped.`));
306
+ }, delay).unref();
307
+ };
308
+ export const proxyDatabase = async (args) => {
309
+ const port = args.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
310
+ const hostingServiceBaseUrl = args.hostingServiceBaseURL;
311
+ const getToken = createTokenProvider(args.idServiceAuthBaseURL, args.mosaicHostingClientId, args.mosaicHostingClientSecret);
312
+ const session = await openSession(args, hostingServiceBaseUrl, getToken);
313
+ const server = net.createServer((client) => databaseProxyConnection(client, session, getToken));
314
+ // Bound before printing: a bind error gives the session back.
315
+ try {
316
+ await listen(server, port);
317
+ }
318
+ catch (error) {
319
+ writeStatus(red(`Could not listen on ${LOOPBACK}:${port}.`));
320
+ writeStatus(red(describeError(error)));
321
+ await closeSession(hostingServiceBaseUrl, getToken, session);
322
+ process.exit(exitCode);
323
+ }
324
+ onShutdownRequested(() => {
325
+ writeStatus(dim('\nClosing the session...'));
326
+ server.close();
327
+ void closeSession(hostingServiceBaseUrl, getToken, session).then((code) => process.exit(code));
328
+ });
329
+ printSession(args, session, port);
330
+ warnBeforeExpiry(session);
331
+ };
332
+ /**
333
+ * Validate arguments for proxying a direct database connection.
334
+ */
335
+ export const validateDatabaseProxyArgs = (args) => {
336
+ const errorMessages = [];
337
+ const idServiceAuthBaseURL = args.idServiceAuthBaseURL ?? process.env.ID_SERVICE_AUTH_BASE_URL ?? '';
338
+ const mosaicHostingClientId = args.mosaicHostingClientId ?? process.env.MOSAIC_HOSTING_CLIENT_ID ?? '';
339
+ const mosaicHostingClientSecret = args.mosaicHostingClientSecret ??
340
+ process.env.MOSAIC_HOSTING_CLIENT_SECRET ??
341
+ '';
342
+ const hostingServiceBaseURL = args.hostingServiceBaseURL ?? process.env.HOSTING_SERVICE_BASE_URL ?? '';
343
+ // Run from a solution, the service's own .env names it.
344
+ const serviceId = args.serviceId ?? process.env.SERVICE_ID ?? '';
345
+ if (isNullOrWhitespace(idServiceAuthBaseURL)) {
346
+ errorMessages.push('[idServiceAuthBaseURL] is required.');
347
+ }
348
+ if (isNullOrWhitespace(mosaicHostingClientId)) {
349
+ errorMessages.push('[mosaicHostingClientId] is required.');
350
+ }
351
+ if (isNullOrWhitespace(mosaicHostingClientSecret)) {
352
+ errorMessages.push('[mosaicHostingClientSecret] is required.');
353
+ }
354
+ if (isNullOrWhitespace(hostingServiceBaseURL)) {
355
+ errorMessages.push('[hostingServiceBaseURL] is required.');
356
+ }
357
+ if (isNullOrWhitespace(serviceId)) {
358
+ errorMessages.push('[serviceId] is required.');
359
+ }
360
+ if (args.ttlMinutes !== undefined &&
361
+ (!Number.isInteger(args.ttlMinutes) || args.ttlMinutes < 1)) {
362
+ errorMessages.push('[ttlMinutes] must be a whole number of 1 or more.');
363
+ }
364
+ if (args.localProxyPort !== undefined &&
365
+ (!Number.isInteger(args.localProxyPort) ||
366
+ args.localProxyPort < 1 ||
367
+ args.localProxyPort > 65535)) {
368
+ errorMessages.push('[localProxyPort] must be a whole number between 1 and 65535.');
369
+ }
370
+ return [
371
+ {
372
+ ...args,
373
+ serviceId,
374
+ idServiceAuthBaseURL,
375
+ mosaicHostingClientId,
376
+ mosaicHostingClientSecret,
377
+ hostingServiceBaseURL,
378
+ },
379
+ errorMessages,
380
+ ];
381
+ };
382
+ //# sourceMappingURL=database-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-proxy.js","sourceRoot":"","sources":["../../../../../../src/commands/hosting/database/proxy/database-proxy.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { databaseCommands } from './database/database-commands.js';
1
2
  import { manifestCommands } from './manifest/manifest-commands.js';
2
3
  import { piletCommands } from './pilet/pilet-commands.js';
3
4
  import { serviceCommands } from './service/service-commands.js';
@@ -8,6 +9,7 @@ export const hosting = {
8
9
  .command('pilet', 'Pilet related actions', piletCommands)
9
10
  .command('manifest', 'Deployment Manifest related actions', manifestCommands)
10
11
  .command('service', 'Service Deployment related actions', serviceCommands)
12
+ .command(databaseCommands)
11
13
  .demandCommand(),
12
14
  handler: (_args) => {
13
15
  console.log('Please pick a sub command related to hosting.');
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/hosting/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,CAAC,MAAM,OAAO,GAAoC;IACtD,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,qCAAqC;IAC/C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,KAAK;SACF,OAAO,CAAC,OAAO,EAAE,uBAAuB,EAAE,aAAa,CAAC;SACxD,OAAO,CACN,UAAU,EACV,qCAAqC,EACrC,gBAAgB,CACjB;SACA,OAAO,CAAC,SAAS,EAAE,oCAAoC,EAAE,eAAe,CAAC;SACzE,aAAa,EAAE;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/commands/hosting/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,MAAM,CAAC,MAAM,OAAO,GAAoC;IACtD,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,qCAAqC;IAC/C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,KAAK;SACF,OAAO,CAAC,OAAO,EAAE,uBAAuB,EAAE,aAAa,CAAC;SACxD,OAAO,CACN,UAAU,EACV,qCAAqC,EACrC,gBAAgB,CACjB;SACA,OAAO,CAAC,SAAS,EAAE,oCAAoC,EAAE,eAAe,CAAC;SACzE,OAAO,CAAC,gBAAgB,CAAC;SACzB,aAAa,EAAE;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC/D,CAAC;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-cli",
3
- "version": "0.62.0-rc.1",
3
+ "version": "0.62.0-rc.11",
4
4
  "engines": {
5
5
  "node": ">=20.19.0"
6
6
  },
@@ -55,8 +55,8 @@
55
55
  "@asyncapi/diff": "^0.5.0",
56
56
  "@asyncapi/modelina": "^5.10.1",
57
57
  "@asyncapi/parser": "^3.6.0",
58
- "@axinom/mosaic-id-link-be": "^0.45.0-rc.1",
59
- "@axinom/mosaic-service-common": "^0.75.0-rc.1",
58
+ "@axinom/mosaic-id-link-be": "^0.45.0-rc.11",
59
+ "@axinom/mosaic-service-common": "^0.75.0-rc.11",
60
60
  "@graphql-inspector/core": "3.1.2",
61
61
  "@inquirer/confirm": "^6.1.1",
62
62
  "@inquirer/core": "^11.2.1",
@@ -73,7 +73,7 @@
73
73
  "envfile": "^6.17.0",
74
74
  "find-nearest-file": "^1.1.0",
75
75
  "form-data": "^4.0.0",
76
- "glob": "^13.0.6",
76
+ "glob": "^14.0.2",
77
77
  "graphile-utils": "^4.13.0",
78
78
  "graphql": "^15.4.0",
79
79
  "inquirer-autocomplete-standalone": "^0.8.1",
@@ -85,11 +85,13 @@
85
85
  "simple-git": "^4.0.1",
86
86
  "sql-formatter": "^4.0.2",
87
87
  "tar": "^7.5.20",
88
+ "ws": "^8.22.0",
88
89
  "yargs": "^18.1.0"
89
90
  },
90
91
  "devDependencies": {
91
92
  "@types/diff": "^5.0.0",
92
93
  "@types/node": "^22.0.0",
94
+ "@types/ws": "^8.18.2",
93
95
  "@types/yargs": "^17.0.0",
94
96
  "@vitest/ui": "^4.1.10",
95
97
  "jest-extended": "^7.0.0",
@@ -100,5 +102,5 @@
100
102
  "publishConfig": {
101
103
  "access": "public"
102
104
  },
103
- "gitHead": "1a74c2ab649dd0de07f736fee743dcfbf8659f45"
105
+ "gitHead": "0e97183c880cf509c5d48bca25268bdc2de7500b"
104
106
  }
@@ -0,0 +1,21 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import { databaseProxy } from './proxy/database-proxy-command.js';
3
+
4
+ export const databaseCommands: CommandModule<unknown, unknown> = {
5
+ command: 'database',
6
+ aliases: ['db'],
7
+ describe: 'Database access related actions',
8
+ builder: (yargs) =>
9
+ yargs
10
+ .command(
11
+ 'proxy',
12
+ 'Open a database access session and start a local database proxy.',
13
+ databaseProxy,
14
+ )
15
+ .demandCommand(),
16
+ handler: () => {
17
+ console.log(
18
+ 'Please pick an action related to the hosting database command.',
19
+ );
20
+ },
21
+ };