1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isInstance } from "../EC2/Instance.ts";
5import { isTask } from "../ECS/Task.ts";
6import { isBindingHost } from "../Lambda/Function.ts";
7import type { LoadBalancer } from "./LoadBalancer.ts";
8import type { TargetGroup } from "./TargetGroup.ts";
9import type { TrustStore } from "./TrustStore.ts";
10
11/**
12 * Shared scaffolding for ELBv2 HTTP bindings.
13 *
14 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
15 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
16 * builders below. Everything except the operation and the IAM action list is
17 * boilerplate: the runtime callable injects the bound resource's ARN
18 * (`TargetGroupArn` / `LoadBalancerArn`) into the request and the deploy-time
19 * half grants `actions` on that ARN — or on `*` for `Describe*` actions,
20 * which do not support resource-level permissions in ELBv2.
21 *
22 * Hosts: Lambda Functions plus self-registering compute (ECS Tasks and EC2
23 * Instances registering themselves into a target group at boot).
24 */
25
26const bindHost = (
27 tag: string,
28 actions: readonly string[],
29 resource: TargetGroup | LoadBalancer | TrustStore,
30 iamResource: Output.Output<string> | "*",
31) =>
32 Effect.gen(function* () {
33 if (!globalThis.__ALCHEMY_RUNTIME__) {
34 const host = yield* Binding.Host;
35 if (isBindingHost(host) || isTask(host) || isInstance(host)) {
36 yield* host.bind`Allow(${host}, ${tag}(${resource}))`({
37 policyStatements: [
38 {
39 Effect: "Allow",
40 Action: [...actions],
41 Resource: [iamResource === "*" ? "*" : iamResource],
42 },
43 ],
44 });
45 }
46 }
47 });
48
49/**
50 * Build the impl Effect for a target-group-addressed operation: the runtime
51 * callable injects the bound {@link TargetGroup}'s ARN as `TargetGroupArn`;
52 * the deploy-time half grants `actions` on the target-group ARN
53 * (`"target-group"`, the default) or on `*` (`Describe*` actions do not
54 * support resource-level permissions).
55 */
56export const makeTargetGroupHttpBinding = <
57 I extends { TargetGroupArn?: string },
58 A,
59 E,
60 R,
61>(options: {
62 /** Fully-qualified binding tag, e.g. `AWS.ELBv2.RegisterTargets`. */
63 tag: string;
64 /** The distilled operation; `TargetGroupArn` is injected from the group. */
65 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
66 /** IAM actions granted by the binding. */
67 actions: readonly string[];
68 /**
69 * IAM resource scope. Write actions support resource-level permissions on
70 * the target-group ARN (`"target-group"`, the default); `Describe*`
71 * actions do not (`"*"`).
72 */
73 resource?: "target-group" | "*";
74}) =>
75 Effect.gen(function* () {
76 const op = yield* options.operation;
77
78 return Effect.fn(function* (targetGroup: TargetGroup) {
79 const TargetGroupArn = yield* targetGroup.targetGroupArn;
80 yield* bindHost(
81 options.tag,
82 options.actions,
83 targetGroup,
84 options.resource === "*"
85 ? "*"
86 : Output.interpolate`${targetGroup.targetGroupArn}`,
87 );
88 return Effect.fn(`${options.tag}(${targetGroup.LogicalId})`)(function* (
89 request: Omit<I, "TargetGroupArn">,
90 ) {
91 return yield* op({
92 ...request,
93 TargetGroupArn: yield* TargetGroupArn,
94 } as I);
95 });
96 });
97 });
98
99/**
100 * Build the impl Effect for a load-balancer-addressed operation: the runtime
101 * callable injects the bound {@link LoadBalancer}'s ARN as `LoadBalancerArn`;
102 * the deploy-time half grants `actions` on the load-balancer ARN
103 * (`"load-balancer"`, the default) or on `*` (`Describe*` actions do not
104 * support resource-level permissions).
105 */
106export const makeLoadBalancerHttpBinding = <
107 I extends { LoadBalancerArn?: string },
108 A,
109 E,
110 R,
111>(options: {
112 /** Fully-qualified binding tag, e.g. `AWS.ELBv2.ModifyCapacityReservation`. */
113 tag: string;
114 /** The distilled operation; `LoadBalancerArn` is injected from the LB. */
115 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
116 /** IAM actions granted by the binding. */
117 actions: readonly string[];
118 /**
119 * IAM resource scope. Write actions support resource-level permissions on
120 * the load-balancer ARN (`"load-balancer"`, the default); `Describe*`
121 * actions do not (`"*"`).
122 */
123 resource?: "load-balancer" | "*";
124}) =>
125 Effect.gen(function* () {
126 const op = yield* options.operation;
127
128 return Effect.fn(function* (loadBalancer: LoadBalancer) {
129 const LoadBalancerArn = yield* loadBalancer.loadBalancerArn;
130 yield* bindHost(
131 options.tag,
132 options.actions,
133 loadBalancer,
134 options.resource === "*"
135 ? "*"
136 : Output.interpolate`${loadBalancer.loadBalancerArn}`,
137 );
138 return Effect.fn(`${options.tag}(${loadBalancer.LogicalId})`)(function* (
139 request?: Omit<I, "LoadBalancerArn">,
140 ) {
141 return yield* op({
142 ...request,
143 LoadBalancerArn: yield* LoadBalancerArn,
144 } as I);
145 });
146 });
147 });
148
149/**
150 * Build the impl Effect for a trust-store-addressed operation: the runtime
151 * callable injects the bound {@link TrustStore}'s ARN as `TrustStoreArn`;
152 * the deploy-time half grants `actions` on the trust-store ARN (mTLS
153 * `GetTrustStore*` reads support resource-level permissions).
154 */
155export const makeTrustStoreHttpBinding = <
156 I extends { TrustStoreArn?: string },
157 A,
158 E,
159 R,
160>(options: {
161 /** Fully-qualified binding tag, e.g. `AWS.ELBv2.GetTrustStoreCaCertificatesBundle`. */
162 tag: string;
163 /** The distilled operation; `TrustStoreArn` is injected from the store. */
164 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
165 /** IAM actions granted on the trust-store ARN. */
166 actions: readonly string[];
167}) =>
168 Effect.gen(function* () {
169 const op = yield* options.operation;
170
171 return Effect.fn(function* (trustStore: TrustStore) {
172 const TrustStoreArn = yield* trustStore.trustStoreArn;
173 yield* bindHost(
174 options.tag,
175 options.actions,
176 trustStore,
177 Output.interpolate`${trustStore.trustStoreArn}`,
178 );
179 return Effect.fn(`${options.tag}(${trustStore.LogicalId})`)(function* (
180 request?: Omit<I, "TrustStoreArn">,
181 ) {
182 return yield* op({
183 ...request,
184 TrustStoreArn: yield* TrustStoreArn,
185 } as I);
186 });
187 });
188 });