s3db.js 13.6.1 → 14.0.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/README.md +56 -15
- package/dist/s3db.cjs +72446 -39022
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72172 -38790
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +85 -50
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/index.js +168 -40
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -80,7 +80,13 @@ import { SchedulerError } from "./scheduler.errors.js";
|
|
|
80
80
|
* if (backupPlugin) {
|
|
81
81
|
* return await backupPlugin.backup('incremental');
|
|
82
82
|
* }
|
|
83
|
-
* throw new
|
|
83
|
+
* throw new PluginError('BackupPlugin not available', {
|
|
84
|
+
* pluginName: 'SchedulerPlugin',
|
|
85
|
+
* operation: 'backupJob',
|
|
86
|
+
* statusCode: 500,
|
|
87
|
+
* retriable: false,
|
|
88
|
+
* suggestion: 'Ensure BackupPlugin is installed before scheduling backup jobs.'
|
|
89
|
+
* });
|
|
84
90
|
* },
|
|
85
91
|
* retries: 2
|
|
86
92
|
* },
|
|
@@ -94,7 +100,13 @@ import { SchedulerError } from "./scheduler.errors.js";
|
|
|
94
100
|
* if (backupPlugin) {
|
|
95
101
|
* return await backupPlugin.backup('full');
|
|
96
102
|
* }
|
|
97
|
-
* throw new
|
|
103
|
+
* throw new PluginError('BackupPlugin not available', {
|
|
104
|
+
* pluginName: 'SchedulerPlugin',
|
|
105
|
+
* operation: 'backupJob',
|
|
106
|
+
* statusCode: 500,
|
|
107
|
+
* retriable: false,
|
|
108
|
+
* suggestion: 'Ensure BackupPlugin is installed before scheduling backup jobs.'
|
|
109
|
+
* });
|
|
98
110
|
* }
|
|
99
111
|
* },
|
|
100
112
|
*
|
|
@@ -414,8 +426,8 @@ export class SchedulerPlugin extends Plugin {
|
|
|
414
426
|
|
|
415
427
|
// Acquire distributed lock with TTL to prevent concurrent execution across instances
|
|
416
428
|
const storage = this.getStorage();
|
|
417
|
-
const
|
|
418
|
-
const lock = await storage.acquireLock(
|
|
429
|
+
const lockName = `job-${jobName}`;
|
|
430
|
+
const lock = await storage.acquireLock(lockName, {
|
|
419
431
|
ttl: Math.ceil(job.timeout / 1000) + 60, // Job timeout + 60 seconds buffer
|
|
420
432
|
timeout: 0, // Don't wait if locked
|
|
421
433
|
workerId: process.pid ? String(process.pid) : 'unknown'
|
|
@@ -571,7 +583,9 @@ export class SchedulerPlugin extends Plugin {
|
|
|
571
583
|
}
|
|
572
584
|
} finally {
|
|
573
585
|
// Always release the distributed lock
|
|
574
|
-
|
|
586
|
+
if (lock) {
|
|
587
|
+
await tryFn(() => storage.releaseLock(lock));
|
|
588
|
+
}
|
|
575
589
|
}
|
|
576
590
|
}
|
|
577
591
|
|
|
@@ -921,4 +935,4 @@ export class SchedulerPlugin extends Plugin {
|
|
|
921
935
|
this.activeJobs.clear();
|
|
922
936
|
this.removeAllListeners();
|
|
923
937
|
}
|
|
924
|
-
}
|
|
938
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Plugin } from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
|
+
import { resolveResourceNames } from "./concerns/resource-names.js";
|
|
3
4
|
import { StateMachineError } from "./state-machine.errors.js";
|
|
4
5
|
import { ErrorClassifier } from "../concerns/error-classifier.js";
|
|
5
6
|
|
|
@@ -102,13 +103,27 @@ export class StateMachinePlugin extends Plugin {
|
|
|
102
103
|
constructor(options = {}) {
|
|
103
104
|
super();
|
|
104
105
|
|
|
106
|
+
const resourceNamesOption = options.resourceNames || {};
|
|
107
|
+
|
|
108
|
+
this._resourceDescriptors = {
|
|
109
|
+
transitionLog: {
|
|
110
|
+
defaultName: 'plg_state_transitions',
|
|
111
|
+
override: resourceNamesOption.transitionLog || options.transitionLogResource
|
|
112
|
+
},
|
|
113
|
+
states: {
|
|
114
|
+
defaultName: 'plg_entity_states',
|
|
115
|
+
override: resourceNamesOption.states || options.stateResource
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
this.resourceNames = this._resolveResourceNames();
|
|
119
|
+
|
|
105
120
|
this.config = {
|
|
106
121
|
stateMachines: options.stateMachines || {},
|
|
107
122
|
actions: options.actions || {},
|
|
108
123
|
guards: options.guards || {},
|
|
109
124
|
persistTransitions: options.persistTransitions !== false,
|
|
110
|
-
transitionLogResource:
|
|
111
|
-
stateResource:
|
|
125
|
+
transitionLogResource: this.resourceNames.transitionLog,
|
|
126
|
+
stateResource: this.resourceNames.states,
|
|
112
127
|
retryAttempts: options.retryAttempts || 3,
|
|
113
128
|
retryDelay: options.retryDelay || 100,
|
|
114
129
|
verbose: options.verbose || false,
|
|
@@ -138,6 +153,20 @@ export class StateMachinePlugin extends Plugin {
|
|
|
138
153
|
this._validateConfiguration();
|
|
139
154
|
}
|
|
140
155
|
|
|
156
|
+
_resolveResourceNames() {
|
|
157
|
+
return resolveResourceNames('state_machine', this._resourceDescriptors, {
|
|
158
|
+
namespace: this.namespace
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
onNamespaceChanged() {
|
|
163
|
+
this.resourceNames = this._resolveResourceNames();
|
|
164
|
+
if (this.config) {
|
|
165
|
+
this.config.transitionLogResource = this.resourceNames.transitionLog;
|
|
166
|
+
this.config.stateResource = this.resourceNames.states;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
141
170
|
/**
|
|
142
171
|
* Wait for all pending event handlers to complete
|
|
143
172
|
* Useful when working with async events (asyncEvents: true)
|
|
@@ -290,7 +319,7 @@ export class StateMachinePlugin extends Plugin {
|
|
|
290
319
|
}
|
|
291
320
|
|
|
292
321
|
// Acquire distributed lock to prevent concurrent transitions
|
|
293
|
-
const
|
|
322
|
+
const lock = await this._acquireTransitionLock(machineId, entityId);
|
|
294
323
|
|
|
295
324
|
try {
|
|
296
325
|
const currentState = await this.getState(machineId, entityId);
|
|
@@ -366,7 +395,7 @@ export class StateMachinePlugin extends Plugin {
|
|
|
366
395
|
};
|
|
367
396
|
} finally {
|
|
368
397
|
// Always release lock, even if transition fails
|
|
369
|
-
await this._releaseTransitionLock(
|
|
398
|
+
await this._releaseTransitionLock(lock);
|
|
370
399
|
}
|
|
371
400
|
}
|
|
372
401
|
|
|
@@ -610,19 +639,21 @@ export class StateMachinePlugin extends Plugin {
|
|
|
610
639
|
});
|
|
611
640
|
}
|
|
612
641
|
|
|
613
|
-
return
|
|
642
|
+
return lock;
|
|
614
643
|
}
|
|
615
644
|
|
|
616
645
|
/**
|
|
617
646
|
* Release distributed lock for transition
|
|
618
647
|
* @private
|
|
619
648
|
*/
|
|
620
|
-
async _releaseTransitionLock(
|
|
649
|
+
async _releaseTransitionLock(lock) {
|
|
650
|
+
if (!lock) return;
|
|
651
|
+
|
|
621
652
|
const storage = this.getStorage();
|
|
622
|
-
const [ok, err] = await tryFn(() => storage.releaseLock(
|
|
653
|
+
const [ok, err] = await tryFn(() => storage.releaseLock(lock));
|
|
623
654
|
|
|
624
655
|
if (!ok && this.config.verbose) {
|
|
625
|
-
console.warn(`[StateMachinePlugin] Failed to release lock '${
|
|
656
|
+
console.warn(`[StateMachinePlugin] Failed to release lock '${lock?.name}':`, err.message);
|
|
626
657
|
}
|
|
627
658
|
}
|
|
628
659
|
|
|
@@ -1500,4 +1531,4 @@ export class StateMachinePlugin extends Plugin {
|
|
|
1500
1531
|
this.machines.clear();
|
|
1501
1532
|
this.removeAllListeners();
|
|
1502
1533
|
}
|
|
1503
|
-
}
|
|
1534
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { TfStateError } from './errors.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Base Driver Class for TfState Plugin
|
|
3
5
|
*
|
|
@@ -14,7 +16,12 @@ export class TfStateDriver {
|
|
|
14
16
|
* Called during plugin installation
|
|
15
17
|
*/
|
|
16
18
|
async initialize() {
|
|
17
|
-
throw new
|
|
19
|
+
throw new TfStateError('Driver must implement initialize()', {
|
|
20
|
+
operation: 'initialize',
|
|
21
|
+
statusCode: 501,
|
|
22
|
+
retriable: false,
|
|
23
|
+
suggestion: 'Extend TfStateDriver and implement initialize() to configure backend connections.'
|
|
24
|
+
});
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
/**
|
|
@@ -22,7 +29,12 @@ export class TfStateDriver {
|
|
|
22
29
|
* @returns {Promise<Array>} Array of state file metadata { path, lastModified, size }
|
|
23
30
|
*/
|
|
24
31
|
async listStateFiles() {
|
|
25
|
-
throw new
|
|
32
|
+
throw new TfStateError('Driver must implement listStateFiles()', {
|
|
33
|
+
operation: 'listStateFiles',
|
|
34
|
+
statusCode: 501,
|
|
35
|
+
retriable: false,
|
|
36
|
+
suggestion: 'Override listStateFiles() to return available Terraform state metadata.'
|
|
37
|
+
});
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
/**
|
|
@@ -31,7 +43,13 @@ export class TfStateDriver {
|
|
|
31
43
|
* @returns {Promise<Object>} Parsed state file content
|
|
32
44
|
*/
|
|
33
45
|
async readStateFile(path) {
|
|
34
|
-
throw new
|
|
46
|
+
throw new TfStateError('Driver must implement readStateFile()', {
|
|
47
|
+
operation: 'readStateFile',
|
|
48
|
+
statusCode: 501,
|
|
49
|
+
retriable: false,
|
|
50
|
+
suggestion: 'Override readStateFile(path) to load and parse the Terraform state JSON.',
|
|
51
|
+
path
|
|
52
|
+
});
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
/**
|
|
@@ -40,7 +58,13 @@ export class TfStateDriver {
|
|
|
40
58
|
* @returns {Promise<Object>} Metadata { path, lastModified, size, etag }
|
|
41
59
|
*/
|
|
42
60
|
async getStateFileMetadata(path) {
|
|
43
|
-
throw new
|
|
61
|
+
throw new TfStateError('Driver must implement getStateFileMetadata()', {
|
|
62
|
+
operation: 'getStateFileMetadata',
|
|
63
|
+
statusCode: 501,
|
|
64
|
+
retriable: false,
|
|
65
|
+
suggestion: 'Override getStateFileMetadata(path) to return lastModified, size, and ETag information.',
|
|
66
|
+
path
|
|
67
|
+
});
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PluginError } from '../../errors.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* TfStatePlugin Error Classes
|
|
3
5
|
* Custom errors for Terraform/OpenTofu state operations
|
|
@@ -6,12 +8,19 @@
|
|
|
6
8
|
/**
|
|
7
9
|
* Base error for all Terraform/OpenTofu state operations
|
|
8
10
|
*/
|
|
9
|
-
export class TfStateError extends
|
|
11
|
+
export class TfStateError extends PluginError {
|
|
10
12
|
constructor(message, context = {}) {
|
|
11
|
-
|
|
13
|
+
const merged = {
|
|
14
|
+
pluginName: context.pluginName || 'TfStatePlugin',
|
|
15
|
+
operation: context.operation || 'unknown',
|
|
16
|
+
statusCode: context.statusCode ?? 500,
|
|
17
|
+
retriable: context.retriable ?? false,
|
|
18
|
+
suggestion: context.suggestion ?? 'Verify Terraform/OpenTofu configuration and state storage before retrying.',
|
|
19
|
+
...context
|
|
20
|
+
};
|
|
21
|
+
super(message, merged);
|
|
12
22
|
this.name = 'TfStateError';
|
|
13
23
|
this.context = context;
|
|
14
|
-
Error.captureStackTrace(this, this.constructor);
|
|
15
24
|
}
|
|
16
25
|
}
|
|
17
26
|
|
|
@@ -20,7 +29,14 @@ export class TfStateError extends Error {
|
|
|
20
29
|
*/
|
|
21
30
|
export class InvalidStateFileError extends TfStateError {
|
|
22
31
|
constructor(filePath, reason, context = {}) {
|
|
23
|
-
super(`Invalid Tfstate file "${filePath}": ${reason}`,
|
|
32
|
+
super(`Invalid Tfstate file "${filePath}": ${reason}`, {
|
|
33
|
+
statusCode: context.statusCode ?? 422,
|
|
34
|
+
retriable: false,
|
|
35
|
+
suggestion: context.suggestion ?? 'Validate Terraform state integrity or re-run terraform state pull.',
|
|
36
|
+
filePath,
|
|
37
|
+
reason,
|
|
38
|
+
...context
|
|
39
|
+
});
|
|
24
40
|
this.name = 'InvalidStateFileError';
|
|
25
41
|
this.filePath = filePath;
|
|
26
42
|
this.reason = reason;
|
|
@@ -34,7 +50,14 @@ export class UnsupportedStateVersionError extends TfStateError {
|
|
|
34
50
|
constructor(version, supportedVersions, context = {}) {
|
|
35
51
|
super(
|
|
36
52
|
`Tfstate version ${version} is not supported. Supported versions: ${supportedVersions.join(', ')}`,
|
|
37
|
-
|
|
53
|
+
{
|
|
54
|
+
statusCode: context.statusCode ?? 400,
|
|
55
|
+
retriable: false,
|
|
56
|
+
suggestion: context.suggestion ?? `Upgrade/downgrade Terraform state to one of the supported versions: ${supportedVersions.join(', ')}.`,
|
|
57
|
+
version,
|
|
58
|
+
supportedVersions,
|
|
59
|
+
...context
|
|
60
|
+
}
|
|
38
61
|
);
|
|
39
62
|
this.name = 'UnsupportedStateVersionError';
|
|
40
63
|
this.version = version;
|
|
@@ -47,7 +70,13 @@ export class UnsupportedStateVersionError extends TfStateError {
|
|
|
47
70
|
*/
|
|
48
71
|
export class StateFileNotFoundError extends TfStateError {
|
|
49
72
|
constructor(filePath, context = {}) {
|
|
50
|
-
super(`Tfstate file not found: ${filePath}`,
|
|
73
|
+
super(`Tfstate file not found: ${filePath}`, {
|
|
74
|
+
statusCode: context.statusCode ?? 404,
|
|
75
|
+
retriable: false,
|
|
76
|
+
suggestion: context.suggestion ?? 'Ensure the state file exists at the configured path/bucket.',
|
|
77
|
+
filePath,
|
|
78
|
+
...context
|
|
79
|
+
});
|
|
51
80
|
this.name = 'StateFileNotFoundError';
|
|
52
81
|
this.filePath = filePath;
|
|
53
82
|
}
|
|
@@ -60,7 +89,13 @@ export class ResourceExtractionError extends TfStateError {
|
|
|
60
89
|
constructor(resourceAddress, originalError, context = {}) {
|
|
61
90
|
super(
|
|
62
91
|
`Failed to extract resource "${resourceAddress}": ${originalError.message}`,
|
|
63
|
-
|
|
92
|
+
{
|
|
93
|
+
retriable: context.retriable ?? false,
|
|
94
|
+
suggestion: context.suggestion ?? 'Check resource address and state structure; rerun extraction after fixing the state.',
|
|
95
|
+
resourceAddress,
|
|
96
|
+
originalError,
|
|
97
|
+
...context
|
|
98
|
+
}
|
|
64
99
|
);
|
|
65
100
|
this.name = 'ResourceExtractionError';
|
|
66
101
|
this.resourceAddress = resourceAddress;
|
|
@@ -75,7 +110,14 @@ export class StateDiffError extends TfStateError {
|
|
|
75
110
|
constructor(oldSerial, newSerial, originalError, context = {}) {
|
|
76
111
|
super(
|
|
77
112
|
`Failed to calculate diff between state serials ${oldSerial} and ${newSerial}: ${originalError.message}`,
|
|
78
|
-
|
|
113
|
+
{
|
|
114
|
+
retriable: context.retriable ?? true,
|
|
115
|
+
suggestion: context.suggestion ?? 'Refresh the latest state snapshots and retry the diff operation.',
|
|
116
|
+
oldSerial,
|
|
117
|
+
newSerial,
|
|
118
|
+
originalError,
|
|
119
|
+
...context
|
|
120
|
+
}
|
|
79
121
|
);
|
|
80
122
|
this.name = 'StateDiffError';
|
|
81
123
|
this.oldSerial = oldSerial;
|
|
@@ -89,7 +131,13 @@ export class StateDiffError extends TfStateError {
|
|
|
89
131
|
*/
|
|
90
132
|
export class FileWatchError extends TfStateError {
|
|
91
133
|
constructor(path, originalError, context = {}) {
|
|
92
|
-
super(`Failed to watch path "${path}": ${originalError.message}`,
|
|
134
|
+
super(`Failed to watch path "${path}": ${originalError.message}`, {
|
|
135
|
+
retriable: context.retriable ?? true,
|
|
136
|
+
suggestion: context.suggestion ?? 'Verify filesystem permissions and that the watch path exists.',
|
|
137
|
+
path,
|
|
138
|
+
originalError,
|
|
139
|
+
...context
|
|
140
|
+
});
|
|
93
141
|
this.name = 'FileWatchError';
|
|
94
142
|
this.path = path;
|
|
95
143
|
this.originalError = originalError;
|
|
@@ -103,7 +151,14 @@ export class ResourceFilterError extends TfStateError {
|
|
|
103
151
|
constructor(filterExpression, originalError, context = {}) {
|
|
104
152
|
super(
|
|
105
153
|
`Failed to apply resource filter "${filterExpression}": ${originalError.message}`,
|
|
106
|
-
|
|
154
|
+
{
|
|
155
|
+
statusCode: context.statusCode ?? 400,
|
|
156
|
+
retriable: context.retriable ?? false,
|
|
157
|
+
suggestion: context.suggestion ?? 'Validate the filter expression syntax and ensure referenced resources exist.',
|
|
158
|
+
filterExpression,
|
|
159
|
+
originalError,
|
|
160
|
+
...context
|
|
161
|
+
}
|
|
107
162
|
);
|
|
108
163
|
this.name = 'ResourceFilterError';
|
|
109
164
|
this.filterExpression = filterExpression;
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { TfStateDriver } from './base-driver.js';
|
|
8
8
|
import { readFile, stat } from 'fs/promises';
|
|
9
|
-
import { join
|
|
9
|
+
import { join } from 'path';
|
|
10
10
|
import { glob } from 'glob';
|
|
11
|
+
import { TfStateError, StateFileNotFoundError } from './errors.js';
|
|
11
12
|
|
|
12
13
|
export class FilesystemTfStateDriver extends TfStateDriver {
|
|
13
14
|
constructor(config = {}) {
|
|
@@ -23,10 +24,23 @@ export class FilesystemTfStateDriver extends TfStateDriver {
|
|
|
23
24
|
try {
|
|
24
25
|
const stats = await stat(this.basePath);
|
|
25
26
|
if (!stats.isDirectory()) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new TfStateError(`Base path is not a directory: ${this.basePath}`, {
|
|
28
|
+
operation: 'initialize',
|
|
29
|
+
statusCode: 400,
|
|
30
|
+
retriable: false,
|
|
31
|
+
suggestion: 'Update the TfState filesystem driver configuration to point to a directory containing .tfstate files.',
|
|
32
|
+
basePath: this.basePath
|
|
33
|
+
});
|
|
27
34
|
}
|
|
28
35
|
} catch (error) {
|
|
29
|
-
throw new
|
|
36
|
+
throw new TfStateError(`Invalid base path: ${this.basePath}`, {
|
|
37
|
+
operation: 'initialize',
|
|
38
|
+
statusCode: 400,
|
|
39
|
+
retriable: false,
|
|
40
|
+
suggestion: 'Ensure the basePath exists and is readable by the current process.',
|
|
41
|
+
basePath: this.basePath,
|
|
42
|
+
original: error
|
|
43
|
+
});
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
|
|
@@ -60,7 +74,15 @@ export class FilesystemTfStateDriver extends TfStateDriver {
|
|
|
60
74
|
|
|
61
75
|
return stateFiles;
|
|
62
76
|
} catch (error) {
|
|
63
|
-
throw new
|
|
77
|
+
throw new TfStateError('Failed to list Terraform state files', {
|
|
78
|
+
operation: 'listStateFiles',
|
|
79
|
+
statusCode: 500,
|
|
80
|
+
retriable: false,
|
|
81
|
+
suggestion: 'Verify filesystem permissions and glob selector pattern.',
|
|
82
|
+
selector: this.selector,
|
|
83
|
+
basePath: this.basePath,
|
|
84
|
+
original: error
|
|
85
|
+
});
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
|
|
@@ -77,9 +99,20 @@ export class FilesystemTfStateDriver extends TfStateDriver {
|
|
|
77
99
|
return JSON.parse(content);
|
|
78
100
|
} catch (error) {
|
|
79
101
|
if (error.code === 'ENOENT') {
|
|
80
|
-
throw new
|
|
102
|
+
throw new StateFileNotFoundError(path, {
|
|
103
|
+
operation: 'readStateFile',
|
|
104
|
+
retriable: false,
|
|
105
|
+
suggestion: 'Ensure the Terraform state file exists at the specified path.',
|
|
106
|
+
original: error
|
|
107
|
+
});
|
|
81
108
|
}
|
|
82
|
-
throw new
|
|
109
|
+
throw new TfStateError(`Failed to read state file ${path}`, {
|
|
110
|
+
operation: 'readStateFile',
|
|
111
|
+
retriable: false,
|
|
112
|
+
suggestion: 'Validate file permissions and state file contents (must be valid JSON).',
|
|
113
|
+
path,
|
|
114
|
+
original: error
|
|
115
|
+
});
|
|
83
116
|
}
|
|
84
117
|
}
|
|
85
118
|
|
|
@@ -103,9 +136,20 @@ export class FilesystemTfStateDriver extends TfStateDriver {
|
|
|
103
136
|
};
|
|
104
137
|
} catch (error) {
|
|
105
138
|
if (error.code === 'ENOENT') {
|
|
106
|
-
throw new
|
|
139
|
+
throw new StateFileNotFoundError(path, {
|
|
140
|
+
operation: 'getStateFileMetadata',
|
|
141
|
+
retriable: false,
|
|
142
|
+
suggestion: 'Ensure the Terraform state file exists at the specified path.',
|
|
143
|
+
original: error
|
|
144
|
+
});
|
|
107
145
|
}
|
|
108
|
-
throw new
|
|
146
|
+
throw new TfStateError(`Failed to get metadata for ${path}`, {
|
|
147
|
+
operation: 'getStateFileMetadata',
|
|
148
|
+
retriable: false,
|
|
149
|
+
suggestion: 'Check filesystem permissions and path configuration for TfStatePlugin.',
|
|
150
|
+
path,
|
|
151
|
+
original: error
|
|
152
|
+
});
|
|
109
153
|
}
|
|
110
154
|
}
|
|
111
155
|
|