@build-astron-co/nimbus 0.2.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/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Operations — Embedded tool (stripped HTTP wrappers)
|
|
3
|
+
*
|
|
4
|
+
* Merged from services/aws-tools-service/src/aws/ec2.ts, s3.ts, iam.ts
|
|
5
|
+
* Uses lazy imports for AWS SDK to keep binary size small.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { logger } from '../utils';
|
|
9
|
+
|
|
10
|
+
// ==========================================
|
|
11
|
+
// Shared Types
|
|
12
|
+
// ==========================================
|
|
13
|
+
|
|
14
|
+
export interface OperationResult<T = any> {
|
|
15
|
+
success: boolean;
|
|
16
|
+
data?: T;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AwsConfig {
|
|
21
|
+
region?: string;
|
|
22
|
+
accessKeyId?: string;
|
|
23
|
+
secretAccessKey?: string;
|
|
24
|
+
sessionToken?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ==========================================
|
|
28
|
+
// EC2 Types
|
|
29
|
+
// ==========================================
|
|
30
|
+
|
|
31
|
+
export interface ListInstancesOptions {
|
|
32
|
+
instanceIds?: string[];
|
|
33
|
+
filters?: Record<string, string[]>;
|
|
34
|
+
maxResults?: number;
|
|
35
|
+
nextToken?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RunInstanceOptions {
|
|
39
|
+
imageId: string;
|
|
40
|
+
instanceType: string;
|
|
41
|
+
minCount?: number;
|
|
42
|
+
maxCount?: number;
|
|
43
|
+
keyName?: string;
|
|
44
|
+
securityGroupIds?: string[];
|
|
45
|
+
subnetId?: string;
|
|
46
|
+
userData?: string;
|
|
47
|
+
tags?: Record<string, string>;
|
|
48
|
+
ebsOptimized?: boolean;
|
|
49
|
+
monitoring?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ==========================================
|
|
53
|
+
// S3 Types
|
|
54
|
+
// ==========================================
|
|
55
|
+
|
|
56
|
+
export interface ListObjectsOptions {
|
|
57
|
+
bucket: string;
|
|
58
|
+
prefix?: string;
|
|
59
|
+
delimiter?: string;
|
|
60
|
+
maxKeys?: number;
|
|
61
|
+
continuationToken?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface PutObjectOptions {
|
|
65
|
+
bucket: string;
|
|
66
|
+
key: string;
|
|
67
|
+
body: string | Buffer;
|
|
68
|
+
contentType?: string;
|
|
69
|
+
metadata?: Record<string, string>;
|
|
70
|
+
tags?: Record<string, string>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface CopyObjectOptions {
|
|
74
|
+
sourceBucket: string;
|
|
75
|
+
sourceKey: string;
|
|
76
|
+
destinationBucket: string;
|
|
77
|
+
destinationKey: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ==========================================
|
|
81
|
+
// IAM Types
|
|
82
|
+
// ==========================================
|
|
83
|
+
|
|
84
|
+
export interface IAMListOptions {
|
|
85
|
+
maxItems?: number;
|
|
86
|
+
marker?: string;
|
|
87
|
+
pathPrefix?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CreateRoleOptions {
|
|
91
|
+
roleName: string;
|
|
92
|
+
assumeRolePolicyDocument: string;
|
|
93
|
+
description?: string;
|
|
94
|
+
path?: string;
|
|
95
|
+
maxSessionDuration?: number;
|
|
96
|
+
tags?: Record<string, string>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface CreatePolicyOptions {
|
|
100
|
+
policyName: string;
|
|
101
|
+
policyDocument: string;
|
|
102
|
+
description?: string;
|
|
103
|
+
path?: string;
|
|
104
|
+
tags?: Record<string, string>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Unified AWS Operations class merging EC2, S3, and IAM operations.
|
|
109
|
+
* All AWS SDK imports are lazy to minimize binary size.
|
|
110
|
+
*/
|
|
111
|
+
export class AwsOperations {
|
|
112
|
+
private config: AwsConfig;
|
|
113
|
+
|
|
114
|
+
constructor(config: AwsConfig = {}) {
|
|
115
|
+
this.config = {
|
|
116
|
+
region: config.region || process.env.AWS_REGION || 'us-east-1',
|
|
117
|
+
accessKeyId: config.accessKeyId,
|
|
118
|
+
secretAccessKey: config.secretAccessKey,
|
|
119
|
+
sessionToken: config.sessionToken,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build common client config
|
|
125
|
+
*/
|
|
126
|
+
private getClientConfig(): Record<string, any> {
|
|
127
|
+
const clientConfig: Record<string, any> = {
|
|
128
|
+
region: this.config.region,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (this.config.accessKeyId && this.config.secretAccessKey) {
|
|
132
|
+
clientConfig.credentials = {
|
|
133
|
+
accessKeyId: this.config.accessKeyId,
|
|
134
|
+
secretAccessKey: this.config.secretAccessKey,
|
|
135
|
+
sessionToken: this.config.sessionToken,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return clientConfig;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ==========================================
|
|
143
|
+
// EC2 Operations
|
|
144
|
+
// ==========================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* List EC2 instances
|
|
148
|
+
*/
|
|
149
|
+
async listInstances(options: ListInstancesOptions = {}): Promise<OperationResult> {
|
|
150
|
+
try {
|
|
151
|
+
const { EC2Client, DescribeInstancesCommand } = await import('@aws-sdk/client-ec2');
|
|
152
|
+
const client = new EC2Client(this.getClientConfig());
|
|
153
|
+
|
|
154
|
+
const input: any = {};
|
|
155
|
+
|
|
156
|
+
if (options.instanceIds && options.instanceIds.length > 0) {
|
|
157
|
+
input.InstanceIds = options.instanceIds;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (options.filters) {
|
|
161
|
+
input.Filters = Object.entries(options.filters).map(([name, values]) => ({
|
|
162
|
+
Name: name,
|
|
163
|
+
Values: values,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (options.maxResults) {
|
|
168
|
+
input.MaxResults = options.maxResults;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (options.nextToken) {
|
|
172
|
+
input.NextToken = options.nextToken;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const command = new DescribeInstancesCommand(input);
|
|
176
|
+
const response = await client.send(command);
|
|
177
|
+
|
|
178
|
+
const instances = response.Reservations?.flatMap(
|
|
179
|
+
(r: any) =>
|
|
180
|
+
r.Instances?.map((i: any) => ({
|
|
181
|
+
instanceId: i.InstanceId,
|
|
182
|
+
instanceType: i.InstanceType,
|
|
183
|
+
state: i.State?.Name,
|
|
184
|
+
publicIpAddress: i.PublicIpAddress,
|
|
185
|
+
privateIpAddress: i.PrivateIpAddress,
|
|
186
|
+
launchTime: i.LaunchTime,
|
|
187
|
+
availabilityZone: i.Placement?.AvailabilityZone,
|
|
188
|
+
vpcId: i.VpcId,
|
|
189
|
+
subnetId: i.SubnetId,
|
|
190
|
+
imageId: i.ImageId,
|
|
191
|
+
keyName: i.KeyName,
|
|
192
|
+
tags: i.Tags?.reduce(
|
|
193
|
+
(acc: any, tag: any) => {
|
|
194
|
+
if (tag.Key) {
|
|
195
|
+
acc[tag.Key] = tag.Value || '';
|
|
196
|
+
}
|
|
197
|
+
return acc;
|
|
198
|
+
},
|
|
199
|
+
{} as Record<string, string>
|
|
200
|
+
),
|
|
201
|
+
})) || []
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
data: {
|
|
207
|
+
instances: instances || [],
|
|
208
|
+
nextToken: response.NextToken,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
} catch (error: any) {
|
|
212
|
+
logger.error('Failed to list instances', error);
|
|
213
|
+
return { success: false, error: error.message };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Start EC2 instances
|
|
219
|
+
*/
|
|
220
|
+
async startInstances(instanceIds: string[]): Promise<OperationResult> {
|
|
221
|
+
try {
|
|
222
|
+
const { EC2Client, StartInstancesCommand } = await import('@aws-sdk/client-ec2');
|
|
223
|
+
const client = new EC2Client(this.getClientConfig());
|
|
224
|
+
|
|
225
|
+
const command = new StartInstancesCommand({ InstanceIds: instanceIds });
|
|
226
|
+
const response = await client.send(command);
|
|
227
|
+
|
|
228
|
+
const results = response.StartingInstances?.map((i: any) => ({
|
|
229
|
+
instanceId: i.InstanceId,
|
|
230
|
+
previousState: i.PreviousState?.Name,
|
|
231
|
+
currentState: i.CurrentState?.Name,
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
return { success: true, data: { instances: results || [] } };
|
|
235
|
+
} catch (error: any) {
|
|
236
|
+
logger.error('Failed to start instances', error);
|
|
237
|
+
return { success: false, error: error.message };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Start a single instance (convenience wrapper)
|
|
243
|
+
*/
|
|
244
|
+
async startInstance(instanceId: string): Promise<OperationResult> {
|
|
245
|
+
return this.startInstances([instanceId]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Stop EC2 instances
|
|
250
|
+
*/
|
|
251
|
+
async stopInstances(instanceIds: string[], force?: boolean): Promise<OperationResult> {
|
|
252
|
+
try {
|
|
253
|
+
const { EC2Client, StopInstancesCommand } = await import('@aws-sdk/client-ec2');
|
|
254
|
+
const client = new EC2Client(this.getClientConfig());
|
|
255
|
+
|
|
256
|
+
const command = new StopInstancesCommand({ InstanceIds: instanceIds, Force: force });
|
|
257
|
+
const response = await client.send(command);
|
|
258
|
+
|
|
259
|
+
const results = response.StoppingInstances?.map((i: any) => ({
|
|
260
|
+
instanceId: i.InstanceId,
|
|
261
|
+
previousState: i.PreviousState?.Name,
|
|
262
|
+
currentState: i.CurrentState?.Name,
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
return { success: true, data: { instances: results || [] } };
|
|
266
|
+
} catch (error: any) {
|
|
267
|
+
logger.error('Failed to stop instances', error);
|
|
268
|
+
return { success: false, error: error.message };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Stop a single instance (convenience wrapper)
|
|
274
|
+
*/
|
|
275
|
+
async stopInstance(instanceId: string, force?: boolean): Promise<OperationResult> {
|
|
276
|
+
return this.stopInstances([instanceId], force);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Reboot EC2 instances
|
|
281
|
+
*/
|
|
282
|
+
async rebootInstances(instanceIds: string[]): Promise<OperationResult> {
|
|
283
|
+
try {
|
|
284
|
+
const { EC2Client, RebootInstancesCommand } = await import('@aws-sdk/client-ec2');
|
|
285
|
+
const client = new EC2Client(this.getClientConfig());
|
|
286
|
+
|
|
287
|
+
const command = new RebootInstancesCommand({ InstanceIds: instanceIds });
|
|
288
|
+
await client.send(command);
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
success: true,
|
|
292
|
+
data: { message: `Reboot initiated for instances: ${instanceIds.join(', ')}` },
|
|
293
|
+
};
|
|
294
|
+
} catch (error: any) {
|
|
295
|
+
logger.error('Failed to reboot instances', error);
|
|
296
|
+
return { success: false, error: error.message };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Terminate EC2 instances
|
|
302
|
+
*/
|
|
303
|
+
async terminateInstances(instanceIds: string[]): Promise<OperationResult> {
|
|
304
|
+
try {
|
|
305
|
+
const { EC2Client, TerminateInstancesCommand } = await import('@aws-sdk/client-ec2');
|
|
306
|
+
const client = new EC2Client(this.getClientConfig());
|
|
307
|
+
|
|
308
|
+
const command = new TerminateInstancesCommand({ InstanceIds: instanceIds });
|
|
309
|
+
const response = await client.send(command);
|
|
310
|
+
|
|
311
|
+
const results = response.TerminatingInstances?.map((i: any) => ({
|
|
312
|
+
instanceId: i.InstanceId,
|
|
313
|
+
previousState: i.PreviousState?.Name,
|
|
314
|
+
currentState: i.CurrentState?.Name,
|
|
315
|
+
}));
|
|
316
|
+
|
|
317
|
+
return { success: true, data: { instances: results || [] } };
|
|
318
|
+
} catch (error: any) {
|
|
319
|
+
logger.error('Failed to terminate instances', error);
|
|
320
|
+
return { success: false, error: error.message };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Terminate a single instance (convenience wrapper)
|
|
326
|
+
*/
|
|
327
|
+
async terminateInstance(instanceId: string): Promise<OperationResult> {
|
|
328
|
+
return this.terminateInstances([instanceId]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Describe instance status
|
|
333
|
+
*/
|
|
334
|
+
async getInstanceStatus(instanceIds: string[]): Promise<OperationResult> {
|
|
335
|
+
try {
|
|
336
|
+
const { EC2Client, DescribeInstanceStatusCommand } = await import('@aws-sdk/client-ec2');
|
|
337
|
+
const client = new EC2Client(this.getClientConfig());
|
|
338
|
+
|
|
339
|
+
const command = new DescribeInstanceStatusCommand({
|
|
340
|
+
InstanceIds: instanceIds,
|
|
341
|
+
IncludeAllInstances: true,
|
|
342
|
+
});
|
|
343
|
+
const response = await client.send(command);
|
|
344
|
+
|
|
345
|
+
const statuses = response.InstanceStatuses?.map((s: any) => ({
|
|
346
|
+
instanceId: s.InstanceId,
|
|
347
|
+
instanceState: s.InstanceState?.Name,
|
|
348
|
+
instanceStatus: s.InstanceStatus?.Status,
|
|
349
|
+
systemStatus: s.SystemStatus?.Status,
|
|
350
|
+
availabilityZone: s.AvailabilityZone,
|
|
351
|
+
}));
|
|
352
|
+
|
|
353
|
+
return { success: true, data: { statuses: statuses || [] } };
|
|
354
|
+
} catch (error: any) {
|
|
355
|
+
logger.error('Failed to get instance status', error);
|
|
356
|
+
return { success: false, error: error.message };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* List VPCs
|
|
362
|
+
*/
|
|
363
|
+
async listVpcs(): Promise<OperationResult> {
|
|
364
|
+
try {
|
|
365
|
+
const { EC2Client, DescribeVpcsCommand } = await import('@aws-sdk/client-ec2');
|
|
366
|
+
const client = new EC2Client(this.getClientConfig());
|
|
367
|
+
|
|
368
|
+
const command = new DescribeVpcsCommand({});
|
|
369
|
+
const response = await client.send(command);
|
|
370
|
+
|
|
371
|
+
const vpcs = response.Vpcs?.map((v: any) => ({
|
|
372
|
+
vpcId: v.VpcId,
|
|
373
|
+
cidrBlock: v.CidrBlock,
|
|
374
|
+
state: v.State,
|
|
375
|
+
isDefault: v.IsDefault,
|
|
376
|
+
tags: v.Tags?.reduce(
|
|
377
|
+
(acc: any, tag: any) => {
|
|
378
|
+
if (tag.Key) {
|
|
379
|
+
acc[tag.Key] = tag.Value || '';
|
|
380
|
+
}
|
|
381
|
+
return acc;
|
|
382
|
+
},
|
|
383
|
+
{} as Record<string, string>
|
|
384
|
+
),
|
|
385
|
+
}));
|
|
386
|
+
|
|
387
|
+
return { success: true, data: { vpcs: vpcs || [] } };
|
|
388
|
+
} catch (error: any) {
|
|
389
|
+
logger.error('Failed to list VPCs', error);
|
|
390
|
+
return { success: false, error: error.message };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* List subnets
|
|
396
|
+
*/
|
|
397
|
+
async listSubnets(vpcId?: string): Promise<OperationResult> {
|
|
398
|
+
try {
|
|
399
|
+
const { EC2Client, DescribeSubnetsCommand } = await import('@aws-sdk/client-ec2');
|
|
400
|
+
const client = new EC2Client(this.getClientConfig());
|
|
401
|
+
|
|
402
|
+
const input: any = {};
|
|
403
|
+
if (vpcId) {
|
|
404
|
+
input.Filters = [{ Name: 'vpc-id', Values: [vpcId] }];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const command = new DescribeSubnetsCommand(input);
|
|
408
|
+
const response = await client.send(command);
|
|
409
|
+
|
|
410
|
+
const subnets = response.Subnets?.map((s: any) => ({
|
|
411
|
+
subnetId: s.SubnetId,
|
|
412
|
+
vpcId: s.VpcId,
|
|
413
|
+
cidrBlock: s.CidrBlock,
|
|
414
|
+
availabilityZone: s.AvailabilityZone,
|
|
415
|
+
availableIpAddressCount: s.AvailableIpAddressCount,
|
|
416
|
+
defaultForAz: s.DefaultForAz,
|
|
417
|
+
tags: s.Tags?.reduce(
|
|
418
|
+
(acc: any, tag: any) => {
|
|
419
|
+
if (tag.Key) {
|
|
420
|
+
acc[tag.Key] = tag.Value || '';
|
|
421
|
+
}
|
|
422
|
+
return acc;
|
|
423
|
+
},
|
|
424
|
+
{} as Record<string, string>
|
|
425
|
+
),
|
|
426
|
+
}));
|
|
427
|
+
|
|
428
|
+
return { success: true, data: { subnets: subnets || [] } };
|
|
429
|
+
} catch (error: any) {
|
|
430
|
+
logger.error('Failed to list subnets', error);
|
|
431
|
+
return { success: false, error: error.message };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* List security groups
|
|
437
|
+
*/
|
|
438
|
+
async listSecurityGroups(vpcId?: string): Promise<OperationResult> {
|
|
439
|
+
try {
|
|
440
|
+
const { EC2Client, DescribeSecurityGroupsCommand } = await import('@aws-sdk/client-ec2');
|
|
441
|
+
const client = new EC2Client(this.getClientConfig());
|
|
442
|
+
|
|
443
|
+
const input: any = {};
|
|
444
|
+
if (vpcId) {
|
|
445
|
+
input.Filters = [{ Name: 'vpc-id', Values: [vpcId] }];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const command = new DescribeSecurityGroupsCommand(input);
|
|
449
|
+
const response = await client.send(command);
|
|
450
|
+
|
|
451
|
+
const groups = response.SecurityGroups?.map((g: any) => ({
|
|
452
|
+
groupId: g.GroupId,
|
|
453
|
+
groupName: g.GroupName,
|
|
454
|
+
description: g.Description,
|
|
455
|
+
vpcId: g.VpcId,
|
|
456
|
+
}));
|
|
457
|
+
|
|
458
|
+
return { success: true, data: { securityGroups: groups || [] } };
|
|
459
|
+
} catch (error: any) {
|
|
460
|
+
logger.error('Failed to list security groups', error);
|
|
461
|
+
return { success: false, error: error.message };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* List regions
|
|
467
|
+
*/
|
|
468
|
+
async listRegions(): Promise<OperationResult> {
|
|
469
|
+
try {
|
|
470
|
+
const { EC2Client, DescribeRegionsCommand } = await import('@aws-sdk/client-ec2');
|
|
471
|
+
const client = new EC2Client(this.getClientConfig());
|
|
472
|
+
|
|
473
|
+
const command = new DescribeRegionsCommand({});
|
|
474
|
+
const response = await client.send(command);
|
|
475
|
+
|
|
476
|
+
const regions = response.Regions?.map((r: any) => ({
|
|
477
|
+
regionName: r.RegionName,
|
|
478
|
+
endpoint: r.Endpoint,
|
|
479
|
+
}));
|
|
480
|
+
|
|
481
|
+
return { success: true, data: { regions: regions || [] } };
|
|
482
|
+
} catch (error: any) {
|
|
483
|
+
logger.error('Failed to list regions', error);
|
|
484
|
+
return { success: false, error: error.message };
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ==========================================
|
|
489
|
+
// S3 Operations
|
|
490
|
+
// ==========================================
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* List all S3 buckets
|
|
494
|
+
*/
|
|
495
|
+
async listBuckets(): Promise<OperationResult> {
|
|
496
|
+
try {
|
|
497
|
+
const { S3Client, ListBucketsCommand } = await import('@aws-sdk/client-s3');
|
|
498
|
+
const client = new S3Client(this.getClientConfig());
|
|
499
|
+
|
|
500
|
+
const command = new ListBucketsCommand({});
|
|
501
|
+
const response = await client.send(command);
|
|
502
|
+
|
|
503
|
+
const buckets = response.Buckets?.map((b: any) => ({
|
|
504
|
+
name: b.Name,
|
|
505
|
+
creationDate: b.CreationDate,
|
|
506
|
+
}));
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
success: true,
|
|
510
|
+
data: {
|
|
511
|
+
buckets: buckets || [],
|
|
512
|
+
owner: {
|
|
513
|
+
id: response.Owner?.ID,
|
|
514
|
+
displayName: response.Owner?.DisplayName,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
} catch (error: any) {
|
|
519
|
+
logger.error('Failed to list buckets', error);
|
|
520
|
+
return { success: false, error: error.message };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* List objects in a bucket
|
|
526
|
+
*/
|
|
527
|
+
async listObjects(options: ListObjectsOptions): Promise<OperationResult> {
|
|
528
|
+
try {
|
|
529
|
+
const { S3Client, ListObjectsV2Command } = await import('@aws-sdk/client-s3');
|
|
530
|
+
const client = new S3Client(this.getClientConfig());
|
|
531
|
+
|
|
532
|
+
const input: any = { Bucket: options.bucket };
|
|
533
|
+
|
|
534
|
+
if (options.prefix) {
|
|
535
|
+
input.Prefix = options.prefix;
|
|
536
|
+
}
|
|
537
|
+
if (options.delimiter) {
|
|
538
|
+
input.Delimiter = options.delimiter;
|
|
539
|
+
}
|
|
540
|
+
if (options.maxKeys) {
|
|
541
|
+
input.MaxKeys = options.maxKeys;
|
|
542
|
+
}
|
|
543
|
+
if (options.continuationToken) {
|
|
544
|
+
input.ContinuationToken = options.continuationToken;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const command = new ListObjectsV2Command(input);
|
|
548
|
+
const response = await client.send(command);
|
|
549
|
+
|
|
550
|
+
const objects = response.Contents?.map((o: any) => ({
|
|
551
|
+
key: o.Key,
|
|
552
|
+
size: o.Size,
|
|
553
|
+
lastModified: o.LastModified,
|
|
554
|
+
etag: o.ETag,
|
|
555
|
+
storageClass: o.StorageClass,
|
|
556
|
+
}));
|
|
557
|
+
|
|
558
|
+
const commonPrefixes = response.CommonPrefixes?.map((p: any) => p.Prefix);
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
success: true,
|
|
562
|
+
data: {
|
|
563
|
+
objects: objects || [],
|
|
564
|
+
commonPrefixes: commonPrefixes || [],
|
|
565
|
+
isTruncated: response.IsTruncated,
|
|
566
|
+
nextContinuationToken: response.NextContinuationToken,
|
|
567
|
+
keyCount: response.KeyCount,
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
} catch (error: any) {
|
|
571
|
+
logger.error('Failed to list objects', error);
|
|
572
|
+
return { success: false, error: error.message };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Get object from bucket
|
|
578
|
+
*/
|
|
579
|
+
async getObject(bucket: string, key: string): Promise<OperationResult> {
|
|
580
|
+
try {
|
|
581
|
+
const { S3Client, GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
582
|
+
const client = new S3Client(this.getClientConfig());
|
|
583
|
+
|
|
584
|
+
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
|
|
585
|
+
const response = await client.send(command);
|
|
586
|
+
|
|
587
|
+
const body = await response.Body?.transformToString();
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
success: true,
|
|
591
|
+
data: {
|
|
592
|
+
body,
|
|
593
|
+
contentType: response.ContentType,
|
|
594
|
+
contentLength: response.ContentLength,
|
|
595
|
+
lastModified: response.LastModified,
|
|
596
|
+
etag: response.ETag,
|
|
597
|
+
metadata: response.Metadata,
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
} catch (error: any) {
|
|
601
|
+
logger.error('Failed to get object', error);
|
|
602
|
+
return { success: false, error: error.message };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Put object to bucket
|
|
608
|
+
*/
|
|
609
|
+
async putObject(options: PutObjectOptions): Promise<OperationResult> {
|
|
610
|
+
try {
|
|
611
|
+
const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
612
|
+
const client = new S3Client(this.getClientConfig());
|
|
613
|
+
|
|
614
|
+
const command = new PutObjectCommand({
|
|
615
|
+
Bucket: options.bucket,
|
|
616
|
+
Key: options.key,
|
|
617
|
+
Body: options.body,
|
|
618
|
+
ContentType: options.contentType,
|
|
619
|
+
Metadata: options.metadata,
|
|
620
|
+
Tagging: options.tags
|
|
621
|
+
? Object.entries(options.tags)
|
|
622
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
623
|
+
.join('&')
|
|
624
|
+
: undefined,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const response = await client.send(command);
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
success: true,
|
|
631
|
+
data: { etag: response.ETag, versionId: response.VersionId },
|
|
632
|
+
};
|
|
633
|
+
} catch (error: any) {
|
|
634
|
+
logger.error('Failed to put object', error);
|
|
635
|
+
return { success: false, error: error.message };
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Delete object from bucket
|
|
641
|
+
*/
|
|
642
|
+
async deleteObject(bucket: string, key: string): Promise<OperationResult> {
|
|
643
|
+
try {
|
|
644
|
+
const { S3Client, DeleteObjectCommand } = await import('@aws-sdk/client-s3');
|
|
645
|
+
const client = new S3Client(this.getClientConfig());
|
|
646
|
+
|
|
647
|
+
const command = new DeleteObjectCommand({ Bucket: bucket, Key: key });
|
|
648
|
+
const response = await client.send(command);
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
success: true,
|
|
652
|
+
data: { deleteMarker: response.DeleteMarker, versionId: response.VersionId },
|
|
653
|
+
};
|
|
654
|
+
} catch (error: any) {
|
|
655
|
+
logger.error('Failed to delete object', error);
|
|
656
|
+
return { success: false, error: error.message };
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Create a bucket
|
|
662
|
+
*/
|
|
663
|
+
async createBucket(bucket: string, region?: string): Promise<OperationResult> {
|
|
664
|
+
try {
|
|
665
|
+
const { S3Client, CreateBucketCommand } = await import('@aws-sdk/client-s3');
|
|
666
|
+
const targetRegion = region || this.config.region || 'us-east-1';
|
|
667
|
+
const client = new S3Client({ ...this.getClientConfig(), region: targetRegion });
|
|
668
|
+
|
|
669
|
+
const command = new CreateBucketCommand({
|
|
670
|
+
Bucket: bucket,
|
|
671
|
+
CreateBucketConfiguration:
|
|
672
|
+
targetRegion !== 'us-east-1' ? { LocationConstraint: targetRegion as any } : undefined,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
await client.send(command);
|
|
676
|
+
|
|
677
|
+
return { success: true, data: { bucket, region: targetRegion } };
|
|
678
|
+
} catch (error: any) {
|
|
679
|
+
logger.error('Failed to create bucket', error);
|
|
680
|
+
return { success: false, error: error.message };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Delete a bucket
|
|
686
|
+
*/
|
|
687
|
+
async deleteBucket(bucket: string): Promise<OperationResult> {
|
|
688
|
+
try {
|
|
689
|
+
const { S3Client, DeleteBucketCommand } = await import('@aws-sdk/client-s3');
|
|
690
|
+
const client = new S3Client(this.getClientConfig());
|
|
691
|
+
|
|
692
|
+
const command = new DeleteBucketCommand({ Bucket: bucket });
|
|
693
|
+
await client.send(command);
|
|
694
|
+
|
|
695
|
+
return { success: true, data: { message: `Bucket ${bucket} deleted` } };
|
|
696
|
+
} catch (error: any) {
|
|
697
|
+
logger.error('Failed to delete bucket', error);
|
|
698
|
+
return { success: false, error: error.message };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ==========================================
|
|
703
|
+
// IAM Operations
|
|
704
|
+
// ==========================================
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* List IAM users
|
|
708
|
+
*/
|
|
709
|
+
async listUsers(options: IAMListOptions = {}): Promise<OperationResult> {
|
|
710
|
+
try {
|
|
711
|
+
const { IAMClient, ListUsersCommand } = await import('@aws-sdk/client-iam');
|
|
712
|
+
const client = new IAMClient(this.getClientConfig());
|
|
713
|
+
|
|
714
|
+
const input: any = {};
|
|
715
|
+
if (options.maxItems) {
|
|
716
|
+
input.MaxItems = options.maxItems;
|
|
717
|
+
}
|
|
718
|
+
if (options.marker) {
|
|
719
|
+
input.Marker = options.marker;
|
|
720
|
+
}
|
|
721
|
+
if (options.pathPrefix) {
|
|
722
|
+
input.PathPrefix = options.pathPrefix;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const command = new ListUsersCommand(input);
|
|
726
|
+
const response = await client.send(command);
|
|
727
|
+
|
|
728
|
+
const users = response.Users?.map((u: any) => ({
|
|
729
|
+
userName: u.UserName,
|
|
730
|
+
userId: u.UserId,
|
|
731
|
+
arn: u.Arn,
|
|
732
|
+
path: u.Path,
|
|
733
|
+
createDate: u.CreateDate,
|
|
734
|
+
passwordLastUsed: u.PasswordLastUsed,
|
|
735
|
+
}));
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
success: true,
|
|
739
|
+
data: {
|
|
740
|
+
users: users || [],
|
|
741
|
+
isTruncated: response.IsTruncated,
|
|
742
|
+
marker: response.Marker,
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
} catch (error: any) {
|
|
746
|
+
logger.error('Failed to list users', error);
|
|
747
|
+
return { success: false, error: error.message };
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* List IAM roles
|
|
753
|
+
*/
|
|
754
|
+
async listRoles(options: IAMListOptions = {}): Promise<OperationResult> {
|
|
755
|
+
try {
|
|
756
|
+
const { IAMClient, ListRolesCommand } = await import('@aws-sdk/client-iam');
|
|
757
|
+
const client = new IAMClient(this.getClientConfig());
|
|
758
|
+
|
|
759
|
+
const input: any = {};
|
|
760
|
+
if (options.maxItems) {
|
|
761
|
+
input.MaxItems = options.maxItems;
|
|
762
|
+
}
|
|
763
|
+
if (options.marker) {
|
|
764
|
+
input.Marker = options.marker;
|
|
765
|
+
}
|
|
766
|
+
if (options.pathPrefix) {
|
|
767
|
+
input.PathPrefix = options.pathPrefix;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const command = new ListRolesCommand(input);
|
|
771
|
+
const response = await client.send(command);
|
|
772
|
+
|
|
773
|
+
const roles = response.Roles?.map((r: any) => ({
|
|
774
|
+
roleName: r.RoleName,
|
|
775
|
+
roleId: r.RoleId,
|
|
776
|
+
arn: r.Arn,
|
|
777
|
+
path: r.Path,
|
|
778
|
+
createDate: r.CreateDate,
|
|
779
|
+
description: r.Description,
|
|
780
|
+
maxSessionDuration: r.MaxSessionDuration,
|
|
781
|
+
}));
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
success: true,
|
|
785
|
+
data: {
|
|
786
|
+
roles: roles || [],
|
|
787
|
+
isTruncated: response.IsTruncated,
|
|
788
|
+
marker: response.Marker,
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
} catch (error: any) {
|
|
792
|
+
logger.error('Failed to list roles', error);
|
|
793
|
+
return { success: false, error: error.message };
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* List IAM policies
|
|
799
|
+
*/
|
|
800
|
+
async listPolicies(
|
|
801
|
+
options: IAMListOptions & { scope?: 'All' | 'AWS' | 'Local'; onlyAttached?: boolean } = {}
|
|
802
|
+
): Promise<OperationResult> {
|
|
803
|
+
try {
|
|
804
|
+
const { IAMClient, ListPoliciesCommand } = await import('@aws-sdk/client-iam');
|
|
805
|
+
const client = new IAMClient(this.getClientConfig());
|
|
806
|
+
|
|
807
|
+
const command = new ListPoliciesCommand({
|
|
808
|
+
MaxItems: options.maxItems,
|
|
809
|
+
Marker: options.marker,
|
|
810
|
+
PathPrefix: options.pathPrefix,
|
|
811
|
+
Scope: options.scope,
|
|
812
|
+
OnlyAttached: options.onlyAttached,
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
const response = await client.send(command);
|
|
816
|
+
|
|
817
|
+
const policies = response.Policies?.map((p: any) => ({
|
|
818
|
+
policyName: p.PolicyName,
|
|
819
|
+
policyId: p.PolicyId,
|
|
820
|
+
arn: p.Arn,
|
|
821
|
+
path: p.Path,
|
|
822
|
+
createDate: p.CreateDate,
|
|
823
|
+
updateDate: p.UpdateDate,
|
|
824
|
+
attachmentCount: p.AttachmentCount,
|
|
825
|
+
isAttachable: p.IsAttachable,
|
|
826
|
+
description: p.Description,
|
|
827
|
+
}));
|
|
828
|
+
|
|
829
|
+
return {
|
|
830
|
+
success: true,
|
|
831
|
+
data: {
|
|
832
|
+
policies: policies || [],
|
|
833
|
+
isTruncated: response.IsTruncated,
|
|
834
|
+
marker: response.Marker,
|
|
835
|
+
},
|
|
836
|
+
};
|
|
837
|
+
} catch (error: any) {
|
|
838
|
+
logger.error('Failed to list policies', error);
|
|
839
|
+
return { success: false, error: error.message };
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* List IAM groups
|
|
845
|
+
*/
|
|
846
|
+
async listGroups(options: IAMListOptions = {}): Promise<OperationResult> {
|
|
847
|
+
try {
|
|
848
|
+
const { IAMClient, ListGroupsCommand } = await import('@aws-sdk/client-iam');
|
|
849
|
+
const client = new IAMClient(this.getClientConfig());
|
|
850
|
+
|
|
851
|
+
const command = new ListGroupsCommand({
|
|
852
|
+
MaxItems: options.maxItems,
|
|
853
|
+
Marker: options.marker,
|
|
854
|
+
PathPrefix: options.pathPrefix,
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const response = await client.send(command);
|
|
858
|
+
|
|
859
|
+
const groups = response.Groups?.map((g: any) => ({
|
|
860
|
+
groupName: g.GroupName,
|
|
861
|
+
groupId: g.GroupId,
|
|
862
|
+
arn: g.Arn,
|
|
863
|
+
path: g.Path,
|
|
864
|
+
createDate: g.CreateDate,
|
|
865
|
+
}));
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
success: true,
|
|
869
|
+
data: {
|
|
870
|
+
groups: groups || [],
|
|
871
|
+
isTruncated: response.IsTruncated,
|
|
872
|
+
marker: response.Marker,
|
|
873
|
+
},
|
|
874
|
+
};
|
|
875
|
+
} catch (error: any) {
|
|
876
|
+
logger.error('Failed to list groups', error);
|
|
877
|
+
return { success: false, error: error.message };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Get IAM user details
|
|
883
|
+
*/
|
|
884
|
+
async getUser(userName?: string): Promise<OperationResult> {
|
|
885
|
+
try {
|
|
886
|
+
const { IAMClient, GetUserCommand } = await import('@aws-sdk/client-iam');
|
|
887
|
+
const client = new IAMClient(this.getClientConfig());
|
|
888
|
+
|
|
889
|
+
const command = new GetUserCommand(userName ? { UserName: userName } : {});
|
|
890
|
+
const response = await client.send(command);
|
|
891
|
+
|
|
892
|
+
const user = response.User;
|
|
893
|
+
|
|
894
|
+
return {
|
|
895
|
+
success: true,
|
|
896
|
+
data: {
|
|
897
|
+
userName: user?.UserName,
|
|
898
|
+
userId: user?.UserId,
|
|
899
|
+
arn: user?.Arn,
|
|
900
|
+
path: user?.Path,
|
|
901
|
+
createDate: user?.CreateDate,
|
|
902
|
+
passwordLastUsed: user?.PasswordLastUsed,
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
} catch (error: any) {
|
|
906
|
+
logger.error('Failed to get user', error);
|
|
907
|
+
return { success: false, error: error.message };
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Attach policy to role
|
|
913
|
+
*/
|
|
914
|
+
async attachRolePolicy(roleName: string, policyArn: string): Promise<OperationResult> {
|
|
915
|
+
try {
|
|
916
|
+
const { IAMClient, AttachRolePolicyCommand } = await import('@aws-sdk/client-iam');
|
|
917
|
+
const client = new IAMClient(this.getClientConfig());
|
|
918
|
+
|
|
919
|
+
const command = new AttachRolePolicyCommand({ RoleName: roleName, PolicyArn: policyArn });
|
|
920
|
+
await client.send(command);
|
|
921
|
+
|
|
922
|
+
return {
|
|
923
|
+
success: true,
|
|
924
|
+
data: { message: `Policy ${policyArn} attached to role ${roleName}` },
|
|
925
|
+
};
|
|
926
|
+
} catch (error: any) {
|
|
927
|
+
logger.error('Failed to attach role policy', error);
|
|
928
|
+
return { success: false, error: error.message };
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Detach policy from role
|
|
934
|
+
*/
|
|
935
|
+
async detachRolePolicy(roleName: string, policyArn: string): Promise<OperationResult> {
|
|
936
|
+
try {
|
|
937
|
+
const { IAMClient, DetachRolePolicyCommand } = await import('@aws-sdk/client-iam');
|
|
938
|
+
const client = new IAMClient(this.getClientConfig());
|
|
939
|
+
|
|
940
|
+
const command = new DetachRolePolicyCommand({ RoleName: roleName, PolicyArn: policyArn });
|
|
941
|
+
await client.send(command);
|
|
942
|
+
|
|
943
|
+
return {
|
|
944
|
+
success: true,
|
|
945
|
+
data: { message: `Policy ${policyArn} detached from role ${roleName}` },
|
|
946
|
+
};
|
|
947
|
+
} catch (error: any) {
|
|
948
|
+
logger.error('Failed to detach role policy', error);
|
|
949
|
+
return { success: false, error: error.message };
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|