mongodb-mcp-server 0.1.0 → 0.1.2
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/.dockerignore +11 -0
- package/.github/CODEOWNERS +0 -2
- package/.github/ISSUE_TEMPLATE/bug_report.yml +8 -0
- package/.github/workflows/check-pr-title.yml +29 -0
- package/.github/workflows/{lint.yml → check.yml} +22 -1
- package/.github/workflows/code_health.yaml +0 -22
- package/.github/workflows/code_health_fork.yaml +7 -63
- package/.github/workflows/docker.yaml +57 -0
- package/.github/workflows/stale.yml +32 -0
- package/.smithery/Dockerfile +30 -0
- package/.smithery/smithery.yaml +63 -0
- package/.vscode/extensions.json +9 -0
- package/.vscode/settings.json +11 -0
- package/CONTRIBUTING.md +1 -1
- package/Dockerfile +10 -0
- package/README.md +173 -35
- package/dist/common/atlas/apiClient.js +151 -35
- package/dist/common/atlas/apiClient.js.map +1 -1
- package/dist/common/atlas/apiClientError.js +38 -5
- package/dist/common/atlas/apiClientError.js.map +1 -1
- package/dist/common/atlas/cluster.js +66 -0
- package/dist/common/atlas/cluster.js.map +1 -0
- package/dist/common/atlas/generatePassword.js +9 -0
- package/dist/common/atlas/generatePassword.js.map +1 -0
- package/dist/helpers/EJsonTransport.js +38 -0
- package/dist/helpers/EJsonTransport.js.map +1 -0
- package/dist/helpers/connectionOptions.js +10 -0
- package/dist/helpers/connectionOptions.js.map +1 -0
- package/dist/{packageInfo.js → helpers/packageInfo.js} +1 -1
- package/dist/helpers/packageInfo.js.map +1 -0
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/logger.js +7 -0
- package/dist/logger.js.map +1 -1
- package/dist/server.js +16 -12
- package/dist/server.js.map +1 -1
- package/dist/session.js +8 -3
- package/dist/session.js.map +1 -1
- package/dist/telemetry/constants.js +1 -3
- package/dist/telemetry/constants.js.map +1 -1
- package/dist/telemetry/eventCache.js.map +1 -1
- package/dist/telemetry/telemetry.js +126 -47
- package/dist/telemetry/telemetry.js.map +1 -1
- package/dist/tools/atlas/atlasTool.js +38 -0
- package/dist/tools/atlas/atlasTool.js.map +1 -1
- package/dist/tools/atlas/create/createDBUser.js +19 -2
- package/dist/tools/atlas/create/createDBUser.js.map +1 -1
- package/dist/tools/atlas/create/createProject.js +5 -1
- package/dist/tools/atlas/create/createProject.js.map +1 -1
- package/dist/tools/atlas/metadata/connectCluster.js +5 -22
- package/dist/tools/atlas/metadata/connectCluster.js.map +1 -1
- package/dist/tools/atlas/read/inspectCluster.js +4 -24
- package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
- package/dist/tools/atlas/read/listAlerts.js +41 -0
- package/dist/tools/atlas/read/listAlerts.js.map +1 -0
- package/dist/tools/atlas/read/listClusters.js +9 -18
- package/dist/tools/atlas/read/listClusters.js.map +1 -1
- package/dist/tools/atlas/read/listProjects.js +3 -1
- package/dist/tools/atlas/read/listProjects.js.map +1 -1
- package/dist/tools/atlas/tools.js +2 -0
- package/dist/tools/atlas/tools.js.map +1 -1
- package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -1
- package/dist/tools/mongodb/read/count.js +2 -2
- package/dist/tools/mongodb/read/count.js.map +1 -1
- package/dist/tools/mongodb/tools.js +2 -4
- package/dist/tools/mongodb/tools.js.map +1 -1
- package/dist/tools/tool.js +38 -6
- package/dist/tools/tool.js.map +1 -1
- package/eslint.config.js +2 -1
- package/{jest.config.ts → jest.config.cjs} +1 -1
- package/package.json +11 -9
- package/scripts/apply.ts +8 -5
- package/scripts/filter.ts +5 -0
- package/src/common/atlas/apiClient.ts +190 -38
- package/src/common/atlas/apiClientError.ts +58 -7
- package/src/common/atlas/cluster.ts +94 -0
- package/src/common/atlas/generatePassword.ts +10 -0
- package/src/common/atlas/openapi.d.ts +1876 -239
- package/src/helpers/EJsonTransport.ts +47 -0
- package/src/helpers/connectionOptions.ts +20 -0
- package/src/{packageInfo.ts → helpers/packageInfo.ts} +1 -1
- package/src/index.ts +27 -3
- package/src/logger.ts +8 -0
- package/src/server.ts +23 -15
- package/src/session.ts +8 -4
- package/src/telemetry/constants.ts +2 -3
- package/src/telemetry/eventCache.ts +1 -1
- package/src/telemetry/telemetry.ts +182 -64
- package/src/telemetry/types.ts +1 -1
- package/src/tools/atlas/atlasTool.ts +47 -1
- package/src/tools/atlas/create/createDBUser.ts +22 -2
- package/src/tools/atlas/create/createProject.ts +7 -1
- package/src/tools/atlas/metadata/connectCluster.ts +5 -27
- package/src/tools/atlas/read/inspectCluster.ts +4 -40
- package/src/tools/atlas/read/listAlerts.ts +45 -0
- package/src/tools/atlas/read/listClusters.ts +19 -36
- package/src/tools/atlas/read/listProjects.ts +4 -2
- package/src/tools/atlas/tools.ts +2 -0
- package/src/tools/mongodb/metadata/listDatabases.ts +0 -1
- package/src/tools/mongodb/read/count.ts +3 -2
- package/src/tools/mongodb/tools.ts +2 -4
- package/src/tools/tool.ts +45 -8
- package/src/types/mongodb-connection-string-url.d.ts +69 -0
- package/tests/integration/helpers.ts +41 -2
- package/tests/integration/tools/atlas/accessLists.test.ts +2 -2
- package/tests/integration/tools/atlas/alerts.test.ts +42 -0
- package/tests/integration/tools/atlas/atlasHelpers.ts +5 -3
- package/tests/integration/tools/atlas/clusters.test.ts +4 -4
- package/tests/integration/tools/atlas/dbUsers.test.ts +58 -33
- package/tests/integration/tools/atlas/orgs.test.ts +2 -2
- package/tests/integration/tools/atlas/projects.test.ts +3 -3
- package/tests/integration/tools/mongodb/create/createCollection.test.ts +2 -2
- package/tests/integration/tools/mongodb/create/createIndex.test.ts +2 -2
- package/tests/integration/tools/mongodb/create/insertMany.test.ts +1 -1
- package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +1 -1
- package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +2 -2
- package/tests/integration/tools/mongodb/metadata/connect.test.ts +2 -6
- package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +4 -4
- package/tests/integration/tools/mongodb/metadata/explain.test.ts +10 -10
- package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +1 -1
- package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +9 -5
- package/tests/integration/tools/mongodb/metadata/logs.test.ts +4 -4
- package/tests/integration/tools/mongodb/mongodbHelpers.ts +15 -24
- package/tests/integration/tools/mongodb/read/aggregate.test.ts +22 -7
- package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +5 -5
- package/tests/integration/tools/mongodb/read/count.test.ts +15 -10
- package/tests/integration/tools/mongodb/read/find.test.ts +32 -4
- package/tests/integration/tools/mongodb/update/renameCollection.test.ts +4 -4
- package/tests/unit/EJsonTransport.test.ts +71 -0
- package/tests/unit/apiClient.test.ts +193 -0
- package/tests/unit/session.test.ts +65 -0
- package/tests/unit/telemetry.test.ts +222 -80
- package/tsconfig.build.json +2 -1
- package/dist/packageInfo.js.map +0 -1
- package/dist/telemetry/device-id.js +0 -20
- package/dist/telemetry/device-id.js.map +0 -1
- package/src/telemetry/device-id.ts +0 -21
|
@@ -4,7 +4,7 @@ import { AccessToken, ClientCredentials } from "simple-oauth2";
|
|
|
4
4
|
import { ApiClientError } from "./apiClientError.js";
|
|
5
5
|
import { paths, operations } from "./openapi.js";
|
|
6
6
|
import { CommonProperties, TelemetryEvent } from "../../telemetry/types.js";
|
|
7
|
-
import { packageInfo } from "../../packageInfo.js";
|
|
7
|
+
import { packageInfo } from "../../helpers/packageInfo.js";
|
|
8
8
|
|
|
9
9
|
const ATLAS_API_VERSION = "2025-03-12";
|
|
10
10
|
|
|
@@ -34,7 +34,9 @@ export class ApiClient {
|
|
|
34
34
|
|
|
35
35
|
private getAccessToken = async () => {
|
|
36
36
|
if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
|
|
37
|
-
this.accessToken = await this.oauth2Client.getToken({
|
|
37
|
+
this.accessToken = await this.oauth2Client.getToken({
|
|
38
|
+
agent: this.options.userAgent,
|
|
39
|
+
});
|
|
38
40
|
}
|
|
39
41
|
return this.accessToken?.token.access_token as string | undefined;
|
|
40
42
|
};
|
|
@@ -55,14 +57,6 @@ export class ApiClient {
|
|
|
55
57
|
},
|
|
56
58
|
};
|
|
57
59
|
|
|
58
|
-
private readonly errorMiddleware: Middleware = {
|
|
59
|
-
async onResponse({ response }) {
|
|
60
|
-
if (!response.ok) {
|
|
61
|
-
throw await ApiClientError.fromResponse(response);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
60
|
constructor(options: ApiClientOptions) {
|
|
67
61
|
this.options = {
|
|
68
62
|
...options,
|
|
@@ -91,13 +85,16 @@ export class ApiClient {
|
|
|
91
85
|
});
|
|
92
86
|
this.client.use(this.authMiddleware);
|
|
93
87
|
}
|
|
94
|
-
this.client.use(this.errorMiddleware);
|
|
95
88
|
}
|
|
96
89
|
|
|
97
90
|
public hasCredentials(): boolean {
|
|
98
91
|
return !!(this.oauth2Client && this.accessToken);
|
|
99
92
|
}
|
|
100
93
|
|
|
94
|
+
public async validateAccessToken(): Promise<void> {
|
|
95
|
+
await this.getAccessToken();
|
|
96
|
+
}
|
|
97
|
+
|
|
101
98
|
public async getIpInfo(): Promise<{
|
|
102
99
|
currentIpv4Address: string;
|
|
103
100
|
}> {
|
|
@@ -123,22 +120,59 @@ export class ApiClient {
|
|
|
123
120
|
}>;
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
|
|
127
|
-
|
|
123
|
+
public async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
|
|
124
|
+
if (!this.options.credentials) {
|
|
125
|
+
await this.sendUnauthEvents(events);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await this.sendAuthEvents(events);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error instanceof ApiClientError) {
|
|
133
|
+
if (error.response.status !== 401) {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// send unauth events if any of the following are true:
|
|
139
|
+
// 1: the token is not valid (not ApiClientError)
|
|
140
|
+
// 2: if the api responded with 401 (ApiClientError with status 401)
|
|
141
|
+
await this.sendUnauthEvents(events);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async sendAuthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
|
|
146
|
+
const accessToken = await this.getAccessToken();
|
|
147
|
+
if (!accessToken) {
|
|
148
|
+
throw new Error("No access token available");
|
|
149
|
+
}
|
|
150
|
+
const authUrl = new URL("api/private/v1.0/telemetry/events", this.options.baseUrl);
|
|
151
|
+
const response = await fetch(authUrl, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
Accept: "application/json",
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
"User-Agent": this.options.userAgent,
|
|
157
|
+
Authorization: `Bearer ${accessToken}`,
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify(events),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
throw await ApiClientError.fromResponse(response);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async sendUnauthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
|
|
128
168
|
const headers: Record<string, string> = {
|
|
129
169
|
Accept: "application/json",
|
|
130
170
|
"Content-Type": "application/json",
|
|
131
171
|
"User-Agent": this.options.userAgent,
|
|
132
172
|
};
|
|
133
173
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
endpoint = "api/private/v1.0/telemetry/events";
|
|
137
|
-
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const url = new URL(endpoint, this.options.baseUrl);
|
|
141
|
-
const response = await fetch(url, {
|
|
174
|
+
const unauthUrl = new URL("api/private/unauth/telemetry/events", this.options.baseUrl);
|
|
175
|
+
const response = await fetch(unauthUrl, {
|
|
142
176
|
method: "POST",
|
|
143
177
|
headers,
|
|
144
178
|
body: JSON.stringify(events),
|
|
@@ -151,83 +185,201 @@ export class ApiClient {
|
|
|
151
185
|
|
|
152
186
|
// DO NOT EDIT. This is auto-generated code.
|
|
153
187
|
async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
|
|
154
|
-
const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
|
|
188
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/clusters", options);
|
|
189
|
+
if (error) {
|
|
190
|
+
throw ApiClientError.fromError(response, error);
|
|
191
|
+
}
|
|
155
192
|
return data;
|
|
156
193
|
}
|
|
157
194
|
|
|
158
195
|
async listProjects(options?: FetchOptions<operations["listProjects"]>) {
|
|
159
|
-
const { data } = await this.client.GET("/api/atlas/v2/groups", options);
|
|
196
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
|
|
197
|
+
if (error) {
|
|
198
|
+
throw ApiClientError.fromError(response, error);
|
|
199
|
+
}
|
|
160
200
|
return data;
|
|
161
201
|
}
|
|
162
202
|
|
|
163
203
|
async createProject(options: FetchOptions<operations["createProject"]>) {
|
|
164
|
-
const { data } = await this.client.POST("/api/atlas/v2/groups", options);
|
|
204
|
+
const { data, error, response } = await this.client.POST("/api/atlas/v2/groups", options);
|
|
205
|
+
if (error) {
|
|
206
|
+
throw ApiClientError.fromError(response, error);
|
|
207
|
+
}
|
|
165
208
|
return data;
|
|
166
209
|
}
|
|
167
210
|
|
|
168
211
|
async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
|
|
169
|
-
await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
|
|
212
|
+
const { error, response } = await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
|
|
213
|
+
if (error) {
|
|
214
|
+
throw ApiClientError.fromError(response, error);
|
|
215
|
+
}
|
|
170
216
|
}
|
|
171
217
|
|
|
172
218
|
async getProject(options: FetchOptions<operations["getProject"]>) {
|
|
173
|
-
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
|
|
219
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
|
|
220
|
+
if (error) {
|
|
221
|
+
throw ApiClientError.fromError(response, error);
|
|
222
|
+
}
|
|
174
223
|
return data;
|
|
175
224
|
}
|
|
176
225
|
|
|
177
226
|
async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
|
|
178
|
-
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
|
|
227
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
|
|
228
|
+
if (error) {
|
|
229
|
+
throw ApiClientError.fromError(response, error);
|
|
230
|
+
}
|
|
179
231
|
return data;
|
|
180
232
|
}
|
|
181
233
|
|
|
182
234
|
async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
|
|
183
|
-
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
|
|
235
|
+
const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
|
|
236
|
+
if (error) {
|
|
237
|
+
throw ApiClientError.fromError(response, error);
|
|
238
|
+
}
|
|
184
239
|
return data;
|
|
185
240
|
}
|
|
186
241
|
|
|
187
242
|
async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
|
|
188
|
-
await this.client.DELETE(
|
|
243
|
+
const { error, response } = await this.client.DELETE(
|
|
244
|
+
"/api/atlas/v2/groups/{groupId}/accessList/{entryValue}",
|
|
245
|
+
options
|
|
246
|
+
);
|
|
247
|
+
if (error) {
|
|
248
|
+
throw ApiClientError.fromError(response, error);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async listAlerts(options: FetchOptions<operations["listAlerts"]>) {
|
|
253
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/alerts", options);
|
|
254
|
+
if (error) {
|
|
255
|
+
throw ApiClientError.fromError(response, error);
|
|
256
|
+
}
|
|
257
|
+
return data;
|
|
189
258
|
}
|
|
190
259
|
|
|
191
260
|
async listClusters(options: FetchOptions<operations["listClusters"]>) {
|
|
192
|
-
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
|
|
261
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
|
|
262
|
+
if (error) {
|
|
263
|
+
throw ApiClientError.fromError(response, error);
|
|
264
|
+
}
|
|
193
265
|
return data;
|
|
194
266
|
}
|
|
195
267
|
|
|
196
268
|
async createCluster(options: FetchOptions<operations["createCluster"]>) {
|
|
197
|
-
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
|
|
269
|
+
const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
|
|
270
|
+
if (error) {
|
|
271
|
+
throw ApiClientError.fromError(response, error);
|
|
272
|
+
}
|
|
198
273
|
return data;
|
|
199
274
|
}
|
|
200
275
|
|
|
201
276
|
async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
|
|
202
|
-
await this.client.DELETE(
|
|
277
|
+
const { error, response } = await this.client.DELETE(
|
|
278
|
+
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
|
|
279
|
+
options
|
|
280
|
+
);
|
|
281
|
+
if (error) {
|
|
282
|
+
throw ApiClientError.fromError(response, error);
|
|
283
|
+
}
|
|
203
284
|
}
|
|
204
285
|
|
|
205
286
|
async getCluster(options: FetchOptions<operations["getCluster"]>) {
|
|
206
|
-
const { data } = await this.client.GET(
|
|
287
|
+
const { data, error, response } = await this.client.GET(
|
|
288
|
+
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
|
|
289
|
+
options
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
if (error) {
|
|
293
|
+
throw ApiClientError.fromError(response, error);
|
|
294
|
+
}
|
|
207
295
|
return data;
|
|
208
296
|
}
|
|
209
297
|
|
|
210
298
|
async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
|
|
211
|
-
const { data } = await this.client.GET(
|
|
299
|
+
const { data, error, response } = await this.client.GET(
|
|
300
|
+
"/api/atlas/v2/groups/{groupId}/databaseUsers",
|
|
301
|
+
options
|
|
302
|
+
);
|
|
303
|
+
if (error) {
|
|
304
|
+
throw ApiClientError.fromError(response, error);
|
|
305
|
+
}
|
|
212
306
|
return data;
|
|
213
307
|
}
|
|
214
308
|
|
|
215
309
|
async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
|
|
216
|
-
const { data } = await this.client.POST(
|
|
310
|
+
const { data, error, response } = await this.client.POST(
|
|
311
|
+
"/api/atlas/v2/groups/{groupId}/databaseUsers",
|
|
312
|
+
options
|
|
313
|
+
);
|
|
314
|
+
if (error) {
|
|
315
|
+
throw ApiClientError.fromError(response, error);
|
|
316
|
+
}
|
|
217
317
|
return data;
|
|
218
318
|
}
|
|
219
319
|
|
|
220
320
|
async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
|
|
221
|
-
await this.client.DELETE(
|
|
321
|
+
const { error, response } = await this.client.DELETE(
|
|
322
|
+
"/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}",
|
|
323
|
+
options
|
|
324
|
+
);
|
|
325
|
+
if (error) {
|
|
326
|
+
throw ApiClientError.fromError(response, error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async listFlexClusters(options: FetchOptions<operations["listFlexClusters"]>) {
|
|
331
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/flexClusters", options);
|
|
332
|
+
if (error) {
|
|
333
|
+
throw ApiClientError.fromError(response, error);
|
|
334
|
+
}
|
|
335
|
+
return data;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async createFlexCluster(options: FetchOptions<operations["createFlexCluster"]>) {
|
|
339
|
+
const { data, error, response } = await this.client.POST(
|
|
340
|
+
"/api/atlas/v2/groups/{groupId}/flexClusters",
|
|
341
|
+
options
|
|
342
|
+
);
|
|
343
|
+
if (error) {
|
|
344
|
+
throw ApiClientError.fromError(response, error);
|
|
345
|
+
}
|
|
346
|
+
return data;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async deleteFlexCluster(options: FetchOptions<operations["deleteFlexCluster"]>) {
|
|
350
|
+
const { error, response } = await this.client.DELETE(
|
|
351
|
+
"/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
|
|
352
|
+
options
|
|
353
|
+
);
|
|
354
|
+
if (error) {
|
|
355
|
+
throw ApiClientError.fromError(response, error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async getFlexCluster(options: FetchOptions<operations["getFlexCluster"]>) {
|
|
360
|
+
const { data, error, response } = await this.client.GET(
|
|
361
|
+
"/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
|
|
362
|
+
options
|
|
363
|
+
);
|
|
364
|
+
if (error) {
|
|
365
|
+
throw ApiClientError.fromError(response, error);
|
|
366
|
+
}
|
|
367
|
+
return data;
|
|
222
368
|
}
|
|
223
369
|
|
|
224
370
|
async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
|
|
225
|
-
const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
|
|
371
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
|
|
372
|
+
if (error) {
|
|
373
|
+
throw ApiClientError.fromError(response, error);
|
|
374
|
+
}
|
|
226
375
|
return data;
|
|
227
376
|
}
|
|
228
377
|
|
|
229
378
|
async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
|
|
230
|
-
const { data } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
|
|
379
|
+
const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
|
|
380
|
+
if (error) {
|
|
381
|
+
throw ApiClientError.fromError(response, error);
|
|
382
|
+
}
|
|
231
383
|
return data;
|
|
232
384
|
}
|
|
233
385
|
|
|
@@ -1,21 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
response?: Response;
|
|
1
|
+
import { ApiError } from "./openapi.js";
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
export class ApiClientError extends Error {
|
|
4
|
+
private constructor(
|
|
5
|
+
message: string,
|
|
6
|
+
public readonly response: Response,
|
|
7
|
+
public readonly apiError?: ApiError
|
|
8
|
+
) {
|
|
5
9
|
super(message);
|
|
6
10
|
this.name = "ApiClientError";
|
|
7
|
-
this.response = response;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
static async fromResponse(
|
|
11
14
|
response: Response,
|
|
12
15
|
message: string = `error calling Atlas API`
|
|
13
16
|
): Promise<ApiClientError> {
|
|
17
|
+
const err = await this.extractError(response);
|
|
18
|
+
|
|
19
|
+
return this.fromError(response, err, message);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static fromError(
|
|
23
|
+
response: Response,
|
|
24
|
+
error?: ApiError | string | Error,
|
|
25
|
+
message: string = `error calling Atlas API`
|
|
26
|
+
): ApiClientError {
|
|
27
|
+
const errorMessage = this.buildErrorMessage(error);
|
|
28
|
+
|
|
29
|
+
const apiError = typeof error === "object" && !(error instanceof Error) ? error : undefined;
|
|
30
|
+
|
|
31
|
+
return new ApiClientError(
|
|
32
|
+
`[${response.status} ${response.statusText}] ${message}: ${errorMessage}`,
|
|
33
|
+
response,
|
|
34
|
+
apiError
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private static async extractError(response: Response): Promise<ApiError | string | undefined> {
|
|
14
39
|
try {
|
|
15
|
-
|
|
16
|
-
return new ApiClientError(`${message}: [${response.status} ${response.statusText}] ${text}`, response);
|
|
40
|
+
return (await response.json()) as ApiError;
|
|
17
41
|
} catch {
|
|
18
|
-
|
|
42
|
+
try {
|
|
43
|
+
return await response.text();
|
|
44
|
+
} catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
19
47
|
}
|
|
20
48
|
}
|
|
49
|
+
|
|
50
|
+
private static buildErrorMessage(error?: string | ApiError | Error): string {
|
|
51
|
+
let errorMessage: string = "unknown error";
|
|
52
|
+
|
|
53
|
+
if (error instanceof Error) {
|
|
54
|
+
return error.message;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
58
|
+
switch (typeof error) {
|
|
59
|
+
case "object":
|
|
60
|
+
errorMessage = error.reason || "unknown error";
|
|
61
|
+
if (error.detail && error.detail.length > 0) {
|
|
62
|
+
errorMessage = `${errorMessage}; ${error.detail}`;
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case "string":
|
|
66
|
+
errorMessage = error;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return errorMessage.trim();
|
|
71
|
+
}
|
|
21
72
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ClusterDescription20240805, FlexClusterDescription20241113 } from "./openapi.js";
|
|
2
|
+
import { ApiClient } from "./apiClient.js";
|
|
3
|
+
import logger, { LogId } from "../../logger.js";
|
|
4
|
+
|
|
5
|
+
export interface Cluster {
|
|
6
|
+
name?: string;
|
|
7
|
+
instanceType: "FREE" | "DEDICATED" | "FLEX";
|
|
8
|
+
instanceSize?: string;
|
|
9
|
+
state?: "IDLE" | "CREATING" | "UPDATING" | "DELETING" | "REPAIRING";
|
|
10
|
+
mongoDBVersion?: string;
|
|
11
|
+
connectionString?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function formatFlexCluster(cluster: FlexClusterDescription20241113): Cluster {
|
|
15
|
+
return {
|
|
16
|
+
name: cluster.name,
|
|
17
|
+
instanceType: "FLEX",
|
|
18
|
+
instanceSize: undefined,
|
|
19
|
+
state: cluster.stateName,
|
|
20
|
+
mongoDBVersion: cluster.mongoDBVersion,
|
|
21
|
+
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatCluster(cluster: ClusterDescription20240805): Cluster {
|
|
26
|
+
const regionConfigs = (cluster.replicationSpecs || [])
|
|
27
|
+
.map(
|
|
28
|
+
(replicationSpec) =>
|
|
29
|
+
(replicationSpec.regionConfigs || []) as {
|
|
30
|
+
providerName: string;
|
|
31
|
+
electableSpecs?: {
|
|
32
|
+
instanceSize: string;
|
|
33
|
+
};
|
|
34
|
+
readOnlySpecs?: {
|
|
35
|
+
instanceSize: string;
|
|
36
|
+
};
|
|
37
|
+
analyticsSpecs?: {
|
|
38
|
+
instanceSize: string;
|
|
39
|
+
};
|
|
40
|
+
}[]
|
|
41
|
+
)
|
|
42
|
+
.flat()
|
|
43
|
+
.map((regionConfig) => {
|
|
44
|
+
return {
|
|
45
|
+
providerName: regionConfig.providerName,
|
|
46
|
+
instanceSize:
|
|
47
|
+
regionConfig.electableSpecs?.instanceSize ||
|
|
48
|
+
regionConfig.readOnlySpecs?.instanceSize ||
|
|
49
|
+
regionConfig.analyticsSpecs?.instanceSize,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const instanceSize = regionConfigs[0]?.instanceSize ?? "UNKNOWN";
|
|
54
|
+
const clusterInstanceType = instanceSize == "M0" ? "FREE" : "DEDICATED";
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
name: cluster.name,
|
|
58
|
+
instanceType: clusterInstanceType,
|
|
59
|
+
instanceSize: clusterInstanceType == "DEDICATED" ? instanceSize : undefined,
|
|
60
|
+
state: cluster.stateName,
|
|
61
|
+
mongoDBVersion: cluster.mongoDBVersion,
|
|
62
|
+
connectionString: cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function inspectCluster(apiClient: ApiClient, projectId: string, clusterName: string): Promise<Cluster> {
|
|
67
|
+
try {
|
|
68
|
+
const cluster = await apiClient.getCluster({
|
|
69
|
+
params: {
|
|
70
|
+
path: {
|
|
71
|
+
groupId: projectId,
|
|
72
|
+
clusterName,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return formatCluster(cluster);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
try {
|
|
79
|
+
const cluster = await apiClient.getFlexCluster({
|
|
80
|
+
params: {
|
|
81
|
+
path: {
|
|
82
|
+
groupId: projectId,
|
|
83
|
+
name: clusterName,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return formatFlexCluster(cluster);
|
|
88
|
+
} catch (flexError) {
|
|
89
|
+
const err = flexError instanceof Error ? flexError : new Error(String(flexError));
|
|
90
|
+
logger.error(LogId.atlasInspectFailure, "inspect-cluster", `error inspecting cluster: ${err.message}`);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
|
|
4
|
+
const randomBytesAsync = promisify(randomBytes);
|
|
5
|
+
|
|
6
|
+
export async function generateSecurePassword(): Promise<string> {
|
|
7
|
+
const buf = await randomBytesAsync(16);
|
|
8
|
+
const pass = buf.toString("base64url");
|
|
9
|
+
return pass;
|
|
10
|
+
}
|