alchemy 0.53.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.
- package/bin/alchemy.js +196353 -0
- package/bin/alchemy.ts +2 -2
- package/bin/commands/init.ts +1190 -0
- package/bin/constants.ts +18 -0
- package/bin/services/execute-alchemy.ts +7 -2
- package/bin/services/package-manager.ts +1 -1
- package/bin/types.ts +14 -5
- package/lib/aws/ec2/index.d.ts +10 -0
- package/lib/aws/ec2/index.d.ts.map +1 -0
- package/lib/aws/ec2/index.js +10 -0
- package/lib/aws/ec2/index.js.map +1 -0
- package/lib/aws/ec2/internet-gateway-attachment.d.ts +150 -0
- package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -0
- package/lib/aws/ec2/internet-gateway-attachment.js +248 -0
- package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -0
- package/lib/aws/ec2/internet-gateway.d.ts +132 -0
- package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
- package/lib/aws/ec2/internet-gateway.js +311 -0
- package/lib/aws/ec2/internet-gateway.js.map +1 -0
- package/lib/aws/ec2/nat-gateway.d.ts +145 -0
- package/lib/aws/ec2/nat-gateway.d.ts.map +1 -0
- package/lib/aws/ec2/nat-gateway.js +618 -0
- package/lib/aws/ec2/nat-gateway.js.map +1 -0
- package/lib/aws/ec2/route-table-association.d.ts +134 -0
- package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
- package/lib/aws/ec2/route-table-association.js +364 -0
- package/lib/aws/ec2/route-table-association.js.map +1 -0
- package/lib/aws/ec2/route-table.d.ts +94 -0
- package/lib/aws/ec2/route-table.d.ts.map +1 -0
- package/lib/aws/ec2/route-table.js +253 -0
- package/lib/aws/ec2/route-table.js.map +1 -0
- package/lib/aws/ec2/route.d.ts +130 -0
- package/lib/aws/ec2/route.d.ts.map +1 -0
- package/lib/aws/ec2/route.js +253 -0
- package/lib/aws/ec2/route.js.map +1 -0
- package/lib/aws/ec2/security-group-rule.d.ts +84 -0
- package/lib/aws/ec2/security-group-rule.d.ts.map +1 -0
- package/lib/aws/ec2/security-group-rule.js +123 -0
- package/lib/aws/ec2/security-group-rule.js.map +1 -0
- package/lib/aws/ec2/security-group.d.ts +84 -0
- package/lib/aws/ec2/security-group.d.ts.map +1 -0
- package/lib/aws/ec2/security-group.js +207 -0
- package/lib/aws/ec2/security-group.js.map +1 -0
- package/lib/aws/ec2/subnet.d.ts +110 -0
- package/lib/aws/ec2/subnet.d.ts.map +1 -0
- package/lib/aws/ec2/subnet.js +336 -0
- package/lib/aws/ec2/subnet.js.map +1 -0
- package/lib/aws/ec2/utils.d.ts +15 -0
- package/lib/aws/ec2/utils.d.ts.map +1 -0
- package/lib/aws/ec2/utils.js +79 -0
- package/lib/aws/ec2/utils.js.map +1 -0
- package/lib/aws/ec2/vpc.d.ts +160 -0
- package/lib/aws/ec2/vpc.d.ts.map +1 -0
- package/lib/aws/ec2/vpc.js +453 -0
- package/lib/aws/ec2/vpc.js.map +1 -0
- package/lib/aws/index.d.ts +7 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +8 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/utils.d.ts +25 -0
- package/lib/aws/utils.d.ts.map +1 -0
- package/lib/aws/utils.js +89 -0
- package/lib/aws/utils.js.map +1 -0
- package/lib/build-date.d.ts +6 -0
- package/lib/build-date.d.ts.map +1 -0
- package/lib/build-date.js +8 -0
- package/lib/build-date.js.map +1 -0
- package/lib/cloudflare/astro.d.ts.map +1 -1
- package/lib/cloudflare/astro.js +4 -3
- package/lib/cloudflare/astro.js.map +1 -1
- package/lib/cloudflare/bundle/externals.d.ts +3 -0
- package/lib/cloudflare/bundle/externals.d.ts.map +1 -0
- package/lib/cloudflare/bundle/externals.js +35 -0
- package/lib/cloudflare/bundle/externals.js.map +1 -0
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +8 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.js +11 -4
- package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +4 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.js +27 -11
- package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +5 -1
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-wasm.d.ts +6 -2
- package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-wasm.js +21 -47
- package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
- package/lib/cloudflare/bundle/validate-node-compat.d.ts +2 -1
- package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -1
- package/lib/cloudflare/bundle/validate-node-compat.js +5 -4
- package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/d1-database.js +1 -1
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
- package/lib/cloudflare/d1-migrations.js +8 -10
- package/lib/cloudflare/d1-migrations.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +3 -3
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-options.js +23 -6
- package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare.js +2 -4
- package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +4 -6
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/react-router.d.ts +4 -0
- package/lib/cloudflare/react-router.d.ts.map +1 -1
- package/lib/cloudflare/react-router.js +8 -5
- package/lib/cloudflare/react-router.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts +80 -0
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -0
- package/lib/cloudflare/worker-bundle.js +266 -0
- package/lib/cloudflare/worker-bundle.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +81 -195
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +1 -0
- package/lib/esbuild/bundle.d.ts.map +1 -1
- package/lib/state/cloudflare-state-store.js +1 -5
- package/lib/state/cloudflare-state-store.js.map +1 -1
- package/lib/state/dofs-state-store/store.d.ts.map +1 -1
- package/lib/state/dofs-state-store/store.js +4 -4
- package/lib/state/dofs-state-store/store.js.map +1 -1
- package/lib/util/params.d.ts +5 -0
- package/lib/util/params.d.ts.map +1 -0
- package/lib/util/params.js +30 -0
- package/lib/util/params.js.map +1 -0
- package/lib/util/timeout.d.ts +31 -0
- package/lib/util/timeout.d.ts.map +1 -0
- package/lib/util/timeout.js +109 -0
- package/lib/util/timeout.js.map +1 -0
- package/lib/util/xml.d.ts +5 -0
- package/lib/util/xml.d.ts.map +1 -0
- package/lib/util/xml.js +8 -0
- package/lib/util/xml.js.map +1 -0
- package/package.json +12 -5
- package/src/aws/ec2/index.ts +9 -0
- package/src/aws/ec2/internet-gateway-attachment.ts +468 -0
- package/src/aws/ec2/internet-gateway.ts +557 -0
- package/src/aws/ec2/nat-gateway.ts +1123 -0
- package/src/aws/ec2/route-table-association.ts +747 -0
- package/src/aws/ec2/route-table.ts +452 -0
- package/src/aws/ec2/route.ts +447 -0
- package/src/aws/ec2/security-group-rule.ts +229 -0
- package/src/aws/ec2/security-group.ts +418 -0
- package/src/aws/ec2/subnet.ts +625 -0
- package/src/aws/ec2/utils.ts +102 -0
- package/src/aws/ec2/vpc.ts +969 -0
- package/src/aws/index.ts +9 -0
- package/src/aws/utils.ts +137 -0
- package/src/build-date.ts +8 -0
- package/src/cloudflare/astro.ts +4 -3
- package/src/cloudflare/bundle/externals.ts +36 -0
- package/src/cloudflare/bundle/internal-worker-bundle.ts +12 -3
- package/src/cloudflare/bundle/plugin-hot-reload.ts +32 -12
- package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +6 -1
- package/src/cloudflare/bundle/plugin-wasm.ts +26 -54
- package/src/cloudflare/bundle/validate-node-compat.ts +6 -5
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/d1-database.ts +1 -1
- package/src/cloudflare/d1-migrations.ts +19 -18
- package/src/cloudflare/miniflare/miniflare-worker-options.ts +27 -13
- package/src/cloudflare/miniflare/miniflare.ts +2 -4
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -7
- package/src/cloudflare/react-router.ts +14 -6
- package/src/cloudflare/worker-bundle.ts +364 -0
- package/src/cloudflare/worker.ts +104 -241
- package/src/state/cloudflare-state-store.ts +1 -5
- package/src/state/dofs-state-store/store.ts +4 -4
- package/src/util/params.ts +33 -0
- package/src/util/timeout.ts +178 -0
- package/src/util/xml.ts +7 -0
- package/templates/nuxt/package.json +1 -1
- package/templates/nuxt/types/env.d.ts +11 -0
- package/templates/react-router/workers/app.ts +2 -2
- package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +385 -0
- package/templates/rwsdk/wrangler.jsonc +10 -7
- package/templates/typescript/tsconfig.json +1 -2
- package/bin/alchemy.mjs +0 -51766
- package/lib/cloudflare/bundle/esbuild.d.ts +0 -16
- package/lib/cloudflare/bundle/esbuild.d.ts.map +0 -1
- package/lib/cloudflare/bundle/esbuild.js +0 -159
- package/lib/cloudflare/bundle/esbuild.js.map +0 -1
- package/lib/cloudflare/bundle/fs.d.ts +0 -18
- package/lib/cloudflare/bundle/fs.d.ts.map +0 -1
- package/lib/cloudflare/bundle/fs.js +0 -78
- package/lib/cloudflare/bundle/fs.js.map +0 -1
- package/lib/cloudflare/bundle/index.d.ts +0 -3
- package/lib/cloudflare/bundle/index.d.ts.map +0 -1
- package/lib/cloudflare/bundle/index.js +0 -2
- package/lib/cloudflare/bundle/index.js.map +0 -1
- package/lib/cloudflare/bundle/inline.d.ts +0 -12
- package/lib/cloudflare/bundle/inline.d.ts.map +0 -1
- package/lib/cloudflare/bundle/inline.js +0 -32
- package/lib/cloudflare/bundle/inline.js.map +0 -1
- package/lib/cloudflare/bundle/normalize.d.ts +0 -21
- package/lib/cloudflare/bundle/normalize.d.ts.map +0 -1
- package/lib/cloudflare/bundle/normalize.js +0 -49
- package/lib/cloudflare/bundle/normalize.js.map +0 -1
- package/lib/cloudflare/bundle/shared.d.ts +0 -30
- package/lib/cloudflare/bundle/shared.d.ts.map +0 -1
- package/lib/cloudflare/bundle/shared.js +0 -34
- package/lib/cloudflare/bundle/shared.js.map +0 -1
- package/src/cloudflare/bundle/esbuild.ts +0 -194
- package/src/cloudflare/bundle/fs.ts +0 -103
- package/src/cloudflare/bundle/index.ts +0 -6
- package/src/cloudflare/bundle/inline.ts +0 -42
- package/src/cloudflare/bundle/normalize.ts +0 -76
- package/src/cloudflare/bundle/shared.ts +0 -73
|
@@ -0,0 +1,447 @@
|
|
|
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 { retry } from "../retry.ts";
|
|
13
|
+
import type { InternetGateway } from "./internet-gateway.ts";
|
|
14
|
+
import type { NatGateway } from "./nat-gateway.ts";
|
|
15
|
+
import type { RouteTable } from "./route-table.ts";
|
|
16
|
+
import { callEC2Api, createEC2Client } from "./utils.ts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Properties for creating or updating a Route
|
|
20
|
+
*/
|
|
21
|
+
export interface RouteProps {
|
|
22
|
+
/**
|
|
23
|
+
* The route table to add the route to
|
|
24
|
+
*/
|
|
25
|
+
routeTable: RouteTable | string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The destination CIDR block for the route
|
|
29
|
+
* @example "0.0.0.0/0" for default route
|
|
30
|
+
*/
|
|
31
|
+
destinationCidrBlock: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The target for the route (one of the following)
|
|
35
|
+
*/
|
|
36
|
+
target:
|
|
37
|
+
| { internetGateway: InternetGateway | string }
|
|
38
|
+
| { natGateway: NatGateway | string }
|
|
39
|
+
| { instanceId: string }
|
|
40
|
+
| { networkInterfaceId: string }
|
|
41
|
+
| { vpcPeeringConnectionId: string }
|
|
42
|
+
| { transitGatewayId: string };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Timeout configuration for Route operations
|
|
46
|
+
* @default Route-specific sensible defaults (30 attempts, 1000ms delay)
|
|
47
|
+
*/
|
|
48
|
+
timeout?: Partial<TimeoutConfig>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Output returned after Route creation/update
|
|
53
|
+
*/
|
|
54
|
+
export interface Route extends Resource<"aws::Route">, RouteProps {
|
|
55
|
+
/**
|
|
56
|
+
* The ID of the route table
|
|
57
|
+
*/
|
|
58
|
+
routeTableId: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The current state of the route
|
|
62
|
+
*/
|
|
63
|
+
state: "active" | "blackhole";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The origin of the route
|
|
67
|
+
*/
|
|
68
|
+
origin: "CreateRouteTable" | "CreateRoute" | "EnableVgwRoutePropagation";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* AWS Route Resource
|
|
73
|
+
*
|
|
74
|
+
* Creates and manages individual routes within route tables, directing traffic
|
|
75
|
+
* to various targets like Internet Gateways, NAT Gateways, or instances.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Create a default route to Internet Gateway
|
|
79
|
+
* const publicRoute = await Route("public-default-route", {
|
|
80
|
+
* routeTable: publicRouteTable,
|
|
81
|
+
* destinationCidrBlock: "0.0.0.0/0",
|
|
82
|
+
* target: { internetGateway: mainIgw }
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // Create a default route to NAT Gateway for private subnets
|
|
87
|
+
* const privateRoute = await Route("private-default-route", {
|
|
88
|
+
* routeTable: privateRouteTable,
|
|
89
|
+
* destinationCidrBlock: "0.0.0.0/0",
|
|
90
|
+
* target: { natGateway: mainNat }
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // Create a route to a specific instance
|
|
95
|
+
* const instanceRoute = await Route("instance-route", {
|
|
96
|
+
* routeTable: "rtb-12345678",
|
|
97
|
+
* destinationCidrBlock: "10.1.0.0/16",
|
|
98
|
+
* target: { instanceId: "i-12345678" }
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Create a route to a network interface
|
|
103
|
+
* const eniRoute = await Route("eni-route", {
|
|
104
|
+
* routeTable: mainRouteTable,
|
|
105
|
+
* destinationCidrBlock: "192.168.1.0/24",
|
|
106
|
+
* target: { networkInterfaceId: "eni-12345678" }
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // Create a route to a VPC peering connection
|
|
111
|
+
* const peeringRoute = await Route("peering-route", {
|
|
112
|
+
* routeTable: mainRouteTable,
|
|
113
|
+
* destinationCidrBlock: "10.2.0.0/16",
|
|
114
|
+
* target: { vpcPeeringConnectionId: "pcx-12345678" }
|
|
115
|
+
* });
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* // Create a route to a Transit Gateway
|
|
119
|
+
* const transitRoute = await Route("transit-route", {
|
|
120
|
+
* routeTable: mainRouteTable,
|
|
121
|
+
* destinationCidrBlock: "10.3.0.0/16",
|
|
122
|
+
* target: { transitGatewayId: "tgw-12345678" }
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Create a route with custom timeout configuration
|
|
127
|
+
* const customRoute = await Route("custom-route", {
|
|
128
|
+
* routeTable: mainRouteTable,
|
|
129
|
+
* destinationCidrBlock: "172.16.0.0/16",
|
|
130
|
+
* target: { internetGateway: "igw-12345678" },
|
|
131
|
+
* timeout: {
|
|
132
|
+
* maxAttempts: 60,
|
|
133
|
+
* delayMs: 2000
|
|
134
|
+
* }
|
|
135
|
+
* });
|
|
136
|
+
*/
|
|
137
|
+
export const Route = Resource(
|
|
138
|
+
"aws::Route",
|
|
139
|
+
async function (
|
|
140
|
+
this: Context<Route>,
|
|
141
|
+
_id: string,
|
|
142
|
+
props: RouteProps,
|
|
143
|
+
): Promise<Route> {
|
|
144
|
+
const client = await createEC2Client();
|
|
145
|
+
const _timeoutConfig = mergeTimeoutConfig(ROUTE_TIMEOUT, props.timeout);
|
|
146
|
+
const routeTableId =
|
|
147
|
+
typeof props.routeTable === "string"
|
|
148
|
+
? props.routeTable
|
|
149
|
+
: props.routeTable.routeTableId;
|
|
150
|
+
|
|
151
|
+
if (this.phase === "delete") {
|
|
152
|
+
if (this.output?.routeTableId) {
|
|
153
|
+
logger.log(
|
|
154
|
+
`🗑️ Deleting Route: ${this.output.destinationCidrBlock} from Route Table ${this.output.routeTableId}`,
|
|
155
|
+
);
|
|
156
|
+
await retry(async () => {
|
|
157
|
+
await ignore("InvalidRoute.NotFound", async () => {
|
|
158
|
+
await callEC2Api(client, "DeleteRoute", parseRouteXmlResponse, {
|
|
159
|
+
RouteTableId: this.output!.routeTableId,
|
|
160
|
+
DestinationCidrBlock: this.output!.destinationCidrBlock,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Wait for route to be fully deleted
|
|
166
|
+
logger.log(
|
|
167
|
+
` Waiting for Route ${this.output.destinationCidrBlock} to be fully deleted...`,
|
|
168
|
+
);
|
|
169
|
+
await waitForRouteDeleted(
|
|
170
|
+
client,
|
|
171
|
+
this.output.routeTableId,
|
|
172
|
+
this.output.destinationCidrBlock,
|
|
173
|
+
_timeoutConfig,
|
|
174
|
+
);
|
|
175
|
+
logger.log(
|
|
176
|
+
` ✅ Route ${this.output.destinationCidrBlock} deletion completed`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return this.destroy();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Prepare route parameters
|
|
183
|
+
const routeParams: Record<string, string> = {
|
|
184
|
+
RouteTableId: routeTableId,
|
|
185
|
+
DestinationCidrBlock: props.destinationCidrBlock,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Set the target based on the type
|
|
189
|
+
if ("internetGateway" in props.target) {
|
|
190
|
+
const igwId =
|
|
191
|
+
typeof props.target.internetGateway === "string"
|
|
192
|
+
? props.target.internetGateway
|
|
193
|
+
: props.target.internetGateway.internetGatewayId;
|
|
194
|
+
routeParams.GatewayId = igwId;
|
|
195
|
+
} else if ("natGateway" in props.target) {
|
|
196
|
+
const natId =
|
|
197
|
+
typeof props.target.natGateway === "string"
|
|
198
|
+
? props.target.natGateway
|
|
199
|
+
: props.target.natGateway.natGatewayId;
|
|
200
|
+
routeParams.NatGatewayId = natId;
|
|
201
|
+
} else if ("instanceId" in props.target) {
|
|
202
|
+
routeParams.InstanceId = props.target.instanceId;
|
|
203
|
+
} else if ("networkInterfaceId" in props.target) {
|
|
204
|
+
routeParams.NetworkInterfaceId = props.target.networkInterfaceId;
|
|
205
|
+
} else if ("vpcPeeringConnectionId" in props.target) {
|
|
206
|
+
routeParams.VpcPeeringConnectionId = props.target.vpcPeeringConnectionId;
|
|
207
|
+
} else if ("transitGatewayId" in props.target) {
|
|
208
|
+
routeParams.TransitGatewayId = props.target.transitGatewayId;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (this.phase === "update" && this.output?.routeTableId) {
|
|
212
|
+
// Check if route exists
|
|
213
|
+
const response = await callEC2Api<DescribeRouteTablesResponse>(
|
|
214
|
+
client,
|
|
215
|
+
"DescribeRouteTables",
|
|
216
|
+
parseRouteXmlResponse,
|
|
217
|
+
{
|
|
218
|
+
"RouteTableId.1": routeTableId,
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const routeTable = response.RouteTables?.[0];
|
|
223
|
+
const existingRoute = routeTable?.Routes?.find(
|
|
224
|
+
(r) => r.DestinationCidrBlock === props.destinationCidrBlock,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
if (existingRoute) {
|
|
228
|
+
// Replace existing route
|
|
229
|
+
await callEC2Api(
|
|
230
|
+
client,
|
|
231
|
+
"ReplaceRoute",
|
|
232
|
+
parseRouteXmlResponse,
|
|
233
|
+
routeParams,
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
// Create new route
|
|
237
|
+
await callEC2Api(
|
|
238
|
+
client,
|
|
239
|
+
"CreateRoute",
|
|
240
|
+
parseRouteXmlResponse,
|
|
241
|
+
routeParams,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
// Create new route
|
|
246
|
+
await callEC2Api(
|
|
247
|
+
client,
|
|
248
|
+
"CreateRoute",
|
|
249
|
+
parseRouteXmlResponse,
|
|
250
|
+
routeParams,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Wait a moment for the route to be fully created
|
|
255
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
256
|
+
|
|
257
|
+
// Get the created/updated route to return current state
|
|
258
|
+
const response = await callEC2Api<DescribeRouteTablesResponse>(
|
|
259
|
+
client,
|
|
260
|
+
"DescribeRouteTables",
|
|
261
|
+
parseRouteXmlResponse,
|
|
262
|
+
{
|
|
263
|
+
"RouteTableId.1": routeTableId,
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const routeTable = response.RouteTables?.[0];
|
|
268
|
+
const route = routeTable?.Routes?.find(
|
|
269
|
+
(r) => r.DestinationCidrBlock === props.destinationCidrBlock,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
if (!route) {
|
|
273
|
+
throw new Error("Failed to find created route");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return this({
|
|
277
|
+
routeTableId,
|
|
278
|
+
state: route.State as "active" | "blackhole",
|
|
279
|
+
origin: route.Origin as
|
|
280
|
+
| "CreateRouteTable"
|
|
281
|
+
| "CreateRoute"
|
|
282
|
+
| "EnableVgwRoutePropagation",
|
|
283
|
+
...props,
|
|
284
|
+
routeTable: routeTableId,
|
|
285
|
+
});
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Wait for Route to be deleted
|
|
291
|
+
*/
|
|
292
|
+
async function waitForRouteDeleted(
|
|
293
|
+
client: AwsClient,
|
|
294
|
+
routeTableId: string,
|
|
295
|
+
destinationCidrBlock: string,
|
|
296
|
+
timeoutConfig: TimeoutConfig,
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
const checkFunction = async () => {
|
|
299
|
+
try {
|
|
300
|
+
const response = await callEC2Api<DescribeRouteTablesResponse>(
|
|
301
|
+
client,
|
|
302
|
+
"DescribeRouteTables",
|
|
303
|
+
parseRouteXmlResponse,
|
|
304
|
+
{
|
|
305
|
+
"RouteTableId.1": routeTableId,
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const routeTable = response.RouteTables?.[0];
|
|
310
|
+
const route = routeTable?.Routes?.find(
|
|
311
|
+
(r) => r.DestinationCidrBlock === destinationCidrBlock,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
return route;
|
|
315
|
+
} catch (error: any) {
|
|
316
|
+
// If route table is not found, route is definitely deleted
|
|
317
|
+
if (
|
|
318
|
+
error.code === "InvalidRouteTableID.NotFound" ||
|
|
319
|
+
error.code === "InvalidRouteTable.NotFound"
|
|
320
|
+
) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const isReady = (route: any) => {
|
|
328
|
+
// Route is deleted if it doesn't exist
|
|
329
|
+
return !route;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
await waitForResourceState(
|
|
333
|
+
checkFunction,
|
|
334
|
+
isReady,
|
|
335
|
+
timeoutConfig,
|
|
336
|
+
`${routeTableId}/${destinationCidrBlock}`,
|
|
337
|
+
"Route",
|
|
338
|
+
"be deleted",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Route timeout constants
|
|
344
|
+
*/
|
|
345
|
+
export const ROUTE_TIMEOUT: TimeoutConfig = {
|
|
346
|
+
maxAttempts: 30,
|
|
347
|
+
delayMs: 1000, // 1 second - Routes are fast resources
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* AWS Route API response types
|
|
352
|
+
*/
|
|
353
|
+
interface DescribeRouteTablesResponse {
|
|
354
|
+
RouteTables: Array<{
|
|
355
|
+
RouteTableId: string;
|
|
356
|
+
VpcId: string;
|
|
357
|
+
Routes?: Array<{
|
|
358
|
+
DestinationCidrBlock?: string;
|
|
359
|
+
GatewayId?: string;
|
|
360
|
+
InstanceId?: string;
|
|
361
|
+
NatGatewayId?: string;
|
|
362
|
+
State: string;
|
|
363
|
+
Origin: string;
|
|
364
|
+
}>;
|
|
365
|
+
}>;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Raw XML parsing interfaces for type safety
|
|
370
|
+
*/
|
|
371
|
+
interface RawRouteTableXmlItem {
|
|
372
|
+
routeTableId: string;
|
|
373
|
+
vpcId: string;
|
|
374
|
+
routeSet?: {
|
|
375
|
+
item?: Array<RawRouteXmlItem> | RawRouteXmlItem;
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
interface RawRouteXmlItem {
|
|
380
|
+
destinationCidrBlock?: string;
|
|
381
|
+
gatewayId?: string;
|
|
382
|
+
instanceId?: string;
|
|
383
|
+
networkInterfaceId?: string;
|
|
384
|
+
vpcPeeringConnectionId?: string;
|
|
385
|
+
natGatewayId?: string;
|
|
386
|
+
transitGatewayId?: string;
|
|
387
|
+
state: string;
|
|
388
|
+
origin: string;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Parse XML responses specifically for Route operations
|
|
393
|
+
*/
|
|
394
|
+
function parseRouteXmlResponse<T>(xmlText: string): T {
|
|
395
|
+
const parser = new XMLParser({
|
|
396
|
+
ignoreAttributes: true,
|
|
397
|
+
parseAttributeValue: true,
|
|
398
|
+
parseTagValue: true,
|
|
399
|
+
trimValues: true,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const parsed = parser.parse(xmlText);
|
|
403
|
+
const result: Record<string, unknown> = {};
|
|
404
|
+
|
|
405
|
+
// Parse DescribeRouteTablesResponse
|
|
406
|
+
if (parsed.DescribeRouteTablesResponse) {
|
|
407
|
+
const routeTableSet = parsed.DescribeRouteTablesResponse.routeTableSet;
|
|
408
|
+
if (routeTableSet?.item) {
|
|
409
|
+
const routeTables = Array.isArray(routeTableSet.item)
|
|
410
|
+
? routeTableSet.item
|
|
411
|
+
: [routeTableSet.item];
|
|
412
|
+
result.RouteTables = routeTables.map((rt: RawRouteTableXmlItem) => ({
|
|
413
|
+
RouteTableId: rt.routeTableId,
|
|
414
|
+
VpcId: rt.vpcId,
|
|
415
|
+
Routes: rt.routeSet?.item
|
|
416
|
+
? (Array.isArray(rt.routeSet.item)
|
|
417
|
+
? rt.routeSet.item
|
|
418
|
+
: [rt.routeSet.item]
|
|
419
|
+
).map((route: RawRouteXmlItem) => ({
|
|
420
|
+
DestinationCidrBlock: route.destinationCidrBlock,
|
|
421
|
+
GatewayId: route.gatewayId,
|
|
422
|
+
InstanceId: route.instanceId,
|
|
423
|
+
NetworkInterfaceId: route.networkInterfaceId,
|
|
424
|
+
VpcPeeringConnectionId: route.vpcPeeringConnectionId,
|
|
425
|
+
NatGatewayId: route.natGatewayId,
|
|
426
|
+
TransitGatewayId: route.transitGatewayId,
|
|
427
|
+
State: route.state,
|
|
428
|
+
Origin: route.origin,
|
|
429
|
+
}))
|
|
430
|
+
: [],
|
|
431
|
+
}));
|
|
432
|
+
} else {
|
|
433
|
+
result.RouteTables = [];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Handle success responses
|
|
438
|
+
if (
|
|
439
|
+
parsed.CreateRouteResponse ||
|
|
440
|
+
parsed.ReplaceRouteResponse ||
|
|
441
|
+
parsed.DeleteRouteResponse
|
|
442
|
+
) {
|
|
443
|
+
result.success = true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return result as T;
|
|
447
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
import type { Context } from "../../context.ts";
|
|
3
|
+
import { Resource } from "../../resource.ts";
|
|
4
|
+
import { ignore } from "../../util/ignore.ts";
|
|
5
|
+
import type { SecurityGroup } from "./security-group.ts";
|
|
6
|
+
import { callEC2Api, createEC2Client } from "./utils.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Properties for creating or updating a Security Group Rule
|
|
10
|
+
*/
|
|
11
|
+
export interface SecurityGroupRuleProps {
|
|
12
|
+
/**
|
|
13
|
+
* The security group to apply the rule to.
|
|
14
|
+
*/
|
|
15
|
+
securityGroup: SecurityGroup | string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The type of rule, either 'ingress' (inbound) or 'egress' (outbound).
|
|
19
|
+
*/
|
|
20
|
+
type: "ingress" | "egress";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The IP protocol name (tcp, udp, icmp, icmpv6) or number.
|
|
24
|
+
* Use '-1' to specify all protocols.
|
|
25
|
+
*/
|
|
26
|
+
protocol: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The start of the port range for the rule.
|
|
30
|
+
* For ICMP, this is the ICMP type number. A value of -1 indicates all types.
|
|
31
|
+
*/
|
|
32
|
+
fromPort: number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The end of the port range for the rule.
|
|
36
|
+
* For ICMP, this is the ICMP code. A value of -1 indicates all codes.
|
|
37
|
+
*/
|
|
38
|
+
toPort: number;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The IPv4 CIDR ranges to allow.
|
|
42
|
+
*/
|
|
43
|
+
cidrBlocks?: string[];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The source security group IDs to allow.
|
|
47
|
+
*/
|
|
48
|
+
sourceSecurityGroups?: (SecurityGroup | string)[];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A description for the rule.
|
|
52
|
+
*/
|
|
53
|
+
description?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Output returned after Security Group Rule creation/update
|
|
58
|
+
*/
|
|
59
|
+
export interface SecurityGroupRule
|
|
60
|
+
extends Resource<"aws::SecurityGroupRule">,
|
|
61
|
+
SecurityGroupRuleProps {
|
|
62
|
+
/**
|
|
63
|
+
* A unique identifier for the rule resource.
|
|
64
|
+
*/
|
|
65
|
+
ruleId: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* AWS Security Group Rule Resource
|
|
70
|
+
*
|
|
71
|
+
* Manages a single ingress or egress rule for a security group.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // A rule to allow HTTP traffic from anywhere
|
|
75
|
+
* const httpRule = await SecurityGroupRule("web-sg-http-rule", {
|
|
76
|
+
* securityGroup: webSecurityGroup,
|
|
77
|
+
* type: "ingress",
|
|
78
|
+
* protocol: "tcp",
|
|
79
|
+
* fromPort: 80,
|
|
80
|
+
* toPort: 80,
|
|
81
|
+
* cidrBlocks: ["0.0.0.0/0"],
|
|
82
|
+
* description: "Allow HTTP access from anywhere"
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* // A rule to allow a database security group to receive traffic
|
|
87
|
+
* // from a web server security group on the MySQL port.
|
|
88
|
+
* const dbAccessRule = await SecurityGroupRule("db-access-from-web", {
|
|
89
|
+
* securityGroup: dbSecurityGroup,
|
|
90
|
+
* type: "ingress",
|
|
91
|
+
* protocol: "tcp",
|
|
92
|
+
* fromPort: 3306,
|
|
93
|
+
* toPort: 3306,
|
|
94
|
+
* sourceSecurityGroups: [webSecurityGroup],
|
|
95
|
+
* description: "Allow MySQL access from web servers"
|
|
96
|
+
* });
|
|
97
|
+
*/
|
|
98
|
+
export const SecurityGroupRule = Resource(
|
|
99
|
+
"aws::SecurityGroupRule",
|
|
100
|
+
async function (
|
|
101
|
+
this: Context<SecurityGroupRule>,
|
|
102
|
+
_id: string,
|
|
103
|
+
props: SecurityGroupRuleProps,
|
|
104
|
+
): Promise<SecurityGroupRule> {
|
|
105
|
+
const client = await createEC2Client();
|
|
106
|
+
const groupId =
|
|
107
|
+
typeof props.securityGroup === "string"
|
|
108
|
+
? props.securityGroup
|
|
109
|
+
: props.securityGroup.groupId;
|
|
110
|
+
|
|
111
|
+
// A deterministic ID for the rule based on its properties
|
|
112
|
+
const ruleId = `${groupId}-${props.type}-${props.protocol}-${
|
|
113
|
+
props.fromPort
|
|
114
|
+
}-${props.toPort}-${(props.cidrBlocks || []).join("-")}-${(
|
|
115
|
+
props.sourceSecurityGroups || []
|
|
116
|
+
)
|
|
117
|
+
.map((sg) => (typeof sg === "string" ? sg : sg.groupId))
|
|
118
|
+
.join("-")}`;
|
|
119
|
+
|
|
120
|
+
if (this.phase === "delete") {
|
|
121
|
+
const revokeParams = convertRuleToApiParams(props, groupId);
|
|
122
|
+
const action =
|
|
123
|
+
props.type === "ingress"
|
|
124
|
+
? "RevokeSecurityGroupIngress"
|
|
125
|
+
: "RevokeSecurityGroupEgress";
|
|
126
|
+
|
|
127
|
+
// Ignore errors if the rule doesn't exist, as it's already deleted.
|
|
128
|
+
await ignore("InvalidPermission.NotFound", async () => {
|
|
129
|
+
await callEC2Api(
|
|
130
|
+
client,
|
|
131
|
+
action,
|
|
132
|
+
parseSecurityGroupRuleXmlResponse,
|
|
133
|
+
revokeParams,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return this.destroy();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// For both create and update, we authorize the rule.
|
|
141
|
+
// The resource framework handles replacement by calling delete then create.
|
|
142
|
+
// The AuthorizeSecurityGroup* API calls are idempotent, so calling this on
|
|
143
|
+
// an existing rule will not cause an error.
|
|
144
|
+
const authorizeParams = convertRuleToApiParams(props, groupId);
|
|
145
|
+
const action =
|
|
146
|
+
props.type === "ingress"
|
|
147
|
+
? "AuthorizeSecurityGroupIngress"
|
|
148
|
+
: "AuthorizeSecurityGroupEgress";
|
|
149
|
+
|
|
150
|
+
// There is no "pending" state for a security group rule. The API call
|
|
151
|
+
// is synchronous, and success is determined by the response.
|
|
152
|
+
await callEC2Api(
|
|
153
|
+
client,
|
|
154
|
+
action,
|
|
155
|
+
parseSecurityGroupRuleXmlResponse,
|
|
156
|
+
authorizeParams,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return this({
|
|
160
|
+
ruleId,
|
|
161
|
+
...props,
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// --- Helper Functions and Interfaces ---
|
|
167
|
+
/**
|
|
168
|
+
* Parse XML responses for Security Group Rule operations
|
|
169
|
+
*/
|
|
170
|
+
function parseSecurityGroupRuleXmlResponse<T>(xmlText: string): T {
|
|
171
|
+
const parser = new XMLParser({
|
|
172
|
+
ignoreAttributes: true,
|
|
173
|
+
parseAttributeValue: true,
|
|
174
|
+
parseTagValue: true,
|
|
175
|
+
});
|
|
176
|
+
const parsed = parser.parse(xmlText);
|
|
177
|
+
const result: Record<string, unknown> = {};
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
parsed.AuthorizeSecurityGroupIngressResponse ||
|
|
181
|
+
parsed.AuthorizeSecurityGroupEgressResponse ||
|
|
182
|
+
parsed.RevokeSecurityGroupIngressResponse ||
|
|
183
|
+
parsed.RevokeSecurityGroupEgressResponse
|
|
184
|
+
) {
|
|
185
|
+
result.success = parsed.return === "true" || parsed.return === true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result as T;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Convert rule properties to the flat format required by the AWS API.
|
|
193
|
+
*/
|
|
194
|
+
function convertRuleToApiParams(
|
|
195
|
+
props: SecurityGroupRuleProps,
|
|
196
|
+
groupId: string,
|
|
197
|
+
): Record<string, string> {
|
|
198
|
+
const params: Record<string, string> = {
|
|
199
|
+
GroupId: groupId,
|
|
200
|
+
"IpPermissions.1.IpProtocol": props.protocol,
|
|
201
|
+
"IpPermissions.1.FromPort": props.fromPort.toString(),
|
|
202
|
+
"IpPermissions.1.ToPort": props.toPort.toString(),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const prefix = "IpPermissions.1";
|
|
206
|
+
|
|
207
|
+
if (props.cidrBlocks && props.cidrBlocks.length > 0) {
|
|
208
|
+
props.cidrBlocks.forEach((cidr, index) => {
|
|
209
|
+
const rangePrefix = `${prefix}.IpRanges.${index + 1}`;
|
|
210
|
+
params[`${rangePrefix}.CidrIp`] = cidr;
|
|
211
|
+
if (props.description) {
|
|
212
|
+
params[`${rangePrefix}.Description`] = props.description;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (props.sourceSecurityGroups && props.sourceSecurityGroups.length > 0) {
|
|
218
|
+
props.sourceSecurityGroups.forEach((sg, index) => {
|
|
219
|
+
const groupPrefix = `${prefix}.UserIdGroupPairs.${index + 1}`;
|
|
220
|
+
params[`${groupPrefix}.GroupId`] =
|
|
221
|
+
typeof sg === "string" ? sg : sg.groupId;
|
|
222
|
+
if (props.description) {
|
|
223
|
+
params[`${groupPrefix}.Description`] = props.description;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return params;
|
|
229
|
+
}
|