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 { Cluster } from "./Cluster.ts";
6
7/**
8 * Shared scaffolding for provisioned Amazon Redshift HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service (except
11 * the bespoke `ConnectHttp`) is a thin `Layer.effect(Cap, make…HttpBinding({
12 * … }))` over one of the builders below. Everything except the operation,
13 * the identifier resolver, and the IAM action list is boilerplate.
14 */
15
16/**
17 * Build the impl Effect for an account-level operation (cluster discovery,
18 * event history, snapshot administration). The deploy-time half grants
19 * `actions` on `*` — these operations span every cluster in the account and
20 * the identifiers they filter on are runtime data.
21 */
22export const makeRedshiftAccountHttpBinding = <I, A, E, R>(options: {
23 /** Fully-qualified binding tag, e.g. `AWS.Redshift.DescribeEvents`. */
24 tag: string;
25 /** The distilled operation, invoked with the caller's request as-is. */
26 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
27 /** IAM actions granted on `*`. */
28 actions: readonly string[];
29}) =>
30 Effect.gen(function* () {
31 const op = yield* options.operation;
32
33 return Effect.fn(function* () {
34 if (!globalThis.__ALCHEMY_RUNTIME__) {
35 const host = yield* Binding.Host;
36 if (isBindingHost(host)) {
37 yield* host.bind`Allow(${host}, ${options.tag}())`({
38 policyStatements: [
39 {
40 Effect: "Allow",
41 Action: [...options.actions],
42 Resource: ["*"],
43 },
44 ],
45 });
46 }
47 }
48 return Effect.fn(options.tag)(function* (request?: I) {
49 return yield* op((request ?? {}) as I);
50 });
51 });
52 });
53
54/**
55 * Build the impl Effect for a cluster-scoped operation: the runtime callable
56 * injects the bound {@link Cluster}'s identifier as `ClusterIdentifier` and
57 * the deploy-time half grants `actions` on the cluster ARN (plus any
58 * `extraResources`, e.g. the `snapshot:{cluster}/*` ARN pattern for snapshot
59 * creation).
60 */
61export const makeRedshiftClusterHttpBinding = <
62 I extends { ClusterIdentifier?: string },
63 A,
64 E,
65 R,
66>(options: {
67 /** Fully-qualified binding tag, e.g. `AWS.Redshift.PauseCluster`. */
68 tag: string;
69 /** The distilled operation; `ClusterIdentifier` is injected. */
70 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
71 /** IAM actions granted on the cluster ARN. */
72 actions: readonly string[];
73 /** Additional IAM resource ARNs derived from the cluster ARN. */
74 extraResources?: (clusterArn: string) => string[];
75}) =>
76 Effect.gen(function* () {
77 const op = yield* options.operation;
78
79 return Effect.fn(function* (cluster: Cluster) {
80 const Identifier = yield* cluster.clusterIdentifier;
81 if (!globalThis.__ALCHEMY_RUNTIME__) {
82 const host = yield* Binding.Host;
83 if (isBindingHost(host)) {
84 const resources = options.extraResources;
85 yield* host.bind`Allow(${host}, ${options.tag}(${cluster}))`({
86 policyStatements: [
87 {
88 Effect: "Allow",
89 Action: [...options.actions],
90 Resource: resources
91 ? Output.map(cluster.clusterArn, (arn) => [
92 arn,
93 ...resources(arn),
94 ])
95 : [Output.interpolate`${cluster.clusterArn}`],
96 },
97 ],
98 });
99 }
100 }
101 return Effect.fn(`${options.tag}(${cluster.LogicalId})`)(function* (
102 request?: Omit<I, "ClusterIdentifier">,
103 ) {
104 return yield* op({
105 ...request,
106 ClusterIdentifier: yield* Identifier,
107 } as I);
108 });
109 });
110 });
111
112/**
113 * The `snapshot:{cluster}/*` ARN derived from a cluster ARN — the extra IAM
114 * resource snapshot-writing operations are scoped to.
115 */
116export const clusterSnapshotArnPattern = (clusterArn: string): string =>
117 `${clusterArn.replace(":cluster:", ":snapshot:")}/*`;