1import * as ec2 from "@distilled.cloud/aws/ec2";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Binding from "../../Binding.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6import { GetAmi } from "./GetAmi.ts";
7import type { FindImageOptions } from "./Image.ts";
8import { isInstance } from "./Instance.ts";
9
10// Bespoke (not the shared scaffold): the lookup filters and sorts the
11// paginated image list to pick the newest available match, and the binding
12// is account-scoped — there is no resource to bind, only options.
13export const GetAmiHttp = Layer.effect(
14 GetAmi,
15 Effect.gen(function* () {
16 const describeImages = yield* ec2.describeImages;
17
18 return Effect.fn(function* (options: FindImageOptions) {
19 if (!globalThis.__ALCHEMY_RUNTIME__) {
20 const host = yield* Binding.Host;
21 if (isBindingHost(host) || isInstance(host)) {
22 yield* host.bind`Allow(${host}, AWS.EC2.GetAmi)`({
23 policyStatements: [
24 {
25 Effect: "Allow",
26 Action: ["ec2:DescribeImages"],
27 Resource: ["*"],
28 },
29 ],
30 });
31 }
32 }
33
34 const {
35 owners,
36 name,
37 architecture = "x86_64",
38 rootDeviceType = "ebs",
39 virtualizationType = "hvm",
40 } = options;
41
42 return Effect.fn("AWS.EC2.GetAmi")(function* () {
43 const response = yield* describeImages({
44 Owners: owners,
45 Filters: [
46 { Name: "name", Values: [...name] },
47 { Name: "architecture", Values: [architecture] },
48 { Name: "state", Values: ["available"] },
49 { Name: "root-device-type", Values: [rootDeviceType] },
50 { Name: "virtualization-type", Values: [virtualizationType] },
51 ],
52 });
53 return (response.Images ?? [])
54 .slice()
55 .sort((a: ec2.Image, b: ec2.Image) =>
56 String(b.CreationDate ?? "").localeCompare(
57 String(a.CreationDate ?? ""),
58 ),
59 )[0];
60 });
61 });
62 }),
63);