@heroku/heroku-cli-util 10.0.0-beta.2 → 10.0.0
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.
- package/README.md +1 -1
- package/dist/errors/ambiguous.d.ts +16 -0
- package/dist/{types/errors → errors}/ambiguous.js +4 -0
- package/dist/errors/not-found.d.ts +20 -0
- package/dist/errors/not-found.js +17 -0
- package/dist/index.d.ts +13 -12
- package/dist/index.js +19 -11
- package/dist/types/pg/data-api.d.ts +35 -6
- package/dist/types/pg/tunnel.d.ts +11 -8
- package/dist/utils/addons/resolve.d.ts +11 -7
- package/dist/utils/addons/resolve.js +30 -21
- package/dist/utils/pg/bastion.d.ts +57 -25
- package/dist/utils/pg/bastion.js +146 -61
- package/dist/utils/pg/config-vars.d.ts +33 -7
- package/dist/utils/pg/config-vars.js +51 -17
- package/dist/utils/pg/databases.d.ts +74 -10
- package/dist/utils/pg/databases.js +172 -107
- package/dist/utils/pg/psql.d.ts +109 -21
- package/dist/utils/pg/psql.js +226 -139
- package/dist/ux/table.d.ts +1 -1
- package/package.json +7 -5
- package/dist/types/errors/ambiguous.d.ts +0 -15
- package/dist/types/errors/not-found.d.ts +0 -5
- package/dist/types/errors/not-found.js +0 -5
package/dist/utils/pg/bastion.js
CHANGED
|
@@ -1,17 +1,95 @@
|
|
|
1
|
-
import { ux } from '@oclif/core';
|
|
2
1
|
import debug from 'debug';
|
|
3
|
-
import
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
4
3
|
import { promisify } from 'node:util';
|
|
5
4
|
import * as createTunnel from 'tunnel-ssh';
|
|
6
5
|
import host from './host.js';
|
|
7
6
|
const pgDebug = debug('pg');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Determines whether the attachment belongs to an add-on installed onto a non-shield Private Space.
|
|
9
|
+
* If true, the bastion information needs to be fetched from the Data API.
|
|
10
|
+
* For add-ons installed onto a Shield Private Space, the bastion information should be fetched from config vars.
|
|
11
|
+
*
|
|
12
|
+
* @param attachment - The add-on attachment to check
|
|
13
|
+
* @returns True if the attachment belongs to a non-shield Private Space, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
export function bastionKeyPlan(attachment) {
|
|
16
|
+
return Boolean(/private/.test(attachment.addon.plan.name.split(':', 2)[1]));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetches the bastion configuration from the Data API (only relevant for add-ons installed onto a
|
|
20
|
+
* non-shield Private Space).
|
|
21
|
+
* For add-ons installed onto a Shield Private Space, the bastion information is stored in the config vars.
|
|
22
|
+
*
|
|
23
|
+
* @param heroku - The Heroku API client
|
|
24
|
+
* @param addon - The add-on information
|
|
25
|
+
* @returns Promise that resolves to the bastion configuration
|
|
26
|
+
*/
|
|
27
|
+
export async function fetchBastionConfig(heroku, addon) {
|
|
28
|
+
const { body: bastionConfig } = await heroku.get(`/client/v11/databases/${encodeURIComponent(addon.id)}/bastion`, { hostname: host() });
|
|
29
|
+
if (bastionConfig.host && bastionConfig.private_key) {
|
|
30
|
+
return {
|
|
31
|
+
bastionHost: bastionConfig.host,
|
|
32
|
+
bastionKey: bastionConfig.private_key,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the bastion configuration from the config vars for add-ons installed onto Shield
|
|
39
|
+
* Private Spaces.
|
|
40
|
+
*
|
|
41
|
+
* If there are bastions, extracts a host and a key from the config vars.
|
|
42
|
+
* If there are no bastions, returns an empty Object.
|
|
43
|
+
*
|
|
44
|
+
* We assert that _BASTIONS and _BASTION_KEY always exist together.
|
|
45
|
+
* If either is falsy, pretend neither exist.
|
|
46
|
+
*
|
|
47
|
+
* @param config - The configuration variables object
|
|
48
|
+
* @param baseName - The base name for the configuration variables
|
|
49
|
+
* @returns The bastion configuration object
|
|
50
|
+
*/
|
|
51
|
+
export const getBastionConfig = function (config, baseName) {
|
|
52
|
+
// <BASE_NAME>_BASTION_KEY contains the private key for the bastion.
|
|
53
|
+
const bastionKey = config[`${baseName}_BASTION_KEY`];
|
|
54
|
+
// <BASE_NAME>_BASTIONS contains a comma-separated list of hosts, select one at random.
|
|
55
|
+
const bastions = (config[`${baseName}_BASTIONS`] || '').split(',');
|
|
56
|
+
const bastionHost = bastions[Math.floor(Math.random() * bastions.length)];
|
|
57
|
+
if (bastionKey && bastionHost) {
|
|
58
|
+
return { bastionHost, bastionKey };
|
|
59
|
+
}
|
|
60
|
+
return {};
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Returns both the required environment variables to effect the psql command execution and the tunnel
|
|
64
|
+
* configuration according to the database connection details.
|
|
65
|
+
*
|
|
66
|
+
* @param connectionDetails - The database connection details with attachment information
|
|
67
|
+
* @returns Object containing database environment variables and tunnel configuration
|
|
68
|
+
*/
|
|
69
|
+
export function getPsqlConfigs(connectionDetails) {
|
|
70
|
+
const dbEnv = baseEnv(connectionDetails);
|
|
71
|
+
const dbTunnelConfig = tunnelConfig(connectionDetails);
|
|
72
|
+
// If a tunnel is required, we need to adjust the environment variables for psql to use the tunnel host and port.
|
|
73
|
+
if (connectionDetails.bastionKey) {
|
|
74
|
+
Object.assign(dbEnv, {
|
|
75
|
+
PGHOST: dbTunnelConfig.localHost,
|
|
76
|
+
PGPORT: dbTunnelConfig.localPort.toString(),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
dbEnv,
|
|
81
|
+
dbTunnelConfig,
|
|
14
82
|
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the base environment variables for the database connection based on the connection details
|
|
86
|
+
* only, without taking into account if a tunnel is required for connecting to the database through a bastion host.
|
|
87
|
+
*
|
|
88
|
+
* @param connectionDetails - The database connection details
|
|
89
|
+
* @returns The base environment variables for the database connection
|
|
90
|
+
*/
|
|
91
|
+
function baseEnv(connectionDetails) {
|
|
92
|
+
// Mapping of environment variables to ConnectionDetails properties
|
|
15
93
|
const mapping = {
|
|
16
94
|
PGDATABASE: 'database',
|
|
17
95
|
PGHOST: 'host',
|
|
@@ -19,52 +97,53 @@ export const env = (db) => {
|
|
|
19
97
|
PGPORT: 'port',
|
|
20
98
|
PGUSER: 'user',
|
|
21
99
|
};
|
|
100
|
+
const baseEnv = {
|
|
101
|
+
PGAPPNAME: 'psql non-interactive',
|
|
102
|
+
PGSSLMODE: (!connectionDetails.host || connectionDetails.host === 'localhost') ? 'prefer' : 'require',
|
|
103
|
+
...process.env,
|
|
104
|
+
};
|
|
22
105
|
for (const envVar of Object.keys(mapping)) {
|
|
23
|
-
const val =
|
|
106
|
+
const val = connectionDetails[mapping[envVar]];
|
|
24
107
|
if (val) {
|
|
25
108
|
baseEnv[envVar] = val;
|
|
26
109
|
}
|
|
27
110
|
}
|
|
28
111
|
return baseEnv;
|
|
29
|
-
};
|
|
30
|
-
export async function fetchConfig(heroku, db) {
|
|
31
|
-
return heroku.get(`/client/v11/databases/${encodeURIComponent(db.id)}/bastion`, {
|
|
32
|
-
hostname: host(),
|
|
33
|
-
});
|
|
34
112
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const bastions = (config[`${baseName}_BASTIONS`] || '').split(',');
|
|
45
|
-
const bastionHost = bastions[Math.floor(Math.random() * bastions.length)];
|
|
46
|
-
return (bastionKey && bastionHost) ? { bastionHost, bastionKey } : {};
|
|
47
|
-
};
|
|
48
|
-
export function getConfigs(db) {
|
|
49
|
-
const dbEnv = env(db);
|
|
50
|
-
const dbTunnelConfig = tunnelConfig(db);
|
|
51
|
-
if (db.bastionKey) {
|
|
52
|
-
Object.assign(dbEnv, {
|
|
53
|
-
PGHOST: dbTunnelConfig.localHost,
|
|
54
|
-
PGPORT: dbTunnelConfig.localPort,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates a tunnel configuration object based on the connection details.
|
|
115
|
+
*
|
|
116
|
+
* @param connectionDetails - The database connection details with attachment information
|
|
117
|
+
* @returns The tunnel configuration object
|
|
118
|
+
*/
|
|
119
|
+
function tunnelConfig(connectionDetails) {
|
|
120
|
+
const localHost = '127.0.0.1';
|
|
121
|
+
const localPort = Math.floor((Math.random() * (65_535 - 49_152)) + 49_152);
|
|
57
122
|
return {
|
|
58
|
-
|
|
59
|
-
|
|
123
|
+
dstHost: connectionDetails.host,
|
|
124
|
+
dstPort: Number.parseInt(connectionDetails.port, 10),
|
|
125
|
+
host: connectionDetails.bastionHost,
|
|
126
|
+
localHost,
|
|
127
|
+
localPort,
|
|
128
|
+
privateKey: connectionDetails.bastionKey,
|
|
129
|
+
username: 'bastion',
|
|
60
130
|
};
|
|
61
131
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Establishes an SSH tunnel to the database using the provided configuration.
|
|
134
|
+
*
|
|
135
|
+
* @param connectionDetails - The database connection details with attachment information
|
|
136
|
+
* @param dbTunnelConfig - The tunnel configuration object
|
|
137
|
+
* @param timeout - The timeout in milliseconds (default: 10000)
|
|
138
|
+
* @param createSSHTunnel - The function to create the SSH tunnel (default: promisified createTunnel.default)
|
|
139
|
+
* @returns Promise that resolves to the tunnel server or null if no bastion key is provided
|
|
140
|
+
* @throws Error if unable to establish the tunnel
|
|
141
|
+
*/
|
|
142
|
+
export async function sshTunnel(connectionDetails, dbTunnelConfig, timeout = 10_000, createSSHTunnel = promisify(createTunnel.default)) {
|
|
143
|
+
if (!connectionDetails.bastionKey) {
|
|
144
|
+
return;
|
|
65
145
|
}
|
|
66
146
|
const timeoutInstance = new Timeout(timeout, 'Establishing a secure tunnel timed out');
|
|
67
|
-
const createSSHTunnel = promisify(createTunnel.default);
|
|
68
147
|
try {
|
|
69
148
|
return await Promise.race([
|
|
70
149
|
timeoutInstance.promise(),
|
|
@@ -73,44 +152,50 @@ export async function sshTunnel(db, dbTunnelConfig, timeout = 10_000) {
|
|
|
73
152
|
}
|
|
74
153
|
catch (error) {
|
|
75
154
|
pgDebug(error);
|
|
76
|
-
|
|
155
|
+
throw new Error(`Unable to establish a secure tunnel to your database: ${error.message}.`);
|
|
77
156
|
}
|
|
78
157
|
finally {
|
|
79
158
|
timeoutInstance.cancel();
|
|
80
159
|
}
|
|
81
160
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
dstHost: db.host || undefined,
|
|
87
|
-
dstPort: (db.port && Number.parseInt(db.port, 10)) || undefined,
|
|
88
|
-
host: db.bastionHost,
|
|
89
|
-
localHost,
|
|
90
|
-
localPort,
|
|
91
|
-
privateKey: db.bastionKey,
|
|
92
|
-
username: 'bastion',
|
|
93
|
-
};
|
|
94
|
-
}
|
|
161
|
+
/**
|
|
162
|
+
* A timeout utility class that can be cancelled.
|
|
163
|
+
*/
|
|
95
164
|
class Timeout {
|
|
96
|
-
|
|
165
|
+
// eslint-disable-next-line unicorn/prefer-event-target
|
|
166
|
+
events = new EventEmitter();
|
|
97
167
|
message;
|
|
98
168
|
timeout;
|
|
99
169
|
timer;
|
|
170
|
+
/**
|
|
171
|
+
* Creates a new Timeout instance.
|
|
172
|
+
*
|
|
173
|
+
* @param timeout - The timeout duration in milliseconds
|
|
174
|
+
* @param message - The error message to display when timeout occurs
|
|
175
|
+
*/
|
|
100
176
|
constructor(timeout, message) {
|
|
101
177
|
this.timeout = timeout;
|
|
102
178
|
this.message = message;
|
|
103
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Cancels the timeout.
|
|
182
|
+
*
|
|
183
|
+
* @returns void
|
|
184
|
+
*/
|
|
104
185
|
cancel() {
|
|
105
186
|
this.events.emit('cancelled');
|
|
106
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns a promise that resolves when the timeout is cancelled or rejects when the timeout occurs.
|
|
190
|
+
*
|
|
191
|
+
* @returns Promise that resolves to void when cancelled or rejects with an error when timeout occurs
|
|
192
|
+
*/
|
|
107
193
|
async promise() {
|
|
108
|
-
this.timer = setTimeout(() =>
|
|
109
|
-
this.events.emit('error', new Error(this.message));
|
|
110
|
-
}, this.timeout);
|
|
194
|
+
this.timer = setTimeout(() => this.events.emit('timeout'), this.timeout);
|
|
111
195
|
try {
|
|
112
|
-
await new Promise(resolve => {
|
|
196
|
+
await new Promise((resolve, reject) => {
|
|
113
197
|
this.events.once('cancelled', () => resolve());
|
|
198
|
+
this.events.once('timeout', () => reject(new Error(this.message)));
|
|
114
199
|
});
|
|
115
200
|
}
|
|
116
201
|
finally {
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
import type { HTTP } from '@heroku/http-call';
|
|
1
2
|
import type { APIClient } from '@heroku-cli/command';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export declare
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import type { ExtendedAddonAttachment } from '../../types/pg/data-api.js';
|
|
4
|
+
/**
|
|
5
|
+
* Cache of app config vars.
|
|
6
|
+
*/
|
|
7
|
+
export declare const configVarsByAppIdCache: Map<string, Promise<HTTP<Record<string, string>>>>;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the app's config vars as a record of key-value pairs, either from the cache or from the API.
|
|
10
|
+
*
|
|
11
|
+
* @param heroku - The Heroku API client
|
|
12
|
+
* @param appId - The ID of the app to get config vars for
|
|
13
|
+
* @returns Promise resolving to a record of config var key-value pairs
|
|
14
|
+
*/
|
|
15
|
+
export declare function getConfig(heroku: APIClient, appId: string): Promise<Record<string, string>>;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the attachment's first config var name that has a `_URL` suffix, expected to be the name of the one
|
|
18
|
+
* that contains the database URL connection string.
|
|
19
|
+
*
|
|
20
|
+
* @param configVarNames - Array of config var names from the attachment
|
|
21
|
+
* @returns The first config var name ending with '_URL'
|
|
22
|
+
* @throws {Error} When no config var names end with '_URL'
|
|
23
|
+
*/
|
|
24
|
+
export declare function getConfigVarName(configVarNames: ExtendedAddonAttachment['config_vars']): string;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the config var name that contains the database URL connection string for the given
|
|
27
|
+
* attachment, based on the contents of the app's config vars.
|
|
28
|
+
*
|
|
29
|
+
* @param attachment - The addon attachment to get the config var name for
|
|
30
|
+
* @param config - Optional record of app config vars (defaults to empty object)
|
|
31
|
+
* @returns The config var name containing the database URL
|
|
32
|
+
* @throws {Error} When no config vars are found or when they don't contain a database URL
|
|
33
|
+
*/
|
|
34
|
+
export declare function getConfigVarNameFromAttachment(attachment: ExtendedAddonAttachment, config?: Record<string, string>): string;
|
|
@@ -1,28 +1,62 @@
|
|
|
1
1
|
import { color } from '@heroku-cli/color';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Cache of app config vars.
|
|
4
|
+
*/
|
|
5
|
+
export const configVarsByAppIdCache = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Returns the app's config vars as a record of key-value pairs, either from the cache or from the API.
|
|
8
|
+
*
|
|
9
|
+
* @param heroku - The Heroku API client
|
|
10
|
+
* @param appId - The ID of the app to get config vars for
|
|
11
|
+
* @returns Promise resolving to a record of config var key-value pairs
|
|
12
|
+
*/
|
|
13
|
+
export async function getConfig(heroku, appId) {
|
|
14
|
+
let promise = configVarsByAppIdCache.get(appId);
|
|
15
|
+
if (!promise) {
|
|
16
|
+
promise = heroku.get(`/apps/${appId}/config-vars`);
|
|
17
|
+
configVarsByAppIdCache.set(appId, promise);
|
|
8
18
|
}
|
|
9
|
-
const
|
|
10
|
-
return
|
|
19
|
+
const { body: config } = await promise;
|
|
20
|
+
return config;
|
|
11
21
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Returns the attachment's first config var name that has a `_URL` suffix, expected to be the name of the one
|
|
24
|
+
* that contains the database URL connection string.
|
|
25
|
+
*
|
|
26
|
+
* @param configVarNames - Array of config var names from the attachment
|
|
27
|
+
* @returns The first config var name ending with '_URL'
|
|
28
|
+
* @throws {Error} When no config var names end with '_URL'
|
|
29
|
+
*/
|
|
30
|
+
export function getConfigVarName(configVarNames) {
|
|
31
|
+
const urlConfigVarNames = configVarNames.filter(cv => (cv.endsWith('_URL')));
|
|
32
|
+
if (urlConfigVarNames.length === 0)
|
|
15
33
|
throw new Error('Database URL not found for this addon');
|
|
16
|
-
return
|
|
34
|
+
return urlConfigVarNames[0];
|
|
17
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the config var name that contains the database URL connection string for the given
|
|
38
|
+
* attachment, based on the contents of the app's config vars.
|
|
39
|
+
*
|
|
40
|
+
* @param attachment - The addon attachment to get the config var name for
|
|
41
|
+
* @param config - Optional record of app config vars (defaults to empty object)
|
|
42
|
+
* @returns The config var name containing the database URL
|
|
43
|
+
* @throws {Error} When no config vars are found or when they don't contain a database URL
|
|
44
|
+
*/
|
|
18
45
|
export function getConfigVarNameFromAttachment(attachment, config = {}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
46
|
+
// Handle the case where no attachment config var names remain after filtering out those that don't contain a
|
|
47
|
+
// database URL connection string in the app's config vars.
|
|
48
|
+
const connStringConfigVarNames = attachment.config_vars
|
|
49
|
+
.filter(cvn => config[cvn]?.startsWith('postgres://'));
|
|
50
|
+
if (connStringConfigVarNames.length === 0) {
|
|
51
|
+
throw new Error(`No config vars found for ${attachment.name}; perhaps they were removed as a side effect of`
|
|
52
|
+
+ ` ${color.cmd('heroku rollback')}? Use ${color.cmd('heroku addons:attach')} to create a new attachment and `
|
|
53
|
+
+ `then ${color.cmd('heroku addons:detach')} to remove the current attachment.`);
|
|
22
54
|
}
|
|
55
|
+
// Generate the default config var name and return it if it contains a database URL connection string.
|
|
23
56
|
const configVarName = `${attachment.name}_URL`;
|
|
24
|
-
if (
|
|
57
|
+
if (connStringConfigVarNames.includes(configVarName) && configVarName in config) {
|
|
25
58
|
return configVarName;
|
|
26
59
|
}
|
|
27
|
-
|
|
60
|
+
// Return the first config var name that has a `_URL` suffix. This might not be needed at all anymore.
|
|
61
|
+
return getConfigVarName(connStringConfigVarNames);
|
|
28
62
|
}
|
|
@@ -1,12 +1,76 @@
|
|
|
1
|
-
import type { AddOnAttachment } from '@heroku-cli/schema';
|
|
2
1
|
import { APIClient } from '@heroku-cli/command';
|
|
3
|
-
import type {
|
|
2
|
+
import type { ExtendedAddonAttachment } from '../../types/pg/data-api.js';
|
|
4
3
|
import type { ConnectionDetails, ConnectionDetailsWithAttachment } from '../../types/pg/tunnel.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
import { fetchBastionConfig } from './bastion.js';
|
|
5
|
+
import { getConfig } from './config-vars.js';
|
|
6
|
+
export default class DatabaseResolver {
|
|
7
|
+
private readonly heroku;
|
|
8
|
+
private readonly getConfigFn;
|
|
9
|
+
private readonly fetchBastionConfigFn;
|
|
10
|
+
private readonly addonAttachmentResolver;
|
|
11
|
+
private readonly attachmentHeaders;
|
|
12
|
+
constructor(heroku: APIClient, getConfigFn?: typeof getConfig, fetchBastionConfigFn?: typeof fetchBastionConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a database attachment based on the provided database identifier
|
|
15
|
+
* (attachment name, id, or config var name) and namespace (credential).
|
|
16
|
+
*
|
|
17
|
+
* @param appId - The ID of the app to get the attachment for
|
|
18
|
+
* @param attachmentId - The database identifier (defaults to 'DATABASE_URL')
|
|
19
|
+
* @param namespace - Optional namespace/credential for the attachment
|
|
20
|
+
* @returns Promise resolving to the database attachment
|
|
21
|
+
* @throws {Error} When no databases exist or when database identifier is unknown
|
|
22
|
+
* @throws {AmbiguousError} When multiple matching attachments are found
|
|
23
|
+
*/
|
|
24
|
+
getAttachment(appId: string, attachmentId?: string, namespace?: string): Promise<ExtendedAddonAttachment>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the connection details for a database attachment resolved through the identifiers passed as
|
|
27
|
+
* arguments: appId, attachmentId and namespace (credential).
|
|
28
|
+
*
|
|
29
|
+
* @param appId - The ID of the app containing the database
|
|
30
|
+
* @param attachmentId - Optional database identifier (defaults to 'DATABASE_URL')
|
|
31
|
+
* @param namespace - Optional namespace/credential for the attachment
|
|
32
|
+
* @returns Promise resolving to connection details with attachment information
|
|
33
|
+
*/
|
|
34
|
+
getDatabase(appId: string, attachmentId?: string, namespace?: string): Promise<ConnectionDetailsWithAttachment>;
|
|
35
|
+
/**
|
|
36
|
+
* Parses a PostgreSQL connection string (or a local database name) into a ConnectionDetails object.
|
|
37
|
+
*
|
|
38
|
+
* @param connStringOrDbName - PostgreSQL connection string or local database name
|
|
39
|
+
* @returns Connection details object with parsed connection information
|
|
40
|
+
*/
|
|
41
|
+
parsePostgresConnectionString(connStringOrDbName: string): ConnectionDetails;
|
|
42
|
+
/**
|
|
43
|
+
* Fetches all Heroku PostgreSQL add-on attachments for a given app.
|
|
44
|
+
*
|
|
45
|
+
* This is used internally by the `getAttachment` function to get all valid Heroku PostgreSQL add-on attachments
|
|
46
|
+
* to generate a list of possible valid attachments when the user passes a database name that doesn't match any
|
|
47
|
+
* attachments.
|
|
48
|
+
*
|
|
49
|
+
* @param appId - The ID of the app to get the attachments for
|
|
50
|
+
* @returns Promise resolving to array of PostgreSQL add-on attachments
|
|
51
|
+
*/
|
|
52
|
+
private allPostgresAttachments;
|
|
53
|
+
/**
|
|
54
|
+
* Returns the connection details for a database attachment according to the app config vars.
|
|
55
|
+
*
|
|
56
|
+
* @param attachment - The attachment to get the connection details for
|
|
57
|
+
* @param config - The record of app config vars with their values
|
|
58
|
+
* @returns Connection details with attachment information
|
|
59
|
+
*/
|
|
60
|
+
private getConnectionDetails;
|
|
61
|
+
/**
|
|
62
|
+
* Helper function that attempts to find a single addon attachment matching the given database identifier
|
|
63
|
+
* (attachment name, id, or config var name).
|
|
64
|
+
*
|
|
65
|
+
* This is used internally by the `getAttachment` function to handle the lookup of addon attachments.
|
|
66
|
+
* It returns either a single match, multiple matches (for ambiguous cases), or null if no matches are found.
|
|
67
|
+
*
|
|
68
|
+
* The AddonAttachmentResolver uses the Platform API add-on attachment resolver endpoint to get the attachment.
|
|
69
|
+
*
|
|
70
|
+
* @param appId - The ID of the app to search for attachments
|
|
71
|
+
* @param attachmentId - The database identifier to match
|
|
72
|
+
* @param namespace - Optional namespace/credential filter
|
|
73
|
+
* @returns Promise resolving to either a single match, multiple matches with error, or no matches with error
|
|
74
|
+
*/
|
|
75
|
+
private matchesHelper;
|
|
76
|
+
}
|