1import * as Effect from "effect/Effect";
2import * as Output from "../../Output.ts";
3import { getAmi } from "./GetAmi.ts";
4
5/**
6 * CPU architecture of the AMI to look up.
7 */
8export type ImageArchitecture = "x86_64" | "arm64";
9
10/**
11 * Filters for looking up the latest matching public AMI via
12 * `ec2:DescribeImages`.
13 */
14export interface FindImageOptions {
15 /**
16 * AMI owners to search, e.g. `["amazon"]` or a vendor account ID like
17 * Canonical's `"099720109477"`.
18 */
19 owners: string[];
20 /**
21 * One or more AMI name patterns (glob-style). The newest available image
22 * matching any pattern wins.
23 */
24 name: [string, ...string[]];
25 /**
26 * CPU architecture to filter on.
27 * @default "x86_64"
28 */
29 architecture?: ImageArchitecture;
30 /**
31 * Human-readable label for the lookup (informational only).
32 */
33 description?: string;
34 /**
35 * Root device type to filter on.
36 * @default "ebs"
37 */
38 rootDeviceType?: "ebs" | "instance-store";
39 /**
40 * Virtualization type to filter on.
41 * @default "hvm"
42 */
43 virtualizationType?: "hvm" | "paravirtual";
44}
45
46const requireImageId = (options: FindImageOptions) =>
47 getAmi(options).pipe(
48 Output.mapEffect((image) =>
49 image?.ImageId
50 ? Effect.succeed(image.ImageId)
51 : Effect.die(
52 new Error(
53 `Could not resolve ${options.description ?? "an AMI"} matching ${options.name.join(", ")}`,
54 ),
55 ),
56 ),
57 );
58
59/**
60 * Look up the latest available AMI ID matching the given filters.
61 *
62 * Returns an `Output<string>` resolved at plan/deploy time via the
63 * {@link getAmi} data source (and dies with a descriptive error when
64 * nothing matches), so it is safe to use both in resource props and in
65 * composition code that is re-executed inside a deployed runtime — the
66 * lookup never runs on the deployed machine. Use the preset helpers
67 * ({@link amazonLinux2023}, {@link ubuntu2404}, ...) for common distros.
68 *
69 * @example Find a custom AMI
70 * ```typescript
71 * const instance = yield* AWS.EC2.Instance("web", {
72 * imageId: AWS.EC2.image({
73 * owners: ["amazon"],
74 * name: ["al2023-ami-ecs-hvm-*"],
75 * architecture: "arm64",
76 * }),
77 * instanceType: "t4g.micro",
78 * subnetId: subnet.subnetId,
79 * });
80 * ```
81 */
82export const image = (options: FindImageOptions) => requireImageId(options);
83
84const amazonLinux2023Options = (options?: {
85 architecture?: ImageArchitecture;
86}): FindImageOptions => ({
87 owners: ["amazon"],
88 // `al2023-ami-2023.*` selects the standard image. The broader
89 // `al2023-ami-*` also matches `al2023-ami-minimal-*`, which ships without
90 // the SSM agent and a stripped toolset and frequently sorts newest.
91 name: ["al2023-ami-2023.*"],
92 architecture: options?.architecture,
93 description: "Amazon Linux 2023",
94});
95
96const amazonLinux2Options = (options?: {
97 architecture?: ImageArchitecture;
98}): FindImageOptions => ({
99 owners: ["amazon"],
100 name: ["amzn2-ami-hvm-*-*-gp2"],
101 architecture: options?.architecture,
102 description: "Amazon Linux 2",
103});
104
105/**
106 * Resolve the latest Amazon Linux 2023 AMI ID for the current region as an
107 * `Output<string>`.
108 *
109 * @example Launch an Instance on Amazon Linux 2023
110 * ```typescript
111 * const instance = yield* AWS.EC2.Instance("web", {
112 * imageId: AWS.EC2.amazonLinux2023(),
113 * instanceType: "t3.micro",
114 * subnetId: subnet.subnetId,
115 * });
116 * ```
117 */
118export const amazonLinux2023 = (options?: {
119 architecture?: ImageArchitecture;
120}) => image(amazonLinux2023Options(options));
121
122/**
123 * Resolve the latest Amazon Linux 2 AMI ID for the current region as an
124 * `Output<string>`.
125 */
126export const amazonLinux2 = (options?: { architecture?: ImageArchitecture }) =>
127 image(amazonLinux2Options(options));
128
129/**
130 * Resolve the newest public Amazon Linux AMI as an `Output<string>`,
131 * preferring Amazon Linux 2023 and falling back to Amazon Linux 2. Dies if
132 * neither is available.
133 */
134export const amazonLinux = (options?: { architecture?: ImageArchitecture }) =>
135 getAmi(amazonLinux2023Options(options)).pipe(
136 Output.flatMap((al2023) =>
137 al2023?.ImageId
138 ? Output.literal(al2023.ImageId)
139 : requireImageId({
140 ...amazonLinux2Options(options),
141 description: "a public Amazon Linux AMI",
142 }),
143 ),
144 );
145
146/**
147 * Resolve the latest Canonical Ubuntu 24.04 LTS AMI ID for the current
148 * region as an `Output<string>`.
149 *
150 * @example Launch an Instance on Ubuntu 24.04
151 * ```typescript
152 * const instance = yield* AWS.EC2.Instance("web", {
153 * imageId: AWS.EC2.ubuntu2404(),
154 * instanceType: "t3.micro",
155 * subnetId: subnet.subnetId,
156 * });
157 * ```
158 */
159export const ubuntu2404 = (options?: { architecture?: ImageArchitecture }) =>
160 image({
161 owners: ["099720109477"],
162 name: [
163 "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-*-server-*",
164 "ubuntu/images/hvm-ssd/ubuntu-noble-24.04-*-server-*",
165 ],
166 architecture: options?.architecture,
167 description: "Ubuntu 24.04 LTS",
168 });
169
170/**
171 * Resolve the latest Canonical Ubuntu 22.04 LTS AMI ID for the current
172 * region as an `Output<string>`.
173 */
174export const ubuntu2204 = (options?: { architecture?: ImageArchitecture }) =>
175 image({
176 owners: ["099720109477"],
177 name: [
178 "ubuntu/images/hvm-ssd-gp3/ubuntu-jammy-22.04-*-server-*",
179 "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-*-server-*",
180 ],
181 architecture: options?.architecture,
182 description: "Ubuntu 22.04 LTS",
183 });