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 { Project } from "./Project.ts";
6import type { ReportGroup } from "./ReportGroup.ts";
7
8/**
9 * Shared scaffolding for AWS CodeBuild HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
12 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the builders
13 * below. Everything except the operation, the IAM action, and (for
14 * name-injecting operations) the injected `projectName`/`reportGroupArn` is
15 * boilerplate.
16 *
17 * CodeBuild authorizes builds, batch builds, sandboxes, and cache operations
18 * against the *project* ARN, and report operations against the
19 * *report-group* ARN — even when the request addresses a build/report id —
20 * so every builder grants on the bound resource's ARN.
21 */
22
23/**
24 * Build the impl Effect for an operation whose input carries a
25 * `projectName` field: the runtime callable injects the bound
26 * {@link Project}'s name and the deploy-time half grants `actions` on the
27 * project ARN.
28 */
29export const makeCodeBuildProjectNameHttpBinding = <
30 I extends { projectName?: string },
31 A,
32 E,
33 R,
34>(options: {
35 /** Fully-qualified binding tag, e.g. `AWS.CodeBuild.StartBuildBatch`. */
36 tag: string;
37 /** The distilled operation; `projectName` is injected from the project. */
38 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
39 /** IAM actions granted on the project ARN. */
40 actions: readonly string[];
41}) =>
42 Effect.gen(function* () {
43 const op = yield* options.operation;
44
45 return Effect.fn(function* <P extends Project>(project: P) {
46 // Outputs yield a DEFERRED effect — resolve again per invocation below.
47 const ProjectName = yield* project.projectName;
48 if (!globalThis.__ALCHEMY_RUNTIME__) {
49 const host = yield* Binding.Host;
50 if (isBindingHost(host)) {
51 yield* host.bind`Allow(${host}, ${options.tag}(${project}))`({
52 policyStatements: [
53 {
54 Effect: "Allow",
55 Action: [...options.actions],
56 Resource: [Output.interpolate`${project.projectArn}`],
57 },
58 ],
59 });
60 }
61 }
62 return Effect.fn(`${options.tag}(${project.LogicalId})`)(function* (
63 request?: Omit<I, "projectName">,
64 ) {
65 return yield* op({
66 ...request,
67 projectName: yield* ProjectName,
68 } as I);
69 });
70 });
71 });
72
73/**
74 * Build the impl Effect for a project-anchored operation whose input
75 * addresses builds/batches/sandboxes by id: the request passes through
76 * as-is and the deploy-time half grants `actions` on the project ARN
77 * (CodeBuild authorizes build-/batch-addressed operations against the
78 * project the id belongs to). Sandbox-addressed actions authorize against
79 * the *sandbox* ARN (`…:sandbox/{project-name}:{uuid}`) — set
80 * `sandboxScoped` to additionally grant on the project's sandbox pattern.
81 */
82export const makeCodeBuildProjectHttpBinding = <I, A, E, R>(options: {
83 /** Fully-qualified binding tag, e.g. `AWS.CodeBuild.StopBuild`. */
84 tag: string;
85 /** The distilled operation, invoked with the caller's request as-is. */
86 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
87 /** IAM actions granted on the project ARN. */
88 actions: readonly string[];
89 /** Also grant on `…:sandbox/{project-name}:*` (sandbox-addressed actions). */
90 sandboxScoped?: boolean;
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* <P extends Project>(project: P) {
96 if (!globalThis.__ALCHEMY_RUNTIME__) {
97 const host = yield* Binding.Host;
98 if (isBindingHost(host)) {
99 yield* host.bind`Allow(${host}, ${options.tag}(${project}))`({
100 policyStatements: [
101 {
102 Effect: "Allow",
103 Action: [...options.actions],
104 Resource: [
105 Output.interpolate`${project.projectArn}`,
106 ...(options.sandboxScoped
107 ? [
108 Output.map(
109 project.projectArn,
110 (arn) => `${arn.replace(":project/", ":sandbox/")}:*`,
111 ),
112 ]
113 : []),
114 ],
115 },
116 ],
117 });
118 }
119 }
120 return Effect.fn(`${options.tag}(${project.LogicalId})`)(function* (
121 request: I,
122 ) {
123 return yield* op(request);
124 });
125 });
126 });
127
128/**
129 * Build the impl Effect for an operation whose input carries a
130 * `reportGroupArn` field: the runtime callable injects the bound
131 * {@link ReportGroup}'s ARN and the deploy-time half grants `actions` on
132 * the report-group ARN.
133 */
134export const makeCodeBuildReportGroupArnHttpBinding = <
135 I extends { reportGroupArn?: string },
136 A,
137 E,
138 R,
139>(options: {
140 /** Fully-qualified binding tag, e.g. `AWS.CodeBuild.GetReportGroupTrend`. */
141 tag: string;
142 /** The distilled operation; `reportGroupArn` is injected from the group. */
143 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
144 /** IAM actions granted on the report-group ARN. */
145 actions: readonly string[];
146}) =>
147 Effect.gen(function* () {
148 const op = yield* options.operation;
149
150 return Effect.fn(function* <G extends ReportGroup>(reportGroup: G) {
151 const ReportGroupArn = yield* reportGroup.reportGroupArn;
152 if (!globalThis.__ALCHEMY_RUNTIME__) {
153 const host = yield* Binding.Host;
154 if (isBindingHost(host)) {
155 yield* host.bind`Allow(${host}, ${options.tag}(${reportGroup}))`({
156 policyStatements: [
157 {
158 Effect: "Allow",
159 Action: [...options.actions],
160 Resource: [Output.interpolate`${reportGroup.reportGroupArn}`],
161 },
162 ],
163 });
164 }
165 }
166 return Effect.fn(`${options.tag}(${reportGroup.LogicalId})`)(function* (
167 request?: Omit<I, "reportGroupArn">,
168 ) {
169 return yield* op({
170 ...request,
171 reportGroupArn: yield* ReportGroupArn,
172 } as I);
173 });
174 });
175 });
176
177/**
178 * Build the impl Effect for a report-group-anchored operation whose input
179 * addresses reports by ARN: the request passes through as-is and the
180 * deploy-time half grants `actions` on the report-group ARN (CodeBuild
181 * authorizes report operations against the report group the report belongs
182 * to).
183 */
184export const makeCodeBuildReportGroupHttpBinding = <I, A, E, R>(options: {
185 /** Fully-qualified binding tag, e.g. `AWS.CodeBuild.DescribeTestCases`. */
186 tag: string;
187 /** The distilled operation, invoked with the caller's request as-is. */
188 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
189 /** IAM actions granted on the report-group ARN. */
190 actions: readonly string[];
191}) =>
192 Effect.gen(function* () {
193 const op = yield* options.operation;
194
195 return Effect.fn(function* <G extends ReportGroup>(reportGroup: G) {
196 if (!globalThis.__ALCHEMY_RUNTIME__) {
197 const host = yield* Binding.Host;
198 if (isBindingHost(host)) {
199 yield* host.bind`Allow(${host}, ${options.tag}(${reportGroup}))`({
200 policyStatements: [
201 {
202 Effect: "Allow",
203 Action: [...options.actions],
204 Resource: [Output.interpolate`${reportGroup.reportGroupArn}`],
205 },
206 ],
207 });
208 }
209 }
210 return Effect.fn(`${options.tag}(${reportGroup.LogicalId})`)(function* (
211 request: I,
212 ) {
213 return yield* op(request);
214 });
215 });
216 });