@influxdata/influxdb3-mcp-server 1.3.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/CHANGELOG.md +194 -0
- package/Dockerfile +22 -0
- package/LICENSE +6 -0
- package/LICENSE-APACHE.txt +201 -0
- package/LICENSE-MIT.txt +25 -0
- package/README.md +318 -0
- package/build/config.js +85 -0
- package/build/helpers/enums/influx-product-types.enum.js +8 -0
- package/build/index.js +33 -0
- package/build/prompts/index.js +98 -0
- package/build/resources/index.js +223 -0
- package/build/server/index.js +104 -0
- package/build/services/base-connection.service.js +318 -0
- package/build/services/cloud-token-management.service.js +179 -0
- package/build/services/context-file.service.js +97 -0
- package/build/services/database-management.service.js +549 -0
- package/build/services/help.service.js +241 -0
- package/build/services/http-client.service.js +109 -0
- package/build/services/influxdb-master.service.js +117 -0
- package/build/services/query.service.js +499 -0
- package/build/services/serverless-schema-management.service.js +266 -0
- package/build/services/token-management.service.js +215 -0
- package/build/services/write.service.js +153 -0
- package/build/tools/categories/cloud-token.tools.js +321 -0
- package/build/tools/categories/database.tools.js +299 -0
- package/build/tools/categories/health.tools.js +75 -0
- package/build/tools/categories/help.tools.js +104 -0
- package/build/tools/categories/query.tools.js +180 -0
- package/build/tools/categories/schema.tools.js +308 -0
- package/build/tools/categories/token.tools.js +378 -0
- package/build/tools/categories/write.tools.js +104 -0
- package/build/tools/index.js +27 -0
- package/env.example +17 -0
- package/example-cloud-dedicated.mcp.json +15 -0
- package/example-cloud-serverless.mcp.json +13 -0
- package/example-clustered.mcp.json +14 -0
- package/example-docker.mcp.json +25 -0
- package/example-local.mcp.json +13 -0
- package/example-npx.mcp.json +13 -0
- package/package.json +78 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluxDB Database Management Service
|
|
3
|
+
*
|
|
4
|
+
* Handles database lifecycle operations: list, create, delete, update
|
|
5
|
+
*/
|
|
6
|
+
import { InfluxProductType } from "../helpers/enums/influx-product-types.enum.js";
|
|
7
|
+
export class DatabaseManagementService {
|
|
8
|
+
baseService;
|
|
9
|
+
constructor(baseService) {
|
|
10
|
+
this.baseService = baseService;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* List all databases (single entrypoint for all product types)
|
|
14
|
+
* For core/enterprise: GET /api/v3/configure/database?format=json
|
|
15
|
+
* For cloud-dedicated/clustered: GET /api/v0/accounts/{account_id}/clusters/{cluster_id}/databases
|
|
16
|
+
* For cloud-serverless: GET /api/v2/buckets (databases are called "buckets" in v2 API)
|
|
17
|
+
*/
|
|
18
|
+
async listDatabases() {
|
|
19
|
+
this.baseService.validateManagementCapabilities();
|
|
20
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
21
|
+
switch (connectionInfo.type) {
|
|
22
|
+
case InfluxProductType.CloudDedicated:
|
|
23
|
+
case InfluxProductType.Clustered:
|
|
24
|
+
return this.listDatabasesCloudDedicated();
|
|
25
|
+
case InfluxProductType.CloudServerless:
|
|
26
|
+
return this.listDatabasesCloudServerless();
|
|
27
|
+
case InfluxProductType.Core:
|
|
28
|
+
case InfluxProductType.Enterprise:
|
|
29
|
+
return this.listDatabasesCoreEnterprise();
|
|
30
|
+
default:
|
|
31
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a new database (single entrypoint for all product types)
|
|
36
|
+
* For core/enterprise: POST /api/v3/configure/database
|
|
37
|
+
* For cloud-dedicated/clustered: POST /api/v0/accounts/{account_id}/clusters/{cluster_id}/databases
|
|
38
|
+
* For cloud-serverless: POST /api/v2/buckets (databases are called "buckets" in v2 API)
|
|
39
|
+
*/
|
|
40
|
+
async createDatabase(name, config) {
|
|
41
|
+
if (!name)
|
|
42
|
+
throw new Error("Database name is required");
|
|
43
|
+
this.baseService.validateManagementCapabilities();
|
|
44
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
45
|
+
switch (connectionInfo.type) {
|
|
46
|
+
case InfluxProductType.CloudDedicated:
|
|
47
|
+
case InfluxProductType.Clustered:
|
|
48
|
+
return this.createDatabaseCloudDedicated(name, config);
|
|
49
|
+
case InfluxProductType.CloudServerless:
|
|
50
|
+
return this.createDatabaseCloudServerless(name, config);
|
|
51
|
+
case InfluxProductType.Core:
|
|
52
|
+
case InfluxProductType.Enterprise:
|
|
53
|
+
return this.createDatabaseCoreEnterprise(name);
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Update database configuration (cloud-dedicated/clustered and cloud-serverless)
|
|
60
|
+
* For cloud-dedicated/clustered: PATCH /api/v0/accounts/{account_id}/clusters/{cluster_id}/databases/{name}
|
|
61
|
+
* For cloud-serverless: PATCH /api/v2/buckets/{bucketID}
|
|
62
|
+
*/
|
|
63
|
+
async updateDatabase(name, config) {
|
|
64
|
+
if (!name)
|
|
65
|
+
throw new Error("Database name is required");
|
|
66
|
+
this.baseService.validateOperationSupport("update_database", [
|
|
67
|
+
InfluxProductType.CloudDedicated,
|
|
68
|
+
InfluxProductType.CloudServerless,
|
|
69
|
+
InfluxProductType.Clustered,
|
|
70
|
+
]);
|
|
71
|
+
this.baseService.validateManagementCapabilities();
|
|
72
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
73
|
+
switch (connectionInfo.type) {
|
|
74
|
+
case InfluxProductType.CloudDedicated:
|
|
75
|
+
case InfluxProductType.Clustered:
|
|
76
|
+
return this.updateDatabaseCloudDedicated(name, config);
|
|
77
|
+
case InfluxProductType.CloudServerless:
|
|
78
|
+
return this.updateDatabaseCloudServerless(name, config);
|
|
79
|
+
case InfluxProductType.Core:
|
|
80
|
+
case InfluxProductType.Enterprise:
|
|
81
|
+
throw new Error("Database update is not supported for core/enterprise InfluxDB");
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Delete a database (single entrypoint for all product types)
|
|
88
|
+
* For core/enterprise: DELETE /api/v3/configure/database?db={name}
|
|
89
|
+
* For cloud-dedicated/clustered: DELETE /api/v0/accounts/{account_id}/clusters/{cluster_id}/databases/{name}
|
|
90
|
+
* For cloud-serverless: DELETE /api/v2/buckets/{bucketID} (databases are called "buckets" in v2 API)
|
|
91
|
+
*/
|
|
92
|
+
async deleteDatabase(name) {
|
|
93
|
+
if (!name)
|
|
94
|
+
throw new Error("Database name is required");
|
|
95
|
+
this.baseService.validateManagementCapabilities();
|
|
96
|
+
const connectionInfo = this.baseService.getConnectionInfo();
|
|
97
|
+
switch (connectionInfo.type) {
|
|
98
|
+
case InfluxProductType.CloudDedicated:
|
|
99
|
+
case InfluxProductType.Clustered:
|
|
100
|
+
return this.deleteDatabaseCloudDedicated(name);
|
|
101
|
+
case InfluxProductType.CloudServerless:
|
|
102
|
+
return this.deleteDatabaseCloudServerless(name);
|
|
103
|
+
case InfluxProductType.Core:
|
|
104
|
+
case InfluxProductType.Enterprise:
|
|
105
|
+
return this.deleteDatabaseCoreEnterprise(name);
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported InfluxDB product type: ${connectionInfo.type}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* List databases for cloud-dedicated/clustered
|
|
112
|
+
*/
|
|
113
|
+
async listDatabasesCloudDedicated() {
|
|
114
|
+
try {
|
|
115
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
116
|
+
const config = this.baseService.getConfig();
|
|
117
|
+
const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/databases`;
|
|
118
|
+
const response = await httpClient.get(endpoint);
|
|
119
|
+
if (!response || typeof response !== "object") {
|
|
120
|
+
throw new Error("Invalid response format from InfluxDB Cloud API");
|
|
121
|
+
}
|
|
122
|
+
let databases = [];
|
|
123
|
+
if (Array.isArray(response.databases)) {
|
|
124
|
+
databases = response.databases;
|
|
125
|
+
}
|
|
126
|
+
else if (Array.isArray(response)) {
|
|
127
|
+
databases = response;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const possibleDatabases = response.data?.databases ||
|
|
131
|
+
response.result?.databases ||
|
|
132
|
+
response.databases;
|
|
133
|
+
if (Array.isArray(possibleDatabases)) {
|
|
134
|
+
databases = possibleDatabases;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
throw new Error(`Unexpected response structure: ${JSON.stringify(response)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return databases.map((item) => {
|
|
141
|
+
if (typeof item === "string") {
|
|
142
|
+
return { name: item };
|
|
143
|
+
}
|
|
144
|
+
else if (item && typeof item === "object" && item.name) {
|
|
145
|
+
return {
|
|
146
|
+
name: item.name,
|
|
147
|
+
maxTables: item.maxTables,
|
|
148
|
+
maxColumnsPerTable: item.maxColumnsPerTable,
|
|
149
|
+
retentionPeriod: item.retentionPeriod,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
return { name: String(item) };
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this.handleDatabaseError(error, "list databases");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create database for cloud-dedicated/clustered
|
|
163
|
+
*/
|
|
164
|
+
async createDatabaseCloudDedicated(name, config) {
|
|
165
|
+
try {
|
|
166
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
167
|
+
const baseConfig = this.baseService.getConfig();
|
|
168
|
+
const endpoint = `/api/v0/accounts/${baseConfig.influx.account_id}/clusters/${baseConfig.influx.cluster_id}/databases`;
|
|
169
|
+
const payload = { name };
|
|
170
|
+
if (config?.maxTables !== undefined) {
|
|
171
|
+
payload.maxTables = config.maxTables;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
payload.maxTables = 500;
|
|
175
|
+
}
|
|
176
|
+
if (config?.maxColumnsPerTable !== undefined) {
|
|
177
|
+
payload.maxColumnsPerTable = config.maxColumnsPerTable;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
payload.maxColumnsPerTable = 200;
|
|
181
|
+
}
|
|
182
|
+
if (config?.retentionPeriod !== undefined) {
|
|
183
|
+
payload.retentionPeriod = config.retentionPeriod;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
payload.retentionPeriod = 0;
|
|
187
|
+
}
|
|
188
|
+
await httpClient.post(endpoint, payload);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.handleDatabaseError(error, `create database '${name}'`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Update database configuration for cloud-dedicated/clustered
|
|
197
|
+
*/
|
|
198
|
+
async updateDatabaseCloudDedicated(name, config) {
|
|
199
|
+
try {
|
|
200
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
201
|
+
const baseConfig = this.baseService.getConfig();
|
|
202
|
+
const endpoint = `/api/v0/accounts/${baseConfig.influx.account_id}/clusters/${baseConfig.influx.cluster_id}/databases/${encodeURIComponent(name)}`;
|
|
203
|
+
const payload = {};
|
|
204
|
+
if (config.maxTables !== undefined) {
|
|
205
|
+
payload.maxTables = config.maxTables;
|
|
206
|
+
}
|
|
207
|
+
if (config.maxColumnsPerTable !== undefined) {
|
|
208
|
+
payload.maxColumnsPerTable = config.maxColumnsPerTable;
|
|
209
|
+
}
|
|
210
|
+
if (config.retentionPeriod !== undefined) {
|
|
211
|
+
payload.retentionPeriod = config.retentionPeriod;
|
|
212
|
+
}
|
|
213
|
+
if (Object.keys(payload).length === 0) {
|
|
214
|
+
throw new Error("No configuration parameters provided for update");
|
|
215
|
+
}
|
|
216
|
+
await httpClient.patch(endpoint, payload);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
this.handleDatabaseError(error, `update database '${name}'`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Delete database for cloud-dedicated/clustered
|
|
225
|
+
*/
|
|
226
|
+
async deleteDatabaseCloudDedicated(name) {
|
|
227
|
+
try {
|
|
228
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
229
|
+
const config = this.baseService.getConfig();
|
|
230
|
+
const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/databases/${encodeURIComponent(name)}`;
|
|
231
|
+
await httpClient.delete(endpoint);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
this.handleDatabaseError(error, `delete database '${name}'`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* List databases for core/enterprise
|
|
240
|
+
*/
|
|
241
|
+
async listDatabasesCoreEnterprise() {
|
|
242
|
+
try {
|
|
243
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
244
|
+
const response = await httpClient.get("/api/v3/configure/database?format=json");
|
|
245
|
+
if (!response || typeof response !== "object") {
|
|
246
|
+
throw new Error("Invalid response format from InfluxDB API");
|
|
247
|
+
}
|
|
248
|
+
let databases = [];
|
|
249
|
+
if (Array.isArray(response.databases)) {
|
|
250
|
+
databases = response.databases;
|
|
251
|
+
}
|
|
252
|
+
else if (Array.isArray(response)) {
|
|
253
|
+
databases = response;
|
|
254
|
+
}
|
|
255
|
+
else if (response && typeof response === "object") {
|
|
256
|
+
const possibleDatabases = response.data?.databases ||
|
|
257
|
+
response.result?.databases ||
|
|
258
|
+
response.databases;
|
|
259
|
+
if (Array.isArray(possibleDatabases)) {
|
|
260
|
+
databases = possibleDatabases;
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
throw new Error(`Unexpected response structure: ${JSON.stringify(response)}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
throw new Error(`Unexpected response structure: ${JSON.stringify(response)}`);
|
|
268
|
+
}
|
|
269
|
+
return databases.map((item) => {
|
|
270
|
+
if (typeof item === "string") {
|
|
271
|
+
return { name: item };
|
|
272
|
+
}
|
|
273
|
+
else if (item && typeof item === "object" && item["iox::database"]) {
|
|
274
|
+
return { name: item["iox::database"] };
|
|
275
|
+
}
|
|
276
|
+
else if (item && typeof item === "object" && item.name) {
|
|
277
|
+
return { name: item.name };
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
return { name: String(item) };
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
this.handleDatabaseError(error, "list databases");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Create database for core/enterprise
|
|
290
|
+
*/
|
|
291
|
+
async createDatabaseCoreEnterprise(name) {
|
|
292
|
+
try {
|
|
293
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
294
|
+
await httpClient.post("/api/v3/configure/database", {
|
|
295
|
+
db: name,
|
|
296
|
+
});
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
this.handleDatabaseError(error, `create database '${name}'`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Delete database for core/enterprise
|
|
305
|
+
*/
|
|
306
|
+
async deleteDatabaseCoreEnterprise(name) {
|
|
307
|
+
try {
|
|
308
|
+
const httpClient = this.baseService.getInfluxHttpClient();
|
|
309
|
+
await httpClient.delete(`/api/v3/configure/database?db=${encodeURIComponent(name)}`);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
this.handleDatabaseError(error, `delete database '${name}'`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* List databases for cloud-serverless (using buckets from /api/v2)
|
|
318
|
+
*/
|
|
319
|
+
async listDatabasesCloudServerless() {
|
|
320
|
+
try {
|
|
321
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
322
|
+
const response = await httpClient.get("/api/v2/buckets");
|
|
323
|
+
if (!response || typeof response !== "object") {
|
|
324
|
+
throw new Error("Invalid response format from InfluxDB Cloud Serverless API");
|
|
325
|
+
}
|
|
326
|
+
let buckets = [];
|
|
327
|
+
if (Array.isArray(response.buckets)) {
|
|
328
|
+
buckets = response.buckets;
|
|
329
|
+
}
|
|
330
|
+
else if (Array.isArray(response)) {
|
|
331
|
+
buckets = response;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const possibleBuckets = response.data?.buckets ||
|
|
335
|
+
response.result?.buckets ||
|
|
336
|
+
response.buckets;
|
|
337
|
+
if (Array.isArray(possibleBuckets)) {
|
|
338
|
+
buckets = possibleBuckets;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
throw new Error(`Unexpected response structure: ${JSON.stringify(response)}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return buckets
|
|
345
|
+
.filter((bucket) => bucket.type !== "system")
|
|
346
|
+
.map((bucket) => {
|
|
347
|
+
if (typeof bucket === "string") {
|
|
348
|
+
return { name: bucket };
|
|
349
|
+
}
|
|
350
|
+
else if (bucket && typeof bucket === "object" && bucket.name) {
|
|
351
|
+
const databaseInfo = {
|
|
352
|
+
name: bucket.name,
|
|
353
|
+
retentionPeriod: bucket.retentionRules?.[0]?.everySeconds
|
|
354
|
+
? bucket.retentionRules[0].everySeconds * 1000000000
|
|
355
|
+
: undefined,
|
|
356
|
+
};
|
|
357
|
+
if (bucket.id) {
|
|
358
|
+
databaseInfo.bucketId = bucket.id;
|
|
359
|
+
}
|
|
360
|
+
if (bucket.orgID) {
|
|
361
|
+
databaseInfo.organizationId = bucket.orgID;
|
|
362
|
+
}
|
|
363
|
+
if (bucket.storageType) {
|
|
364
|
+
databaseInfo.storageType = bucket.storageType;
|
|
365
|
+
}
|
|
366
|
+
if (bucket.createdAt) {
|
|
367
|
+
databaseInfo.createdAt = bucket.createdAt;
|
|
368
|
+
}
|
|
369
|
+
if (bucket.updatedAt) {
|
|
370
|
+
databaseInfo.updatedAt = bucket.updatedAt;
|
|
371
|
+
}
|
|
372
|
+
if (bucket.description) {
|
|
373
|
+
databaseInfo.description = bucket.description;
|
|
374
|
+
}
|
|
375
|
+
if (bucket.rp) {
|
|
376
|
+
databaseInfo.retentionPolicy = bucket.rp;
|
|
377
|
+
}
|
|
378
|
+
return databaseInfo;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
return { name: String(bucket) };
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
this.handleDatabaseError(error, "list databases (buckets)");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Create database for cloud-serverless (create bucket via /api/v2)
|
|
391
|
+
*/
|
|
392
|
+
async createDatabaseCloudServerless(name, config) {
|
|
393
|
+
try {
|
|
394
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
395
|
+
const orgsResponse = await httpClient.get("/api/v2/orgs");
|
|
396
|
+
let orgID;
|
|
397
|
+
if (orgsResponse?.orgs && orgsResponse.orgs.length > 0) {
|
|
398
|
+
orgID = orgsResponse.orgs[0].id;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
throw new Error("Could not find organization ID for bucket creation");
|
|
402
|
+
}
|
|
403
|
+
const payload = {
|
|
404
|
+
name,
|
|
405
|
+
orgID,
|
|
406
|
+
retentionRules: [
|
|
407
|
+
{
|
|
408
|
+
type: "expire",
|
|
409
|
+
everySeconds: config?.retentionPeriod
|
|
410
|
+
? Math.floor(config.retentionPeriod / 1000000000)
|
|
411
|
+
: 2592000,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
if (config?.description) {
|
|
416
|
+
payload.description = config.description;
|
|
417
|
+
}
|
|
418
|
+
await httpClient.post("/api/v2/buckets", payload);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
this.handleDatabaseError(error, `create database (bucket) '${name}'`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Delete database for cloud-serverless (delete bucket via /api/v2)
|
|
427
|
+
*/
|
|
428
|
+
async deleteDatabaseCloudServerless(name) {
|
|
429
|
+
try {
|
|
430
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
431
|
+
const bucketsResponse = await httpClient.get("/api/v2/buckets");
|
|
432
|
+
let bucketID;
|
|
433
|
+
if (bucketsResponse?.buckets) {
|
|
434
|
+
const bucket = bucketsResponse.buckets.find((b) => b.name === name && b.type !== "system");
|
|
435
|
+
if (bucket) {
|
|
436
|
+
bucketID = bucket.id;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (!bucketID) {
|
|
440
|
+
throw new Error(`Database (bucket) '${name}' not found`);
|
|
441
|
+
}
|
|
442
|
+
await httpClient.delete(`/api/v2/buckets/${bucketID}`);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
this.handleDatabaseError(error, `delete database (bucket) '${name}'`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Update database (bucket) for cloud-serverless via /api/v2
|
|
451
|
+
* PATCH /api/v2/buckets/{bucketID}
|
|
452
|
+
*/
|
|
453
|
+
async updateDatabaseCloudServerless(name, config) {
|
|
454
|
+
try {
|
|
455
|
+
const httpClient = this.baseService.getInfluxHttpClient(true);
|
|
456
|
+
const bucketsResponse = await httpClient.get("/api/v2/buckets");
|
|
457
|
+
let bucket;
|
|
458
|
+
if (bucketsResponse?.buckets) {
|
|
459
|
+
bucket = bucketsResponse.buckets.find((b) => b.name === name && b.type !== "system");
|
|
460
|
+
}
|
|
461
|
+
if (!bucket) {
|
|
462
|
+
throw new Error(`Database (bucket) '${name}' not found`);
|
|
463
|
+
}
|
|
464
|
+
const updatePayload = {};
|
|
465
|
+
if (config.name && config.name !== bucket.name) {
|
|
466
|
+
updatePayload.name = config.name;
|
|
467
|
+
}
|
|
468
|
+
if (config.description !== undefined) {
|
|
469
|
+
updatePayload.description = config.description;
|
|
470
|
+
}
|
|
471
|
+
if (config.retentionPeriod !== undefined) {
|
|
472
|
+
updatePayload.retentionRules = [
|
|
473
|
+
{
|
|
474
|
+
type: "expire",
|
|
475
|
+
everySeconds: Math.floor(config.retentionPeriod / 1000000000),
|
|
476
|
+
},
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
updatePayload.retentionRules = bucket.retentionRules || [
|
|
481
|
+
{
|
|
482
|
+
type: "expire",
|
|
483
|
+
everySeconds: 2592000,
|
|
484
|
+
},
|
|
485
|
+
];
|
|
486
|
+
}
|
|
487
|
+
if (!updatePayload.retentionRules ||
|
|
488
|
+
updatePayload.retentionRules.length === 0) {
|
|
489
|
+
updatePayload.retentionRules = [
|
|
490
|
+
{
|
|
491
|
+
type: "expire",
|
|
492
|
+
everySeconds: 2592000,
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
}
|
|
496
|
+
await httpClient.patch(`/api/v2/buckets/${bucket.id}`, updatePayload);
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
this.handleDatabaseError(error, `update database (bucket) '${name}'`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Common error handling for database operations with comprehensive status code handling
|
|
505
|
+
*/
|
|
506
|
+
handleDatabaseError(error, operation) {
|
|
507
|
+
const status = error.response?.status;
|
|
508
|
+
const originalMessage = error.response?.data?.message ||
|
|
509
|
+
error.response?.data?.error ||
|
|
510
|
+
(typeof error.response?.data === "string" ? error.response.data : null) ||
|
|
511
|
+
error.response?.statusText;
|
|
512
|
+
const statusText = error.response?.statusText || "";
|
|
513
|
+
const formatError = (userMessage) => {
|
|
514
|
+
const parts = [`HTTP ${status}`, userMessage];
|
|
515
|
+
if (originalMessage && originalMessage !== statusText) {
|
|
516
|
+
parts.push(`Server message: ${originalMessage}`);
|
|
517
|
+
}
|
|
518
|
+
return parts.join(" - ");
|
|
519
|
+
};
|
|
520
|
+
switch (status) {
|
|
521
|
+
case 400:
|
|
522
|
+
throw new Error(formatError("Bad Request: Invalid request parameters or malformed request"));
|
|
523
|
+
case 401:
|
|
524
|
+
throw new Error(formatError("Unauthorized: Check your InfluxDB token permissions"));
|
|
525
|
+
case 403:
|
|
526
|
+
throw new Error(formatError("Forbidden: Token does not have sufficient permissions for this operation"));
|
|
527
|
+
case 404:
|
|
528
|
+
throw new Error(formatError("Not Found: Resource does not exist or endpoint not available"));
|
|
529
|
+
case 409:
|
|
530
|
+
throw new Error(formatError("Conflict: Resource already exists or operation conflicts with current state"));
|
|
531
|
+
case 500:
|
|
532
|
+
throw new Error(formatError("Internal Server Error: InfluxDB server encountered an error"));
|
|
533
|
+
default:
|
|
534
|
+
if (error.code === "ECONNREFUSED") {
|
|
535
|
+
throw new Error("Connection refused: Check if InfluxDB is running and URL is correct");
|
|
536
|
+
}
|
|
537
|
+
else if (error.code === "ENOTFOUND") {
|
|
538
|
+
throw new Error("Host not found: Check your InfluxDB URL");
|
|
539
|
+
}
|
|
540
|
+
else if (error.response?.data) {
|
|
541
|
+
const message = originalMessage || JSON.stringify(error.response.data);
|
|
542
|
+
throw new Error(`HTTP ${status} - InfluxDB API error: ${message}`);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
throw new Error(`Failed to ${operation}: ${error.message}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|