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 { Application } from "./Application.ts";
6import type { DeploymentGroup } from "./DeploymentGroup.ts";
7
8/**
9 * Shared scaffolding for AWS CodeDeploy 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 `applicationName` /
15 * `deploymentGroupName` is boilerplate.
16 *
17 * CodeDeploy authorizes deployment-addressed operations (get/stop/continue a
18 * deployment, lifecycle-hook results, deployment targets) against the
19 * *deployment group* the deployment belongs to, and revision operations
20 * against the *application* ARN — so every builder grants on the bound
21 * resource's ARN.
22 */
23
24/**
25 * Build the impl Effect for an operation whose input carries
26 * `applicationName` + `deploymentGroupName` fields: the runtime callable
27 * injects both from the bound {@link DeploymentGroup} and the deploy-time
28 * half grants `actions` on the deployment-group ARN.
29 */
30export const makeCodeDeployGroupNameHttpBinding = <
31 I extends { applicationName?: string; deploymentGroupName?: string },
32 A,
33 E,
34 R,
35>(options: {
36 /** Fully-qualified binding tag, e.g. `AWS.CodeDeploy.ListDeployments`. */
37 tag: string;
38 /**
39 * The distilled operation; `applicationName` and `deploymentGroupName`
40 * are injected from the group.
41 */
42 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
43 /** IAM actions granted on the deployment-group ARN. */
44 actions: readonly string[];
45}) =>
46 Effect.gen(function* () {
47 const op = yield* options.operation;
48
49 return Effect.fn(function* <G extends DeploymentGroup>(group: G) {
50 // Outputs yield a DEFERRED effect — resolve again per invocation below.
51 const ApplicationName = yield* group.applicationName;
52 const DeploymentGroupName = yield* group.deploymentGroupName;
53 if (!globalThis.__ALCHEMY_RUNTIME__) {
54 const host = yield* Binding.Host;
55 if (isBindingHost(host)) {
56 yield* host.bind`Allow(${host}, ${options.tag}(${group}))`({
57 policyStatements: [
58 {
59 Effect: "Allow",
60 Action: [...options.actions],
61 Resource: [Output.interpolate`${group.deploymentGroupArn}`],
62 },
63 ],
64 });
65 }
66 }
67 return Effect.fn(`${options.tag}(${group.LogicalId})`)(function* (
68 request?: Omit<I, "applicationName" | "deploymentGroupName">,
69 ) {
70 return yield* op({
71 ...request,
72 applicationName: yield* ApplicationName,
73 deploymentGroupName: yield* DeploymentGroupName,
74 } as I);
75 });
76 });
77 });
78
79/**
80 * Build the impl Effect for a group-anchored operation whose input
81 * addresses deployments by id (get/stop/continue, lifecycle-hook results,
82 * deployment targets): the request passes through as-is and the deploy-time
83 * half grants `actions` on the deployment-group ARN (CodeDeploy authorizes
84 * deployment-addressed operations against the group the deployment belongs
85 * to).
86 */
87export const makeCodeDeployGroupHttpBinding = <I, A, E, R>(options: {
88 /** Fully-qualified binding tag, e.g. `AWS.CodeDeploy.GetDeployment`. */
89 tag: string;
90 /** The distilled operation, invoked with the caller's request as-is. */
91 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
92 /** IAM actions granted on the deployment-group ARN. */
93 actions: readonly string[];
94}) =>
95 Effect.gen(function* () {
96 const op = yield* options.operation;
97
98 return Effect.fn(function* <G extends DeploymentGroup>(group: G) {
99 if (!globalThis.__ALCHEMY_RUNTIME__) {
100 const host = yield* Binding.Host;
101 if (isBindingHost(host)) {
102 yield* host.bind`Allow(${host}, ${options.tag}(${group}))`({
103 policyStatements: [
104 {
105 Effect: "Allow",
106 Action: [...options.actions],
107 Resource: [Output.interpolate`${group.deploymentGroupArn}`],
108 },
109 ],
110 });
111 }
112 }
113 return Effect.fn(`${options.tag}(${group.LogicalId})`)(function* (
114 request: I,
115 ) {
116 return yield* op(request);
117 });
118 });
119 });
120
121/**
122 * Build the impl Effect for an operation whose input carries an
123 * `applicationName` field (revision management): the runtime callable
124 * injects the bound {@link Application}'s name and the deploy-time half
125 * grants `actions` on the application ARN.
126 */
127export const makeCodeDeployApplicationHttpBinding = <
128 I extends { applicationName?: string },
129 A,
130 E,
131 R,
132>(options: {
133 /** Fully-qualified binding tag, e.g. `AWS.CodeDeploy.GetApplicationRevision`. */
134 tag: string;
135 /** The distilled operation; `applicationName` is injected from the app. */
136 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
137 /** IAM actions granted on the application ARN. */
138 actions: readonly string[];
139}) =>
140 Effect.gen(function* () {
141 const op = yield* options.operation;
142
143 return Effect.fn(function* <P extends Application>(application: P) {
144 const ApplicationName = yield* application.applicationName;
145 if (!globalThis.__ALCHEMY_RUNTIME__) {
146 const host = yield* Binding.Host;
147 if (isBindingHost(host)) {
148 yield* host.bind`Allow(${host}, ${options.tag}(${application}))`({
149 policyStatements: [
150 {
151 Effect: "Allow",
152 Action: [...options.actions],
153 Resource: [Output.interpolate`${application.applicationArn}`],
154 },
155 ],
156 });
157 }
158 }
159 return Effect.fn(`${options.tag}(${application.LogicalId})`)(function* (
160 request?: Omit<I, "applicationName">,
161 ) {
162 return yield* op({
163 ...request,
164 applicationName: yield* ApplicationName,
165 } as I);
166 });
167 });
168 });