infrawise 0.4.2 → 0.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 +21 -48
- package/dist/adapters/aws.js +50 -64
- package/dist/adapters/dynamodb.js +17 -22
- package/dist/adapters/logs.js +12 -16
- package/dist/adapters/mongodb.js +10 -16
- package/dist/adapters/mysql.js +8 -17
- package/dist/adapters/postgres.js +9 -13
- package/dist/adapters/terraform.js +14 -56
- package/dist/analyzers/aws-services.js +7 -17
- package/dist/analyzers/dynamodb.js +6 -12
- package/dist/analyzers/index.js +13 -41
- package/dist/analyzers/mongodb.js +4 -9
- package/dist/analyzers/mysql.js +4 -9
- package/dist/analyzers/postgres.js +3 -9
- package/dist/analyzers/rds.js +5 -13
- package/dist/analyzers/terraform.js +1 -5
- package/dist/cli/commands/analyze.js +178 -215
- package/dist/cli/commands/auth.js +24 -30
- package/dist/cli/commands/dev.js +84 -43
- package/dist/cli/commands/doctor.js +35 -74
- package/dist/cli/commands/init.js +33 -72
- package/dist/cli/index.js +21 -28
- package/dist/cli/utils.js +40 -86
- package/dist/context/index.js +49 -85
- package/dist/core/cache.js +5 -43
- package/dist/core/config.js +43 -81
- package/dist/core/errors.js +7 -17
- package/dist/core/index.js +4 -22
- package/dist/core/logger.js +4 -10
- package/dist/graph/index.js +15 -32
- package/dist/server/index.js +236 -302
- package/dist/types.js +1 -2
- package/package.json +34 -31
package/README.md
CHANGED
|
@@ -78,6 +78,8 @@ infrawise doctor
|
|
|
78
78
|
infrawise analyze
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
Or skip this step — `infrawise dev` auto-runs analysis if no cache exists.
|
|
82
|
+
|
|
81
83
|
```
|
|
82
84
|
Findings (3 total)
|
|
83
85
|
|
|
@@ -104,11 +106,22 @@ infrawise dev
|
|
|
104
106
|
```
|
|
105
107
|
|
|
106
108
|
```
|
|
107
|
-
✔
|
|
108
|
-
✔
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
✔ Config loaded infrawise.yaml
|
|
110
|
+
✔ Cached analysis loaded 42 nodes · 18 edges · 7 finding(s)
|
|
111
|
+
✔ Server running
|
|
112
|
+
|
|
113
|
+
┌────────────────────────────────────────────────────┐
|
|
114
|
+
│ MCP Server │
|
|
115
|
+
├────────────────────────────────────────────────────┤
|
|
116
|
+
│ POST http://localhost:3000/mcp │
|
|
117
|
+
│ GET http://localhost:3000/health │
|
|
118
|
+
├────────────────────────────────────────────────────┤
|
|
119
|
+
│ Tools (13 active) │
|
|
120
|
+
│ get_infra_overview · get_graph_summary │
|
|
121
|
+
│ ... │
|
|
122
|
+
└────────────────────────────────────────────────────┘
|
|
123
|
+
|
|
124
|
+
Watching for file changes... Press Ctrl+C to stop
|
|
112
125
|
```
|
|
113
126
|
|
|
114
127
|
### Step 2: Add to your editor settings
|
|
@@ -160,41 +173,6 @@ To let Claude Code manage the server lifecycle automatically:
|
|
|
160
173
|
|
|
161
174
|
---
|
|
162
175
|
|
|
163
|
-
## GitHub Action
|
|
164
|
-
|
|
165
|
-
Add infrastructure analysis to any PR workflow. No `infrawise.yaml` required — the action auto-generates one from your AWS credentials.
|
|
166
|
-
|
|
167
|
-
```yaml
|
|
168
|
-
# .github/workflows/infrawise.yml
|
|
169
|
-
name: Infrawise
|
|
170
|
-
on: [pull_request]
|
|
171
|
-
|
|
172
|
-
jobs:
|
|
173
|
-
analyze:
|
|
174
|
-
runs-on: ubuntu-latest
|
|
175
|
-
steps:
|
|
176
|
-
- uses: actions/checkout@v4
|
|
177
|
-
- uses: Sidd27/infrawise@v0
|
|
178
|
-
with:
|
|
179
|
-
fail-on: high # fail the PR on high severity findings (default)
|
|
180
|
-
env:
|
|
181
|
-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
182
|
-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
183
|
-
AWS_REGION: ${{ secrets.AWS_REGION }}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
If `infrawise.yaml` is not present in the repo, the action generates a minimal config with DynamoDB, Lambda, SQS, SNS, SSM, Secrets Manager, and RDS enabled — using `AWS_REGION` from the environment. Add an `infrawise.yaml` to your repo to customize which services to include or to add database connections.
|
|
187
|
-
|
|
188
|
-
Findings appear as annotations on the PR diff and in the job summary.
|
|
189
|
-
|
|
190
|
-
| Input | Default | Description |
|
|
191
|
-
|---|---|---|
|
|
192
|
-
| `version` | `latest` | infrawise version to install |
|
|
193
|
-
| `fail-on` | `high` | Exit code 1 if findings at or above this severity (`high`, `medium`, `low`, `never`) |
|
|
194
|
-
| `config` | `infrawise.yaml` | Path to config file — auto-generated if not present |
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
176
|
## CLI reference
|
|
199
177
|
|
|
200
178
|
| Command | What it does |
|
|
@@ -202,7 +180,7 @@ Findings appear as annotations on the PR diff and in the job summary.
|
|
|
202
180
|
| `infrawise init` | Detect AWS + repo, generate `infrawise.yaml` |
|
|
203
181
|
| `infrawise auth` | Select or switch AWS profile |
|
|
204
182
|
| `infrawise analyze` | Scan repo + AWS, build graph, print findings |
|
|
205
|
-
| `infrawise dev` | Start MCP server
|
|
183
|
+
| `infrawise dev` | Start MCP server — auto-analyzes if no cache, watches files for live refresh |
|
|
206
184
|
| `infrawise doctor` | Validate AWS access, DB connectivity, and config |
|
|
207
185
|
|
|
208
186
|
---
|
|
@@ -425,7 +403,7 @@ src/
|
|
|
425
403
|
mongodb.ts, aws.ts, logs.ts, terraform.ts
|
|
426
404
|
analyzers/ 23 rule-based analyzers
|
|
427
405
|
context/ Repository scanner (ts-morph AST)
|
|
428
|
-
server/ Fastify MCP
|
|
406
|
+
server/ Fastify MCP server (@modelcontextprotocol/sdk, Streamable HTTP)
|
|
429
407
|
cli/ CLI commands (Commander.js)
|
|
430
408
|
```
|
|
431
409
|
|
|
@@ -503,19 +481,14 @@ export class MyAnalyzer implements Analyzer {
|
|
|
503
481
|
|
|
504
482
|
### Releasing
|
|
505
483
|
|
|
506
|
-
The git tag is the source of truth. The version in root `package.json` and the tag must always match.
|
|
507
|
-
|
|
508
484
|
```bash
|
|
509
485
|
pnpm release patch # 0.1.2 → 0.1.3 (bug fixes)
|
|
510
486
|
pnpm release minor # 0.1.2 → 0.2.0 (new features, backwards compatible)
|
|
511
487
|
pnpm release major # 0.1.2 → 1.0.0 (breaking changes)
|
|
512
488
|
pnpm release 1.5.0 # explicit version
|
|
513
|
-
|
|
514
|
-
git push origin main v1.2.3
|
|
515
|
-
git push origin v1 --force # update the floating major tag
|
|
516
489
|
```
|
|
517
490
|
|
|
518
|
-
|
|
491
|
+
Bumps `package.json`, commits, tags, pushes, and creates a draft GitHub release with notes from commit messages. Then publish the draft on GitHub to trigger npm publish.
|
|
519
492
|
|
|
520
493
|
### PR checklist
|
|
521
494
|
|
package/dist/adapters/aws.js
CHANGED
|
@@ -1,32 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
exports.extractSecretsMetadata = extractSecretsMetadata;
|
|
10
|
-
exports.validateSecretsAccess = validateSecretsAccess;
|
|
11
|
-
exports.extractLambdaMetadata = extractLambdaMetadata;
|
|
12
|
-
exports.validateLambdaAccess = validateLambdaAccess;
|
|
13
|
-
exports.extractRDSMetadata = extractRDSMetadata;
|
|
14
|
-
exports.validateRDSAccess = validateRDSAccess;
|
|
15
|
-
const client_sqs_1 = require("@aws-sdk/client-sqs");
|
|
16
|
-
const client_sns_1 = require("@aws-sdk/client-sns");
|
|
17
|
-
const client_ssm_1 = require("@aws-sdk/client-ssm");
|
|
18
|
-
const client_secrets_manager_1 = require("@aws-sdk/client-secrets-manager");
|
|
19
|
-
const client_lambda_1 = require("@aws-sdk/client-lambda");
|
|
20
|
-
const client_rds_1 = require("@aws-sdk/client-rds");
|
|
21
|
-
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
22
|
-
const core_1 = require("../core");
|
|
1
|
+
import { SQSClient, ListQueuesCommand, GetQueueAttributesCommand, } from '@aws-sdk/client-sqs';
|
|
2
|
+
import { SNSClient, ListTopicsCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, } from '@aws-sdk/client-sns';
|
|
3
|
+
import { SSMClient, DescribeParametersCommand, } from '@aws-sdk/client-ssm';
|
|
4
|
+
import { SecretsManagerClient, ListSecretsCommand, } from '@aws-sdk/client-secrets-manager';
|
|
5
|
+
import { LambdaClient, ListFunctionsCommand, } from '@aws-sdk/client-lambda';
|
|
6
|
+
import { RDSClient, DescribeDBInstancesCommand, } from '@aws-sdk/client-rds';
|
|
7
|
+
import { fromIni } from '@aws-sdk/credential-providers';
|
|
8
|
+
import { logger } from '../core/index.js';
|
|
23
9
|
function clientConfig(cfg) {
|
|
24
10
|
const region = cfg.region ?? 'us-east-1';
|
|
25
11
|
const base = { region };
|
|
26
12
|
if (cfg.endpoint)
|
|
27
13
|
base.endpoint = cfg.endpoint;
|
|
28
14
|
if (cfg.profile)
|
|
29
|
-
base.credentials =
|
|
15
|
+
base.credentials = fromIni({ profile: cfg.profile });
|
|
30
16
|
else if (cfg.endpoint)
|
|
31
17
|
base.credentials = {
|
|
32
18
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'test',
|
|
@@ -35,20 +21,20 @@ function clientConfig(cfg) {
|
|
|
35
21
|
return base;
|
|
36
22
|
}
|
|
37
23
|
// ─── SQS ─────────────────────────────────────────────────────────────────────
|
|
38
|
-
async function extractSQSMetadata(cfg = {}) {
|
|
39
|
-
const client = new
|
|
24
|
+
export async function extractSQSMetadata(cfg = {}) {
|
|
25
|
+
const client = new SQSClient(clientConfig(cfg));
|
|
40
26
|
const queues = [];
|
|
41
27
|
try {
|
|
42
28
|
let nextToken;
|
|
43
29
|
const queueUrls = [];
|
|
44
30
|
do {
|
|
45
|
-
const res = await client.send(new
|
|
31
|
+
const res = await client.send(new ListQueuesCommand({ NextToken: nextToken, MaxResults: 1000 }));
|
|
46
32
|
queueUrls.push(...(res.QueueUrls ?? []));
|
|
47
33
|
nextToken = res.NextToken;
|
|
48
34
|
} while (nextToken);
|
|
49
35
|
for (const url of queueUrls) {
|
|
50
36
|
try {
|
|
51
|
-
const attrs = await client.send(new
|
|
37
|
+
const attrs = await client.send(new GetQueueAttributesCommand({
|
|
52
38
|
QueueUrl: url,
|
|
53
39
|
AttributeNames: [
|
|
54
40
|
'QueueArn', 'VisibilityTimeout', 'MessageRetentionPeriod',
|
|
@@ -79,35 +65,35 @@ async function extractSQSMetadata(cfg = {}) {
|
|
|
79
65
|
});
|
|
80
66
|
}
|
|
81
67
|
catch (err) {
|
|
82
|
-
|
|
68
|
+
logger.warn(`SQS attrs failed for ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
83
69
|
}
|
|
84
70
|
}
|
|
85
71
|
}
|
|
86
72
|
catch (err) {
|
|
87
|
-
|
|
73
|
+
logger.warn(`SQS list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
88
74
|
}
|
|
89
75
|
return queues;
|
|
90
76
|
}
|
|
91
|
-
async function validateSQSAccess(cfg = {}) {
|
|
92
|
-
await new
|
|
77
|
+
export async function validateSQSAccess(cfg = {}) {
|
|
78
|
+
await new SQSClient(clientConfig(cfg)).send(new ListQueuesCommand({ MaxResults: 1 }));
|
|
93
79
|
}
|
|
94
80
|
// ─── SNS ─────────────────────────────────────────────────────────────────────
|
|
95
|
-
async function extractSNSMetadata(cfg = {}) {
|
|
96
|
-
const client = new
|
|
81
|
+
export async function extractSNSMetadata(cfg = {}) {
|
|
82
|
+
const client = new SNSClient(clientConfig(cfg));
|
|
97
83
|
const topics = [];
|
|
98
84
|
try {
|
|
99
85
|
let nextToken;
|
|
100
86
|
const topicArns = [];
|
|
101
87
|
do {
|
|
102
|
-
const res = await client.send(new
|
|
88
|
+
const res = await client.send(new ListTopicsCommand({ NextToken: nextToken }));
|
|
103
89
|
topicArns.push(...(res.Topics ?? []).map((t) => t.TopicArn ?? '').filter(Boolean));
|
|
104
90
|
nextToken = res.NextToken;
|
|
105
91
|
} while (nextToken);
|
|
106
92
|
for (const arn of topicArns) {
|
|
107
93
|
try {
|
|
108
94
|
const [attrsRes, subsRes] = await Promise.all([
|
|
109
|
-
client.send(new
|
|
110
|
-
client.send(new
|
|
95
|
+
client.send(new GetTopicAttributesCommand({ TopicArn: arn })),
|
|
96
|
+
client.send(new ListSubscriptionsByTopicCommand({ TopicArn: arn })),
|
|
111
97
|
]);
|
|
112
98
|
const attrs = attrsRes.Attributes ?? {};
|
|
113
99
|
const subs = subsRes.Subscriptions ?? [];
|
|
@@ -120,26 +106,26 @@ async function extractSNSMetadata(cfg = {}) {
|
|
|
120
106
|
});
|
|
121
107
|
}
|
|
122
108
|
catch (err) {
|
|
123
|
-
|
|
109
|
+
logger.warn(`SNS attrs failed for ${arn}: ${err instanceof Error ? err.message : String(err)}`);
|
|
124
110
|
}
|
|
125
111
|
}
|
|
126
112
|
}
|
|
127
113
|
catch (err) {
|
|
128
|
-
|
|
114
|
+
logger.warn(`SNS list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
129
115
|
}
|
|
130
116
|
return topics;
|
|
131
117
|
}
|
|
132
|
-
async function validateSNSAccess(cfg = {}) {
|
|
133
|
-
await new
|
|
118
|
+
export async function validateSNSAccess(cfg = {}) {
|
|
119
|
+
await new SNSClient(clientConfig(cfg)).send(new ListTopicsCommand({}));
|
|
134
120
|
}
|
|
135
121
|
// ─── SSM Parameter Store ──────────────────────────────────────────────────────
|
|
136
|
-
async function extractSSMMetadata(cfg = {}) {
|
|
137
|
-
const client = new
|
|
122
|
+
export async function extractSSMMetadata(cfg = {}) {
|
|
123
|
+
const client = new SSMClient(clientConfig(cfg));
|
|
138
124
|
const parameters = [];
|
|
139
125
|
try {
|
|
140
126
|
let nextToken;
|
|
141
127
|
do {
|
|
142
|
-
const res = await client.send(new
|
|
128
|
+
const res = await client.send(new DescribeParametersCommand({
|
|
143
129
|
NextToken: nextToken,
|
|
144
130
|
MaxResults: 50,
|
|
145
131
|
// Only metadata — GetParameter/GetParameters would return values
|
|
@@ -158,22 +144,22 @@ async function extractSSMMetadata(cfg = {}) {
|
|
|
158
144
|
} while (nextToken && parameters.length < 500);
|
|
159
145
|
}
|
|
160
146
|
catch (err) {
|
|
161
|
-
|
|
147
|
+
logger.warn(`SSM list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
162
148
|
}
|
|
163
149
|
return parameters;
|
|
164
150
|
}
|
|
165
|
-
async function validateSSMAccess(cfg = {}) {
|
|
166
|
-
await new
|
|
151
|
+
export async function validateSSMAccess(cfg = {}) {
|
|
152
|
+
await new SSMClient(clientConfig(cfg)).send(new DescribeParametersCommand({ MaxResults: 1 }));
|
|
167
153
|
}
|
|
168
154
|
// ─── Secrets Manager ──────────────────────────────────────────────────────────
|
|
169
|
-
async function extractSecretsMetadata(cfg = {}) {
|
|
170
|
-
const client = new
|
|
155
|
+
export async function extractSecretsMetadata(cfg = {}) {
|
|
156
|
+
const client = new SecretsManagerClient(clientConfig(cfg));
|
|
171
157
|
const secrets = [];
|
|
172
158
|
try {
|
|
173
159
|
let nextToken;
|
|
174
160
|
do {
|
|
175
161
|
// ListSecrets never returns secret values
|
|
176
|
-
const res = await client.send(new
|
|
162
|
+
const res = await client.send(new ListSecretsCommand({ NextToken: nextToken, MaxResults: 100 }));
|
|
177
163
|
for (const s of res.SecretList ?? []) {
|
|
178
164
|
secrets.push({
|
|
179
165
|
name: s.Name ?? '',
|
|
@@ -189,21 +175,21 @@ async function extractSecretsMetadata(cfg = {}) {
|
|
|
189
175
|
} while (nextToken && secrets.length < 200);
|
|
190
176
|
}
|
|
191
177
|
catch (err) {
|
|
192
|
-
|
|
178
|
+
logger.warn(`Secrets Manager list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
179
|
}
|
|
194
180
|
return secrets;
|
|
195
181
|
}
|
|
196
|
-
async function validateSecretsAccess(cfg = {}) {
|
|
197
|
-
await new
|
|
182
|
+
export async function validateSecretsAccess(cfg = {}) {
|
|
183
|
+
await new SecretsManagerClient(clientConfig(cfg)).send(new ListSecretsCommand({ MaxResults: 1 }));
|
|
198
184
|
}
|
|
199
185
|
// ─── Lambda ───────────────────────────────────────────────────────────────────
|
|
200
|
-
async function extractLambdaMetadata(cfg = {}) {
|
|
201
|
-
const client = new
|
|
186
|
+
export async function extractLambdaMetadata(cfg = {}) {
|
|
187
|
+
const client = new LambdaClient(clientConfig(cfg));
|
|
202
188
|
const functions = [];
|
|
203
189
|
try {
|
|
204
190
|
let marker;
|
|
205
191
|
do {
|
|
206
|
-
const res = await client.send(new
|
|
192
|
+
const res = await client.send(new ListFunctionsCommand({ Marker: marker, MaxItems: 50 }));
|
|
207
193
|
for (const fn of res.Functions ?? []) {
|
|
208
194
|
functions.push({
|
|
209
195
|
name: fn.FunctionName ?? '',
|
|
@@ -221,21 +207,21 @@ async function extractLambdaMetadata(cfg = {}) {
|
|
|
221
207
|
} while (marker && functions.length < 200);
|
|
222
208
|
}
|
|
223
209
|
catch (err) {
|
|
224
|
-
|
|
210
|
+
logger.warn(`Lambda list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
225
211
|
}
|
|
226
212
|
return functions;
|
|
227
213
|
}
|
|
228
|
-
async function validateLambdaAccess(cfg = {}) {
|
|
229
|
-
await new
|
|
214
|
+
export async function validateLambdaAccess(cfg = {}) {
|
|
215
|
+
await new LambdaClient(clientConfig(cfg)).send(new ListFunctionsCommand({ MaxItems: 1 }));
|
|
230
216
|
}
|
|
231
217
|
// ─── RDS ─────────────────────────────────────────────────────────────────────
|
|
232
|
-
async function extractRDSMetadata(cfg = {}) {
|
|
233
|
-
const client = new
|
|
218
|
+
export async function extractRDSMetadata(cfg = {}) {
|
|
219
|
+
const client = new RDSClient(clientConfig(cfg));
|
|
234
220
|
const instances = [];
|
|
235
221
|
try {
|
|
236
222
|
let marker;
|
|
237
223
|
do {
|
|
238
|
-
const res = await client.send(new
|
|
224
|
+
const res = await client.send(new DescribeDBInstancesCommand({ Marker: marker, MaxRecords: 100 }));
|
|
239
225
|
for (const db of res.DBInstances ?? []) {
|
|
240
226
|
instances.push({
|
|
241
227
|
dbInstanceIdentifier: db.DBInstanceIdentifier ?? '',
|
|
@@ -254,10 +240,10 @@ async function extractRDSMetadata(cfg = {}) {
|
|
|
254
240
|
} while (marker && instances.length < 200);
|
|
255
241
|
}
|
|
256
242
|
catch (err) {
|
|
257
|
-
|
|
243
|
+
logger.warn(`RDS list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
258
244
|
}
|
|
259
245
|
return instances;
|
|
260
246
|
}
|
|
261
|
-
async function validateRDSAccess(cfg = {}) {
|
|
262
|
-
await new
|
|
247
|
+
export async function validateRDSAccess(cfg = {}) {
|
|
248
|
+
await new RDSClient(clientConfig(cfg)).send(new DescribeDBInstancesCommand({ MaxRecords: 20 }));
|
|
263
249
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.validateDynamoAccess = validateDynamoAccess;
|
|
5
|
-
exports.probeDynamoAccess = probeDynamoAccess;
|
|
6
|
-
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
7
|
-
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
8
|
-
const core_1 = require("../core");
|
|
1
|
+
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { fromIni } from '@aws-sdk/credential-providers';
|
|
3
|
+
import { DynamoDBError, logger } from '../core/index.js';
|
|
9
4
|
function createDynamoClient(config) {
|
|
10
5
|
const region = config.aws?.region ?? 'us-east-1';
|
|
11
6
|
const profile = config.aws?.profile;
|
|
@@ -14,13 +9,13 @@ function createDynamoClient(config) {
|
|
|
14
9
|
if (endpoint)
|
|
15
10
|
clientConfig.endpoint = endpoint;
|
|
16
11
|
if (profile)
|
|
17
|
-
clientConfig.credentials =
|
|
12
|
+
clientConfig.credentials = fromIni({ profile });
|
|
18
13
|
else if (endpoint)
|
|
19
14
|
clientConfig.credentials = {
|
|
20
15
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'test',
|
|
21
16
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'test',
|
|
22
17
|
};
|
|
23
|
-
return new
|
|
18
|
+
return new DynamoDBClient(clientConfig);
|
|
24
19
|
}
|
|
25
20
|
function parseTableDescription(desc) {
|
|
26
21
|
const tableName = desc.TableName ?? 'unknown';
|
|
@@ -47,7 +42,7 @@ async function listAllTables(client) {
|
|
|
47
42
|
const tableNames = [];
|
|
48
43
|
let lastEvaluatedTableName;
|
|
49
44
|
do {
|
|
50
|
-
const command = new
|
|
45
|
+
const command = new ListTablesCommand({
|
|
51
46
|
ExclusiveStartTableName: lastEvaluatedTableName,
|
|
52
47
|
Limit: 100,
|
|
53
48
|
});
|
|
@@ -59,7 +54,7 @@ async function listAllTables(client) {
|
|
|
59
54
|
} while (lastEvaluatedTableName);
|
|
60
55
|
return tableNames;
|
|
61
56
|
}
|
|
62
|
-
async function extractDynamoMetadata(config) {
|
|
57
|
+
export async function extractDynamoMetadata(config) {
|
|
63
58
|
const client = createDynamoClient(config);
|
|
64
59
|
const includeTables = config.dynamodb?.includeTables;
|
|
65
60
|
let tableNames;
|
|
@@ -67,43 +62,43 @@ async function extractDynamoMetadata(config) {
|
|
|
67
62
|
const allTables = await listAllTables(client);
|
|
68
63
|
if (includeTables && includeTables.length > 0) {
|
|
69
64
|
tableNames = allTables.filter((name) => includeTables.includes(name));
|
|
70
|
-
|
|
65
|
+
logger.debug(`Filtered to ${tableNames.length} tables from config`);
|
|
71
66
|
}
|
|
72
67
|
else {
|
|
73
68
|
tableNames = allTables;
|
|
74
69
|
}
|
|
75
|
-
|
|
70
|
+
logger.debug(`Found ${tableNames.length} DynamoDB table(s)`);
|
|
76
71
|
}
|
|
77
72
|
catch (err) {
|
|
78
|
-
throw new
|
|
73
|
+
throw new DynamoDBError(err instanceof Error ? err.message : 'Failed to list DynamoDB tables');
|
|
79
74
|
}
|
|
80
75
|
const results = [];
|
|
81
76
|
for (const tableName of tableNames) {
|
|
82
77
|
try {
|
|
83
|
-
const command = new
|
|
78
|
+
const command = new DescribeTableCommand({ TableName: tableName });
|
|
84
79
|
const response = await client.send(command);
|
|
85
80
|
if (response.Table) {
|
|
86
81
|
results.push(parseTableDescription(response.Table));
|
|
87
|
-
|
|
82
|
+
logger.debug(`Described table: ${tableName}`);
|
|
88
83
|
}
|
|
89
84
|
}
|
|
90
85
|
catch (err) {
|
|
91
|
-
|
|
86
|
+
logger.warn(`Failed to describe table ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
92
87
|
}
|
|
93
88
|
}
|
|
94
89
|
return results;
|
|
95
90
|
}
|
|
96
|
-
async function validateDynamoAccess(config) {
|
|
91
|
+
export async function validateDynamoAccess(config) {
|
|
97
92
|
const client = createDynamoClient(config);
|
|
98
93
|
try {
|
|
99
|
-
await client.send(new
|
|
94
|
+
await client.send(new ListTablesCommand({ Limit: 1 }));
|
|
100
95
|
return true;
|
|
101
96
|
}
|
|
102
97
|
catch {
|
|
103
98
|
return false;
|
|
104
99
|
}
|
|
105
100
|
}
|
|
106
|
-
async function probeDynamoAccess(config) {
|
|
101
|
+
export async function probeDynamoAccess(config) {
|
|
107
102
|
const client = createDynamoClient(config);
|
|
108
|
-
await client.send(new
|
|
103
|
+
await client.send(new ListTablesCommand({ Limit: 1 }));
|
|
109
104
|
}
|
package/dist/adapters/logs.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.validateLogsAccess = validateLogsAccess;
|
|
5
|
-
const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
|
|
6
|
-
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
7
|
-
const core_1 = require("../core");
|
|
1
|
+
import { CloudWatchLogsClient, DescribeLogGroupsCommand, FilterLogEventsCommand, } from '@aws-sdk/client-cloudwatch-logs';
|
|
2
|
+
import { fromIni } from '@aws-sdk/credential-providers';
|
|
3
|
+
import { logger } from '../core/index.js';
|
|
8
4
|
// Hard caps to prevent context bloat
|
|
9
5
|
const MAX_LOG_GROUPS = 50;
|
|
10
6
|
const MAX_EVENTS_PER_GROUP = 50;
|
|
@@ -14,7 +10,7 @@ function clientConfig(cfg) {
|
|
|
14
10
|
if (cfg.endpoint)
|
|
15
11
|
base.endpoint = cfg.endpoint;
|
|
16
12
|
if (cfg.profile)
|
|
17
|
-
base.credentials =
|
|
13
|
+
base.credentials = fromIni({ profile: cfg.profile });
|
|
18
14
|
else if (cfg.endpoint)
|
|
19
15
|
base.credentials = {
|
|
20
16
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'test',
|
|
@@ -44,8 +40,8 @@ function topPatterns(messages, limit = 5) {
|
|
|
44
40
|
.slice(0, limit)
|
|
45
41
|
.map(([pattern, count]) => ({ pattern, count }));
|
|
46
42
|
}
|
|
47
|
-
async function extractLogsMetadata(cfg = {}) {
|
|
48
|
-
const client = new
|
|
43
|
+
export async function extractLogsMetadata(cfg = {}) {
|
|
44
|
+
const client = new CloudWatchLogsClient(clientConfig(cfg));
|
|
49
45
|
const windowMs = (cfg.windowHours ?? 24) * 60 * 60 * 1000;
|
|
50
46
|
const startTime = Date.now() - windowMs;
|
|
51
47
|
const summaries = [];
|
|
@@ -56,7 +52,7 @@ async function extractLogsMetadata(cfg = {}) {
|
|
|
56
52
|
for (const prefix of prefixes) {
|
|
57
53
|
let nextToken;
|
|
58
54
|
do {
|
|
59
|
-
const res = await client.send(new
|
|
55
|
+
const res = await client.send(new DescribeLogGroupsCommand({
|
|
60
56
|
nextToken,
|
|
61
57
|
limit: Math.min(50, MAX_LOG_GROUPS - logGroups.length),
|
|
62
58
|
...(prefix ? { logGroupNamePrefix: prefix } : {}),
|
|
@@ -73,7 +69,7 @@ async function extractLogsMetadata(cfg = {}) {
|
|
|
73
69
|
}
|
|
74
70
|
}
|
|
75
71
|
catch (err) {
|
|
76
|
-
|
|
72
|
+
logger.warn(`CloudWatch Logs discovery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
73
|
return summaries;
|
|
78
74
|
}
|
|
79
75
|
// Sample errors from each group — patterns only, never raw messages for unrelated groups
|
|
@@ -85,7 +81,7 @@ async function extractLogsMetadata(cfg = {}) {
|
|
|
85
81
|
if (errorMessages.length + warnMessages.length >= MAX_EVENTS_PER_GROUP)
|
|
86
82
|
break;
|
|
87
83
|
try {
|
|
88
|
-
const res = await client.send(new
|
|
84
|
+
const res = await client.send(new FilterLogEventsCommand({
|
|
89
85
|
logGroupName: lg.name,
|
|
90
86
|
filterPattern,
|
|
91
87
|
startTime,
|
|
@@ -119,9 +115,9 @@ async function extractLogsMetadata(cfg = {}) {
|
|
|
119
115
|
lastErrorTime,
|
|
120
116
|
});
|
|
121
117
|
}
|
|
122
|
-
|
|
118
|
+
logger.debug(`CloudWatch Logs: sampled ${summaries.length} log group(s)`);
|
|
123
119
|
return summaries;
|
|
124
120
|
}
|
|
125
|
-
async function validateLogsAccess(cfg = {}) {
|
|
126
|
-
await new
|
|
121
|
+
export async function validateLogsAccess(cfg = {}) {
|
|
122
|
+
await new CloudWatchLogsClient(clientConfig(cfg)).send(new DescribeLogGroupsCommand({ limit: 1 }));
|
|
127
123
|
}
|
package/dist/adapters/mongodb.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.MongoConnectionError = void 0;
|
|
4
|
-
exports.extractMongoMetadata = extractMongoMetadata;
|
|
5
|
-
exports.validateMongoAccess = validateMongoAccess;
|
|
6
|
-
const mongodb_1 = require("mongodb");
|
|
7
|
-
const core_1 = require("../core");
|
|
1
|
+
import { MongoClient } from 'mongodb';
|
|
2
|
+
import { InfrawiseError, logger } from '../core/index.js';
|
|
8
3
|
const SYSTEM_DATABASES = new Set(['admin', 'local', 'config']);
|
|
9
|
-
class MongoConnectionError extends
|
|
4
|
+
export class MongoConnectionError extends InfrawiseError {
|
|
10
5
|
constructor(details) {
|
|
11
6
|
super('Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor', undefined, undefined);
|
|
12
7
|
this.name = 'MongoConnectionError';
|
|
@@ -15,9 +10,8 @@ class MongoConnectionError extends core_1.InfrawiseError {
|
|
|
15
10
|
}
|
|
16
11
|
}
|
|
17
12
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const client = new mongodb_1.MongoClient(connectionString, {
|
|
13
|
+
export async function extractMongoMetadata(connectionString, databases) {
|
|
14
|
+
const client = new MongoClient(connectionString, {
|
|
21
15
|
serverSelectionTimeoutMS: 5000,
|
|
22
16
|
connectTimeoutMS: 5000,
|
|
23
17
|
});
|
|
@@ -35,7 +29,7 @@ async function extractMongoMetadata(connectionString, databases) {
|
|
|
35
29
|
.map((d) => d.name)
|
|
36
30
|
.filter((name) => !SYSTEM_DATABASES.has(name));
|
|
37
31
|
}
|
|
38
|
-
|
|
32
|
+
logger.debug(`Introspecting ${dbNames.length} MongoDB database(s)`);
|
|
39
33
|
const results = [];
|
|
40
34
|
for (const dbName of dbNames) {
|
|
41
35
|
const db = client.db(dbName);
|
|
@@ -45,7 +39,7 @@ async function extractMongoMetadata(connectionString, databases) {
|
|
|
45
39
|
collectionNames = collections.map((c) => c.name);
|
|
46
40
|
}
|
|
47
41
|
catch (err) {
|
|
48
|
-
|
|
42
|
+
logger.warn(`Skipping database "${dbName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
49
43
|
continue;
|
|
50
44
|
}
|
|
51
45
|
for (const collName of collectionNames) {
|
|
@@ -78,7 +72,7 @@ async function extractMongoMetadata(connectionString, databases) {
|
|
|
78
72
|
});
|
|
79
73
|
}
|
|
80
74
|
}
|
|
81
|
-
|
|
75
|
+
logger.debug(`Found ${results.length} MongoDB collection(s)`);
|
|
82
76
|
return results;
|
|
83
77
|
}
|
|
84
78
|
catch (err) {
|
|
@@ -90,8 +84,8 @@ async function extractMongoMetadata(connectionString, databases) {
|
|
|
90
84
|
await client.close().catch(() => undefined);
|
|
91
85
|
}
|
|
92
86
|
}
|
|
93
|
-
async function validateMongoAccess(connectionString) {
|
|
94
|
-
const client = new
|
|
87
|
+
export async function validateMongoAccess(connectionString) {
|
|
88
|
+
const client = new MongoClient(connectionString, {
|
|
95
89
|
serverSelectionTimeoutMS: 5000,
|
|
96
90
|
connectTimeoutMS: 5000,
|
|
97
91
|
});
|
package/dist/adapters/mysql.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.MySQLConnectionError = void 0;
|
|
7
|
-
exports.extractMySQLMetadata = extractMySQLMetadata;
|
|
8
|
-
exports.validateMySQLAccess = validateMySQLAccess;
|
|
9
|
-
const promise_1 = __importDefault(require("mysql2/promise"));
|
|
10
|
-
const core_1 = require("../core");
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
import { InfrawiseError, logger } from '../core/index.js';
|
|
11
3
|
const SYSTEM_SCHEMAS = new Set(['information_schema', 'performance_schema', 'mysql', 'sys']);
|
|
12
|
-
class MySQLConnectionError extends
|
|
4
|
+
export class MySQLConnectionError extends InfrawiseError {
|
|
13
5
|
constructor(details) {
|
|
14
6
|
super('Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor', undefined, undefined);
|
|
15
7
|
this.name = 'MySQLConnectionError';
|
|
@@ -18,11 +10,10 @@ class MySQLConnectionError extends core_1.InfrawiseError {
|
|
|
18
10
|
}
|
|
19
11
|
}
|
|
20
12
|
}
|
|
21
|
-
|
|
22
|
-
async function extractMySQLMetadata(connectionString) {
|
|
13
|
+
export async function extractMySQLMetadata(connectionString) {
|
|
23
14
|
let connection;
|
|
24
15
|
try {
|
|
25
|
-
connection = await
|
|
16
|
+
connection = await mysql.createConnection(connectionString);
|
|
26
17
|
// Get all user tables
|
|
27
18
|
const [tableRows] = await connection.execute(`
|
|
28
19
|
SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE
|
|
@@ -31,7 +22,7 @@ async function extractMySQLMetadata(connectionString) {
|
|
|
31
22
|
AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => '?').join(', ')})
|
|
32
23
|
ORDER BY TABLE_SCHEMA, TABLE_NAME
|
|
33
24
|
`, [...SYSTEM_SCHEMAS]);
|
|
34
|
-
|
|
25
|
+
logger.debug(`Found ${tableRows.length} MySQL table(s)`);
|
|
35
26
|
// Get all columns
|
|
36
27
|
const [columnRows] = await connection.execute(`
|
|
37
28
|
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
|
|
@@ -117,10 +108,10 @@ async function extractMySQLMetadata(connectionString) {
|
|
|
117
108
|
}
|
|
118
109
|
}
|
|
119
110
|
}
|
|
120
|
-
async function validateMySQLAccess(connectionString) {
|
|
111
|
+
export async function validateMySQLAccess(connectionString) {
|
|
121
112
|
let connection;
|
|
122
113
|
try {
|
|
123
|
-
connection = await
|
|
114
|
+
connection = await mysql.createConnection(connectionString);
|
|
124
115
|
await connection.execute('SELECT 1');
|
|
125
116
|
return true;
|
|
126
117
|
}
|