1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { ImagePipeline } from "./ImagePipeline.ts";
6
7/**
8 * Shared scaffolding for the EC2 Image Builder HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
11 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two builders
12 * below. Everything except the operation and the IAM action list is
13 * boilerplate.
14 */
15
16/**
17 * Build the impl Effect for an Image Builder operation scoped to a bound
18 * {@link ImagePipeline}: the runtime callable injects the pipeline's ARN as
19 * `imagePipelineArn` and the deploy-time half grants `actions` on the
20 * pipeline's ARN.
21 *
22 * `clientToken` (an idempotency token) is auto-generated by distilled when
23 * absent, so it is omitted from the runtime request shape.
24 */
25export const makeImageBuilderPipelineHttpBinding = <
26 I extends { imagePipelineArn?: string },
27 A,
28 E,
29 R,
30>(options: {
31 /** Fully-qualified binding tag, e.g. `AWS.ImageBuilder.GetImagePipeline`. */
32 tag: string;
33 /** The distilled operation; `imagePipelineArn` is injected. */
34 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
35 /** IAM actions granted on the pipeline ARN. */
36 actions: readonly string[];
37}) =>
38 Effect.gen(function* () {
39 const op = yield* options.operation;
40
41 return Effect.fn(function* (pipeline: ImagePipeline) {
42 // Outputs yield a DEFERRED effect — resolve again per invocation below.
43 const PipelineArn = yield* pipeline.imagePipelineArn;
44 if (!globalThis.__ALCHEMY_RUNTIME__) {
45 const host = yield* Binding.Host;
46 if (isBindingHost(host)) {
47 yield* host.bind`Allow(${host}, ${options.tag}(${pipeline}))`({
48 policyStatements: [
49 {
50 Effect: "Allow",
51 Action: [...options.actions],
52 Resource: [Output.interpolate`${pipeline.imagePipelineArn}`],
53 },
54 ],
55 });
56 }
57 }
58 return Effect.fn(`${options.tag}(${pipeline.LogicalId})`)(function* (
59 request?: Omit<I, "imagePipelineArn" | "clientToken">,
60 ) {
61 return yield* op({
62 ...request,
63 imagePipelineArn: yield* PipelineArn,
64 } as I);
65 });
66 });
67 });
68
69/**
70 * Build the impl Effect for an account-level Image Builder operation (image
71 * build-version reads and control, account image listings). Image build
72 * versions are created dynamically by pipeline runs — their ARNs are not
73 * known at deploy time — so the deploy-time half grants `actions` on `*` and
74 * the runtime callable passes the caller's request through as-is.
75 */
76export const makeImageBuilderAccountHttpBinding = <I, A, E, R>(options: {
77 /** Fully-qualified binding tag, e.g. `AWS.ImageBuilder.GetImage`. */
78 tag: string;
79 /** The distilled operation, invoked with the caller's request as-is. */
80 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
81 /** IAM actions granted on `*`. */
82 actions: readonly string[];
83}) =>
84 Effect.gen(function* () {
85 const op = yield* options.operation;
86
87 return Effect.fn(function* () {
88 if (!globalThis.__ALCHEMY_RUNTIME__) {
89 const host = yield* Binding.Host;
90 if (isBindingHost(host)) {
91 yield* host.bind`Allow(${host}, ${options.tag}())`({
92 policyStatements: [
93 {
94 Effect: "Allow",
95 Action: [...options.actions],
96 Resource: ["*"],
97 },
98 ],
99 });
100 }
101 }
102 return Effect.fn(options.tag)(function* (
103 request?: Omit<I, "clientToken">,
104 ) {
105 return yield* op((request ?? {}) as I);
106 });
107 });
108 });