s3db.js 13.6.0 → 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 +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- 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 +94 -49
- 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/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- 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/api/utils/template-engine.js +77 -3
- 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/README.md +126 -126
- 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
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
# TfState Plugin - Internal Development Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document is the internal development guide for the TfState Plugin.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 📋
|
|
7
|
+
## 📋 General Architecture
|
|
8
8
|
|
|
9
|
-
###
|
|
9
|
+
### Philosophy
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The TfState Plugin transforms Terraform states (`.tfstate` files) into queryable data in s3db.
|
|
12
12
|
|
|
13
|
-
**
|
|
14
|
-
- ✅ **
|
|
15
|
-
- ✅ **Performance**: Partitions
|
|
16
|
-
- ✅ **
|
|
17
|
-
- ✅ **
|
|
18
|
-
- ✅ **
|
|
13
|
+
**Principles:**
|
|
14
|
+
- ✅ **Simplicity**: Clear and direct API
|
|
15
|
+
- ✅ **Performance**: Partitions for fast queries (sync mode)
|
|
16
|
+
- ✅ **Flexibility**: Supports local files, S3, glob patterns
|
|
17
|
+
- ✅ **Traceability**: Diff tracking between versions
|
|
18
|
+
- ✅ **Deduplication**: SHA256 hash to avoid re-imports
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## 🗄️
|
|
22
|
+
## 🗄️ The 3 Resources
|
|
23
23
|
|
|
24
24
|
### 1. State Files Resource (`plg_tfstate_states`)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Stores metadata about each imported `.tfstate`.
|
|
27
27
|
|
|
28
|
-
**Schema
|
|
28
|
+
**Complete Schema:**
|
|
29
29
|
```javascript
|
|
30
30
|
{
|
|
31
|
-
id: 'string|required', // nanoid
|
|
31
|
+
id: 'string|required', // generated nanoid
|
|
32
32
|
sourceFile: 'string|required', // 'prod/terraform.tfstate'
|
|
33
|
-
serial: 'number|required', //
|
|
33
|
+
serial: 'number|required', // State serial
|
|
34
34
|
lineage: 'string', // Terraform lineage
|
|
35
35
|
terraformVersion: 'string', // e.g. '1.5.0'
|
|
36
|
-
resourceCount: 'number', //
|
|
37
|
-
sha256Hash: 'string|required', //
|
|
36
|
+
resourceCount: 'number', // How many resources
|
|
37
|
+
sha256Hash: 'string|required', // For dedup
|
|
38
38
|
importedAt: 'number|required', // timestamp
|
|
39
|
-
stateVersion: 'number' // 3
|
|
39
|
+
stateVersion: 'number' // 3 or 4
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
@@ -47,18 +47,18 @@ Armazena metadados sobre cada `.tfstate` importado.
|
|
|
47
47
|
bySerial: { fields: { serial: 'number' } }
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
asyncPartitions: false // Sync
|
|
50
|
+
asyncPartitions: false // Sync for immediate queries
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
**Queries
|
|
53
|
+
**Common Queries:**
|
|
54
54
|
```javascript
|
|
55
|
-
//
|
|
55
|
+
// Fetch latest version of a state
|
|
56
56
|
const latest = await stateFilesResource.listPartition({
|
|
57
57
|
partition: 'bySourceFile',
|
|
58
58
|
partitionValues: { sourceFile: 'prod/terraform.tfstate' }
|
|
59
59
|
}).then(results => results.sort((a, b) => b.serial - a.serial)[0]);
|
|
60
60
|
|
|
61
|
-
//
|
|
61
|
+
// Fetch specific serial
|
|
62
62
|
const v100 = await stateFilesResource.listPartition({
|
|
63
63
|
partition: 'bySerial',
|
|
64
64
|
partitionValues: { serial: 100 }
|
|
@@ -69,34 +69,34 @@ const v100 = await stateFilesResource.listPartition({
|
|
|
69
69
|
|
|
70
70
|
### 2. Resources Resource (`plg_tfstate_resources`)
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
The main resource containing all infrastructure resources extracted from states.
|
|
73
73
|
|
|
74
|
-
**Schema
|
|
74
|
+
**Complete Schema:**
|
|
75
75
|
```javascript
|
|
76
76
|
{
|
|
77
|
-
id: 'string|required', // nanoid
|
|
78
|
-
stateFileId: 'string|required', // FK
|
|
77
|
+
id: 'string|required', // generated nanoid
|
|
78
|
+
stateFileId: 'string|required', // FK to states resource
|
|
79
79
|
|
|
80
|
-
// Denormalized
|
|
81
|
-
stateSerial: 'number|required', //
|
|
82
|
-
sourceFile: 'string|required', //
|
|
80
|
+
// Denormalized for queries
|
|
81
|
+
stateSerial: 'number|required', // Which version it came from
|
|
82
|
+
sourceFile: 'string|required', // Which file it came from
|
|
83
83
|
|
|
84
|
-
//
|
|
84
|
+
// Resource identity
|
|
85
85
|
resourceType: 'string|required', // 'aws_instance'
|
|
86
86
|
resourceName: 'string|required', // 'web_server'
|
|
87
87
|
resourceAddress: 'string|required', // 'aws_instance.web_server'
|
|
88
88
|
providerName: 'string|required', // 'aws', 'google', 'azure', etc
|
|
89
89
|
|
|
90
|
-
//
|
|
91
|
-
mode: 'string', // 'managed'
|
|
92
|
-
attributes: 'json', //
|
|
93
|
-
dependencies: 'array', //
|
|
90
|
+
// Resource data
|
|
91
|
+
mode: 'string', // 'managed' or 'data'
|
|
92
|
+
attributes: 'json', // Complete resource attributes
|
|
93
|
+
dependencies: 'array', // Dependency list
|
|
94
94
|
|
|
95
95
|
importedAt: 'number|required' // timestamp
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
**Partitions (
|
|
99
|
+
**Partitions (critical for performance!):**
|
|
100
100
|
```javascript
|
|
101
101
|
{
|
|
102
102
|
byType: {
|
|
@@ -119,7 +119,7 @@ O resource principal contendo todos os recursos de infraestrutura extraídos dos
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
asyncPartitions: false //
|
|
122
|
+
asyncPartitions: false // IMPORTANT: Sync for immediate queries!
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
**Provider Detection Logic:**
|
|
@@ -146,21 +146,21 @@ function detectProvider(resourceType) {
|
|
|
146
146
|
}
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
**Queries
|
|
149
|
+
**Common Queries:**
|
|
150
150
|
```javascript
|
|
151
|
-
// Query
|
|
151
|
+
// Query by type (uses partition - O(1))
|
|
152
152
|
const ec2 = await resource.listPartition({
|
|
153
153
|
partition: 'byType',
|
|
154
154
|
partitionValues: { resourceType: 'aws_instance' }
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
// Query
|
|
157
|
+
// Query by provider (uses partition - O(1))
|
|
158
158
|
const awsResources = await resource.listPartition({
|
|
159
159
|
partition: 'byProvider',
|
|
160
160
|
partitionValues: { providerName: 'aws' }
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
// Query
|
|
163
|
+
// Query by provider + type (combined partition - O(1))
|
|
164
164
|
const awsRds = await resource.listPartition({
|
|
165
165
|
partition: 'byProviderAndType',
|
|
166
166
|
partitionValues: {
|
|
@@ -174,22 +174,22 @@ const awsRds = await resource.listPartition({
|
|
|
174
174
|
|
|
175
175
|
### 3. Diffs Resource (`plg_tfstate_diffs`)
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
Tracks changes between state versions.
|
|
178
178
|
|
|
179
|
-
**Schema
|
|
179
|
+
**Complete Schema:**
|
|
180
180
|
```javascript
|
|
181
181
|
{
|
|
182
|
-
id: 'string|required', // nanoid
|
|
183
|
-
sourceFile: 'string|required', //
|
|
184
|
-
oldSerial: 'number|required', //
|
|
185
|
-
newSerial: 'number|required', //
|
|
182
|
+
id: 'string|required', // generated nanoid
|
|
183
|
+
sourceFile: 'string|required', // Which state
|
|
184
|
+
oldSerial: 'number|required', // Old version
|
|
185
|
+
newSerial: 'number|required', // New version
|
|
186
186
|
|
|
187
187
|
summary: {
|
|
188
188
|
type: 'object',
|
|
189
189
|
props: {
|
|
190
|
-
addedCount: 'number', //
|
|
191
|
-
modifiedCount: 'number', //
|
|
192
|
-
deletedCount: 'number' //
|
|
190
|
+
addedCount: 'number', // How many added
|
|
191
|
+
modifiedCount: 'number', // How many modified
|
|
192
|
+
deletedCount: 'number' // How many deleted
|
|
193
193
|
}
|
|
194
194
|
},
|
|
195
195
|
|
|
@@ -220,7 +220,7 @@ Rastreia mudanças entre versões de states.
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
asyncPartitions: false // Sync
|
|
223
|
+
asyncPartitions: false // Sync for immediate queries
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
**Diff Calculation Logic:**
|
|
@@ -234,7 +234,7 @@ async function calculateDiff(oldState, newState) {
|
|
|
234
234
|
const deleted = [];
|
|
235
235
|
const modified = [];
|
|
236
236
|
|
|
237
|
-
//
|
|
237
|
+
// Detect added
|
|
238
238
|
for (const [address, resource] of Object.entries(newResources)) {
|
|
239
239
|
if (!oldResources[address]) {
|
|
240
240
|
added.push({
|
|
@@ -246,7 +246,7 @@ async function calculateDiff(oldState, newState) {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
//
|
|
249
|
+
// Detect deleted
|
|
250
250
|
for (const [address, resource] of Object.entries(oldResources)) {
|
|
251
251
|
if (!newResources[address]) {
|
|
252
252
|
deleted.push({
|
|
@@ -258,7 +258,7 @@ async function calculateDiff(oldState, newState) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
//
|
|
261
|
+
// Detect modified
|
|
262
262
|
for (const [address, newResource] of Object.entries(newResources)) {
|
|
263
263
|
const oldResource = oldResources[address];
|
|
264
264
|
if (oldResource) {
|
|
@@ -291,7 +291,7 @@ async function calculateDiff(oldState, newState) {
|
|
|
291
291
|
function detectChanges(oldAttrs, newAttrs, path = '') {
|
|
292
292
|
const changes = [];
|
|
293
293
|
|
|
294
|
-
//
|
|
294
|
+
// Compare each field
|
|
295
295
|
const allKeys = new Set([...Object.keys(oldAttrs), ...Object.keys(newAttrs)]);
|
|
296
296
|
|
|
297
297
|
for (const key of allKeys) {
|
|
@@ -314,43 +314,43 @@ function detectChanges(oldAttrs, newAttrs, path = '') {
|
|
|
314
314
|
|
|
315
315
|
---
|
|
316
316
|
|
|
317
|
-
## 🔧
|
|
317
|
+
## 🔧 Main Methods
|
|
318
318
|
|
|
319
319
|
### Import Flow
|
|
320
320
|
|
|
321
321
|
```
|
|
322
322
|
importState(filePath)
|
|
323
323
|
↓
|
|
324
|
-
1.
|
|
325
|
-
2.
|
|
326
|
-
3.
|
|
327
|
-
4.
|
|
328
|
-
5.
|
|
329
|
-
-
|
|
330
|
-
-
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
334
|
-
-
|
|
324
|
+
1. Read file from filesystem
|
|
325
|
+
2. Parse JSON
|
|
326
|
+
3. Calculate SHA256
|
|
327
|
+
4. Check if already exists (dedup)
|
|
328
|
+
5. If new:
|
|
329
|
+
- Create record in stateFilesResource
|
|
330
|
+
- Extract resources
|
|
331
|
+
- Create records in resource
|
|
332
|
+
- If previous version exists:
|
|
333
|
+
- Calculate diff
|
|
334
|
+
- Create record in diffsResource
|
|
335
335
|
```
|
|
336
336
|
|
|
337
|
-
**
|
|
337
|
+
**Code:**
|
|
338
338
|
```javascript
|
|
339
339
|
async importState(filePath, options = {}) {
|
|
340
|
-
// 1.
|
|
340
|
+
// 1. Read and parse
|
|
341
341
|
const content = await fs.readFile(filePath, 'utf8');
|
|
342
342
|
const state = JSON.parse(content);
|
|
343
343
|
|
|
344
344
|
// 2. SHA256
|
|
345
345
|
const sha256Hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
346
346
|
|
|
347
|
-
// 3.
|
|
347
|
+
// 3. Check if already exists
|
|
348
348
|
const existing = await this.stateFilesResource.query({ sha256Hash });
|
|
349
349
|
if (existing.length > 0) {
|
|
350
350
|
return { alreadyImported: true, stateFileId: existing[0].id };
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
// 4.
|
|
353
|
+
// 4. Create state file record
|
|
354
354
|
const sourceFile = options.sourceFile || path.basename(filePath);
|
|
355
355
|
const stateFileRecord = await this.stateFilesResource.insert({
|
|
356
356
|
sourceFile,
|
|
@@ -363,10 +363,10 @@ async importState(filePath, options = {}) {
|
|
|
363
363
|
stateVersion: state.version
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
-
// 5.
|
|
366
|
+
// 5. Extract and insert resources
|
|
367
367
|
const extractedResources = await this._extractResources(state, stateFileRecord.id);
|
|
368
368
|
|
|
369
|
-
// 6.
|
|
369
|
+
// 6. Calculate diff if previous version exists
|
|
370
370
|
if (this.trackDiffs) {
|
|
371
371
|
await this._maybeCalculateDiff(sourceFile, state.serial);
|
|
372
372
|
}
|
|
@@ -386,12 +386,12 @@ async _extractResources(state, stateFileId) {
|
|
|
386
386
|
const extracted = [];
|
|
387
387
|
|
|
388
388
|
for (const resource of resources) {
|
|
389
|
-
//
|
|
389
|
+
// Apply filters
|
|
390
390
|
if (!this._shouldIncludeResource(resource)) {
|
|
391
391
|
continue;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
//
|
|
394
|
+
// Process each resource instance
|
|
395
395
|
for (const instance of resource.instances || []) {
|
|
396
396
|
const providerName = this._detectProvider(resource.type);
|
|
397
397
|
|
|
@@ -418,14 +418,14 @@ async _extractResources(state, stateFileId) {
|
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
_shouldIncludeResource(resource) {
|
|
421
|
-
//
|
|
421
|
+
// Filter by type
|
|
422
422
|
if (this.filters?.types && this.filters.types.length > 0) {
|
|
423
423
|
if (!this.filters.types.includes(resource.type)) {
|
|
424
424
|
return false;
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
//
|
|
428
|
+
// Filter by provider
|
|
429
429
|
if (this.filters?.providers && this.filters.providers.length > 0) {
|
|
430
430
|
const provider = this._detectProvider(resource.type);
|
|
431
431
|
if (!this.filters.providers.includes(provider)) {
|
|
@@ -433,7 +433,7 @@ _shouldIncludeResource(resource) {
|
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
//
|
|
436
|
+
// Exclusion filter
|
|
437
437
|
if (this.filters?.exclude && this.filters.exclude.length > 0) {
|
|
438
438
|
for (const pattern of this.filters.exclude) {
|
|
439
439
|
if (this._matchesPattern(resource.type, pattern)) {
|
|
@@ -470,24 +470,24 @@ _detectProvider(resourceType) {
|
|
|
470
470
|
|
|
471
471
|
```javascript
|
|
472
472
|
async _maybeCalculateDiff(sourceFile, newSerial) {
|
|
473
|
-
//
|
|
473
|
+
// Fetch previous version
|
|
474
474
|
const previousStates = await this.stateFilesResource.listPartition({
|
|
475
475
|
partition: 'bySourceFile',
|
|
476
476
|
partitionValues: { sourceFile }
|
|
477
477
|
});
|
|
478
478
|
|
|
479
479
|
if (previousStates.length < 2) {
|
|
480
|
-
return; //
|
|
480
|
+
return; // First version, no diff
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
//
|
|
483
|
+
// Sort by serial
|
|
484
484
|
previousStates.sort((a, b) => b.serial - a.serial);
|
|
485
485
|
|
|
486
486
|
const newState = previousStates[0];
|
|
487
487
|
const oldState = previousStates[1];
|
|
488
488
|
|
|
489
489
|
if (newState.serial === newSerial) {
|
|
490
|
-
//
|
|
490
|
+
// Fetch resources from both versions
|
|
491
491
|
const newResources = await this.resource.listPartition({
|
|
492
492
|
partition: 'bySerial',
|
|
493
493
|
partitionValues: { stateSerial: newState.serial }
|
|
@@ -498,10 +498,10 @@ async _maybeCalculateDiff(sourceFile, newSerial) {
|
|
|
498
498
|
partitionValues: { stateSerial: oldState.serial }
|
|
499
499
|
});
|
|
500
500
|
|
|
501
|
-
//
|
|
501
|
+
// Calculate diff
|
|
502
502
|
const diff = this._calculateDiff(oldResources, newResources);
|
|
503
503
|
|
|
504
|
-
//
|
|
504
|
+
// Save diff
|
|
505
505
|
await this.diffsResource.insert({
|
|
506
506
|
sourceFile,
|
|
507
507
|
oldSerial: oldState.serial,
|
|
@@ -518,7 +518,7 @@ async _maybeCalculateDiff(sourceFile, newSerial) {
|
|
|
518
518
|
|
|
519
519
|
## 🎯 Query Helpers
|
|
520
520
|
|
|
521
|
-
|
|
521
|
+
Convenient methods that use partitions for fast queries:
|
|
522
522
|
|
|
523
523
|
```javascript
|
|
524
524
|
async getResourcesByType(type) {
|
|
@@ -563,7 +563,7 @@ async getLatestDiff(sourceFile) {
|
|
|
563
563
|
|
|
564
564
|
if (diffs.length === 0) return null;
|
|
565
565
|
|
|
566
|
-
//
|
|
566
|
+
// Sort by calculatedAt desc
|
|
567
567
|
diffs.sort((a, b) => b.calculatedAt - a.calculatedAt);
|
|
568
568
|
return diffs[0];
|
|
569
569
|
}
|
|
@@ -640,33 +640,33 @@ async getStatsByType() {
|
|
|
640
640
|
|
|
641
641
|
## ⚡ Performance Considerations
|
|
642
642
|
|
|
643
|
-
### 1. Partitions
|
|
643
|
+
### 1. Partitions in Sync Mode
|
|
644
644
|
|
|
645
|
-
**
|
|
645
|
+
**CRITICAL**: All 3 resources use `asyncPartitions: false`.
|
|
646
646
|
|
|
647
|
-
**
|
|
648
|
-
- Queries
|
|
649
|
-
-
|
|
650
|
-
- Diff tracking
|
|
647
|
+
**Why?**
|
|
648
|
+
- Queries need to be immediate after import
|
|
649
|
+
- Async partitions create race conditions
|
|
650
|
+
- Diff tracking requires immediate data
|
|
651
651
|
|
|
652
652
|
**Trade-off:**
|
|
653
|
-
- Insert
|
|
654
|
-
- Queries
|
|
653
|
+
- Insert is slightly slower (but still fast)
|
|
654
|
+
- Queries are O(1) using partitions
|
|
655
655
|
|
|
656
|
-
### 2.
|
|
656
|
+
### 2. Denormalization
|
|
657
657
|
|
|
658
|
-
|
|
658
|
+
Fields `stateSerial` and `sourceFile` are denormalized in the resources resource to enable fast queries without joins.
|
|
659
659
|
|
|
660
660
|
### 3. SHA256 Deduplication
|
|
661
661
|
|
|
662
|
-
|
|
662
|
+
Before importing, we always check if SHA256 already exists. This avoids unnecessary re-imports.
|
|
663
663
|
|
|
664
664
|
### 4. Batch Operations
|
|
665
665
|
|
|
666
|
-
|
|
666
|
+
For glob imports, we process in parallel but with limit:
|
|
667
667
|
|
|
668
668
|
```javascript
|
|
669
|
-
const concurrency = 5; // Max 5 imports
|
|
669
|
+
const concurrency = 5; // Max 5 simultaneous imports
|
|
670
670
|
await PromisePool
|
|
671
671
|
.withConcurrency(concurrency)
|
|
672
672
|
.for(files)
|
|
@@ -679,62 +679,62 @@ await PromisePool
|
|
|
679
679
|
|
|
680
680
|
### 1. Unit Tests
|
|
681
681
|
|
|
682
|
-
|
|
683
|
-
- `_detectProvider()` →
|
|
684
|
-
- `_shouldIncludeResource()` →
|
|
685
|
-
- `_calculateDiff()` →
|
|
682
|
+
Test isolated methods:
|
|
683
|
+
- `_detectProvider()` → Correct provider detection
|
|
684
|
+
- `_shouldIncludeResource()` → Filters working
|
|
685
|
+
- `_calculateDiff()` → Correct diff calculation
|
|
686
686
|
|
|
687
687
|
### 2. Integration Tests
|
|
688
688
|
|
|
689
|
-
|
|
690
|
-
- Import →
|
|
691
|
-
- Import 2x →
|
|
692
|
-
- Import v1 + v2 →
|
|
689
|
+
Test complete flows:
|
|
690
|
+
- Import → Verify resources created
|
|
691
|
+
- Import 2x → Verify dedup works
|
|
692
|
+
- Import v1 + v2 → Verify diff created
|
|
693
693
|
|
|
694
694
|
### 3. Partition Tests
|
|
695
695
|
|
|
696
|
-
|
|
697
|
-
- `getResourcesByType()` →
|
|
698
|
-
- `getResourcesByProvider()` →
|
|
699
|
-
- `getResourcesByProviderAndType()` →
|
|
696
|
+
Test queries using partitions:
|
|
697
|
+
- `getResourcesByType()` → Should use partition
|
|
698
|
+
- `getResourcesByProvider()` → Should use partition
|
|
699
|
+
- `getResourcesByProviderAndType()` → Should use combined partition
|
|
700
700
|
|
|
701
701
|
### 4. Performance Tests
|
|
702
702
|
|
|
703
|
-
|
|
703
|
+
Verify partitions are fast:
|
|
704
704
|
- Import 1000 resources
|
|
705
|
-
- Query
|
|
705
|
+
- Query by type → Should be < 100ms
|
|
706
706
|
|
|
707
707
|
---
|
|
708
708
|
|
|
709
709
|
## 🐛 Common Issues
|
|
710
710
|
|
|
711
|
-
### Issue: Partitions
|
|
711
|
+
### Issue: Partitions return empty
|
|
712
712
|
|
|
713
|
-
**
|
|
713
|
+
**Cause**: `asyncPartitions: true` (default)
|
|
714
714
|
|
|
715
|
-
**
|
|
715
|
+
**Solution**: Always use `asyncPartitions: false` in all 3 resources
|
|
716
716
|
|
|
717
|
-
### Issue: Diff
|
|
717
|
+
### Issue: Diff not being created
|
|
718
718
|
|
|
719
|
-
**
|
|
719
|
+
**Cause**: `trackDiffs: false` or first version of state
|
|
720
720
|
|
|
721
|
-
**
|
|
721
|
+
**Solution**: Verify that `trackDiffs: true` and there are at least 2 versions of the state
|
|
722
722
|
|
|
723
|
-
### Issue:
|
|
723
|
+
### Issue: Wrong provider detection
|
|
724
724
|
|
|
725
|
-
**
|
|
725
|
+
**Cause**: Provider not in `providerMap`
|
|
726
726
|
|
|
727
|
-
**
|
|
727
|
+
**Solution**: Add provider to map in `_detectProvider()`
|
|
728
728
|
|
|
729
729
|
---
|
|
730
730
|
|
|
731
731
|
## 🚀 Future Enhancements
|
|
732
732
|
|
|
733
|
-
1. **Partial imports**:
|
|
734
|
-
2. **Compression**:
|
|
735
|
-
3. **Resource relationships**:
|
|
736
|
-
4. **Cost estimation**:
|
|
737
|
-
5. **Compliance checks**:
|
|
733
|
+
1. **Partial imports**: Import only modified resources
|
|
734
|
+
2. **Compression**: Compress `attributes` JSON to save space
|
|
735
|
+
3. **Resource relationships**: Map dependencies between resources
|
|
736
|
+
4. **Cost estimation**: Integrate with pricing APIs
|
|
737
|
+
5. **Compliance checks**: Validate resources against policies
|
|
738
738
|
|
|
739
739
|
---
|
|
740
740
|
|
|
@@ -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
|
/**
|