1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { EnabledBaseline } from "./EnabledBaseline.ts";
6import type { EnabledControl } from "./EnabledControl.ts";
7import type { LandingZone } from "./LandingZone.ts";
8
9/**
10 * Shared HTTP scaffolding for the AWS Control Tower runtime bindings.
11 *
12 * Every capability follows the same shape — resolve the distilled operation,
13 * register an IAM policy statement on the binding host, and return a runtime
14 * callable. The only variation is the operation, the IAM action(s), and
15 * whether the binding is scoped to one Control Tower resource (injecting its
16 * identifier ARN into the request) or to the whole account (the baseline
17 * catalog, list, and operation-status APIs, which are not resource-scoped).
18 *
19 * @internal — not exported from `index.ts`.
20 */
21
22/** The Control Tower resources that resource-scoped bindings can target. */
23export type ControlTowerBindable =
24 | EnabledControl
25 | EnabledBaseline
26 | LandingZone;
27
28/**
29 * Build the implementation effect for a resource-scoped capability: the
30 * runtime callable injects the bound resource's identifier ARN as the
31 * request's `requestKey` field, and the deploy-time half grants `iamActions`
32 * on the resource's ARN.
33 */
34export const makeControlTowerHttpBinding = <
35 Res extends ControlTowerBindable,
36 I extends object,
37 K extends keyof I & string,
38 A,
39 E,
40 R,
41 IdReq = never,
42>(options: {
43 /**
44 * Short capability name used in the binding sid and runtime span, e.g.
45 * `"ResetEnabledControl"`.
46 */
47 capability: string;
48 /**
49 * IAM actions granted on the resource's ARN, e.g.
50 * `["controltower:ResetEnabledControl"]`.
51 */
52 iamActions: readonly string[];
53 /** The request field the resolved identifier ARN is injected as. */
54 requestKey: K;
55 /** Resolve the injected identifier ARN from the bound resource. */
56 identifier: (resource: Res) => Output.Output<string, IdReq>;
57 /** The distilled operation; `requestKey` is injected from the resource. */
58 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
59}) =>
60 Effect.gen(function* () {
61 const op = yield* options.operation;
62
63 return Effect.fn(function* (resource: Res) {
64 const Identifier = yield* options.identifier(resource);
65 if (!globalThis.__ALCHEMY_RUNTIME__) {
66 const host = yield* Binding.Host;
67 if (isBindingHost(host)) {
68 yield* host.bind`Allow(${host}, AWS.ControlTower.${options.capability}(${resource}))`(
69 {
70 policyStatements: [
71 {
72 Effect: "Allow",
73 Action: [...options.iamActions],
74 Resource: [options.identifier(resource)],
75 },
76 ],
77 },
78 );
79 }
80 }
81 return Effect.fn(
82 `AWS.ControlTower.${options.capability}(${resource.LogicalId})`,
83 )(function* (request?: Omit<I, K>) {
84 return yield* op({
85 ...request,
86 [options.requestKey]: yield* Identifier,
87 } as I);
88 });
89 });
90 });
91
92/**
93 * Build the implementation effect for an account-level capability (no
94 * resource argument — the baseline catalog, list, and operation-status
95 * APIs). The deploy-time half grants `iamActions` on `Resource: ["*"]`
96 * because these operations are not resource-scoped.
97 */
98export const makeControlTowerAccountHttpBinding = <
99 I extends object,
100 A,
101 E,
102 R,
103>(options: {
104 /**
105 * Short capability name used in the binding sid and runtime span, e.g.
106 * `"ListBaselines"`.
107 */
108 capability: string;
109 /**
110 * IAM actions granted on `Resource: ["*"]`.
111 */
112 iamActions: readonly string[];
113 /**
114 * The distilled operation implementing the capability.
115 */
116 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
117}) =>
118 Effect.gen(function* () {
119 const op = yield* options.operation;
120
121 return Effect.fn(function* () {
122 if (!globalThis.__ALCHEMY_RUNTIME__) {
123 const host = yield* Binding.Host;
124 if (isBindingHost(host)) {
125 yield* host.bind`Allow(${host}, AWS.ControlTower.${options.capability}())`(
126 {
127 policyStatements: [
128 {
129 Effect: "Allow",
130 Action: [...options.iamActions],
131 Resource: ["*"],
132 },
133 ],
134 },
135 );
136 }
137 }
138 return Effect.fn(`AWS.ControlTower.${options.capability}`)(function* (
139 request?: I,
140 ) {
141 return yield* op((request ?? {}) as I);
142 });
143 });
144 });