alchemy 0.54.0 → 0.55.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.
Files changed (91) hide show
  1. package/lib/aws/ec2/index.d.ts +10 -0
  2. package/lib/aws/ec2/index.d.ts.map +1 -0
  3. package/lib/aws/ec2/index.js +10 -0
  4. package/lib/aws/ec2/index.js.map +1 -0
  5. package/lib/aws/ec2/internet-gateway-attachment.d.ts +150 -0
  6. package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -0
  7. package/lib/aws/ec2/internet-gateway-attachment.js +248 -0
  8. package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -0
  9. package/lib/aws/ec2/internet-gateway.d.ts +132 -0
  10. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
  11. package/lib/aws/ec2/internet-gateway.js +311 -0
  12. package/lib/aws/ec2/internet-gateway.js.map +1 -0
  13. package/lib/aws/ec2/nat-gateway.d.ts +145 -0
  14. package/lib/aws/ec2/nat-gateway.d.ts.map +1 -0
  15. package/lib/aws/ec2/nat-gateway.js +618 -0
  16. package/lib/aws/ec2/nat-gateway.js.map +1 -0
  17. package/lib/aws/ec2/route-table-association.d.ts +134 -0
  18. package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
  19. package/lib/aws/ec2/route-table-association.js +364 -0
  20. package/lib/aws/ec2/route-table-association.js.map +1 -0
  21. package/lib/aws/ec2/route-table.d.ts +94 -0
  22. package/lib/aws/ec2/route-table.d.ts.map +1 -0
  23. package/lib/aws/ec2/route-table.js +253 -0
  24. package/lib/aws/ec2/route-table.js.map +1 -0
  25. package/lib/aws/ec2/route.d.ts +130 -0
  26. package/lib/aws/ec2/route.d.ts.map +1 -0
  27. package/lib/aws/ec2/route.js +253 -0
  28. package/lib/aws/ec2/route.js.map +1 -0
  29. package/lib/aws/ec2/security-group-rule.d.ts +84 -0
  30. package/lib/aws/ec2/security-group-rule.d.ts.map +1 -0
  31. package/lib/aws/ec2/security-group-rule.js +123 -0
  32. package/lib/aws/ec2/security-group-rule.js.map +1 -0
  33. package/lib/aws/ec2/security-group.d.ts +84 -0
  34. package/lib/aws/ec2/security-group.d.ts.map +1 -0
  35. package/lib/aws/ec2/security-group.js +207 -0
  36. package/lib/aws/ec2/security-group.js.map +1 -0
  37. package/lib/aws/ec2/subnet.d.ts +110 -0
  38. package/lib/aws/ec2/subnet.d.ts.map +1 -0
  39. package/lib/aws/ec2/subnet.js +336 -0
  40. package/lib/aws/ec2/subnet.js.map +1 -0
  41. package/lib/aws/ec2/utils.d.ts +15 -0
  42. package/lib/aws/ec2/utils.d.ts.map +1 -0
  43. package/lib/aws/ec2/utils.js +79 -0
  44. package/lib/aws/ec2/utils.js.map +1 -0
  45. package/lib/aws/ec2/vpc.d.ts +160 -0
  46. package/lib/aws/ec2/vpc.d.ts.map +1 -0
  47. package/lib/aws/ec2/vpc.js +453 -0
  48. package/lib/aws/ec2/vpc.js.map +1 -0
  49. package/lib/aws/index.d.ts +7 -0
  50. package/lib/aws/index.d.ts.map +1 -1
  51. package/lib/aws/index.js +8 -0
  52. package/lib/aws/index.js.map +1 -1
  53. package/lib/aws/utils.d.ts +25 -0
  54. package/lib/aws/utils.d.ts.map +1 -0
  55. package/lib/aws/utils.js +89 -0
  56. package/lib/aws/utils.js.map +1 -0
  57. package/lib/build-date.d.ts +6 -0
  58. package/lib/build-date.d.ts.map +1 -0
  59. package/lib/build-date.js +8 -0
  60. package/lib/build-date.js.map +1 -0
  61. package/lib/util/params.d.ts +5 -0
  62. package/lib/util/params.d.ts.map +1 -0
  63. package/lib/util/params.js +30 -0
  64. package/lib/util/params.js.map +1 -0
  65. package/lib/util/timeout.d.ts +31 -0
  66. package/lib/util/timeout.d.ts.map +1 -0
  67. package/lib/util/timeout.js +109 -0
  68. package/lib/util/timeout.js.map +1 -0
  69. package/lib/util/xml.d.ts +5 -0
  70. package/lib/util/xml.d.ts.map +1 -0
  71. package/lib/util/xml.js +8 -0
  72. package/lib/util/xml.js.map +1 -0
  73. package/package.json +3 -1
  74. package/src/aws/ec2/index.ts +9 -0
  75. package/src/aws/ec2/internet-gateway-attachment.ts +468 -0
  76. package/src/aws/ec2/internet-gateway.ts +557 -0
  77. package/src/aws/ec2/nat-gateway.ts +1123 -0
  78. package/src/aws/ec2/route-table-association.ts +747 -0
  79. package/src/aws/ec2/route-table.ts +452 -0
  80. package/src/aws/ec2/route.ts +447 -0
  81. package/src/aws/ec2/security-group-rule.ts +229 -0
  82. package/src/aws/ec2/security-group.ts +418 -0
  83. package/src/aws/ec2/subnet.ts +625 -0
  84. package/src/aws/ec2/utils.ts +102 -0
  85. package/src/aws/ec2/vpc.ts +969 -0
  86. package/src/aws/index.ts +9 -0
  87. package/src/aws/utils.ts +137 -0
  88. package/src/build-date.ts +8 -0
  89. package/src/util/params.ts +33 -0
  90. package/src/util/timeout.ts +178 -0
  91. package/src/util/xml.ts +7 -0
@@ -0,0 +1,969 @@
1
+ import type { AwsClient } from "aws4fetch";
2
+ import { XMLParser } from "fast-xml-parser";
3
+ import type { Context } from "../../context.ts";
4
+ import { Resource } from "../../resource.ts";
5
+ import { ignore } from "../../util/ignore.ts";
6
+ import { logger } from "../../util/logger.ts";
7
+ import {
8
+ mergeTimeoutConfig,
9
+ type TimeoutConfig,
10
+ waitForResourceState,
11
+ } from "../../util/timeout.ts";
12
+ import { callEC2Api, createEC2Client } from "./utils.ts";
13
+
14
+ /**
15
+ * Properties for creating or updating a VPC
16
+ */
17
+ export interface VpcProps {
18
+ /**
19
+ * The IPv4 network range for the VPC, in CIDR notation
20
+ * @example "10.0.0.0/16"
21
+ */
22
+ cidrBlock: string;
23
+
24
+ /**
25
+ * Whether to enable DNS resolution for the VPC
26
+ * @default true
27
+ */
28
+ enableDnsSupport?: boolean;
29
+
30
+ /**
31
+ * Whether to enable DNS hostnames for the VPC
32
+ * @default true
33
+ */
34
+ enableDnsHostnames?: boolean;
35
+
36
+ /**
37
+ * The tenancy option for instances launched into the VPC
38
+ * @default "default"
39
+ */
40
+ instanceTenancy?: "default" | "dedicated" | "host";
41
+
42
+ /**
43
+ * Additional IPv4 CIDR blocks to associate with the VPC
44
+ * These will be associated after VPC creation
45
+ */
46
+ additionalCidrBlocks?: string[];
47
+
48
+ /**
49
+ * IPv6 CIDR block configuration
50
+ */
51
+ ipv6Config?: {
52
+ /**
53
+ * Whether to assign an Amazon-provided IPv6 CIDR block
54
+ */
55
+ amazonProvidedIpv6CidrBlock?: boolean;
56
+ /**
57
+ * The ID of an IPv6 address pool from which to allocate the IPv6 CIDR block
58
+ */
59
+ ipv6Pool?: string;
60
+ /**
61
+ * The netmask length of the IPv6 CIDR block
62
+ * @default 56
63
+ */
64
+ ipv6CidrBlockNetworkBorderGroup?: string;
65
+ };
66
+
67
+ /**
68
+ * Tags to apply to the VPC
69
+ */
70
+ tags?: Record<string, string>;
71
+
72
+ /**
73
+ * Timeout configuration for VPC operations
74
+ * @default VPC-specific sensible defaults (60 attempts, 2000ms delay)
75
+ */
76
+ timeout?: Partial<TimeoutConfig>;
77
+ }
78
+
79
+ /**
80
+ * Output returned after VPC creation/update
81
+ */
82
+ export interface Vpc extends Resource<"aws::Vpc">, VpcProps {
83
+ /**
84
+ * The ID of the VPC
85
+ */
86
+ vpcId: string;
87
+
88
+ /**
89
+ * The current state of the VPC
90
+ */
91
+ state: "pending" | "available";
92
+
93
+ /**
94
+ * Whether the VPC is the default VPC
95
+ */
96
+ isDefault: boolean;
97
+
98
+ /**
99
+ * The ID of the set of DHCP options associated with the VPC
100
+ */
101
+ dhcpOptionsId: string;
102
+
103
+ /**
104
+ * Information about the IPv4 CIDR blocks associated with the VPC
105
+ */
106
+ cidrBlockAssociationSet?: Array<{
107
+ associationId: string;
108
+ cidrBlock: string;
109
+ cidrBlockState: {
110
+ state:
111
+ | "associating"
112
+ | "associated"
113
+ | "disassociating"
114
+ | "disassociated"
115
+ | "failing"
116
+ | "failed";
117
+ statusMessage?: string;
118
+ };
119
+ }>;
120
+
121
+ /**
122
+ * Information about the IPv6 CIDR blocks associated with the VPC
123
+ */
124
+ ipv6CidrBlockAssociationSet?: Array<{
125
+ associationId: string;
126
+ ipv6CidrBlock: string;
127
+ ipv6CidrBlockState: {
128
+ state:
129
+ | "associating"
130
+ | "associated"
131
+ | "disassociating"
132
+ | "disassociated"
133
+ | "failing"
134
+ | "failed";
135
+ statusMessage?: string;
136
+ };
137
+ networkBorderGroup?: string;
138
+ ipv6Pool?: string;
139
+ }>;
140
+
141
+ /**
142
+ * The ID of the AWS account that owns the VPC
143
+ */
144
+ ownerId?: string;
145
+ }
146
+
147
+ /**
148
+ * AWS VPC (Virtual Private Cloud) Resource
149
+ *
150
+ * Creates and manages Amazon VPC instances with configurable CIDR blocks,
151
+ * DNS settings, and instance tenancy options.
152
+ *
153
+ * @example
154
+ * // Create a basic VPC with default settings
155
+ * const vpc = await Vpc("main-vpc", {
156
+ * cidrBlock: "10.0.0.0/16",
157
+ * tags: {
158
+ * Name: "main-vpc",
159
+ * Environment: "production"
160
+ * }
161
+ * });
162
+ *
163
+ * @example
164
+ * // Create a VPC with custom DNS settings
165
+ * const vpc = await Vpc("custom-vpc", {
166
+ * cidrBlock: "172.16.0.0/16",
167
+ * enableDnsSupport: true,
168
+ * enableDnsHostnames: true,
169
+ * instanceTenancy: "default",
170
+ * tags: {
171
+ * Name: "custom-vpc"
172
+ * }
173
+ * });
174
+ *
175
+ * @example
176
+ * // Create a VPC with dedicated tenancy and custom timeout
177
+ * const dedicatedVpc = await Vpc("dedicated-vpc", {
178
+ * cidrBlock: "192.168.0.0/16",
179
+ * instanceTenancy: "dedicated",
180
+ * enableDnsSupport: false,
181
+ * enableDnsHostnames: false,
182
+ * timeout: {
183
+ * maxAttempts: 90,
184
+ * delayMs: 3000
185
+ * },
186
+ * tags: {
187
+ * Name: "dedicated-vpc",
188
+ * Type: "isolated"
189
+ * }
190
+ * });
191
+ */
192
+ export const Vpc = Resource(
193
+ "aws::Vpc",
194
+ async function (
195
+ this: Context<Vpc>,
196
+ _id: string,
197
+ props: VpcProps,
198
+ ): Promise<Vpc> {
199
+ const client = await createEC2Client();
200
+ const timeoutConfig = mergeTimeoutConfig(VPC_TIMEOUT, props.timeout);
201
+
202
+ if (this.phase === "delete") {
203
+ if (this.output?.vpcId) {
204
+ logger.log(`🗑️ Deleting VPC: ${this.output.vpcId}`);
205
+
206
+ // Simply delete the VPC - other resources should handle themselves
207
+ await ignore("InvalidVpcID.NotFound", async () => {
208
+ await callEC2Api(client, "DeleteVpc", parseVpcXmlResponse, {
209
+ VpcId: this.output.vpcId,
210
+ });
211
+ });
212
+
213
+ // Wait for VPC to be fully deleted
214
+ await waitForVpcDeleted(client, this.output.vpcId, timeoutConfig);
215
+
216
+ logger.log(` ✅ VPC ${this.output.vpcId} deletion completed`);
217
+ }
218
+ return this.destroy();
219
+ }
220
+
221
+ let vpc: AwsVpc;
222
+
223
+ if (this.phase === "update" && this.output?.vpcId) {
224
+ // Get existing VPC
225
+ const response = await callEC2Api<DescribeVpcsResponse>(
226
+ client,
227
+ "DescribeVpcs",
228
+ parseVpcXmlResponse,
229
+ {
230
+ "VpcId.1": this.output.vpcId,
231
+ },
232
+ );
233
+
234
+ if (!response.Vpcs?.[0]) {
235
+ throw new Error(`VPC ${this.output.vpcId} not found`);
236
+ }
237
+
238
+ vpc = response.Vpcs[0];
239
+
240
+ // Update DNS attributes if they've changed
241
+ if (props.enableDnsSupport !== undefined) {
242
+ const modifyParams: ModifyVpcAttributeParams = {
243
+ VpcId: vpc.VpcId,
244
+ EnableDnsSupport: {
245
+ Value: props.enableDnsSupport,
246
+ },
247
+ };
248
+ await callEC2Api(
249
+ client,
250
+ "ModifyVpcAttribute",
251
+ parseVpcXmlResponse,
252
+ convertModifyVpcAttributeParamsToAwsFormat(modifyParams),
253
+ );
254
+ }
255
+
256
+ if (props.enableDnsHostnames !== undefined) {
257
+ const modifyParams: ModifyVpcAttributeParams = {
258
+ VpcId: vpc.VpcId,
259
+ EnableDnsHostnames: {
260
+ Value: props.enableDnsHostnames,
261
+ },
262
+ };
263
+ await callEC2Api(
264
+ client,
265
+ "ModifyVpcAttribute",
266
+ parseVpcXmlResponse,
267
+ convertModifyVpcAttributeParamsToAwsFormat(modifyParams),
268
+ );
269
+ }
270
+ } else {
271
+ // Create new VPC
272
+ const createVpcParams: CreateVpcParams = {
273
+ CidrBlock: props.cidrBlock,
274
+ InstanceTenancy: props.instanceTenancy || "default",
275
+ };
276
+
277
+ // Add tags if specified
278
+ if (props.tags) {
279
+ createVpcParams.TagSpecifications = [
280
+ {
281
+ ResourceType: "vpc",
282
+ Tags: Object.entries(props.tags).map(([key, value]) => ({
283
+ Key: key,
284
+ Value: value,
285
+ })),
286
+ },
287
+ ];
288
+ }
289
+
290
+ const createParams = convertVpcParamsToAwsFormat(createVpcParams);
291
+
292
+ const response = await callEC2Api<CreateVpcResponse>(
293
+ client,
294
+ "CreateVpc",
295
+ parseVpcXmlResponse,
296
+ createParams,
297
+ );
298
+
299
+ if (!response.Vpc) {
300
+ throw new Error(
301
+ `Failed to create VPC - Response: ${JSON.stringify(response)}`,
302
+ );
303
+ }
304
+
305
+ vpc = response.Vpc;
306
+
307
+ // Wait for VPC to be available
308
+ await waitForVpcAvailable(client, vpc.VpcId!, timeoutConfig);
309
+
310
+ // Set DNS attributes if specified
311
+ if (props.enableDnsSupport !== undefined) {
312
+ const modifyParams: ModifyVpcAttributeParams = {
313
+ VpcId: vpc.VpcId,
314
+ EnableDnsSupport: {
315
+ Value: props.enableDnsSupport,
316
+ },
317
+ };
318
+ await callEC2Api(
319
+ client,
320
+ "ModifyVpcAttribute",
321
+ parseVpcXmlResponse,
322
+ convertModifyVpcAttributeParamsToAwsFormat(modifyParams),
323
+ );
324
+ }
325
+
326
+ if (props.enableDnsHostnames !== undefined) {
327
+ const modifyParams: ModifyVpcAttributeParams = {
328
+ VpcId: vpc.VpcId,
329
+ EnableDnsHostnames: {
330
+ Value: props.enableDnsHostnames,
331
+ },
332
+ };
333
+ await callEC2Api(
334
+ client,
335
+ "ModifyVpcAttribute",
336
+ parseVpcXmlResponse,
337
+ convertModifyVpcAttributeParamsToAwsFormat(modifyParams),
338
+ );
339
+ }
340
+ }
341
+
342
+ return this({
343
+ vpcId: vpc.VpcId!,
344
+ state: vpc.State as "pending" | "available",
345
+ isDefault: vpc.IsDefault || false,
346
+ dhcpOptionsId: vpc.DhcpOptionsId!,
347
+ ...props,
348
+ });
349
+ },
350
+ );
351
+
352
+ /**
353
+ * VPC timeout constants
354
+ */
355
+ export const VPC_TIMEOUT: TimeoutConfig = {
356
+ maxAttempts: 60,
357
+ delayMs: 2000, // 2 seconds - VPCs are medium speed resources
358
+ };
359
+
360
+ /**
361
+ * Comprehensive AWS VPC API Parameter Types
362
+ */
363
+
364
+ // Common structures
365
+ interface TagSpecification {
366
+ ResourceType: string;
367
+ Tags: Array<{
368
+ Key: string;
369
+ Value: string;
370
+ }>;
371
+ }
372
+
373
+ // VPC API Request Parameters
374
+ interface CreateVpcParams {
375
+ CidrBlock: string;
376
+ InstanceTenancy?: "default" | "dedicated" | "host";
377
+ TagSpecifications?: TagSpecification[];
378
+ AmazonProvidedIpv6CidrBlock?: boolean;
379
+ Ipv6Pool?: string;
380
+ Ipv6CidrBlock?: string;
381
+ Ipv4IpamPoolId?: string;
382
+ Ipv4NetmaskLength?: number;
383
+ Ipv6IpamPoolId?: string;
384
+ Ipv6NetmaskLength?: number;
385
+ DryRun?: boolean;
386
+ }
387
+
388
+ interface ModifyVpcAttributeParams {
389
+ VpcId: string;
390
+ EnableDnsHostnames?: {
391
+ Value: boolean;
392
+ };
393
+ EnableDnsSupport?: {
394
+ Value: boolean;
395
+ };
396
+ EnableNetworkAddressUsageMetrics?: {
397
+ Value: boolean;
398
+ };
399
+ DryRun?: boolean;
400
+ }
401
+
402
+ /**
403
+ * Helper function to convert typed VPC parameters to AWS API format
404
+ */
405
+ function convertVpcParamsToAwsFormat(
406
+ params: CreateVpcParams,
407
+ ): Record<string, string> {
408
+ const awsParams: Record<string, string> = {
409
+ CidrBlock: params.CidrBlock,
410
+ };
411
+
412
+ if (params.InstanceTenancy) {
413
+ awsParams.InstanceTenancy = params.InstanceTenancy;
414
+ }
415
+
416
+ if (params.AmazonProvidedIpv6CidrBlock !== undefined) {
417
+ awsParams.AmazonProvidedIpv6CidrBlock =
418
+ params.AmazonProvidedIpv6CidrBlock.toString();
419
+ }
420
+
421
+ if (params.Ipv6Pool) {
422
+ awsParams.Ipv6Pool = params.Ipv6Pool;
423
+ }
424
+
425
+ if (params.Ipv6CidrBlock) {
426
+ awsParams.Ipv6CidrBlock = params.Ipv6CidrBlock;
427
+ }
428
+
429
+ if (params.Ipv4IpamPoolId) {
430
+ awsParams.Ipv4IpamPoolId = params.Ipv4IpamPoolId;
431
+ }
432
+
433
+ if (params.Ipv4NetmaskLength !== undefined) {
434
+ awsParams.Ipv4NetmaskLength = params.Ipv4NetmaskLength.toString();
435
+ }
436
+
437
+ if (params.Ipv6IpamPoolId) {
438
+ awsParams.Ipv6IpamPoolId = params.Ipv6IpamPoolId;
439
+ }
440
+
441
+ if (params.Ipv6NetmaskLength !== undefined) {
442
+ awsParams.Ipv6NetmaskLength = params.Ipv6NetmaskLength.toString();
443
+ }
444
+
445
+ if (params.DryRun !== undefined) {
446
+ awsParams.DryRun = params.DryRun.toString();
447
+ }
448
+
449
+ // Handle TagSpecifications
450
+ if (params.TagSpecifications) {
451
+ params.TagSpecifications.forEach((tagSpec, specIndex) => {
452
+ const specNum = specIndex + 1;
453
+ awsParams[`TagSpecification.${specNum}.ResourceType`] =
454
+ tagSpec.ResourceType;
455
+
456
+ tagSpec.Tags.forEach((tag, tagIndex) => {
457
+ const tagNum = tagIndex + 1;
458
+ awsParams[`TagSpecification.${specNum}.Tag.${tagNum}.Key`] = tag.Key;
459
+ awsParams[`TagSpecification.${specNum}.Tag.${tagNum}.Value`] =
460
+ tag.Value;
461
+ });
462
+ });
463
+ }
464
+
465
+ return awsParams;
466
+ }
467
+
468
+ function convertModifyVpcAttributeParamsToAwsFormat(
469
+ params: ModifyVpcAttributeParams,
470
+ ): Record<string, string> {
471
+ const awsParams: Record<string, string> = {
472
+ VpcId: params.VpcId,
473
+ };
474
+
475
+ if (params.EnableDnsHostnames) {
476
+ awsParams["EnableDnsHostnames.Value"] =
477
+ params.EnableDnsHostnames.Value.toString();
478
+ }
479
+
480
+ if (params.EnableDnsSupport) {
481
+ awsParams["EnableDnsSupport.Value"] =
482
+ params.EnableDnsSupport.Value.toString();
483
+ }
484
+
485
+ if (params.EnableNetworkAddressUsageMetrics) {
486
+ awsParams["EnableNetworkAddressUsageMetrics.Value"] =
487
+ params.EnableNetworkAddressUsageMetrics.Value.toString();
488
+ }
489
+
490
+ if (params.DryRun !== undefined) {
491
+ awsParams.DryRun = params.DryRun.toString();
492
+ }
493
+
494
+ return awsParams;
495
+ }
496
+
497
+ /**
498
+ * AWS VPC API response types
499
+ */
500
+ interface AwsVpc {
501
+ VpcId: string;
502
+ State: "pending" | "available";
503
+ CidrBlock: string;
504
+ DhcpOptionsId: string;
505
+ InstanceTenancy: "default" | "dedicated" | "host";
506
+ IsDefault: boolean;
507
+ Tags?: Array<{
508
+ Key: string;
509
+ Value: string;
510
+ }>;
511
+ CidrBlockAssociationSet?: Array<{
512
+ AssociationId: string;
513
+ CidrBlock: string;
514
+ CidrBlockState: {
515
+ State:
516
+ | "associating"
517
+ | "associated"
518
+ | "disassociating"
519
+ | "disassociated"
520
+ | "failing"
521
+ | "failed";
522
+ StatusMessage?: string;
523
+ };
524
+ }>;
525
+ Ipv6CidrBlockAssociationSet?: Array<{
526
+ AssociationId: string;
527
+ Ipv6CidrBlock: string;
528
+ Ipv6CidrBlockState: {
529
+ State:
530
+ | "associating"
531
+ | "associated"
532
+ | "disassociating"
533
+ | "disassociated"
534
+ | "failing"
535
+ | "failed";
536
+ StatusMessage?: string;
537
+ };
538
+ NetworkBorderGroup?: string;
539
+ Ipv6Pool?: string;
540
+ }>;
541
+ OwnerId?: string;
542
+ }
543
+
544
+ interface CreateVpcResponse {
545
+ Vpc: AwsVpc;
546
+ }
547
+
548
+ interface DescribeVpcsResponse {
549
+ Vpcs: AwsVpc[];
550
+ }
551
+
552
+ interface AwsSecurityGroup {
553
+ GroupId: string;
554
+ GroupName: string;
555
+ VpcId: string;
556
+ }
557
+
558
+ interface DescribeSecurityGroupsResponse {
559
+ SecurityGroups: AwsSecurityGroup[];
560
+ }
561
+
562
+ /**
563
+ * Raw XML parsing interfaces for type safety
564
+ */
565
+ interface RawVpcXmlItem {
566
+ vpcId: string;
567
+ state: string;
568
+ cidrBlock: string;
569
+ dhcpOptionsId: string;
570
+ instanceTenancy: string;
571
+ isDefault: string | boolean;
572
+ tagSet?: {
573
+ item?:
574
+ | Array<{
575
+ key: string;
576
+ value: string;
577
+ }>
578
+ | {
579
+ key: string;
580
+ value: string;
581
+ };
582
+ };
583
+ cidrBlockAssociationSet?: {
584
+ item?:
585
+ | Array<{
586
+ associationId: string;
587
+ cidrBlock: string;
588
+ cidrBlockState: {
589
+ state: string;
590
+ statusMessage?: string;
591
+ };
592
+ }>
593
+ | {
594
+ associationId: string;
595
+ cidrBlock: string;
596
+ cidrBlockState: {
597
+ state: string;
598
+ statusMessage?: string;
599
+ };
600
+ };
601
+ };
602
+ ipv6CidrBlockAssociationSet?: {
603
+ item?:
604
+ | Array<{
605
+ associationId: string;
606
+ ipv6CidrBlock: string;
607
+ ipv6CidrBlockState: {
608
+ state: string;
609
+ statusMessage?: string;
610
+ };
611
+ networkBorderGroup?: string;
612
+ ipv6Pool?: string;
613
+ }>
614
+ | {
615
+ associationId: string;
616
+ ipv6CidrBlock: string;
617
+ ipv6CidrBlockState: {
618
+ state: string;
619
+ statusMessage?: string;
620
+ };
621
+ networkBorderGroup?: string;
622
+ ipv6Pool?: string;
623
+ };
624
+ };
625
+ ownerId?: string;
626
+ }
627
+
628
+ interface RawSecurityGroupXmlItem {
629
+ groupId: string;
630
+ groupName: string;
631
+ vpcId: string;
632
+ }
633
+
634
+ /**
635
+ * Parse XML responses specifically for VPC operations
636
+ */
637
+ function parseVpcXmlResponse<
638
+ T extends
639
+ | CreateVpcResponse
640
+ | DescribeVpcsResponse
641
+ | DescribeSecurityGroupsResponse
642
+ | { success: boolean },
643
+ >(xmlText: string): T {
644
+ const parser = new XMLParser({
645
+ ignoreAttributes: true,
646
+ parseAttributeValue: true,
647
+ parseTagValue: true,
648
+ trimValues: true,
649
+ });
650
+
651
+ const parsed = parser.parse(xmlText);
652
+ const result: Record<string, unknown> = {};
653
+
654
+ // Parse CreateVpcResponse
655
+ if (parsed.CreateVpcResponse) {
656
+ const vpc = parsed.CreateVpcResponse.vpc as RawVpcXmlItem;
657
+ if (vpc) {
658
+ result.Vpc = {
659
+ VpcId: vpc.vpcId,
660
+ State: vpc.state as "pending" | "available",
661
+ CidrBlock: vpc.cidrBlock,
662
+ DhcpOptionsId: vpc.dhcpOptionsId,
663
+ InstanceTenancy: vpc.instanceTenancy as
664
+ | "default"
665
+ | "dedicated"
666
+ | "host",
667
+ IsDefault: vpc.isDefault === "true" || vpc.isDefault === true,
668
+ Tags: vpc.tagSet?.item
669
+ ? Array.isArray(vpc.tagSet.item)
670
+ ? vpc.tagSet.item.map((tag) => ({ Key: tag.key, Value: tag.value }))
671
+ : [{ Key: vpc.tagSet.item.key, Value: vpc.tagSet.item.value }]
672
+ : undefined,
673
+ CidrBlockAssociationSet: vpc.cidrBlockAssociationSet?.item
674
+ ? Array.isArray(vpc.cidrBlockAssociationSet.item)
675
+ ? vpc.cidrBlockAssociationSet.item.map((assoc) => ({
676
+ AssociationId: assoc.associationId,
677
+ CidrBlock: assoc.cidrBlock,
678
+ CidrBlockState: {
679
+ State: assoc.cidrBlockState.state as
680
+ | "associating"
681
+ | "associated"
682
+ | "disassociating"
683
+ | "disassociated"
684
+ | "failing"
685
+ | "failed",
686
+ StatusMessage: assoc.cidrBlockState.statusMessage,
687
+ },
688
+ }))
689
+ : [
690
+ {
691
+ AssociationId: vpc.cidrBlockAssociationSet.item.associationId,
692
+ CidrBlock: vpc.cidrBlockAssociationSet.item.cidrBlock,
693
+ CidrBlockState: {
694
+ State: vpc.cidrBlockAssociationSet.item.cidrBlockState
695
+ .state as
696
+ | "associating"
697
+ | "associated"
698
+ | "disassociating"
699
+ | "disassociated"
700
+ | "failing"
701
+ | "failed",
702
+ StatusMessage:
703
+ vpc.cidrBlockAssociationSet.item.cidrBlockState
704
+ .statusMessage,
705
+ },
706
+ },
707
+ ]
708
+ : undefined,
709
+ Ipv6CidrBlockAssociationSet: vpc.ipv6CidrBlockAssociationSet?.item
710
+ ? Array.isArray(vpc.ipv6CidrBlockAssociationSet.item)
711
+ ? vpc.ipv6CidrBlockAssociationSet.item.map((assoc) => ({
712
+ AssociationId: assoc.associationId,
713
+ Ipv6CidrBlock: assoc.ipv6CidrBlock,
714
+ Ipv6CidrBlockState: {
715
+ State: assoc.ipv6CidrBlockState.state as
716
+ | "associating"
717
+ | "associated"
718
+ | "disassociating"
719
+ | "disassociated"
720
+ | "failing"
721
+ | "failed",
722
+ StatusMessage: assoc.ipv6CidrBlockState.statusMessage,
723
+ },
724
+ NetworkBorderGroup: assoc.networkBorderGroup,
725
+ Ipv6Pool: assoc.ipv6Pool,
726
+ }))
727
+ : [
728
+ {
729
+ AssociationId:
730
+ vpc.ipv6CidrBlockAssociationSet.item.associationId,
731
+ Ipv6CidrBlock:
732
+ vpc.ipv6CidrBlockAssociationSet.item.ipv6CidrBlock,
733
+ Ipv6CidrBlockState: {
734
+ State: vpc.ipv6CidrBlockAssociationSet.item
735
+ .ipv6CidrBlockState.state as
736
+ | "associating"
737
+ | "associated"
738
+ | "disassociating"
739
+ | "disassociated"
740
+ | "failing"
741
+ | "failed",
742
+ StatusMessage:
743
+ vpc.ipv6CidrBlockAssociationSet.item.ipv6CidrBlockState
744
+ .statusMessage,
745
+ },
746
+ NetworkBorderGroup:
747
+ vpc.ipv6CidrBlockAssociationSet.item.networkBorderGroup,
748
+ Ipv6Pool: vpc.ipv6CidrBlockAssociationSet.item.ipv6Pool,
749
+ },
750
+ ]
751
+ : undefined,
752
+ OwnerId: vpc.ownerId,
753
+ };
754
+ }
755
+ }
756
+
757
+ // Parse DescribeVpcsResponse
758
+ if (parsed.DescribeVpcsResponse) {
759
+ const vpcSet = parsed.DescribeVpcsResponse.vpcSet;
760
+ if (vpcSet?.item) {
761
+ const vpcs = Array.isArray(vpcSet.item) ? vpcSet.item : [vpcSet.item];
762
+ result.Vpcs = vpcs.map((vpc: RawVpcXmlItem) => ({
763
+ VpcId: vpc.vpcId,
764
+ State: vpc.state as "pending" | "available",
765
+ CidrBlock: vpc.cidrBlock,
766
+ DhcpOptionsId: vpc.dhcpOptionsId,
767
+ InstanceTenancy: vpc.instanceTenancy as
768
+ | "default"
769
+ | "dedicated"
770
+ | "host",
771
+ IsDefault: vpc.isDefault === "true" || vpc.isDefault === true,
772
+ Tags: vpc.tagSet?.item
773
+ ? Array.isArray(vpc.tagSet.item)
774
+ ? vpc.tagSet.item.map((tag) => ({ Key: tag.key, Value: tag.value }))
775
+ : [{ Key: vpc.tagSet.item.key, Value: vpc.tagSet.item.value }]
776
+ : undefined,
777
+ CidrBlockAssociationSet: vpc.cidrBlockAssociationSet?.item
778
+ ? Array.isArray(vpc.cidrBlockAssociationSet.item)
779
+ ? vpc.cidrBlockAssociationSet.item.map((assoc) => ({
780
+ AssociationId: assoc.associationId,
781
+ CidrBlock: assoc.cidrBlock,
782
+ CidrBlockState: {
783
+ State: assoc.cidrBlockState.state as
784
+ | "associating"
785
+ | "associated"
786
+ | "disassociating"
787
+ | "disassociated"
788
+ | "failing"
789
+ | "failed",
790
+ StatusMessage: assoc.cidrBlockState.statusMessage,
791
+ },
792
+ }))
793
+ : [
794
+ {
795
+ AssociationId: vpc.cidrBlockAssociationSet.item.associationId,
796
+ CidrBlock: vpc.cidrBlockAssociationSet.item.cidrBlock,
797
+ CidrBlockState: {
798
+ State: vpc.cidrBlockAssociationSet.item.cidrBlockState
799
+ .state as
800
+ | "associating"
801
+ | "associated"
802
+ | "disassociating"
803
+ | "disassociated"
804
+ | "failing"
805
+ | "failed",
806
+ StatusMessage:
807
+ vpc.cidrBlockAssociationSet.item.cidrBlockState
808
+ .statusMessage,
809
+ },
810
+ },
811
+ ]
812
+ : undefined,
813
+ Ipv6CidrBlockAssociationSet: vpc.ipv6CidrBlockAssociationSet?.item
814
+ ? Array.isArray(vpc.ipv6CidrBlockAssociationSet.item)
815
+ ? vpc.ipv6CidrBlockAssociationSet.item.map((assoc) => ({
816
+ AssociationId: assoc.associationId,
817
+ Ipv6CidrBlock: assoc.ipv6CidrBlock,
818
+ Ipv6CidrBlockState: {
819
+ State: assoc.ipv6CidrBlockState.state as
820
+ | "associating"
821
+ | "associated"
822
+ | "disassociating"
823
+ | "disassociated"
824
+ | "failing"
825
+ | "failed",
826
+ StatusMessage: assoc.ipv6CidrBlockState.statusMessage,
827
+ },
828
+ NetworkBorderGroup: assoc.networkBorderGroup,
829
+ Ipv6Pool: assoc.ipv6Pool,
830
+ }))
831
+ : [
832
+ {
833
+ AssociationId:
834
+ vpc.ipv6CidrBlockAssociationSet.item.associationId,
835
+ Ipv6CidrBlock:
836
+ vpc.ipv6CidrBlockAssociationSet.item.ipv6CidrBlock,
837
+ Ipv6CidrBlockState: {
838
+ State: vpc.ipv6CidrBlockAssociationSet.item
839
+ .ipv6CidrBlockState.state as
840
+ | "associating"
841
+ | "associated"
842
+ | "disassociating"
843
+ | "disassociated"
844
+ | "failing"
845
+ | "failed",
846
+ StatusMessage:
847
+ vpc.ipv6CidrBlockAssociationSet.item.ipv6CidrBlockState
848
+ .statusMessage,
849
+ },
850
+ NetworkBorderGroup:
851
+ vpc.ipv6CidrBlockAssociationSet.item.networkBorderGroup,
852
+ Ipv6Pool: vpc.ipv6CidrBlockAssociationSet.item.ipv6Pool,
853
+ },
854
+ ]
855
+ : undefined,
856
+ OwnerId: vpc.ownerId,
857
+ }));
858
+ } else {
859
+ result.Vpcs = [];
860
+ }
861
+ }
862
+
863
+ // Parse DescribeSecurityGroupsResponse
864
+ if (parsed.DescribeSecurityGroupsResponse) {
865
+ const securityGroupSet =
866
+ parsed.DescribeSecurityGroupsResponse.securityGroupInfo;
867
+ if (securityGroupSet?.item) {
868
+ const securityGroups = Array.isArray(securityGroupSet.item)
869
+ ? securityGroupSet.item
870
+ : [securityGroupSet.item];
871
+ result.SecurityGroups = securityGroups.map(
872
+ (sg: RawSecurityGroupXmlItem) => ({
873
+ GroupId: sg.groupId,
874
+ GroupName: sg.groupName,
875
+ VpcId: sg.vpcId,
876
+ }),
877
+ );
878
+ } else {
879
+ result.SecurityGroups = [];
880
+ }
881
+ }
882
+
883
+ // Handle success responses
884
+ if (parsed.ModifyVpcAttributeResponse || parsed.DeleteVpcResponse) {
885
+ result.success = true;
886
+ }
887
+
888
+ return result as T;
889
+ }
890
+
891
+ /**
892
+ * Wait for VPC to be in available state
893
+ */
894
+ async function waitForVpcAvailable(
895
+ client: AwsClient,
896
+ vpcId: string,
897
+ timeoutConfig: TimeoutConfig,
898
+ ): Promise<void> {
899
+ const checkFunction = async () => {
900
+ const response = await callEC2Api<DescribeVpcsResponse>(
901
+ client,
902
+ "DescribeVpcs",
903
+ parseVpcXmlResponse,
904
+ {
905
+ "VpcId.1": vpcId,
906
+ },
907
+ );
908
+ return response.Vpcs?.[0];
909
+ };
910
+
911
+ const isReady = (vpc: AwsVpc | undefined) => {
912
+ return vpc?.State === "available";
913
+ };
914
+
915
+ await waitForResourceState(
916
+ checkFunction,
917
+ isReady,
918
+ timeoutConfig,
919
+ vpcId,
920
+ "VPC",
921
+ "is now available",
922
+ );
923
+ }
924
+
925
+ /**
926
+ * Wait for VPC to be deleted
927
+ */
928
+ async function waitForVpcDeleted(
929
+ client: AwsClient,
930
+ vpcId: string,
931
+ timeoutConfig: TimeoutConfig,
932
+ ): Promise<void> {
933
+ const checkFunction = async () => {
934
+ try {
935
+ const response = await callEC2Api<DescribeVpcsResponse>(
936
+ client,
937
+ "DescribeVpcs",
938
+ parseVpcXmlResponse,
939
+ {
940
+ "VpcId.1": vpcId,
941
+ },
942
+ );
943
+ return response.Vpcs?.[0];
944
+ } catch (error: any) {
945
+ // If VPC is not found, it's been deleted - return undefined
946
+ if (
947
+ error.code === "InvalidVpcID.NotFound" ||
948
+ error.code === "InvalidVpc.NotFound"
949
+ ) {
950
+ return undefined;
951
+ }
952
+ throw error;
953
+ }
954
+ };
955
+
956
+ const isReady = (vpc: AwsVpc | undefined) => {
957
+ // VPC is deleted if it doesn't exist
958
+ return !vpc;
959
+ };
960
+
961
+ await waitForResourceState(
962
+ checkFunction,
963
+ isReady,
964
+ timeoutConfig,
965
+ vpcId,
966
+ "VPC",
967
+ "be deleted",
968
+ );
969
+ }