| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Binding from "../../Binding.ts"; |
| 3 | import * as Output from "../../Output.ts"; |
| 4 | import { isBindingHost } from "../Lambda/Function.ts"; |
| 5 | import type { Channel } from "./Channel.ts"; |
| 6 | import type { ChannelGroup } from "./ChannelGroup.ts"; |
| 7 | import type { OriginEndpoint } from "./OriginEndpoint.ts"; |
| 8 | |
| 9 | /** |
| 10 | * Shared scaffolding for AWS Elemental MediaPackage v2 HTTP bindings. |
| 11 | * |
| 12 | * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a |
| 13 | * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the three |
| 14 | * builders below. Everything except the operation and the IAM action list |
| 15 | * is boilerplate: each builder injects the bound resource's identifying |
| 16 | * names (`ChannelGroupName` / `ChannelName` / `OriginEndpointName`) into |
| 17 | * every request and grants `actions` on the resource's ARN. |
| 18 | * |
| 19 | * Harvest-job operations authorize against the *harvest job* ARN, which is |
| 20 | * the origin endpoint ARN with a `/harvestJob/{name}` suffix — set |
| 21 | * `harvestJobScoped` to grant on that pattern instead of the endpoint ARN. |
| 22 | */ |
| 23 | |
| 24 | /** |
| 25 | * Build the impl Effect for a MediaPackage v2 operation scoped to a |
| 26 | * {@link ChannelGroup}: the runtime callable injects the group's |
| 27 | * `ChannelGroupName` and the deploy-time half grants `actions` on the |
| 28 | * group ARN (plus everything beneath it — channels, endpoints, harvest |
| 29 | * jobs — for list operations that enumerate child resources). |
| 30 | */ |
| 31 | export const makeMediaPackageV2ChannelGroupHttpBinding = < |
| 32 | I extends { ChannelGroupName: string }, |
| 33 | A, |
| 34 | E, |
| 35 | R, |
| 36 | >(options: { |
| 37 | /** Fully-qualified binding tag, e.g. `AWS.MediaPackageV2.ListHarvestJobs`. */ |
| 38 | tag: string; |
| 39 | /** The distilled operation; `ChannelGroupName` is injected from the group. */ |
| 40 | operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>; |
| 41 | /** IAM actions granted on the group ARN and its child-resource pattern. */ |
| 42 | actions: readonly string[]; |
| 43 | }) => |
| 44 | Effect.gen(function* () { |
| 45 | const op = yield* options.operation; |
| 46 | |
| 47 | return Effect.fn(function* <G extends ChannelGroup>(group: G) { |
| 48 | const ChannelGroupName = yield* group.channelGroupName; |
| 49 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 50 | const host = yield* Binding.Host; |
| 51 | if (isBindingHost(host)) { |
| 52 | yield* host.bind`Allow(${host}, ${options.tag}(${group}))`({ |
| 53 | policyStatements: [ |
| 54 | { |
| 55 | Effect: "Allow", |
| 56 | Action: [...options.actions], |
| 57 | Resource: [ |
| 58 | Output.interpolate`${group.channelGroupArn}`, |
| 59 | Output.interpolate`${group.channelGroupArn}/*`, |
| 60 | ], |
| 61 | }, |
| 62 | ], |
| 63 | }); |
| 64 | } |
| 65 | } |
| 66 | return Effect.fn(`${options.tag}(${group.LogicalId})`)(function* ( |
| 67 | request?: Omit<I, "ChannelGroupName">, |
| 68 | ) { |
| 69 | return yield* op({ |
| 70 | ...request, |
| 71 | ChannelGroupName: yield* ChannelGroupName, |
| 72 | } as I); |
| 73 | }); |
| 74 | }); |
| 75 | }); |
| 76 | |
| 77 | /** |
| 78 | * Build the impl Effect for a MediaPackage v2 operation scoped to a |
| 79 | * {@link Channel}: the runtime callable injects the channel's |
| 80 | * `ChannelGroupName` + `ChannelName` and the deploy-time half grants |
| 81 | * `actions` on the channel ARN. |
| 82 | */ |
| 83 | export const makeMediaPackageV2ChannelHttpBinding = < |
| 84 | I extends { ChannelGroupName: string; ChannelName: string }, |
| 85 | A, |
| 86 | E, |
| 87 | R, |
| 88 | >(options: { |
| 89 | /** Fully-qualified binding tag, e.g. `AWS.MediaPackageV2.ResetChannelState`. */ |
| 90 | tag: string; |
| 91 | /** The distilled operation; the channel's names are injected. */ |
| 92 | operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>; |
| 93 | /** IAM actions granted on the channel ARN. */ |
| 94 | actions: readonly string[]; |
| 95 | }) => |
| 96 | Effect.gen(function* () { |
| 97 | const op = yield* options.operation; |
| 98 | |
| 99 | return Effect.fn(function* <C extends Channel>(channel: C) { |
| 100 | const ChannelGroupName = yield* channel.channelGroupName; |
| 101 | const ChannelName = yield* channel.channelName; |
| 102 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 103 | const host = yield* Binding.Host; |
| 104 | if (isBindingHost(host)) { |
| 105 | yield* host.bind`Allow(${host}, ${options.tag}(${channel}))`({ |
| 106 | policyStatements: [ |
| 107 | { |
| 108 | Effect: "Allow", |
| 109 | Action: [...options.actions], |
| 110 | Resource: [Output.interpolate`${channel.channelArn}`], |
| 111 | }, |
| 112 | ], |
| 113 | }); |
| 114 | } |
| 115 | } |
| 116 | return Effect.fn(`${options.tag}(${channel.LogicalId})`)(function* ( |
| 117 | request?: Omit<I, "ChannelGroupName" | "ChannelName">, |
| 118 | ) { |
| 119 | return yield* op({ |
| 120 | ...request, |
| 121 | ChannelGroupName: yield* ChannelGroupName, |
| 122 | ChannelName: yield* ChannelName, |
| 123 | } as I); |
| 124 | }); |
| 125 | }); |
| 126 | }); |
| 127 | |
| 128 | /** |
| 129 | * Build the impl Effect for a MediaPackage v2 operation scoped to an |
| 130 | * {@link OriginEndpoint}: the runtime callable injects the endpoint's |
| 131 | * `ChannelGroupName` + `ChannelName` + `OriginEndpointName` and the |
| 132 | * deploy-time half grants `actions` on the endpoint ARN — plus, when |
| 133 | * `harvestJobScoped` is set, on the endpoint's harvest-job ARN pattern |
| 134 | * (`{endpointArn}/harvestJob/*`) and the parent channel-group ARN: |
| 135 | * MediaPackage authorizes `CreateHarvestJob` against the bare channel |
| 136 | * group (observed live: "not authorized to perform |
| 137 | * mediapackagev2:CreateHarvestJob on resource: arn:…:channelGroup/{g}"). |
| 138 | */ |
| 139 | export const makeMediaPackageV2OriginEndpointHttpBinding = < |
| 140 | I extends { |
| 141 | ChannelGroupName: string; |
| 142 | ChannelName: string; |
| 143 | OriginEndpointName: string; |
| 144 | }, |
| 145 | A, |
| 146 | E, |
| 147 | R, |
| 148 | >(options: { |
| 149 | /** Fully-qualified binding tag, e.g. `AWS.MediaPackageV2.CreateHarvestJob`. */ |
| 150 | tag: string; |
| 151 | /** The distilled operation; the endpoint's names are injected. */ |
| 152 | operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>; |
| 153 | /** IAM actions granted on the endpoint (or harvest-job pattern) ARN. */ |
| 154 | actions: readonly string[]; |
| 155 | /** Additionally grant on `{endpointArn}/harvestJob/*` (harvest-job ops). */ |
| 156 | harvestJobScoped?: boolean; |
| 157 | }) => |
| 158 | Effect.gen(function* () { |
| 159 | const op = yield* options.operation; |
| 160 | |
| 161 | return Effect.fn(function* <O extends OriginEndpoint>(endpoint: O) { |
| 162 | const ChannelGroupName = yield* endpoint.channelGroupName; |
| 163 | const ChannelName = yield* endpoint.channelName; |
| 164 | const OriginEndpointName = yield* endpoint.originEndpointName; |
| 165 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 166 | const host = yield* Binding.Host; |
| 167 | if (isBindingHost(host)) { |
| 168 | yield* host.bind`Allow(${host}, ${options.tag}(${endpoint}))`({ |
| 169 | policyStatements: [ |
| 170 | { |
| 171 | Effect: "Allow", |
| 172 | Action: [...options.actions], |
| 173 | Resource: [ |
| 174 | Output.interpolate`${endpoint.originEndpointArn}`, |
| 175 | ...(options.harvestJobScoped |
| 176 | ? [ |
| 177 | Output.interpolate`${endpoint.originEndpointArn}/harvestJob/*`, |
| 178 | // The endpoint ARN is `…:channelGroup/{g}/channel/{c}/originEndpoint/{e}`; |
| 179 | // harvest-job actions authorize against the bare |
| 180 | // channel-group ARN prefix. |
| 181 | Output.map( |
| 182 | endpoint.originEndpointArn, |
| 183 | (arn): string => arn.split("/channel/")[0]!, |
| 184 | ), |
| 185 | ] |
| 186 | : []), |
| 187 | ], |
| 188 | }, |
| 189 | ], |
| 190 | }); |
| 191 | } |
| 192 | } |
| 193 | return Effect.fn(`${options.tag}(${endpoint.LogicalId})`)(function* ( |
| 194 | request?: Omit< |
| 195 | I, |
| 196 | "ChannelGroupName" | "ChannelName" | "OriginEndpointName" |
| 197 | >, |
| 198 | ) { |
| 199 | return yield* op({ |
| 200 | ...request, |
| 201 | ChannelGroupName: yield* ChannelGroupName, |
| 202 | ChannelName: yield* ChannelName, |
| 203 | OriginEndpointName: yield* OriginEndpointName, |
| 204 | } as I); |
| 205 | }); |
| 206 | }); |
| 207 | }); |