s3db.js 13.4.0 → 13.6.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 +25 -10
- package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +38653 -32291
- package/dist/s3db.es.js.map +1 -1
- package/package.json +218 -22
- package/src/concerns/id.js +90 -6
- package/src/concerns/index.js +2 -1
- package/src/concerns/password-hashing.js +150 -0
- package/src/database.class.js +6 -2
- package/src/plugins/api/auth/basic-auth.js +40 -10
- package/src/plugins/api/auth/index.js +49 -3
- package/src/plugins/api/auth/oauth2-auth.js +171 -0
- package/src/plugins/api/auth/oidc-auth.js +789 -0
- package/src/plugins/api/auth/oidc-client.js +462 -0
- package/src/plugins/api/auth/path-auth-matcher.js +284 -0
- package/src/plugins/api/concerns/event-emitter.js +134 -0
- package/src/plugins/api/concerns/failban-manager.js +651 -0
- package/src/plugins/api/concerns/guards-helpers.js +402 -0
- package/src/plugins/api/concerns/metrics-collector.js +346 -0
- package/src/plugins/api/index.js +510 -57
- package/src/plugins/api/middlewares/failban.js +305 -0
- package/src/plugins/api/middlewares/rate-limit.js +301 -0
- package/src/plugins/api/middlewares/request-id.js +74 -0
- package/src/plugins/api/middlewares/security-headers.js +120 -0
- package/src/plugins/api/middlewares/session-tracking.js +194 -0
- package/src/plugins/api/routes/auth-routes.js +119 -78
- package/src/plugins/api/routes/resource-routes.js +73 -30
- package/src/plugins/api/server.js +1139 -45
- package/src/plugins/api/utils/custom-routes.js +102 -0
- package/src/plugins/api/utils/guards.js +213 -0
- package/src/plugins/api/utils/mime-types.js +154 -0
- package/src/plugins/api/utils/openapi-generator.js +91 -12
- package/src/plugins/api/utils/path-matcher.js +173 -0
- package/src/plugins/api/utils/static-filesystem.js +262 -0
- package/src/plugins/api/utils/static-s3.js +231 -0
- package/src/plugins/api/utils/template-engine.js +188 -0
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
- package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
- package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
- package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
- package/src/plugins/cloud-inventory/index.js +20 -0
- package/src/plugins/cloud-inventory/registry.js +146 -0
- package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
- package/src/plugins/cloud-inventory.plugin.js +1333 -0
- package/src/plugins/concerns/plugin-dependencies.js +62 -2
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/eventual-consistency/consolidation.js +2 -2
- package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
- package/src/plugins/eventual-consistency/install.js +2 -2
- package/src/plugins/identity/README.md +335 -0
- package/src/plugins/identity/concerns/mfa-manager.js +204 -0
- package/src/plugins/identity/concerns/password.js +138 -0
- package/src/plugins/identity/concerns/resource-schemas.js +273 -0
- package/src/plugins/identity/concerns/token-generator.js +172 -0
- package/src/plugins/identity/email-service.js +422 -0
- package/src/plugins/identity/index.js +1052 -0
- package/src/plugins/identity/oauth2-server.js +1033 -0
- package/src/plugins/identity/oidc-discovery.js +285 -0
- package/src/plugins/identity/rsa-keys.js +323 -0
- package/src/plugins/identity/server.js +500 -0
- package/src/plugins/identity/session-manager.js +453 -0
- package/src/plugins/identity/ui/layouts/base.js +251 -0
- package/src/plugins/identity/ui/middleware.js +135 -0
- package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
- package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
- package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
- package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
- package/src/plugins/identity/ui/pages/admin/users.js +263 -0
- package/src/plugins/identity/ui/pages/consent.js +262 -0
- package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
- package/src/plugins/identity/ui/pages/login.js +144 -0
- package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
- package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
- package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
- package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
- package/src/plugins/identity/ui/pages/profile.js +361 -0
- package/src/plugins/identity/ui/pages/register.js +226 -0
- package/src/plugins/identity/ui/pages/reset-password.js +128 -0
- package/src/plugins/identity/ui/pages/verify-email.js +172 -0
- package/src/plugins/identity/ui/routes.js +2541 -0
- package/src/plugins/identity/ui/styles/main.css +465 -0
- package/src/plugins/index.js +4 -1
- package/src/plugins/ml/base-model.class.js +65 -16
- package/src/plugins/ml/classification-model.class.js +1 -1
- package/src/plugins/ml/timeseries-model.class.js +3 -1
- package/src/plugins/ml.plugin.js +584 -31
- package/src/plugins/shared/error-handler.js +147 -0
- package/src/plugins/shared/index.js +9 -0
- package/src/plugins/shared/middlewares/compression.js +117 -0
- package/src/plugins/shared/middlewares/cors.js +49 -0
- package/src/plugins/shared/middlewares/index.js +11 -0
- package/src/plugins/shared/middlewares/logging.js +54 -0
- package/src/plugins/shared/middlewares/rate-limit.js +73 -0
- package/src/plugins/shared/middlewares/security.js +158 -0
- package/src/plugins/shared/response-formatter.js +264 -0
- package/src/plugins/state-machine.plugin.js +57 -2
- package/src/resource.class.js +140 -12
- package/src/schema.class.js +30 -1
- package/src/validator.class.js +57 -6
- package/dist/s3db.cjs.js.map +0 -1
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { BaseCloudDriver } from './base-driver.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Production-ready Linode (Akamai Cloud) inventory driver using official @linode/api-v4 SDK.
|
|
5
|
+
*
|
|
6
|
+
* Covers 12+ services with 18+ resource types:
|
|
7
|
+
* - Compute (Linodes/instances, placement groups)
|
|
8
|
+
* - Kubernetes (LKE clusters, node pools)
|
|
9
|
+
* - Storage (volumes, object storage buckets)
|
|
10
|
+
* - Networking (NodeBalancers, firewalls, VLANs)
|
|
11
|
+
* - DNS (domains, records)
|
|
12
|
+
* - Databases (managed MySQL, PostgreSQL, MongoDB)
|
|
13
|
+
* - Images, StackScripts
|
|
14
|
+
*
|
|
15
|
+
* @see https://www.linode.com/docs/api/
|
|
16
|
+
* @see https://www.npmjs.com/package/@linode/api-v4
|
|
17
|
+
*/
|
|
18
|
+
export class LinodeInventoryDriver extends BaseCloudDriver {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
super({ ...options, driver: options.driver || 'linode' });
|
|
21
|
+
|
|
22
|
+
this._apiToken = null;
|
|
23
|
+
this._accountId = this.config?.accountId || 'linode';
|
|
24
|
+
|
|
25
|
+
// Services to collect (can be filtered via config.services)
|
|
26
|
+
this._services = this.config?.services || [
|
|
27
|
+
'linodes',
|
|
28
|
+
'kubernetes',
|
|
29
|
+
'volumes',
|
|
30
|
+
'nodebalancers',
|
|
31
|
+
'firewalls',
|
|
32
|
+
'vlans',
|
|
33
|
+
'domains',
|
|
34
|
+
'images',
|
|
35
|
+
'objectstorage',
|
|
36
|
+
'databases',
|
|
37
|
+
'stackscripts',
|
|
38
|
+
'placementgroups'
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// Regions to scan (null = all)
|
|
42
|
+
this._regions = this.config?.regions || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize Linode API client.
|
|
47
|
+
*/
|
|
48
|
+
async _initializeClient() {
|
|
49
|
+
if (this._apiToken) return;
|
|
50
|
+
|
|
51
|
+
const credentials = this.credentials || {};
|
|
52
|
+
this._apiToken = credentials.token || credentials.apiToken || process.env.LINODE_TOKEN;
|
|
53
|
+
|
|
54
|
+
if (!this._apiToken) {
|
|
55
|
+
throw new Error('Linode API token is required. Provide via credentials.token or LINODE_TOKEN env var.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Import and set token
|
|
59
|
+
const { setToken } = await import('@linode/api-v4');
|
|
60
|
+
setToken(this._apiToken);
|
|
61
|
+
|
|
62
|
+
this.logger('info', 'Linode API client initialized', {
|
|
63
|
+
accountId: this._accountId,
|
|
64
|
+
services: this._services.length
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Main entry point - lists all resources from configured services.
|
|
70
|
+
*/
|
|
71
|
+
async *listResources(options = {}) {
|
|
72
|
+
await this._initializeClient();
|
|
73
|
+
|
|
74
|
+
const serviceCollectors = {
|
|
75
|
+
linodes: () => this._collectLinodes(),
|
|
76
|
+
kubernetes: () => this._collectKubernetes(),
|
|
77
|
+
volumes: () => this._collectVolumes(),
|
|
78
|
+
nodebalancers: () => this._collectNodeBalancers(),
|
|
79
|
+
firewalls: () => this._collectFirewalls(),
|
|
80
|
+
vlans: () => this._collectVLANs(),
|
|
81
|
+
domains: () => this._collectDomains(),
|
|
82
|
+
images: () => this._collectImages(),
|
|
83
|
+
objectstorage: () => this._collectObjectStorage(),
|
|
84
|
+
databases: () => this._collectDatabases(),
|
|
85
|
+
stackscripts: () => this._collectStackScripts(),
|
|
86
|
+
placementgroups: () => this._collectPlacementGroups()
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
for (const service of this._services) {
|
|
90
|
+
const collector = serviceCollectors[service];
|
|
91
|
+
if (!collector) {
|
|
92
|
+
this.logger('warn', `Unknown Linode service: ${service}`, { service });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
this.logger('info', `Collecting Linode ${service} resources`, { service });
|
|
98
|
+
yield* collector();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// Continue with next service instead of failing entire sync
|
|
101
|
+
this.logger('error', `Linode service collection failed, skipping to next service`, {
|
|
102
|
+
service,
|
|
103
|
+
error: err.message,
|
|
104
|
+
errorName: err.name,
|
|
105
|
+
stack: err.stack
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Collect Linodes (compute instances).
|
|
113
|
+
*/
|
|
114
|
+
async *_collectLinodes() {
|
|
115
|
+
try {
|
|
116
|
+
const { getLinodes } = await import('@linode/api-v4/lib/linodes');
|
|
117
|
+
|
|
118
|
+
const response = await getLinodes();
|
|
119
|
+
const linodes = response.data || [];
|
|
120
|
+
|
|
121
|
+
for (const linode of linodes) {
|
|
122
|
+
yield {
|
|
123
|
+
provider: 'linode',
|
|
124
|
+
accountId: this._accountId,
|
|
125
|
+
region: linode.region,
|
|
126
|
+
service: 'linodes',
|
|
127
|
+
resourceType: 'linode.compute.instance',
|
|
128
|
+
resourceId: linode.id?.toString(),
|
|
129
|
+
name: linode.label,
|
|
130
|
+
tags: linode.tags || [],
|
|
131
|
+
configuration: this._sanitize(linode)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.logger('info', `Collected ${linodes.length} Linode instances`);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
this.logger('error', 'Failed to collect Linode instances', {
|
|
138
|
+
error: err.message,
|
|
139
|
+
stack: err.stack
|
|
140
|
+
});
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Collect Kubernetes (LKE) clusters.
|
|
147
|
+
*/
|
|
148
|
+
async *_collectKubernetes() {
|
|
149
|
+
try {
|
|
150
|
+
const { getKubernetesClusters, getKubernetesClusterPools } = await import('@linode/api-v4/lib/kubernetes');
|
|
151
|
+
|
|
152
|
+
const response = await getKubernetesClusters();
|
|
153
|
+
const clusters = response.data || [];
|
|
154
|
+
|
|
155
|
+
for (const cluster of clusters) {
|
|
156
|
+
yield {
|
|
157
|
+
provider: 'linode',
|
|
158
|
+
accountId: this._accountId,
|
|
159
|
+
region: cluster.region,
|
|
160
|
+
service: 'kubernetes',
|
|
161
|
+
resourceType: 'linode.kubernetes.cluster',
|
|
162
|
+
resourceId: cluster.id?.toString(),
|
|
163
|
+
name: cluster.label,
|
|
164
|
+
tags: cluster.tags || [],
|
|
165
|
+
configuration: this._sanitize(cluster)
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Collect node pools for this cluster
|
|
169
|
+
try {
|
|
170
|
+
const poolsResponse = await getKubernetesClusterPools(cluster.id);
|
|
171
|
+
const pools = poolsResponse.data || [];
|
|
172
|
+
|
|
173
|
+
for (const pool of pools) {
|
|
174
|
+
yield {
|
|
175
|
+
provider: 'linode',
|
|
176
|
+
accountId: this._accountId,
|
|
177
|
+
region: cluster.region,
|
|
178
|
+
service: 'kubernetes',
|
|
179
|
+
resourceType: 'linode.kubernetes.nodepool',
|
|
180
|
+
resourceId: pool.id?.toString(),
|
|
181
|
+
name: `${cluster.label}-${pool.type}`,
|
|
182
|
+
tags: cluster.tags || [],
|
|
183
|
+
metadata: { clusterId: cluster.id, clusterLabel: cluster.label },
|
|
184
|
+
configuration: this._sanitize(pool)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
} catch (poolErr) {
|
|
188
|
+
this.logger('warn', `Failed to collect node pools for cluster ${cluster.id}`, {
|
|
189
|
+
clusterId: cluster.id,
|
|
190
|
+
error: poolErr.message
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.logger('info', `Collected ${clusters.length} Linode Kubernetes clusters`);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
this.logger('error', 'Failed to collect Linode Kubernetes', {
|
|
198
|
+
error: err.message,
|
|
199
|
+
stack: err.stack
|
|
200
|
+
});
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Collect Block Storage volumes.
|
|
207
|
+
*/
|
|
208
|
+
async *_collectVolumes() {
|
|
209
|
+
try {
|
|
210
|
+
const { getVolumes } = await import('@linode/api-v4/lib/volumes');
|
|
211
|
+
|
|
212
|
+
const response = await getVolumes();
|
|
213
|
+
const volumes = response.data || [];
|
|
214
|
+
|
|
215
|
+
for (const volume of volumes) {
|
|
216
|
+
yield {
|
|
217
|
+
provider: 'linode',
|
|
218
|
+
accountId: this._accountId,
|
|
219
|
+
region: volume.region,
|
|
220
|
+
service: 'volumes',
|
|
221
|
+
resourceType: 'linode.volume',
|
|
222
|
+
resourceId: volume.id?.toString(),
|
|
223
|
+
name: volume.label,
|
|
224
|
+
tags: volume.tags || [],
|
|
225
|
+
configuration: this._sanitize(volume)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.logger('info', `Collected ${volumes.length} Linode volumes`);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
this.logger('error', 'Failed to collect Linode volumes', {
|
|
232
|
+
error: err.message,
|
|
233
|
+
stack: err.stack
|
|
234
|
+
});
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Collect NodeBalancers (load balancers).
|
|
241
|
+
*/
|
|
242
|
+
async *_collectNodeBalancers() {
|
|
243
|
+
try {
|
|
244
|
+
const { getNodeBalancers } = await import('@linode/api-v4/lib/nodebalancers');
|
|
245
|
+
|
|
246
|
+
const response = await getNodeBalancers();
|
|
247
|
+
const nodebalancers = response.data || [];
|
|
248
|
+
|
|
249
|
+
for (const nb of nodebalancers) {
|
|
250
|
+
yield {
|
|
251
|
+
provider: 'linode',
|
|
252
|
+
accountId: this._accountId,
|
|
253
|
+
region: nb.region,
|
|
254
|
+
service: 'nodebalancers',
|
|
255
|
+
resourceType: 'linode.nodebalancer',
|
|
256
|
+
resourceId: nb.id?.toString(),
|
|
257
|
+
name: nb.label,
|
|
258
|
+
tags: nb.tags || [],
|
|
259
|
+
configuration: this._sanitize(nb)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.logger('info', `Collected ${nodebalancers.length} Linode NodeBalancers`);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
this.logger('error', 'Failed to collect Linode NodeBalancers', {
|
|
266
|
+
error: err.message,
|
|
267
|
+
stack: err.stack
|
|
268
|
+
});
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Collect Firewalls.
|
|
275
|
+
*/
|
|
276
|
+
async *_collectFirewalls() {
|
|
277
|
+
try {
|
|
278
|
+
const { getFirewalls } = await import('@linode/api-v4/lib/firewalls');
|
|
279
|
+
|
|
280
|
+
const response = await getFirewalls();
|
|
281
|
+
const firewalls = response.data || [];
|
|
282
|
+
|
|
283
|
+
for (const firewall of firewalls) {
|
|
284
|
+
yield {
|
|
285
|
+
provider: 'linode',
|
|
286
|
+
accountId: this._accountId,
|
|
287
|
+
region: null, // Firewalls are global
|
|
288
|
+
service: 'firewalls',
|
|
289
|
+
resourceType: 'linode.firewall',
|
|
290
|
+
resourceId: firewall.id?.toString(),
|
|
291
|
+
name: firewall.label,
|
|
292
|
+
tags: firewall.tags || [],
|
|
293
|
+
configuration: this._sanitize(firewall)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.logger('info', `Collected ${firewalls.length} Linode firewalls`);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
this.logger('error', 'Failed to collect Linode firewalls', {
|
|
300
|
+
error: err.message,
|
|
301
|
+
stack: err.stack
|
|
302
|
+
});
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Collect VLANs.
|
|
309
|
+
*/
|
|
310
|
+
async *_collectVLANs() {
|
|
311
|
+
try {
|
|
312
|
+
const { getVLANs } = await import('@linode/api-v4/lib/vlans');
|
|
313
|
+
|
|
314
|
+
const response = await getVLANs();
|
|
315
|
+
const vlans = response.data || [];
|
|
316
|
+
|
|
317
|
+
for (const vlan of vlans) {
|
|
318
|
+
yield {
|
|
319
|
+
provider: 'linode',
|
|
320
|
+
accountId: this._accountId,
|
|
321
|
+
region: vlan.region,
|
|
322
|
+
service: 'vlans',
|
|
323
|
+
resourceType: 'linode.vlan',
|
|
324
|
+
resourceId: vlan.label, // VLANs use label as ID
|
|
325
|
+
name: vlan.label,
|
|
326
|
+
tags: [],
|
|
327
|
+
configuration: this._sanitize(vlan)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.logger('info', `Collected ${vlans.length} Linode VLANs`);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
this.logger('error', 'Failed to collect Linode VLANs', {
|
|
334
|
+
error: err.message,
|
|
335
|
+
stack: err.stack
|
|
336
|
+
});
|
|
337
|
+
throw err;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Collect DNS domains and records.
|
|
343
|
+
*/
|
|
344
|
+
async *_collectDomains() {
|
|
345
|
+
try {
|
|
346
|
+
const { getDomains, getDomainRecords } = await import('@linode/api-v4/lib/domains');
|
|
347
|
+
|
|
348
|
+
const response = await getDomains();
|
|
349
|
+
const domains = response.data || [];
|
|
350
|
+
|
|
351
|
+
for (const domain of domains) {
|
|
352
|
+
yield {
|
|
353
|
+
provider: 'linode',
|
|
354
|
+
accountId: this._accountId,
|
|
355
|
+
region: null, // DNS is global
|
|
356
|
+
service: 'domains',
|
|
357
|
+
resourceType: 'linode.dns.domain',
|
|
358
|
+
resourceId: domain.id?.toString(),
|
|
359
|
+
name: domain.domain,
|
|
360
|
+
tags: domain.tags || [],
|
|
361
|
+
configuration: this._sanitize(domain)
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// Collect DNS records for this domain
|
|
365
|
+
try {
|
|
366
|
+
const recordsResponse = await getDomainRecords(domain.id);
|
|
367
|
+
const records = recordsResponse.data || [];
|
|
368
|
+
|
|
369
|
+
for (const record of records) {
|
|
370
|
+
yield {
|
|
371
|
+
provider: 'linode',
|
|
372
|
+
accountId: this._accountId,
|
|
373
|
+
region: null,
|
|
374
|
+
service: 'domains',
|
|
375
|
+
resourceType: 'linode.dns.record',
|
|
376
|
+
resourceId: `${domain.id}/${record.id}`,
|
|
377
|
+
name: `${record.name}.${domain.domain}`,
|
|
378
|
+
tags: [],
|
|
379
|
+
metadata: { domainId: domain.id, domain: domain.domain },
|
|
380
|
+
configuration: this._sanitize(record)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
} catch (recordErr) {
|
|
384
|
+
this.logger('warn', `Failed to collect DNS records for domain ${domain.domain}`, {
|
|
385
|
+
domainId: domain.id,
|
|
386
|
+
error: recordErr.message
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
this.logger('info', `Collected ${domains.length} Linode DNS domains`);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
this.logger('error', 'Failed to collect Linode domains', {
|
|
394
|
+
error: err.message,
|
|
395
|
+
stack: err.stack
|
|
396
|
+
});
|
|
397
|
+
throw err;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Collect custom Images.
|
|
403
|
+
*/
|
|
404
|
+
async *_collectImages() {
|
|
405
|
+
try {
|
|
406
|
+
const { getImages } = await import('@linode/api-v4/lib/images');
|
|
407
|
+
|
|
408
|
+
const response = await getImages();
|
|
409
|
+
const images = response.data || [];
|
|
410
|
+
|
|
411
|
+
// Filter to only custom images (not official Linode images)
|
|
412
|
+
const customImages = images.filter(img => img.is_public === false);
|
|
413
|
+
|
|
414
|
+
for (const image of customImages) {
|
|
415
|
+
yield {
|
|
416
|
+
provider: 'linode',
|
|
417
|
+
accountId: this._accountId,
|
|
418
|
+
region: image.region || null,
|
|
419
|
+
service: 'images',
|
|
420
|
+
resourceType: 'linode.image',
|
|
421
|
+
resourceId: image.id,
|
|
422
|
+
name: image.label,
|
|
423
|
+
tags: [],
|
|
424
|
+
configuration: this._sanitize(image)
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
this.logger('info', `Collected ${customImages.length} custom Linode images`);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
this.logger('error', 'Failed to collect Linode images', {
|
|
431
|
+
error: err.message,
|
|
432
|
+
stack: err.stack
|
|
433
|
+
});
|
|
434
|
+
throw err;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Collect Object Storage buckets.
|
|
440
|
+
*/
|
|
441
|
+
async *_collectObjectStorage() {
|
|
442
|
+
try {
|
|
443
|
+
const { getObjectStorageBuckets } = await import('@linode/api-v4/lib/object-storage');
|
|
444
|
+
|
|
445
|
+
const response = await getObjectStorageBuckets();
|
|
446
|
+
const buckets = response.data || [];
|
|
447
|
+
|
|
448
|
+
for (const bucket of buckets) {
|
|
449
|
+
yield {
|
|
450
|
+
provider: 'linode',
|
|
451
|
+
accountId: this._accountId,
|
|
452
|
+
region: bucket.region,
|
|
453
|
+
service: 'objectstorage',
|
|
454
|
+
resourceType: 'linode.objectstorage.bucket',
|
|
455
|
+
resourceId: `${bucket.cluster}/${bucket.label}`,
|
|
456
|
+
name: bucket.label,
|
|
457
|
+
tags: [],
|
|
458
|
+
metadata: { cluster: bucket.cluster },
|
|
459
|
+
configuration: this._sanitize(bucket)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
this.logger('info', `Collected ${buckets.length} Linode object storage buckets`);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
this.logger('error', 'Failed to collect Linode object storage', {
|
|
466
|
+
error: err.message,
|
|
467
|
+
stack: err.stack
|
|
468
|
+
});
|
|
469
|
+
throw err;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Collect Managed Databases (MySQL, PostgreSQL, MongoDB).
|
|
475
|
+
*/
|
|
476
|
+
async *_collectDatabases() {
|
|
477
|
+
try {
|
|
478
|
+
const { getDatabases } = await import('@linode/api-v4/lib/databases');
|
|
479
|
+
|
|
480
|
+
const response = await getDatabases();
|
|
481
|
+
const databases = response.data || [];
|
|
482
|
+
|
|
483
|
+
for (const db of databases) {
|
|
484
|
+
yield {
|
|
485
|
+
provider: 'linode',
|
|
486
|
+
accountId: this._accountId,
|
|
487
|
+
region: db.region,
|
|
488
|
+
service: 'databases',
|
|
489
|
+
resourceType: 'linode.database',
|
|
490
|
+
resourceId: db.id?.toString(),
|
|
491
|
+
name: db.label,
|
|
492
|
+
tags: [],
|
|
493
|
+
metadata: {
|
|
494
|
+
engine: db.engine,
|
|
495
|
+
version: db.version,
|
|
496
|
+
status: db.status
|
|
497
|
+
},
|
|
498
|
+
configuration: this._sanitize(db)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
this.logger('info', `Collected ${databases.length} Linode databases`);
|
|
503
|
+
} catch (err) {
|
|
504
|
+
this.logger('error', 'Failed to collect Linode databases', {
|
|
505
|
+
error: err.message,
|
|
506
|
+
stack: err.stack
|
|
507
|
+
});
|
|
508
|
+
throw err;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Collect StackScripts (deployment scripts).
|
|
514
|
+
*/
|
|
515
|
+
async *_collectStackScripts() {
|
|
516
|
+
try {
|
|
517
|
+
const { getStackScripts } = await import('@linode/api-v4/lib/stackscripts');
|
|
518
|
+
|
|
519
|
+
// Only collect user's own StackScripts (not public ones)
|
|
520
|
+
const response = await getStackScripts({ mine: true });
|
|
521
|
+
const stackScripts = response.data || [];
|
|
522
|
+
|
|
523
|
+
for (const script of stackScripts) {
|
|
524
|
+
yield {
|
|
525
|
+
provider: 'linode',
|
|
526
|
+
accountId: this._accountId,
|
|
527
|
+
region: null, // StackScripts are global
|
|
528
|
+
service: 'stackscripts',
|
|
529
|
+
resourceType: 'linode.stackscript',
|
|
530
|
+
resourceId: script.id?.toString(),
|
|
531
|
+
name: script.label,
|
|
532
|
+
tags: [],
|
|
533
|
+
metadata: {
|
|
534
|
+
isPublic: script.is_public,
|
|
535
|
+
deploymentsTotal: script.deployments_total
|
|
536
|
+
},
|
|
537
|
+
configuration: this._sanitize(script)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
this.logger('info', `Collected ${stackScripts.length} Linode StackScripts`);
|
|
542
|
+
} catch (err) {
|
|
543
|
+
this.logger('error', 'Failed to collect Linode StackScripts', {
|
|
544
|
+
error: err.message,
|
|
545
|
+
stack: err.stack
|
|
546
|
+
});
|
|
547
|
+
throw err;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Collect Placement Groups (anti-affinity groups).
|
|
553
|
+
*/
|
|
554
|
+
async *_collectPlacementGroups() {
|
|
555
|
+
try {
|
|
556
|
+
const { getPlacementGroups } = await import('@linode/api-v4/lib/placement-groups');
|
|
557
|
+
|
|
558
|
+
const response = await getPlacementGroups();
|
|
559
|
+
const placementGroups = response.data || [];
|
|
560
|
+
|
|
561
|
+
for (const pg of placementGroups) {
|
|
562
|
+
yield {
|
|
563
|
+
provider: 'linode',
|
|
564
|
+
accountId: this._accountId,
|
|
565
|
+
region: pg.region,
|
|
566
|
+
service: 'placementgroups',
|
|
567
|
+
resourceType: 'linode.placementgroup',
|
|
568
|
+
resourceId: pg.id?.toString(),
|
|
569
|
+
name: pg.label,
|
|
570
|
+
tags: [],
|
|
571
|
+
metadata: {
|
|
572
|
+
placementGroupType: pg.placement_group_type,
|
|
573
|
+
placementGroupPolicy: pg.placement_group_policy
|
|
574
|
+
},
|
|
575
|
+
configuration: this._sanitize(pg)
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
this.logger('info', `Collected ${placementGroups.length} Linode placement groups`);
|
|
580
|
+
} catch (err) {
|
|
581
|
+
this.logger('error', 'Failed to collect Linode placement groups', {
|
|
582
|
+
error: err.message,
|
|
583
|
+
stack: err.stack
|
|
584
|
+
});
|
|
585
|
+
throw err;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Sanitize configuration by removing sensitive data.
|
|
591
|
+
*/
|
|
592
|
+
_sanitize(config) {
|
|
593
|
+
if (!config || typeof config !== 'object') return config;
|
|
594
|
+
|
|
595
|
+
const sanitized = { ...config };
|
|
596
|
+
const sensitiveFields = [
|
|
597
|
+
'root_pass',
|
|
598
|
+
'password',
|
|
599
|
+
'token',
|
|
600
|
+
'secret',
|
|
601
|
+
'api_key',
|
|
602
|
+
'private_key',
|
|
603
|
+
'public_key'
|
|
604
|
+
];
|
|
605
|
+
|
|
606
|
+
for (const field of sensitiveFields) {
|
|
607
|
+
if (field in sanitized) {
|
|
608
|
+
sanitized[field] = '***REDACTED***';
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return sanitized;
|
|
613
|
+
}
|
|
614
|
+
}
|