alchemy-effect 0.4.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.
Files changed (251) hide show
  1. package/README.md +13 -7
  2. package/bin/alchemy-effect.js +307 -266
  3. package/bin/alchemy-effect.js.map +1 -1
  4. package/lib/apply.d.ts.map +1 -1
  5. package/lib/apply.js +253 -246
  6. package/lib/apply.js.map +1 -1
  7. package/lib/aws/client.d.ts.map +1 -1
  8. package/lib/aws/client.js +15 -2
  9. package/lib/aws/client.js.map +1 -1
  10. package/lib/aws/dynamodb/secondary-index.d.ts.map +1 -1
  11. package/lib/aws/dynamodb/table.d.ts.map +1 -1
  12. package/lib/aws/dynamodb/table.js.map +1 -1
  13. package/lib/aws/dynamodb/table.provider.d.ts.map +1 -1
  14. package/lib/aws/dynamodb/table.provider.js +10 -2
  15. package/lib/aws/dynamodb/table.provider.js.map +1 -1
  16. package/lib/aws/ec2/egress-only-igw.d.ts +55 -0
  17. package/lib/aws/ec2/egress-only-igw.d.ts.map +1 -0
  18. package/lib/aws/ec2/egress-only-igw.js +4 -0
  19. package/lib/aws/ec2/egress-only-igw.js.map +1 -0
  20. package/lib/aws/ec2/egress-only-igw.provider.d.ts +6 -0
  21. package/lib/aws/ec2/egress-only-igw.provider.d.ts.map +1 -0
  22. package/lib/aws/ec2/egress-only-igw.provider.js +114 -0
  23. package/lib/aws/ec2/egress-only-igw.provider.js.map +1 -0
  24. package/lib/aws/ec2/eip.d.ts +85 -0
  25. package/lib/aws/ec2/eip.d.ts.map +1 -0
  26. package/lib/aws/ec2/eip.js +4 -0
  27. package/lib/aws/ec2/eip.js.map +1 -0
  28. package/lib/aws/ec2/eip.provider.d.ts +6 -0
  29. package/lib/aws/ec2/eip.provider.d.ts.map +1 -0
  30. package/lib/aws/ec2/eip.provider.js +135 -0
  31. package/lib/aws/ec2/eip.provider.js.map +1 -0
  32. package/lib/aws/ec2/index.d.ts +18 -0
  33. package/lib/aws/ec2/index.d.ts.map +1 -1
  34. package/lib/aws/ec2/index.js +18 -0
  35. package/lib/aws/ec2/index.js.map +1 -1
  36. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -1
  37. package/lib/aws/ec2/internet-gateway.provider.d.ts.map +1 -1
  38. package/lib/aws/ec2/internet-gateway.provider.js +9 -5
  39. package/lib/aws/ec2/internet-gateway.provider.js.map +1 -1
  40. package/lib/aws/ec2/nat-gateway.d.ts +127 -0
  41. package/lib/aws/ec2/nat-gateway.d.ts.map +1 -0
  42. package/lib/aws/ec2/nat-gateway.js +4 -0
  43. package/lib/aws/ec2/nat-gateway.js.map +1 -0
  44. package/lib/aws/ec2/nat-gateway.provider.d.ts +6 -0
  45. package/lib/aws/ec2/nat-gateway.provider.d.ts.map +1 -0
  46. package/lib/aws/ec2/nat-gateway.provider.js +222 -0
  47. package/lib/aws/ec2/nat-gateway.provider.js.map +1 -0
  48. package/lib/aws/ec2/network-acl-association.d.ts +44 -0
  49. package/lib/aws/ec2/network-acl-association.d.ts.map +1 -0
  50. package/lib/aws/ec2/network-acl-association.js +4 -0
  51. package/lib/aws/ec2/network-acl-association.js.map +1 -0
  52. package/lib/aws/ec2/network-acl-association.provider.d.ts +4 -0
  53. package/lib/aws/ec2/network-acl-association.provider.d.ts.map +1 -0
  54. package/lib/aws/ec2/network-acl-association.provider.js +115 -0
  55. package/lib/aws/ec2/network-acl-association.provider.js.map +1 -0
  56. package/lib/aws/ec2/network-acl-entry.d.ts +118 -0
  57. package/lib/aws/ec2/network-acl-entry.d.ts.map +1 -0
  58. package/lib/aws/ec2/network-acl-entry.js +3 -0
  59. package/lib/aws/ec2/network-acl-entry.js.map +1 -0
  60. package/lib/aws/ec2/network-acl-entry.provider.d.ts +4 -0
  61. package/lib/aws/ec2/network-acl-entry.provider.d.ts.map +1 -0
  62. package/lib/aws/ec2/network-acl-entry.provider.js +129 -0
  63. package/lib/aws/ec2/network-acl-entry.provider.js.map +1 -0
  64. package/lib/aws/ec2/network-acl.d.ts +82 -0
  65. package/lib/aws/ec2/network-acl.d.ts.map +1 -0
  66. package/lib/aws/ec2/network-acl.js +4 -0
  67. package/lib/aws/ec2/network-acl.js.map +1 -0
  68. package/lib/aws/ec2/network-acl.provider.d.ts +6 -0
  69. package/lib/aws/ec2/network-acl.provider.d.ts.map +1 -0
  70. package/lib/aws/ec2/network-acl.provider.js +136 -0
  71. package/lib/aws/ec2/network-acl.provider.js.map +1 -0
  72. package/lib/aws/ec2/route-table-association.d.ts.map +1 -1
  73. package/lib/aws/ec2/route-table-association.provider.d.ts.map +1 -1
  74. package/lib/aws/ec2/route-table-association.provider.js.map +1 -1
  75. package/lib/aws/ec2/route-table.d.ts.map +1 -1
  76. package/lib/aws/ec2/route-table.provider.d.ts.map +1 -1
  77. package/lib/aws/ec2/route-table.provider.js.map +1 -1
  78. package/lib/aws/ec2/route.d.ts.map +1 -1
  79. package/lib/aws/ec2/route.provider.d.ts.map +1 -1
  80. package/lib/aws/ec2/route.provider.js.map +1 -1
  81. package/lib/aws/ec2/security-group-rule.d.ts +118 -0
  82. package/lib/aws/ec2/security-group-rule.d.ts.map +1 -0
  83. package/lib/aws/ec2/security-group-rule.js +4 -0
  84. package/lib/aws/ec2/security-group-rule.js.map +1 -0
  85. package/lib/aws/ec2/security-group-rule.provider.d.ts +4 -0
  86. package/lib/aws/ec2/security-group-rule.provider.d.ts.map +1 -0
  87. package/lib/aws/ec2/security-group-rule.provider.js +193 -0
  88. package/lib/aws/ec2/security-group-rule.provider.js.map +1 -0
  89. package/lib/aws/ec2/security-group.d.ts +147 -0
  90. package/lib/aws/ec2/security-group.d.ts.map +1 -0
  91. package/lib/aws/ec2/security-group.js +4 -0
  92. package/lib/aws/ec2/security-group.js.map +1 -0
  93. package/lib/aws/ec2/security-group.provider.d.ts +6 -0
  94. package/lib/aws/ec2/security-group.provider.d.ts.map +1 -0
  95. package/lib/aws/ec2/security-group.provider.js +291 -0
  96. package/lib/aws/ec2/security-group.provider.js.map +1 -0
  97. package/lib/aws/ec2/subnet.d.ts.map +1 -1
  98. package/lib/aws/ec2/subnet.provider.d.ts.map +1 -1
  99. package/lib/aws/ec2/subnet.provider.js +33 -30
  100. package/lib/aws/ec2/subnet.provider.js.map +1 -1
  101. package/lib/aws/ec2/vpc-endpoint.d.ts +176 -0
  102. package/lib/aws/ec2/vpc-endpoint.d.ts.map +1 -0
  103. package/lib/aws/ec2/vpc-endpoint.js +4 -0
  104. package/lib/aws/ec2/vpc-endpoint.js.map +1 -0
  105. package/lib/aws/ec2/vpc-endpoint.provider.d.ts +6 -0
  106. package/lib/aws/ec2/vpc-endpoint.provider.d.ts.map +1 -0
  107. package/lib/aws/ec2/vpc-endpoint.provider.js +315 -0
  108. package/lib/aws/ec2/vpc-endpoint.provider.js.map +1 -0
  109. package/lib/aws/ec2/vpc.d.ts.map +1 -1
  110. package/lib/aws/ec2/vpc.provider.d.ts.map +1 -1
  111. package/lib/aws/ec2/vpc.provider.js +38 -32
  112. package/lib/aws/ec2/vpc.provider.js.map +1 -1
  113. package/lib/aws/index.d.ts +2 -2
  114. package/lib/aws/index.d.ts.map +1 -1
  115. package/lib/aws/index.js +1 -1
  116. package/lib/aws/index.js.map +1 -1
  117. package/lib/aws/lambda/function.d.ts.map +1 -1
  118. package/lib/aws/lambda/function.invoke.d.ts.map +1 -1
  119. package/lib/aws/lambda/function.invoke.js.map +1 -1
  120. package/lib/aws/lambda/function.js.map +1 -1
  121. package/lib/aws/lambda/function.provider.d.ts.map +1 -1
  122. package/lib/aws/lambda/function.provider.js +19 -23
  123. package/lib/aws/lambda/function.provider.js.map +1 -1
  124. package/lib/aws/sqs/queue.d.ts +1 -2
  125. package/lib/aws/sqs/queue.d.ts.map +1 -1
  126. package/lib/aws/sqs/queue.event-source.d.ts.map +1 -1
  127. package/lib/aws/sqs/queue.event-source.js +2 -2
  128. package/lib/aws/sqs/queue.event-source.js.map +1 -1
  129. package/lib/aws/sqs/queue.js.map +1 -1
  130. package/lib/aws/sqs/queue.provider.d.ts +1 -2
  131. package/lib/aws/sqs/queue.provider.d.ts.map +1 -1
  132. package/lib/aws/sqs/queue.provider.js +35 -20
  133. package/lib/aws/sqs/queue.provider.js.map +1 -1
  134. package/lib/aws/sqs/queue.send-message.d.ts.map +1 -1
  135. package/lib/aws/sqs/queue.send-message.js.map +1 -1
  136. package/lib/binding.d.ts.map +1 -1
  137. package/lib/cli/components/PlanProgress.d.ts.map +1 -1
  138. package/lib/cli/components/PlanProgress.js.map +1 -1
  139. package/lib/cli/index.d.ts +8 -4
  140. package/lib/cli/index.d.ts.map +1 -1
  141. package/lib/cli/index.js.map +1 -1
  142. package/lib/cloudflare/kv/namespace.binding.d.ts.map +1 -1
  143. package/lib/cloudflare/kv/namespace.binding.js.map +1 -1
  144. package/lib/cloudflare/kv/namespace.d.ts.map +1 -1
  145. package/lib/cloudflare/kv/namespace.provider.d.ts +1 -2
  146. package/lib/cloudflare/kv/namespace.provider.d.ts.map +1 -1
  147. package/lib/cloudflare/kv/namespace.provider.js +11 -8
  148. package/lib/cloudflare/kv/namespace.provider.js.map +1 -1
  149. package/lib/cloudflare/r2/bucket.binding.d.ts.map +1 -1
  150. package/lib/cloudflare/r2/bucket.binding.js +1 -1
  151. package/lib/cloudflare/r2/bucket.binding.js.map +1 -1
  152. package/lib/cloudflare/r2/bucket.d.ts +1 -1
  153. package/lib/cloudflare/r2/bucket.d.ts.map +1 -1
  154. package/lib/cloudflare/r2/bucket.provider.d.ts +1 -2
  155. package/lib/cloudflare/r2/bucket.provider.d.ts.map +1 -1
  156. package/lib/cloudflare/r2/bucket.provider.js +20 -14
  157. package/lib/cloudflare/r2/bucket.provider.js.map +1 -1
  158. package/lib/cloudflare/worker/worker.d.ts +2 -2
  159. package/lib/cloudflare/worker/worker.d.ts.map +1 -1
  160. package/lib/cloudflare/worker/worker.provider.d.ts +1 -2
  161. package/lib/cloudflare/worker/worker.provider.d.ts.map +1 -1
  162. package/lib/cloudflare/worker/worker.provider.js +40 -13
  163. package/lib/cloudflare/worker/worker.provider.js.map +1 -1
  164. package/lib/instance-id.d.ts +5 -1
  165. package/lib/instance-id.d.ts.map +1 -1
  166. package/lib/instance-id.js +4 -0
  167. package/lib/instance-id.js.map +1 -1
  168. package/lib/output.d.ts.map +1 -1
  169. package/lib/output.js.map +1 -1
  170. package/lib/physical-name.d.ts +15 -4
  171. package/lib/physical-name.d.ts.map +1 -1
  172. package/lib/physical-name.js +12 -3
  173. package/lib/physical-name.js.map +1 -1
  174. package/lib/plan.d.ts +2 -2
  175. package/lib/plan.d.ts.map +1 -1
  176. package/lib/plan.js +73 -24
  177. package/lib/plan.js.map +1 -1
  178. package/lib/provider.d.ts +1 -2
  179. package/lib/provider.d.ts.map +1 -1
  180. package/lib/resource.d.ts +3 -2
  181. package/lib/resource.d.ts.map +1 -1
  182. package/lib/resource.js.map +1 -1
  183. package/lib/runtime.d.ts.map +1 -1
  184. package/lib/tags.d.ts +12 -0
  185. package/lib/tags.d.ts.map +1 -1
  186. package/lib/tags.js +24 -0
  187. package/lib/tags.js.map +1 -1
  188. package/lib/tsconfig.test.tsbuildinfo +1 -1
  189. package/package.json +51 -51
  190. package/src/apply.ts +316 -297
  191. package/src/aws/client.ts +22 -1
  192. package/src/aws/dynamodb/secondary-index.ts +5 -5
  193. package/src/aws/dynamodb/table.provider.ts +15 -4
  194. package/src/aws/dynamodb/table.ts +8 -11
  195. package/src/aws/ec2/egress-only-igw.provider.ts +181 -0
  196. package/src/aws/ec2/egress-only-igw.ts +77 -0
  197. package/src/aws/ec2/eip.provider.ts +191 -0
  198. package/src/aws/ec2/eip.ts +106 -0
  199. package/src/aws/ec2/index.ts +18 -0
  200. package/src/aws/ec2/internet-gateway.provider.ts +15 -6
  201. package/src/aws/ec2/internet-gateway.ts +6 -6
  202. package/src/aws/ec2/nat-gateway.provider.ts +341 -0
  203. package/src/aws/ec2/nat-gateway.ts +155 -0
  204. package/src/aws/ec2/network-acl-association.provider.ts +181 -0
  205. package/src/aws/ec2/network-acl-association.ts +60 -0
  206. package/src/aws/ec2/network-acl-entry.provider.ts +218 -0
  207. package/src/aws/ec2/network-acl-entry.ts +140 -0
  208. package/src/aws/ec2/network-acl.provider.ts +195 -0
  209. package/src/aws/ec2/network-acl.ts +102 -0
  210. package/src/aws/ec2/route-table-association.provider.ts +1 -2
  211. package/src/aws/ec2/route-table-association.ts +6 -6
  212. package/src/aws/ec2/route-table.provider.ts +1 -2
  213. package/src/aws/ec2/route-table.ts +6 -6
  214. package/src/aws/ec2/route.provider.ts +1 -2
  215. package/src/aws/ec2/route.ts +6 -6
  216. package/src/aws/ec2/security-group-rule.provider.ts +264 -0
  217. package/src/aws/ec2/security-group-rule.ts +151 -0
  218. package/src/aws/ec2/security-group.provider.ts +392 -0
  219. package/src/aws/ec2/security-group.ts +182 -0
  220. package/src/aws/ec2/subnet.provider.ts +57 -56
  221. package/src/aws/ec2/subnet.ts +6 -6
  222. package/src/aws/ec2/vpc-endpoint.provider.ts +466 -0
  223. package/src/aws/ec2/vpc-endpoint.ts +213 -0
  224. package/src/aws/ec2/vpc.provider.ts +60 -52
  225. package/src/aws/ec2/vpc.ts +6 -6
  226. package/src/aws/index.ts +9 -0
  227. package/src/aws/lambda/function.invoke.ts +4 -2
  228. package/src/aws/lambda/function.provider.ts +42 -31
  229. package/src/aws/lambda/function.ts +4 -2
  230. package/src/aws/sqs/queue.event-source.ts +12 -14
  231. package/src/aws/sqs/queue.provider.ts +40 -24
  232. package/src/aws/sqs/queue.send-message.ts +4 -2
  233. package/src/aws/sqs/queue.ts +1 -8
  234. package/src/binding.ts +7 -7
  235. package/src/cli/components/PlanProgress.tsx +3 -2
  236. package/src/cloudflare/kv/namespace.binding.ts +4 -2
  237. package/src/cloudflare/kv/namespace.provider.ts +18 -16
  238. package/src/cloudflare/kv/namespace.ts +6 -6
  239. package/src/cloudflare/r2/bucket.binding.ts +5 -3
  240. package/src/cloudflare/r2/bucket.provider.ts +34 -30
  241. package/src/cloudflare/r2/bucket.ts +7 -7
  242. package/src/cloudflare/worker/worker.provider.ts +39 -13
  243. package/src/cloudflare/worker/worker.ts +2 -2
  244. package/src/instance-id.ts +5 -1
  245. package/src/output.ts +5 -3
  246. package/src/physical-name.ts +27 -5
  247. package/src/plan.ts +108 -41
  248. package/src/provider.ts +12 -12
  249. package/src/resource.ts +17 -6
  250. package/src/runtime.ts +2 -2
  251. package/src/tags.ts +29 -0
@@ -0,0 +1,151 @@
1
+ import type { Input } from "../../input.ts";
2
+ import { Resource } from "../../resource.ts";
3
+ import type { SecurityGroupId } from "./security-group.ts";
4
+
5
+ export const SecurityGroupRule = Resource<{
6
+ <const ID extends string, const Props extends SecurityGroupRuleProps>(
7
+ id: ID,
8
+ props: Props,
9
+ ): SecurityGroupRule<ID, Props>;
10
+ }>("AWS.EC2.SecurityGroupRule");
11
+
12
+ export interface SecurityGroupRule<
13
+ ID extends string = string,
14
+ Props extends SecurityGroupRuleProps = SecurityGroupRuleProps,
15
+ > extends Resource<
16
+ "AWS.EC2.SecurityGroupRule",
17
+ ID,
18
+ Props,
19
+ SecurityGroupRuleAttrs<Input.Resolve<Props>>,
20
+ SecurityGroupRule
21
+ > {}
22
+
23
+ export type SecurityGroupRuleId<ID extends string = string> = `sgr-${ID}`;
24
+ export const SecurityGroupRuleId = <ID extends string>(
25
+ id: ID,
26
+ ): ID & SecurityGroupRuleId<ID> => `sgr-${id}` as ID & SecurityGroupRuleId<ID>;
27
+
28
+ export interface SecurityGroupRuleProps {
29
+ /**
30
+ * The ID of the security group.
31
+ */
32
+ groupId: Input<SecurityGroupId>;
33
+
34
+ /**
35
+ * Whether this is an ingress (inbound) or egress (outbound) rule.
36
+ */
37
+ type: "ingress" | "egress";
38
+
39
+ /**
40
+ * The IP protocol name or number.
41
+ * Use -1 to specify all protocols.
42
+ */
43
+ ipProtocol: string;
44
+
45
+ /**
46
+ * The start of the port range.
47
+ * For ICMP, use the ICMP type number.
48
+ */
49
+ fromPort?: number;
50
+
51
+ /**
52
+ * The end of the port range.
53
+ * For ICMP, use the ICMP code.
54
+ */
55
+ toPort?: number;
56
+
57
+ /**
58
+ * IPv4 CIDR range to allow.
59
+ */
60
+ cidrIpv4?: string;
61
+
62
+ /**
63
+ * IPv6 CIDR range to allow.
64
+ */
65
+ cidrIpv6?: string;
66
+
67
+ /**
68
+ * ID of a security group to allow traffic from/to.
69
+ */
70
+ referencedGroupId?: Input<SecurityGroupId>;
71
+
72
+ /**
73
+ * ID of a prefix list.
74
+ */
75
+ prefixListId?: Input<string>;
76
+
77
+ /**
78
+ * Description for the rule.
79
+ */
80
+ description?: string;
81
+
82
+ /**
83
+ * Tags to assign to the security group rule.
84
+ */
85
+ tags?: Record<string, Input<string>>;
86
+ }
87
+
88
+ export interface SecurityGroupRuleAttrs<
89
+ Props extends Input.Resolve<SecurityGroupRuleProps> =
90
+ Input.Resolve<SecurityGroupRuleProps>,
91
+ > {
92
+ /**
93
+ * The ID of the security group rule.
94
+ */
95
+ securityGroupRuleId: SecurityGroupRuleId;
96
+
97
+ /**
98
+ * The ID of the security group.
99
+ */
100
+ groupId: Props["groupId"];
101
+
102
+ /**
103
+ * The ID of the AWS account that owns the security group.
104
+ */
105
+ groupOwnerId: string;
106
+
107
+ /**
108
+ * Whether this is an egress rule.
109
+ */
110
+ isEgress: Props["type"] extends "egress" ? true : false;
111
+
112
+ /**
113
+ * The IP protocol.
114
+ */
115
+ ipProtocol: Props["ipProtocol"];
116
+
117
+ /**
118
+ * The start of the port range.
119
+ */
120
+ fromPort?: number;
121
+
122
+ /**
123
+ * The end of the port range.
124
+ */
125
+ toPort?: number;
126
+
127
+ /**
128
+ * The IPv4 CIDR range.
129
+ */
130
+ cidrIpv4?: Props["cidrIpv4"];
131
+
132
+ /**
133
+ * The IPv6 CIDR range.
134
+ */
135
+ cidrIpv6?: Props["cidrIpv6"];
136
+
137
+ /**
138
+ * The ID of the referenced security group.
139
+ */
140
+ referencedGroupId?: string;
141
+
142
+ /**
143
+ * The ID of the prefix list.
144
+ */
145
+ prefixListId?: string;
146
+
147
+ /**
148
+ * The description.
149
+ */
150
+ description?: Props["description"];
151
+ }
@@ -0,0 +1,392 @@
1
+ import * as EC2 from "itty-aws/ec2";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Schedule from "effect/Schedule";
4
+
5
+ import { createPhysicalName } from "../../physical-name.ts";
6
+ import { createTagger, createTagsList, diffTags } from "../../tags.ts";
7
+ import { Account } from "../account.ts";
8
+ import { Region } from "../region.ts";
9
+ import { EC2Client } from "./client.ts";
10
+ import {
11
+ type SecurityGroupArn,
12
+ type SecurityGroupRuleData,
13
+ SecurityGroup,
14
+ type SecurityGroupAttrs,
15
+ type SecurityGroupId,
16
+ } from "./security-group.ts";
17
+ import type { VpcId } from "./vpc.ts";
18
+
19
+ export const securityGroupProvider = () =>
20
+ SecurityGroup.provider.effect(
21
+ Effect.gen(function* () {
22
+ const ec2 = yield* EC2Client;
23
+ const region = yield* Region;
24
+ const accountId = yield* Account;
25
+ const tagged = yield* createTagger();
26
+
27
+ const createTags = (
28
+ id: string,
29
+ tags?: Record<string, string>,
30
+ ): Record<string, string> => ({
31
+ Name: id,
32
+ ...tagged(id),
33
+ ...tags,
34
+ });
35
+
36
+ const createGroupName = (id: string, name: string | undefined) =>
37
+ Effect.gen(function* () {
38
+ if (name) return name;
39
+ return yield* createPhysicalName({ id, maxLength: 255 });
40
+ });
41
+
42
+ const describeSecurityGroup = (groupId: string) =>
43
+ ec2.describeSecurityGroups({ GroupIds: [groupId] }).pipe(
44
+ Effect.map((r) => r.SecurityGroups?.[0]),
45
+ Effect.flatMap((sg) =>
46
+ sg
47
+ ? Effect.succeed(sg)
48
+ : Effect.fail(new Error(`Security Group ${groupId} not found`)),
49
+ ),
50
+ );
51
+
52
+ const describeSecurityGroupRules = (groupId: string) =>
53
+ ec2.describeSecurityGroupRules({
54
+ Filters: [{ Name: "group-id", Values: [groupId] }],
55
+ });
56
+
57
+ const toAttrs = (
58
+ sg: EC2.SecurityGroup,
59
+ rules: EC2.SecurityGroupRule[],
60
+ ): SecurityGroupAttrs => ({
61
+ groupId: sg.GroupId as SecurityGroupId,
62
+ groupArn:
63
+ `arn:aws:ec2:${region}:${accountId}:security-group/${sg.GroupId as SecurityGroupId}` as SecurityGroupArn,
64
+ groupName: sg.GroupName!,
65
+ description: sg.Description!,
66
+ vpcId: sg.VpcId as VpcId,
67
+ ownerId: sg.OwnerId!,
68
+ ingressRules: rules
69
+ .filter((r) => !r.IsEgress)
70
+ .map((r) => ({
71
+ securityGroupRuleId: r.SecurityGroupRuleId!,
72
+ ipProtocol: r.IpProtocol!,
73
+ fromPort: r.FromPort,
74
+ toPort: r.ToPort,
75
+ cidrIpv4: r.CidrIpv4,
76
+ cidrIpv6: r.CidrIpv6,
77
+ referencedGroupId: r.ReferencedGroupInfo?.GroupId,
78
+ prefixListId: r.PrefixListId,
79
+ description: r.Description,
80
+ isEgress: false as const,
81
+ })),
82
+ egressRules: rules
83
+ .filter((r) => r.IsEgress)
84
+ .map((r) => ({
85
+ securityGroupRuleId: r.SecurityGroupRuleId!,
86
+ ipProtocol: r.IpProtocol!,
87
+ fromPort: r.FromPort,
88
+ toPort: r.ToPort,
89
+ cidrIpv4: r.CidrIpv4,
90
+ cidrIpv6: r.CidrIpv6,
91
+ referencedGroupId: r.ReferencedGroupInfo?.GroupId,
92
+ prefixListId: r.PrefixListId,
93
+ description: r.Description,
94
+ isEgress: true as const,
95
+ })),
96
+ });
97
+
98
+ const toIpPermission = (
99
+ rule: SecurityGroupRuleData,
100
+ ): EC2.IpPermission => ({
101
+ IpProtocol: rule.ipProtocol,
102
+ FromPort: rule.fromPort,
103
+ ToPort: rule.toPort,
104
+ IpRanges: rule.cidrIpv4
105
+ ? [{ CidrIp: rule.cidrIpv4, Description: rule.description }]
106
+ : undefined,
107
+ Ipv6Ranges: rule.cidrIpv6
108
+ ? [{ CidrIpv6: rule.cidrIpv6, Description: rule.description }]
109
+ : undefined,
110
+ UserIdGroupPairs: rule.referencedGroupId
111
+ ? [
112
+ {
113
+ GroupId: rule.referencedGroupId as string,
114
+ Description: rule.description,
115
+ },
116
+ ]
117
+ : undefined,
118
+ PrefixListIds: rule.prefixListId
119
+ ? [
120
+ {
121
+ PrefixListId: rule.prefixListId as string,
122
+ Description: rule.description,
123
+ },
124
+ ]
125
+ : undefined,
126
+ });
127
+
128
+ return {
129
+ stables: ["groupId", "groupArn", "ownerId"],
130
+
131
+ read: Effect.fn(function* ({ output }) {
132
+ if (!output) return undefined;
133
+ const sg = yield* describeSecurityGroup(output.groupId);
134
+ const rulesResult = yield* describeSecurityGroupRules(output.groupId);
135
+ return toAttrs(sg, rulesResult.SecurityGroupRules ?? []);
136
+ }),
137
+
138
+ diff: Effect.fn(function* ({ id, news, olds, output }) {
139
+ // VPC change requires replacement
140
+ if (news.vpcId !== olds.vpcId) {
141
+ return { action: "replace" };
142
+ }
143
+
144
+ // Group name change requires replacement
145
+ const newGroupName = yield* createGroupName(id, news.groupName);
146
+ if (newGroupName !== output.groupName) {
147
+ return { action: "replace" };
148
+ }
149
+
150
+ // Other changes can be updated in-place
151
+ }),
152
+
153
+ create: Effect.fn(function* ({ id, news, session }) {
154
+ const groupName = yield* createGroupName(id, news.groupName);
155
+
156
+ yield* session.note(`Creating Security Group: ${groupName}`);
157
+
158
+ const result = yield* ec2.createSecurityGroup({
159
+ GroupName: groupName,
160
+ Description: news.description ?? "Managed by Alchemy",
161
+ VpcId: news.vpcId as string,
162
+ TagSpecifications: [
163
+ {
164
+ ResourceType: "security-group",
165
+ Tags: createTagsList(createTags(id, news.tags)),
166
+ },
167
+ ],
168
+ DryRun: false,
169
+ });
170
+
171
+ const groupId = result.GroupId! as SecurityGroupId;
172
+ yield* session.note(`Security Group created: ${groupId}`);
173
+
174
+ // Revoke the default egress rule if we have custom egress rules
175
+ if (news.egress && news.egress.length > 0) {
176
+ yield* ec2
177
+ .revokeSecurityGroupEgress({
178
+ GroupId: groupId,
179
+ IpPermissions: [
180
+ {
181
+ IpProtocol: "-1",
182
+ IpRanges: [{ CidrIp: "0.0.0.0/0" }],
183
+ },
184
+ ],
185
+ DryRun: false,
186
+ })
187
+ .pipe(
188
+ Effect.catchTag(
189
+ "InvalidPermission.NotFound",
190
+ () => Effect.void,
191
+ ),
192
+ );
193
+ }
194
+
195
+ // Add ingress rules
196
+ if (news.ingress && news.ingress.length > 0) {
197
+ yield* ec2.authorizeSecurityGroupIngress({
198
+ GroupId: groupId,
199
+ IpPermissions: news.ingress.map(toIpPermission),
200
+ DryRun: false,
201
+ });
202
+ yield* session.note(`Added ${news.ingress.length} ingress rules`);
203
+ }
204
+
205
+ // Add egress rules
206
+ if (news.egress && news.egress.length > 0) {
207
+ yield* ec2.authorizeSecurityGroupEgress({
208
+ GroupId: groupId,
209
+ IpPermissions: news.egress.map(toIpPermission),
210
+ DryRun: false,
211
+ });
212
+ yield* session.note(`Added ${news.egress.length} egress rules`);
213
+ }
214
+
215
+ // Fetch the final state
216
+ const sg = yield* describeSecurityGroup(groupId);
217
+ const rulesResult = yield* describeSecurityGroupRules(groupId);
218
+ return toAttrs(sg, rulesResult.SecurityGroupRules ?? []);
219
+ }),
220
+
221
+ update: Effect.fn(function* ({ id, news, olds, output, session }) {
222
+ const groupId = output.groupId;
223
+
224
+ // Handle description update (requires modifying the group)
225
+ if (news.description !== olds.description) {
226
+ yield* ec2.modifySecurityGroupRules({
227
+ GroupId: groupId,
228
+ // Description can't actually be changed after creation in EC2
229
+ // This is a no-op but we log it
230
+ SecurityGroupRules: [],
231
+ });
232
+ }
233
+
234
+ // Handle tag updates
235
+ const newTags = createTags(id, news.tags);
236
+ const oldTags =
237
+ (yield* ec2
238
+ .describeTags({
239
+ Filters: [
240
+ { Name: "resource-id", Values: [groupId] },
241
+ { Name: "resource-type", Values: ["security-group"] },
242
+ ],
243
+ })
244
+ .pipe(
245
+ Effect.map(
246
+ (r) =>
247
+ Object.fromEntries(
248
+ r.Tags?.map((t) => [t.Key!, t.Value!]) ?? [],
249
+ ) as Record<string, string>,
250
+ ),
251
+ )) ?? {};
252
+
253
+ const { removed, upsert } = diffTags(oldTags, newTags);
254
+
255
+ if (removed.length > 0) {
256
+ yield* ec2.deleteTags({
257
+ Resources: [groupId],
258
+ Tags: removed.map((key) => ({ Key: key })),
259
+ DryRun: false,
260
+ });
261
+ }
262
+ if (upsert.length > 0) {
263
+ yield* ec2.createTags({
264
+ Resources: [groupId],
265
+ Tags: upsert,
266
+ DryRun: false,
267
+ });
268
+ yield* session.note("Updated tags");
269
+ }
270
+
271
+ // Handle rule updates - simple approach: revoke all, then add all
272
+ // Get current rules
273
+ const currentRulesResult = yield* describeSecurityGroupRules(groupId);
274
+ const currentRules = currentRulesResult.SecurityGroupRules ?? [];
275
+
276
+ // Revoke existing ingress rules (except default)
277
+ const currentIngress = currentRules.filter((r) => !r.IsEgress);
278
+ if (currentIngress.length > 0) {
279
+ yield* ec2
280
+ .revokeSecurityGroupIngress({
281
+ GroupId: groupId,
282
+ SecurityGroupRuleIds: currentIngress.map(
283
+ (r) => r.SecurityGroupRuleId!,
284
+ ),
285
+ DryRun: false,
286
+ })
287
+ .pipe(
288
+ Effect.catchTag(
289
+ "InvalidPermission.NotFound",
290
+ () => Effect.void,
291
+ ),
292
+ );
293
+ }
294
+
295
+ // Revoke existing egress rules
296
+ const currentEgress = currentRules.filter((r) => r.IsEgress);
297
+ if (currentEgress.length > 0) {
298
+ yield* ec2
299
+ .revokeSecurityGroupEgress({
300
+ GroupId: groupId,
301
+ SecurityGroupRuleIds: currentEgress.map(
302
+ (r) => r.SecurityGroupRuleId!,
303
+ ),
304
+ DryRun: false,
305
+ })
306
+ .pipe(
307
+ Effect.catchTag(
308
+ "InvalidPermission.NotFound",
309
+ () => Effect.void,
310
+ ),
311
+ );
312
+ }
313
+
314
+ // Add new ingress rules
315
+ if (news.ingress && news.ingress.length > 0) {
316
+ yield* ec2.authorizeSecurityGroupIngress({
317
+ GroupId: groupId,
318
+ IpPermissions: news.ingress.map(toIpPermission),
319
+ DryRun: false,
320
+ });
321
+ yield* session.note(
322
+ `Updated ingress rules (${news.ingress.length} rules)`,
323
+ );
324
+ }
325
+
326
+ // Add new egress rules (or restore default)
327
+ if (news.egress && news.egress.length > 0) {
328
+ yield* ec2.authorizeSecurityGroupEgress({
329
+ GroupId: groupId,
330
+ IpPermissions: news.egress.map(toIpPermission),
331
+ DryRun: false,
332
+ });
333
+ yield* session.note(
334
+ `Updated egress rules (${news.egress.length} rules)`,
335
+ );
336
+ } else {
337
+ // Restore default egress rule
338
+ yield* ec2.authorizeSecurityGroupEgress({
339
+ GroupId: groupId,
340
+ IpPermissions: [
341
+ {
342
+ IpProtocol: "-1",
343
+ IpRanges: [{ CidrIp: "0.0.0.0/0" }],
344
+ },
345
+ ],
346
+ DryRun: false,
347
+ });
348
+ }
349
+
350
+ // Fetch the final state
351
+ const sg = yield* describeSecurityGroup(groupId);
352
+ const rulesResult = yield* describeSecurityGroupRules(groupId);
353
+ return toAttrs(sg, rulesResult.SecurityGroupRules ?? []);
354
+ }),
355
+
356
+ delete: Effect.fn(function* ({ output, session }) {
357
+ const groupId = output.groupId;
358
+
359
+ yield* session.note(`Deleting Security Group: ${groupId}`);
360
+
361
+ yield* ec2
362
+ .deleteSecurityGroup({
363
+ GroupId: groupId,
364
+ DryRun: false,
365
+ })
366
+ .pipe(
367
+ Effect.catchTag("InvalidGroup.NotFound", () => Effect.void),
368
+ // Retry on dependency violations (e.g., ENIs still using the security group)
369
+ Effect.retry({
370
+ while: (e) => {
371
+ return (
372
+ e._tag === "DependencyViolation" ||
373
+ (e._tag === "ValidationError" &&
374
+ e.message?.includes("DependencyViolation"))
375
+ );
376
+ },
377
+ schedule: Schedule.exponential(1000, 1.5).pipe(
378
+ Schedule.intersect(Schedule.recurs(15)),
379
+ Schedule.tapOutput(([, attempt]) =>
380
+ session.note(
381
+ `Waiting for dependencies to clear... (attempt ${attempt + 1})`,
382
+ ),
383
+ ),
384
+ ),
385
+ }),
386
+ );
387
+
388
+ yield* session.note(`Security Group ${groupId} deleted`);
389
+ }),
390
+ };
391
+ }),
392
+ );