1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { isInstance } from "../EC2/Instance.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { AutoScalingGroup } from "./AutoScalingGroup.ts";
6
7/**
8 * Shared scaffolding for EC2 Auto Scaling HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every single-operation `{Op}Http.ts` in this
11 * service is a thin `Layer.effect(Cap, makeGroupHttpBinding({ … }))` over the
12 * builder below. Everything except the operation and the IAM action list is
13 * boilerplate: the runtime callable injects the bound
14 * {@link AutoScalingGroup}'s `AutoScalingGroupName` into the request (the
15 * serializer drops the field for the few operations that do not accept it —
16 * those are authorized against the instance's owning group) and the
17 * deploy-time half grants `actions` on the group ARN, or on `*` for
18 * `Describe*` actions (EC2 Auto Scaling `Describe*` actions do not support
19 * resource-level permissions).
20 *
21 * Genuinely-different bindings (multi-operation clients like
22 * `CompleteLifecycleAction`, `Standby`, and `InstanceRefresh`, or custom
23 * request shaping like `DescribeAutoScalingGroup`) stay bespoke.
24 */
25export const makeGroupHttpBinding = <I extends object, A, E, R>(options: {
26 /** Fully-qualified binding tag, e.g. `AWS.AutoScaling.SetDesiredCapacity`. */
27 tag: string;
28 /** The distilled operation; `AutoScalingGroupName` is injected from the group. */
29 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
30 /** IAM actions granted by the binding. */
31 actions: readonly string[];
32 /**
33 * IAM resource scope. Write actions support resource-level permissions on
34 * the group ARN (`"group"`, the default); `Describe*` actions do not
35 * (`"*"`).
36 */
37 resource?: "group" | "*";
38}) =>
39 Effect.gen(function* () {
40 const op = yield* options.operation;
41
42 return Effect.fn(function* (group: AutoScalingGroup) {
43 const AutoScalingGroupName = yield* group.autoScalingGroupName;
44 if (!globalThis.__ALCHEMY_RUNTIME__) {
45 const host = yield* Binding.Host;
46 if (isBindingHost(host) || isInstance(host)) {
47 yield* host.bind`Allow(${host}, ${options.tag}(${group}))`({
48 policyStatements: [
49 {
50 Effect: "Allow",
51 Action: [...options.actions],
52 Resource:
53 options.resource === "*"
54 ? ["*"]
55 : [group.autoScalingGroupArn],
56 },
57 ],
58 });
59 }
60 }
61 return Effect.fn(`${options.tag}(${group.LogicalId})`)(function* (
62 request?: Omit<I, "AutoScalingGroupName">,
63 ) {
64 return yield* op({
65 ...request,
66 AutoScalingGroupName: yield* AutoScalingGroupName,
67 } as I);
68 });
69 });
70 });