alchemy-effect 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/bin/alchemy-effect.js +16 -17
- package/bin/alchemy-effect.js.map +1 -1
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +19 -17
- package/lib/apply.js.map +1 -1
- package/lib/aws/client.d.ts.map +1 -1
- package/lib/aws/client.js +15 -2
- package/lib/aws/client.js.map +1 -1
- package/lib/aws/dynamodb/secondary-index.d.ts.map +1 -1
- package/lib/aws/dynamodb/table.d.ts.map +1 -1
- package/lib/aws/dynamodb/table.js.map +1 -1
- package/lib/aws/ec2/egress-only-igw.d.ts +55 -0
- package/lib/aws/ec2/egress-only-igw.d.ts.map +1 -0
- package/lib/aws/ec2/egress-only-igw.js +4 -0
- package/lib/aws/ec2/egress-only-igw.js.map +1 -0
- package/lib/aws/ec2/egress-only-igw.provider.d.ts +6 -0
- package/lib/aws/ec2/egress-only-igw.provider.d.ts.map +1 -0
- package/lib/aws/ec2/egress-only-igw.provider.js +114 -0
- package/lib/aws/ec2/egress-only-igw.provider.js.map +1 -0
- package/lib/aws/ec2/eip.d.ts +85 -0
- package/lib/aws/ec2/eip.d.ts.map +1 -0
- package/lib/aws/ec2/eip.js +4 -0
- package/lib/aws/ec2/eip.js.map +1 -0
- package/lib/aws/ec2/eip.provider.d.ts +6 -0
- package/lib/aws/ec2/eip.provider.d.ts.map +1 -0
- package/lib/aws/ec2/eip.provider.js +135 -0
- package/lib/aws/ec2/eip.provider.js.map +1 -0
- package/lib/aws/ec2/index.d.ts +18 -0
- package/lib/aws/ec2/index.d.ts.map +1 -1
- package/lib/aws/ec2/index.js +18 -0
- package/lib/aws/ec2/index.js.map +1 -1
- package/lib/aws/ec2/internet-gateway.d.ts.map +1 -1
- package/lib/aws/ec2/internet-gateway.provider.d.ts.map +1 -1
- package/lib/aws/ec2/internet-gateway.provider.js +9 -5
- package/lib/aws/ec2/internet-gateway.provider.js.map +1 -1
- package/lib/aws/ec2/nat-gateway.d.ts +127 -0
- package/lib/aws/ec2/nat-gateway.d.ts.map +1 -0
- package/lib/aws/ec2/nat-gateway.js +4 -0
- package/lib/aws/ec2/nat-gateway.js.map +1 -0
- package/lib/aws/ec2/nat-gateway.provider.d.ts +6 -0
- package/lib/aws/ec2/nat-gateway.provider.d.ts.map +1 -0
- package/lib/aws/ec2/nat-gateway.provider.js +222 -0
- package/lib/aws/ec2/nat-gateway.provider.js.map +1 -0
- package/lib/aws/ec2/network-acl-association.d.ts +44 -0
- package/lib/aws/ec2/network-acl-association.d.ts.map +1 -0
- package/lib/aws/ec2/network-acl-association.js +4 -0
- package/lib/aws/ec2/network-acl-association.js.map +1 -0
- package/lib/aws/ec2/network-acl-association.provider.d.ts +4 -0
- package/lib/aws/ec2/network-acl-association.provider.d.ts.map +1 -0
- package/lib/aws/ec2/network-acl-association.provider.js +115 -0
- package/lib/aws/ec2/network-acl-association.provider.js.map +1 -0
- package/lib/aws/ec2/network-acl-entry.d.ts +118 -0
- package/lib/aws/ec2/network-acl-entry.d.ts.map +1 -0
- package/lib/aws/ec2/network-acl-entry.js +3 -0
- package/lib/aws/ec2/network-acl-entry.js.map +1 -0
- package/lib/aws/ec2/network-acl-entry.provider.d.ts +4 -0
- package/lib/aws/ec2/network-acl-entry.provider.d.ts.map +1 -0
- package/lib/aws/ec2/network-acl-entry.provider.js +129 -0
- package/lib/aws/ec2/network-acl-entry.provider.js.map +1 -0
- package/lib/aws/ec2/network-acl.d.ts +82 -0
- package/lib/aws/ec2/network-acl.d.ts.map +1 -0
- package/lib/aws/ec2/network-acl.js +4 -0
- package/lib/aws/ec2/network-acl.js.map +1 -0
- package/lib/aws/ec2/network-acl.provider.d.ts +6 -0
- package/lib/aws/ec2/network-acl.provider.d.ts.map +1 -0
- package/lib/aws/ec2/network-acl.provider.js +136 -0
- package/lib/aws/ec2/network-acl.provider.js.map +1 -0
- package/lib/aws/ec2/route-table-association.d.ts.map +1 -1
- package/lib/aws/ec2/route-table-association.provider.d.ts.map +1 -1
- package/lib/aws/ec2/route-table-association.provider.js.map +1 -1
- package/lib/aws/ec2/route-table.d.ts.map +1 -1
- package/lib/aws/ec2/route-table.provider.d.ts.map +1 -1
- package/lib/aws/ec2/route-table.provider.js.map +1 -1
- package/lib/aws/ec2/route.d.ts.map +1 -1
- package/lib/aws/ec2/route.provider.d.ts.map +1 -1
- package/lib/aws/ec2/route.provider.js.map +1 -1
- package/lib/aws/ec2/security-group-rule.d.ts +118 -0
- package/lib/aws/ec2/security-group-rule.d.ts.map +1 -0
- package/lib/aws/ec2/security-group-rule.js +4 -0
- package/lib/aws/ec2/security-group-rule.js.map +1 -0
- package/lib/aws/ec2/security-group-rule.provider.d.ts +4 -0
- package/lib/aws/ec2/security-group-rule.provider.d.ts.map +1 -0
- package/lib/aws/ec2/security-group-rule.provider.js +193 -0
- package/lib/aws/ec2/security-group-rule.provider.js.map +1 -0
- package/lib/aws/ec2/security-group.d.ts +147 -0
- package/lib/aws/ec2/security-group.d.ts.map +1 -0
- package/lib/aws/ec2/security-group.js +4 -0
- package/lib/aws/ec2/security-group.js.map +1 -0
- package/lib/aws/ec2/security-group.provider.d.ts +6 -0
- package/lib/aws/ec2/security-group.provider.d.ts.map +1 -0
- package/lib/aws/ec2/security-group.provider.js +291 -0
- package/lib/aws/ec2/security-group.provider.js.map +1 -0
- package/lib/aws/ec2/subnet.d.ts.map +1 -1
- package/lib/aws/ec2/subnet.provider.d.ts.map +1 -1
- package/lib/aws/ec2/subnet.provider.js +33 -30
- package/lib/aws/ec2/subnet.provider.js.map +1 -1
- package/lib/aws/ec2/vpc-endpoint.d.ts +176 -0
- package/lib/aws/ec2/vpc-endpoint.d.ts.map +1 -0
- package/lib/aws/ec2/vpc-endpoint.js +4 -0
- package/lib/aws/ec2/vpc-endpoint.js.map +1 -0
- package/lib/aws/ec2/vpc-endpoint.provider.d.ts +6 -0
- package/lib/aws/ec2/vpc-endpoint.provider.d.ts.map +1 -0
- package/lib/aws/ec2/vpc-endpoint.provider.js +315 -0
- package/lib/aws/ec2/vpc-endpoint.provider.js.map +1 -0
- package/lib/aws/ec2/vpc.d.ts.map +1 -1
- package/lib/aws/ec2/vpc.provider.d.ts +2 -2
- package/lib/aws/ec2/vpc.provider.d.ts.map +1 -1
- package/lib/aws/ec2/vpc.provider.js +38 -31
- package/lib/aws/ec2/vpc.provider.js.map +1 -1
- package/lib/aws/index.d.ts +2 -2
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -1
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/lambda/function.d.ts.map +1 -1
- package/lib/aws/lambda/function.invoke.d.ts.map +1 -1
- package/lib/aws/lambda/function.invoke.js.map +1 -1
- package/lib/aws/lambda/function.js.map +1 -1
- package/lib/aws/sqs/queue.d.ts +1 -2
- package/lib/aws/sqs/queue.d.ts.map +1 -1
- package/lib/aws/sqs/queue.event-source.d.ts.map +1 -1
- package/lib/aws/sqs/queue.event-source.js +2 -2
- package/lib/aws/sqs/queue.event-source.js.map +1 -1
- package/lib/aws/sqs/queue.js.map +1 -1
- package/lib/aws/sqs/queue.provider.d.ts.map +1 -1
- package/lib/aws/sqs/queue.provider.js +22 -14
- package/lib/aws/sqs/queue.provider.js.map +1 -1
- package/lib/aws/sqs/queue.send-message.d.ts.map +1 -1
- package/lib/aws/sqs/queue.send-message.js.map +1 -1
- package/lib/binding.d.ts.map +1 -1
- package/lib/cli/components/PlanProgress.d.ts.map +1 -1
- package/lib/cli/components/PlanProgress.js.map +1 -1
- package/lib/cli/index.d.ts +1 -1
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cli/index.js.map +1 -1
- package/lib/cloudflare/kv/namespace.binding.d.ts.map +1 -1
- package/lib/cloudflare/kv/namespace.binding.js.map +1 -1
- package/lib/cloudflare/kv/namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv/namespace.provider.d.ts +1 -2
- package/lib/cloudflare/kv/namespace.provider.d.ts.map +1 -1
- package/lib/cloudflare/kv/namespace.provider.js +0 -2
- package/lib/cloudflare/kv/namespace.provider.js.map +1 -1
- package/lib/cloudflare/r2/bucket.binding.d.ts.map +1 -1
- package/lib/cloudflare/r2/bucket.binding.js.map +1 -1
- package/lib/cloudflare/r2/bucket.d.ts.map +1 -1
- package/lib/output.d.ts.map +1 -1
- package/lib/output.js.map +1 -1
- package/lib/plan.d.ts +2 -2
- package/lib/plan.d.ts.map +1 -1
- package/lib/plan.js +14 -13
- package/lib/plan.js.map +1 -1
- package/lib/provider.d.ts +1 -1
- package/lib/provider.d.ts.map +1 -1
- package/lib/provider.js.map +1 -1
- package/lib/runtime.d.ts.map +1 -1
- package/lib/tags.d.ts +12 -0
- package/lib/tags.d.ts.map +1 -1
- package/lib/tags.js +24 -0
- package/lib/tags.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +51 -51
- package/src/apply.ts +21 -12
- package/src/aws/client.ts +22 -1
- package/src/aws/dynamodb/secondary-index.ts +5 -5
- package/src/aws/dynamodb/table.ts +8 -11
- package/src/aws/ec2/egress-only-igw.provider.ts +181 -0
- package/src/aws/ec2/egress-only-igw.ts +77 -0
- package/src/aws/ec2/eip.provider.ts +191 -0
- package/src/aws/ec2/eip.ts +106 -0
- package/src/aws/ec2/index.ts +18 -0
- package/src/aws/ec2/internet-gateway.provider.ts +15 -6
- package/src/aws/ec2/internet-gateway.ts +6 -6
- package/src/aws/ec2/nat-gateway.provider.ts +341 -0
- package/src/aws/ec2/nat-gateway.ts +155 -0
- package/src/aws/ec2/network-acl-association.provider.ts +181 -0
- package/src/aws/ec2/network-acl-association.ts +60 -0
- package/src/aws/ec2/network-acl-entry.provider.ts +218 -0
- package/src/aws/ec2/network-acl-entry.ts +140 -0
- package/src/aws/ec2/network-acl.provider.ts +195 -0
- package/src/aws/ec2/network-acl.ts +102 -0
- package/src/aws/ec2/route-table-association.provider.ts +1 -2
- package/src/aws/ec2/route-table-association.ts +6 -6
- package/src/aws/ec2/route-table.provider.ts +1 -2
- package/src/aws/ec2/route-table.ts +6 -6
- package/src/aws/ec2/route.provider.ts +1 -2
- package/src/aws/ec2/route.ts +6 -6
- package/src/aws/ec2/security-group-rule.provider.ts +264 -0
- package/src/aws/ec2/security-group-rule.ts +151 -0
- package/src/aws/ec2/security-group.provider.ts +392 -0
- package/src/aws/ec2/security-group.ts +182 -0
- package/src/aws/ec2/subnet.provider.ts +57 -56
- package/src/aws/ec2/subnet.ts +6 -6
- package/src/aws/ec2/vpc-endpoint.provider.ts +466 -0
- package/src/aws/ec2/vpc-endpoint.ts +213 -0
- package/src/aws/ec2/vpc.provider.ts +58 -51
- package/src/aws/ec2/vpc.ts +6 -6
- package/src/aws/index.ts +9 -0
- package/src/aws/lambda/function.invoke.ts +4 -2
- package/src/aws/lambda/function.ts +4 -2
- package/src/aws/sqs/queue.event-source.ts +12 -14
- package/src/aws/sqs/queue.provider.ts +25 -15
- package/src/aws/sqs/queue.send-message.ts +4 -2
- package/src/aws/sqs/queue.ts +1 -8
- package/src/binding.ts +7 -7
- package/src/cli/components/PlanProgress.tsx +3 -2
- package/src/cloudflare/kv/namespace.binding.ts +4 -2
- package/src/cloudflare/kv/namespace.provider.ts +0 -2
- package/src/cloudflare/kv/namespace.ts +6 -6
- package/src/cloudflare/r2/bucket.binding.ts +4 -2
- package/src/cloudflare/r2/bucket.ts +6 -6
- package/src/output.ts +5 -3
- package/src/plan.ts +26 -20
- package/src/provider.ts +12 -11
- package/src/runtime.ts +2 -2
- package/src/tags.ts +29 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Schedule from "effect/Schedule";
|
|
4
|
+
import type * as EC2 from "itty-aws/ec2";
|
|
5
|
+
|
|
6
|
+
import type { ScopedPlanStatusSession } from "../../cli/service.ts";
|
|
7
|
+
import {
|
|
8
|
+
createAlchemyTagFilters,
|
|
9
|
+
createTagger,
|
|
10
|
+
createTagsList,
|
|
11
|
+
diffTags,
|
|
12
|
+
} from "../../tags.ts";
|
|
13
|
+
import { Account } from "../account.ts";
|
|
14
|
+
import { Region } from "../region.ts";
|
|
15
|
+
import { EC2Client } from "./client.ts";
|
|
16
|
+
import {
|
|
17
|
+
NatGateway,
|
|
18
|
+
type NatGatewayAttrs,
|
|
19
|
+
type NatGatewayId,
|
|
20
|
+
type NatGatewayProps,
|
|
21
|
+
} from "./nat-gateway.ts";
|
|
22
|
+
|
|
23
|
+
export const natGatewayProvider = () =>
|
|
24
|
+
NatGateway.provider.effect(
|
|
25
|
+
// @ts-expect-error
|
|
26
|
+
Effect.gen(function* () {
|
|
27
|
+
const ec2 = yield* EC2Client;
|
|
28
|
+
const region = yield* Region;
|
|
29
|
+
const accountId = yield* Account;
|
|
30
|
+
const tagged = yield* createTagger();
|
|
31
|
+
|
|
32
|
+
const createTags = (
|
|
33
|
+
id: string,
|
|
34
|
+
tags?: Record<string, string>,
|
|
35
|
+
): Record<string, string> => ({
|
|
36
|
+
Name: id,
|
|
37
|
+
...tagged(id),
|
|
38
|
+
...tags,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const describeNatGateway = (natGatewayId: string) =>
|
|
42
|
+
ec2.describeNatGateways({ NatGatewayIds: [natGatewayId] }).pipe(
|
|
43
|
+
Effect.map((r) => r.NatGateways?.[0]),
|
|
44
|
+
Effect.flatMap((gw) =>
|
|
45
|
+
gw
|
|
46
|
+
? Effect.succeed(gw)
|
|
47
|
+
: Effect.fail(new Error(`NAT Gateway ${natGatewayId} not found`)),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const toAttrs = (
|
|
52
|
+
gw: EC2.NatGateway,
|
|
53
|
+
): NatGatewayAttrs<NatGatewayProps> => {
|
|
54
|
+
const primaryAddress =
|
|
55
|
+
gw.NatGatewayAddresses?.find((a) => a.IsPrimary) ??
|
|
56
|
+
gw.NatGatewayAddresses?.[0];
|
|
57
|
+
return {
|
|
58
|
+
natGatewayId: gw.NatGatewayId as NatGatewayId,
|
|
59
|
+
natGatewayArn:
|
|
60
|
+
`arn:aws:ec2:${region}:${accountId}:natgateway/${gw.NatGatewayId}` as NatGatewayAttrs<NatGatewayProps>["natGatewayArn"],
|
|
61
|
+
subnetId: gw.SubnetId as NatGatewayAttrs<NatGatewayProps>["subnetId"],
|
|
62
|
+
vpcId: gw.VpcId!,
|
|
63
|
+
state: gw.State!,
|
|
64
|
+
connectivityType: gw.ConnectivityType!,
|
|
65
|
+
publicIp: primaryAddress?.PublicIp,
|
|
66
|
+
privateIp: primaryAddress?.PrivateIp,
|
|
67
|
+
natGatewayAddresses: gw.NatGatewayAddresses?.map((a) => ({
|
|
68
|
+
allocationId: a.AllocationId,
|
|
69
|
+
networkInterfaceId: a.NetworkInterfaceId,
|
|
70
|
+
privateIp: a.PrivateIp,
|
|
71
|
+
publicIp: a.PublicIp,
|
|
72
|
+
associationId: a.AssociationId,
|
|
73
|
+
isPrimary: a.IsPrimary,
|
|
74
|
+
failureMessage: a.FailureMessage,
|
|
75
|
+
status: a.Status,
|
|
76
|
+
})),
|
|
77
|
+
failureCode: gw.FailureCode,
|
|
78
|
+
failureMessage: gw.FailureMessage,
|
|
79
|
+
createTime:
|
|
80
|
+
gw.CreateTime instanceof Date
|
|
81
|
+
? gw.CreateTime.toISOString()
|
|
82
|
+
: (gw.CreateTime as string | undefined),
|
|
83
|
+
deleteTime:
|
|
84
|
+
gw.DeleteTime instanceof Date
|
|
85
|
+
? gw.DeleteTime.toISOString()
|
|
86
|
+
: (gw.DeleteTime as string | undefined),
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Find NAT Gateway by alchemy tags when we don't have the ID
|
|
91
|
+
const findNatGatewayByTags = Effect.fn(function* (id: string) {
|
|
92
|
+
const filters = yield* createAlchemyTagFilters(id);
|
|
93
|
+
const result = yield* ec2.describeNatGateways({ Filter: filters });
|
|
94
|
+
|
|
95
|
+
// Find a NAT Gateway that's not deleted and has matching tags
|
|
96
|
+
for (const gw of result.NatGateways ?? []) {
|
|
97
|
+
return gw;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
stables: ["natGatewayId", "natGatewayArn", "vpcId"],
|
|
104
|
+
|
|
105
|
+
read: Effect.fn(function* ({ id, output }) {
|
|
106
|
+
if (output) {
|
|
107
|
+
// We have the NAT Gateway ID, use it directly
|
|
108
|
+
return toAttrs(yield* describeNatGateway(output.natGatewayId));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// No output - try to find by tags (recovery from incomplete create)
|
|
112
|
+
const gw = yield* findNatGatewayByTags(id);
|
|
113
|
+
if (gw) {
|
|
114
|
+
return toAttrs(gw);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Not found
|
|
118
|
+
return undefined;
|
|
119
|
+
}),
|
|
120
|
+
|
|
121
|
+
diff: Effect.fn(function* ({ news, olds }) {
|
|
122
|
+
// NAT Gateway is mostly immutable - any change to core properties requires replacement
|
|
123
|
+
if (
|
|
124
|
+
news.subnetId !== olds.subnetId ||
|
|
125
|
+
news.connectivityType !== olds.connectivityType ||
|
|
126
|
+
news.allocationId !== olds.allocationId
|
|
127
|
+
) {
|
|
128
|
+
return { action: "replace" };
|
|
129
|
+
}
|
|
130
|
+
// Tags can be updated in-place
|
|
131
|
+
}),
|
|
132
|
+
|
|
133
|
+
create: Effect.fn(function* ({ id, news, session }) {
|
|
134
|
+
yield* session.note("Creating NAT Gateway...");
|
|
135
|
+
|
|
136
|
+
const result = yield* ec2.createNatGateway({
|
|
137
|
+
SubnetId: news.subnetId as string,
|
|
138
|
+
AllocationId: news.allocationId as string | undefined,
|
|
139
|
+
ConnectivityType: news.connectivityType ?? "public",
|
|
140
|
+
PrivateIpAddress: news.privateIpAddress,
|
|
141
|
+
SecondaryAllocationIds: news.secondaryAllocationIds as
|
|
142
|
+
| string[]
|
|
143
|
+
| undefined,
|
|
144
|
+
SecondaryPrivateIpAddresses: news.secondaryPrivateIpAddresses,
|
|
145
|
+
SecondaryPrivateIpAddressCount: news.secondaryPrivateIpAddressCount,
|
|
146
|
+
TagSpecifications: [
|
|
147
|
+
{
|
|
148
|
+
ResourceType: "natgateway",
|
|
149
|
+
Tags: createTagsList(createTags(id, news.tags)),
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
DryRun: false,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const natGatewayId = result.NatGateway!.NatGatewayId!;
|
|
156
|
+
yield* session.note(`NAT Gateway created: ${natGatewayId}`);
|
|
157
|
+
|
|
158
|
+
// Wait for NAT Gateway to be available
|
|
159
|
+
const gw = yield* waitForNatGatewayAvailable(natGatewayId, session);
|
|
160
|
+
|
|
161
|
+
return toAttrs(gw);
|
|
162
|
+
}),
|
|
163
|
+
|
|
164
|
+
update: Effect.fn(function* ({ id, news, output, session }) {
|
|
165
|
+
const natGatewayId = output.natGatewayId;
|
|
166
|
+
|
|
167
|
+
// Handle tag updates
|
|
168
|
+
const newTags = createTags(id, news.tags);
|
|
169
|
+
const oldTags =
|
|
170
|
+
(yield* ec2
|
|
171
|
+
.describeTags({
|
|
172
|
+
Filters: [
|
|
173
|
+
{ Name: "resource-id", Values: [natGatewayId] },
|
|
174
|
+
{ Name: "resource-type", Values: ["natgateway"] },
|
|
175
|
+
],
|
|
176
|
+
})
|
|
177
|
+
.pipe(
|
|
178
|
+
Effect.map(
|
|
179
|
+
(r) =>
|
|
180
|
+
Object.fromEntries(
|
|
181
|
+
r.Tags?.map((t) => [t.Key!, t.Value!]) ?? [],
|
|
182
|
+
) as Record<string, string>,
|
|
183
|
+
),
|
|
184
|
+
)) ?? {};
|
|
185
|
+
|
|
186
|
+
const { removed, upsert } = diffTags(oldTags, newTags);
|
|
187
|
+
|
|
188
|
+
if (removed.length > 0) {
|
|
189
|
+
yield* ec2.deleteTags({
|
|
190
|
+
Resources: [natGatewayId],
|
|
191
|
+
Tags: removed.map((key) => ({ Key: key })),
|
|
192
|
+
DryRun: false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (upsert.length > 0) {
|
|
196
|
+
yield* ec2.createTags({
|
|
197
|
+
Resources: [natGatewayId],
|
|
198
|
+
Tags: upsert,
|
|
199
|
+
DryRun: false,
|
|
200
|
+
});
|
|
201
|
+
yield* session.note("Updated tags");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Refresh state
|
|
205
|
+
const gw = yield* describeNatGateway(natGatewayId);
|
|
206
|
+
return toAttrs(gw);
|
|
207
|
+
}),
|
|
208
|
+
|
|
209
|
+
delete: Effect.fn(function* ({ output, session }) {
|
|
210
|
+
const natGatewayId = output.natGatewayId;
|
|
211
|
+
|
|
212
|
+
yield* session.note(`Deleting NAT Gateway: ${natGatewayId}`);
|
|
213
|
+
|
|
214
|
+
yield* ec2
|
|
215
|
+
.deleteNatGateway({
|
|
216
|
+
NatGatewayId: natGatewayId,
|
|
217
|
+
DryRun: false,
|
|
218
|
+
})
|
|
219
|
+
.pipe(Effect.catchTag("NatGatewayNotFound", () => Effect.void));
|
|
220
|
+
|
|
221
|
+
// Wait for NAT Gateway to be deleted
|
|
222
|
+
yield* waitForNatGatewayDeleted(natGatewayId, session);
|
|
223
|
+
|
|
224
|
+
yield* session.note(`NAT Gateway ${natGatewayId} deleted`);
|
|
225
|
+
}),
|
|
226
|
+
};
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Retryable error: NAT Gateway is still pending
|
|
231
|
+
class NatGatewayPending extends Data.TaggedError("NatGatewayPending")<{
|
|
232
|
+
natGatewayId: string;
|
|
233
|
+
state: string;
|
|
234
|
+
}> {}
|
|
235
|
+
|
|
236
|
+
// Terminal error: NAT Gateway creation failed
|
|
237
|
+
class NatGatewayFailed extends Data.TaggedError("NatGatewayFailed")<{
|
|
238
|
+
natGatewayId: string;
|
|
239
|
+
failureCode?: string;
|
|
240
|
+
failureMessage?: string;
|
|
241
|
+
}> {}
|
|
242
|
+
|
|
243
|
+
// Terminal error: NAT Gateway not found
|
|
244
|
+
class NatGatewayNotFound extends Data.TaggedError("NatGatewayNotFound")<{
|
|
245
|
+
natGatewayId: string;
|
|
246
|
+
}> {}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Wait for NAT Gateway to be in available state
|
|
250
|
+
*/
|
|
251
|
+
const waitForNatGatewayAvailable = (
|
|
252
|
+
natGatewayId: string,
|
|
253
|
+
session: ScopedPlanStatusSession,
|
|
254
|
+
) =>
|
|
255
|
+
Effect.gen(function* () {
|
|
256
|
+
const ec2 = yield* EC2Client;
|
|
257
|
+
const result = yield* ec2.describeNatGateways({
|
|
258
|
+
NatGatewayIds: [natGatewayId],
|
|
259
|
+
});
|
|
260
|
+
const gw = result.NatGateways?.[0];
|
|
261
|
+
|
|
262
|
+
if (!gw) {
|
|
263
|
+
return yield* new NatGatewayNotFound({ natGatewayId });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (gw.State === "available") {
|
|
267
|
+
return gw;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (gw.State === "failed") {
|
|
271
|
+
return yield* new NatGatewayFailed({
|
|
272
|
+
natGatewayId,
|
|
273
|
+
failureCode: gw.FailureCode,
|
|
274
|
+
failureMessage: gw.FailureMessage,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Still pending - this is the only retryable case
|
|
279
|
+
return yield* new NatGatewayPending({ natGatewayId, state: gw.State! });
|
|
280
|
+
}).pipe(
|
|
281
|
+
Effect.tapError(Effect.logDebug),
|
|
282
|
+
Effect.retry({
|
|
283
|
+
while: (e) => e._tag === "NatGatewayPending",
|
|
284
|
+
schedule: Schedule.fixed(5000).pipe(
|
|
285
|
+
Schedule.intersect(Schedule.recurs(60)), // Max 5 minutes
|
|
286
|
+
Schedule.tapOutput(([, attempt]) =>
|
|
287
|
+
session.note(
|
|
288
|
+
`Waiting for NAT Gateway to be available... (${(attempt + 1) * 5}s)`,
|
|
289
|
+
),
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Retryable error: NAT Gateway is still deleting
|
|
296
|
+
class NatGatewayDeleting extends Data.TaggedError("NatGatewayDeleting")<{
|
|
297
|
+
natGatewayId: string;
|
|
298
|
+
state: string;
|
|
299
|
+
}> {}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Wait for NAT Gateway to be deleted
|
|
303
|
+
*/
|
|
304
|
+
const waitForNatGatewayDeleted = (
|
|
305
|
+
natGatewayId: string,
|
|
306
|
+
session: ScopedPlanStatusSession,
|
|
307
|
+
) =>
|
|
308
|
+
Effect.gen(function* () {
|
|
309
|
+
const ec2 = yield* EC2Client;
|
|
310
|
+
const result = yield* ec2
|
|
311
|
+
.describeNatGateways({ NatGatewayIds: [natGatewayId] })
|
|
312
|
+
.pipe(
|
|
313
|
+
Effect.catchTag("NatGatewayNotFound", () =>
|
|
314
|
+
Effect.succeed({ NatGateways: [] }),
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const gw = result.NatGateways?.[0];
|
|
319
|
+
|
|
320
|
+
if (!gw || gw.State === "deleted") {
|
|
321
|
+
return; // Successfully deleted
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
yield* Effect.logDebug(gw);
|
|
325
|
+
|
|
326
|
+
// Still deleting - this is the only retryable case
|
|
327
|
+
return yield* new NatGatewayDeleting({ natGatewayId, state: gw.State! });
|
|
328
|
+
}).pipe(
|
|
329
|
+
Effect.tapError(Effect.logDebug),
|
|
330
|
+
Effect.retry({
|
|
331
|
+
while: (e) => e._tag === "NatGatewayDeleting",
|
|
332
|
+
schedule: Schedule.fixed(5000).pipe(
|
|
333
|
+
Schedule.intersect(Schedule.recurs(60)), // Max 5 minutes
|
|
334
|
+
Schedule.tapOutput(([, attempt]) =>
|
|
335
|
+
session.note(
|
|
336
|
+
`Waiting for NAT Gateway deletion... (${(attempt + 1) * 5}s)`,
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
),
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type * as EC2 from "itty-aws/ec2";
|
|
2
|
+
import type { Input } from "../../input.ts";
|
|
3
|
+
import { Resource } from "../../resource.ts";
|
|
4
|
+
import type { AccountID } from "../account.ts";
|
|
5
|
+
import type { RegionID } from "../region.ts";
|
|
6
|
+
import type { AllocationId } from "./eip.ts";
|
|
7
|
+
import type { SubnetId } from "./subnet.ts";
|
|
8
|
+
|
|
9
|
+
export const NatGateway = Resource<{
|
|
10
|
+
<const ID extends string, const Props extends NatGatewayProps>(
|
|
11
|
+
id: ID,
|
|
12
|
+
props: Props,
|
|
13
|
+
): NatGateway<ID, Props>;
|
|
14
|
+
}>("AWS.EC2.NatGateway");
|
|
15
|
+
|
|
16
|
+
export interface NatGateway<
|
|
17
|
+
ID extends string = string,
|
|
18
|
+
Props extends NatGatewayProps = NatGatewayProps,
|
|
19
|
+
> extends Resource<
|
|
20
|
+
"AWS.EC2.NatGateway",
|
|
21
|
+
ID,
|
|
22
|
+
Props,
|
|
23
|
+
NatGatewayAttrs<Input.Resolve<Props>>,
|
|
24
|
+
NatGateway
|
|
25
|
+
> {}
|
|
26
|
+
|
|
27
|
+
export type NatGatewayId<ID extends string = string> = `nat-${ID}`;
|
|
28
|
+
export const NatGatewayId = <ID extends string>(
|
|
29
|
+
id: ID,
|
|
30
|
+
): ID & NatGatewayId<ID> => `nat-${id}` as ID & NatGatewayId<ID>;
|
|
31
|
+
|
|
32
|
+
export interface NatGatewayProps {
|
|
33
|
+
/**
|
|
34
|
+
* The subnet in which to create the NAT gateway.
|
|
35
|
+
* For public NAT gateways, this must be a public subnet.
|
|
36
|
+
*/
|
|
37
|
+
subnetId: Input<SubnetId>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The allocation ID of the Elastic IP address for the gateway.
|
|
41
|
+
* Required for public NAT gateways.
|
|
42
|
+
*/
|
|
43
|
+
allocationId?: Input<AllocationId>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Indicates whether the NAT gateway supports public or private connectivity.
|
|
47
|
+
* @default "public"
|
|
48
|
+
*/
|
|
49
|
+
connectivityType?: EC2.ConnectivityType;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The private IPv4 address to assign to the NAT gateway.
|
|
53
|
+
* If you don't provide an address, a private IPv4 address will be automatically assigned.
|
|
54
|
+
*/
|
|
55
|
+
privateIpAddress?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Secondary allocation IDs for additional private IP addresses.
|
|
59
|
+
* Only valid for private NAT gateways.
|
|
60
|
+
*/
|
|
61
|
+
secondaryAllocationIds?: Input<AllocationId>[];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Secondary private IPv4 addresses.
|
|
65
|
+
* Only valid for private NAT gateways.
|
|
66
|
+
*/
|
|
67
|
+
secondaryPrivateIpAddresses?: string[];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The number of secondary private IPv4 addresses to assign.
|
|
71
|
+
* Only valid for private NAT gateways.
|
|
72
|
+
*/
|
|
73
|
+
secondaryPrivateIpAddressCount?: number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Tags to assign to the NAT gateway.
|
|
77
|
+
*/
|
|
78
|
+
tags?: Record<string, Input<string>>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface NatGatewayAttrs<Props extends NatGatewayProps> {
|
|
82
|
+
/**
|
|
83
|
+
* The ID of the NAT gateway.
|
|
84
|
+
*/
|
|
85
|
+
natGatewayId: NatGatewayId;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The Amazon Resource Name (ARN) of the NAT gateway.
|
|
89
|
+
*/
|
|
90
|
+
natGatewayArn: `arn:aws:ec2:${RegionID}:${AccountID}:natgateway/${this["natGatewayId"]}`;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The ID of the subnet in which the NAT gateway is located.
|
|
94
|
+
*/
|
|
95
|
+
subnetId: Props["subnetId"];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The ID of the VPC in which the NAT gateway is located.
|
|
99
|
+
*/
|
|
100
|
+
vpcId: string;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The current state of the NAT gateway.
|
|
104
|
+
*/
|
|
105
|
+
state: EC2.NatGatewayState;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The connectivity type of the NAT gateway.
|
|
109
|
+
*/
|
|
110
|
+
connectivityType: EC2.ConnectivityType;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The Elastic IP address associated with the NAT gateway (for public NAT gateways).
|
|
114
|
+
*/
|
|
115
|
+
publicIp?: string;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The private IP address associated with the NAT gateway.
|
|
119
|
+
*/
|
|
120
|
+
privateIp?: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Information about the IP addresses and network interface associated with the NAT gateway.
|
|
124
|
+
*/
|
|
125
|
+
natGatewayAddresses?: Array<{
|
|
126
|
+
allocationId?: string;
|
|
127
|
+
networkInterfaceId?: string;
|
|
128
|
+
privateIp?: string;
|
|
129
|
+
publicIp?: string;
|
|
130
|
+
associationId?: string;
|
|
131
|
+
isPrimary?: boolean;
|
|
132
|
+
failureMessage?: string;
|
|
133
|
+
status?: EC2.NatGatewayAddressStatus;
|
|
134
|
+
}>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* If the NAT gateway could not be created, specifies the error code for the failure.
|
|
138
|
+
*/
|
|
139
|
+
failureCode?: string;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* If the NAT gateway could not be created, specifies the error message for the failure.
|
|
143
|
+
*/
|
|
144
|
+
failureMessage?: string;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The date and time the NAT gateway was created.
|
|
148
|
+
*/
|
|
149
|
+
createTime?: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The date and time the NAT gateway was deleted, if applicable.
|
|
153
|
+
*/
|
|
154
|
+
deleteTime?: string;
|
|
155
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
|
|
3
|
+
import type { ProviderService } from "../../provider.ts";
|
|
4
|
+
import { EC2Client } from "./client.ts";
|
|
5
|
+
import {
|
|
6
|
+
NetworkAclAssociation,
|
|
7
|
+
type NetworkAclAssociationId,
|
|
8
|
+
} from "./network-acl-association.ts";
|
|
9
|
+
import type { NetworkAclId } from "./network-acl.ts";
|
|
10
|
+
import type { SubnetId } from "./subnet.ts";
|
|
11
|
+
|
|
12
|
+
export const networkAclAssociationProvider = () =>
|
|
13
|
+
NetworkAclAssociation.provider.effect(
|
|
14
|
+
Effect.gen(function* () {
|
|
15
|
+
const ec2 = yield* EC2Client;
|
|
16
|
+
|
|
17
|
+
const findAssociation = (subnetId: string) =>
|
|
18
|
+
ec2
|
|
19
|
+
.describeNetworkAcls({
|
|
20
|
+
Filters: [{ Name: "association.subnet-id", Values: [subnetId] }],
|
|
21
|
+
})
|
|
22
|
+
.pipe(
|
|
23
|
+
Effect.map((r) => {
|
|
24
|
+
const acl = r.NetworkAcls?.[0];
|
|
25
|
+
const assoc = acl?.Associations?.find(
|
|
26
|
+
(a) => a.SubnetId === subnetId,
|
|
27
|
+
);
|
|
28
|
+
return assoc
|
|
29
|
+
? {
|
|
30
|
+
associationId: assoc.NetworkAclAssociationId!,
|
|
31
|
+
networkAclId: assoc.NetworkAclId!,
|
|
32
|
+
subnetId: assoc.SubnetId!,
|
|
33
|
+
}
|
|
34
|
+
: undefined;
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
stables: ["subnetId"],
|
|
40
|
+
|
|
41
|
+
read: Effect.fn(function* ({ olds }) {
|
|
42
|
+
if (!olds) return undefined;
|
|
43
|
+
const assoc = yield* findAssociation(olds.subnetId as string);
|
|
44
|
+
if (!assoc) {
|
|
45
|
+
return yield* Effect.fail(
|
|
46
|
+
new Error(
|
|
47
|
+
`Network ACL Association not found for subnet ${olds.subnetId}`,
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
associationId: assoc.associationId as NetworkAclAssociationId,
|
|
53
|
+
networkAclId: assoc.networkAclId as NetworkAclId,
|
|
54
|
+
subnetId: assoc.subnetId as SubnetId,
|
|
55
|
+
};
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
diff: Effect.fn(function* ({ news, olds }) {
|
|
59
|
+
// Subnet change requires replacement
|
|
60
|
+
if (news.subnetId !== olds.subnetId) {
|
|
61
|
+
return { action: "replace" };
|
|
62
|
+
}
|
|
63
|
+
// Network ACL change can be done via replaceNetworkAclAssociation
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
create: Effect.fn(function* ({ news, session }) {
|
|
67
|
+
yield* session.note(
|
|
68
|
+
`Creating Network ACL Association for subnet ${news.subnetId}...`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// First, find the current association for this subnet (every subnet has one)
|
|
72
|
+
const currentAssoc = yield* findAssociation(news.subnetId as string);
|
|
73
|
+
if (!currentAssoc) {
|
|
74
|
+
return yield* Effect.fail(
|
|
75
|
+
new Error(
|
|
76
|
+
`No existing Network ACL Association found for subnet ${news.subnetId}`,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Replace the association with the new network ACL
|
|
82
|
+
const result = yield* ec2.replaceNetworkAclAssociation({
|
|
83
|
+
AssociationId: currentAssoc.associationId,
|
|
84
|
+
NetworkAclId: news.networkAclId as string,
|
|
85
|
+
DryRun: false,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const newAssociationId = result.NewAssociationId!;
|
|
89
|
+
yield* session.note(
|
|
90
|
+
`Network ACL Association created: ${newAssociationId}`,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
associationId: newAssociationId as NetworkAclAssociationId,
|
|
95
|
+
networkAclId: news.networkAclId as NetworkAclId,
|
|
96
|
+
subnetId: news.subnetId as SubnetId,
|
|
97
|
+
};
|
|
98
|
+
}),
|
|
99
|
+
|
|
100
|
+
update: Effect.fn(function* ({ news, output, session }) {
|
|
101
|
+
yield* session.note(`Updating Network ACL Association...`);
|
|
102
|
+
|
|
103
|
+
// Replace the association with the new network ACL
|
|
104
|
+
const result = yield* ec2.replaceNetworkAclAssociation({
|
|
105
|
+
AssociationId: output.associationId,
|
|
106
|
+
NetworkAclId: news.networkAclId as string,
|
|
107
|
+
DryRun: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const newAssociationId = result.NewAssociationId!;
|
|
111
|
+
yield* session.note(
|
|
112
|
+
`Network ACL Association updated: ${newAssociationId}`,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
associationId: newAssociationId as NetworkAclAssociationId,
|
|
117
|
+
networkAclId: news.networkAclId as NetworkAclId,
|
|
118
|
+
subnetId: news.subnetId as SubnetId,
|
|
119
|
+
};
|
|
120
|
+
}),
|
|
121
|
+
|
|
122
|
+
delete: Effect.fn(function* ({ olds, output, session }) {
|
|
123
|
+
yield* session.note(`Deleting Network ACL Association...`);
|
|
124
|
+
|
|
125
|
+
// When deleting, we need to associate the subnet back to the default NACL
|
|
126
|
+
// Find the default NACL for the VPC
|
|
127
|
+
const subnetResult = yield* ec2.describeSubnets({
|
|
128
|
+
SubnetIds: [olds.subnetId as string],
|
|
129
|
+
});
|
|
130
|
+
const vpcId = subnetResult.Subnets?.[0]?.VpcId;
|
|
131
|
+
|
|
132
|
+
if (vpcId) {
|
|
133
|
+
const defaultAclResult = yield* ec2.describeNetworkAcls({
|
|
134
|
+
Filters: [
|
|
135
|
+
{ Name: "vpc-id", Values: [vpcId] },
|
|
136
|
+
{ Name: "default", Values: ["true"] },
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const defaultAclId =
|
|
141
|
+
defaultAclResult.NetworkAcls?.[0]?.NetworkAclId;
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
defaultAclId &&
|
|
145
|
+
defaultAclId !== (olds.networkAclId as string)
|
|
146
|
+
) {
|
|
147
|
+
// Replace with default NACL
|
|
148
|
+
yield* ec2
|
|
149
|
+
.replaceNetworkAclAssociation({
|
|
150
|
+
AssociationId: output.associationId,
|
|
151
|
+
NetworkAclId: defaultAclId,
|
|
152
|
+
DryRun: false,
|
|
153
|
+
})
|
|
154
|
+
.pipe(
|
|
155
|
+
Effect.catchTag(
|
|
156
|
+
"InvalidAssociationID.NotFound",
|
|
157
|
+
() => Effect.void,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
yield* session.note(
|
|
162
|
+
`Network ACL Association reverted to default`,
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
yield* session.note(
|
|
166
|
+
`Already using default Network ACL, nothing to do`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
} satisfies ProviderService<
|
|
172
|
+
NetworkAclAssociation,
|
|
173
|
+
any,
|
|
174
|
+
any,
|
|
175
|
+
any,
|
|
176
|
+
any,
|
|
177
|
+
any,
|
|
178
|
+
any
|
|
179
|
+
>;
|
|
180
|
+
}),
|
|
181
|
+
);
|