1import * as ec2 from "@distilled.cloud/aws/ec2";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4import * as Schedule from "effect/Schedule";
5import { getDefaultVpc } from "../DefaultVpc.ts";
6
7class DefaultSubnetNotVisible extends Data.TaggedError(
8 "DefaultSubnetNotVisible",
9)<{}> {}
10
11/**
12 * Resolve the newest Amazon Linux 2023 AMI id out-of-band via distilled
13 * `ec2.describeImages`, for launch templates created directly with the raw
14 * SDK (outside `stack.deploy`, where the `amazonLinux2023()` Output would be
15 * resolved by the engine). Launch templates do not validate the AMI at
16 * creation time, so fall back to a syntactically valid id when the lookup
17 * returns nothing.
18 */
19export const getTestAmiId: Effect.Effect<string, any, any> = ec2
20 .describeImages({
21 Owners: ["amazon"],
22 Filters: [
23 { Name: "name", Values: ["al2023-ami-2023.*"] },
24 { Name: "architecture", Values: ["x86_64"] },
25 { Name: "state", Values: ["available"] },
26 { Name: "root-device-type", Values: ["ebs"] },
27 { Name: "virtualization-type", Values: ["hvm"] },
28 ],
29 })
30 .pipe(
31 Effect.map(
32 (response) =>
33 (response.Images ?? [])
34 .slice()
35 .sort((a, b) =>
36 String(b.CreationDate ?? "").localeCompare(
37 String(a.CreationDate ?? ""),
38 ),
39 )[0]?.ImageId ?? "ami-00000000000000000",
40 ),
41 );
42
43/**
44 * Resolve one deterministic subnet from the standing default VPC. The account
45 * nuke may remove that VPC, and CreateDefaultVpc can return before its default
46 * subnets are queryable, so restore it and wait for subnet readiness boundedly.
47 */
48export const getAutoScalingTestSubnetId: Effect.Effect<
49 `subnet-${string}`,
50 any,
51 any
52> = Effect.gen(function* () {
53 const { vpcId } = yield* getDefaultVpc;
54 const response = yield* ec2.describeSubnets({
55 Filters: [
56 { Name: "vpc-id", Values: [vpcId] },
57 { Name: "default-for-az", Values: ["true"] },
58 ],
59 } as any);
60 const subnetId = (response.Subnets ?? [])
61 .flatMap((subnet) => (subnet.SubnetId ? [subnet.SubnetId] : []))
62 .sort()[0];
63 return subnetId
64 ? (subnetId as `subnet-${string}`)
65 : yield* Effect.fail(new DefaultSubnetNotVisible());
66}).pipe(
67 Effect.retry({
68 while: (error) => error._tag === "DefaultSubnetNotVisible",
69 schedule: Schedule.max([Schedule.fixed("1 second"), Schedule.recurs(30)]),
70 }),
71);