1import * as location from "@distilled.cloud/aws/location";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Binding from "../../Binding.ts";
5import * as Output from "../../Output.ts";
6import type { Role } from "../IAM/Role.ts";
7import { isBindingHost } from "../Lambda/Function.ts";
8import { StartJob, type StartJobRequest } from "./StartJob.ts";
9
10/**
11 * Bespoke (not via `BindingHttp.ts`): StartJob is the one Location binding
12 * that injects an identifier from a *foreign* resource (the IAM execution
13 * role) and needs a second `iam:PassRole` grant alongside the `geo:` action.
14 */
15export const StartJobHttp = Layer.effect(
16 StartJob,
17 Effect.gen(function* () {
18 const startJob = yield* location.startJob;
19
20 return Effect.fn(function* (executionRole: Role) {
21 const RoleArn = yield* executionRole.roleArn;
22 if (!globalThis.__ALCHEMY_RUNTIME__) {
23 const host = yield* Binding.Host;
24 if (isBindingHost(host)) {
25 yield* host.bind`Allow(${host}, AWS.Location.StartJob(${executionRole}))`(
26 {
27 policyStatements: [
28 // Jobs are named at runtime, so their ARNs are unknowable at
29 // deploy time.
30 {
31 Effect: "Allow",
32 Action: ["geo:StartJob"],
33 Resource: ["*"],
34 },
35 // CRITICAL: without iam:PassRole on the execution role,
36 // StartJob fails only at runtime with an AccessDenied.
37 {
38 Effect: "Allow",
39 Action: ["iam:PassRole"],
40 Resource: [Output.interpolate`${executionRole.roleArn}`],
41 Condition: {
42 StringEquals: {
43 "iam:PassedToService": "location.amazonaws.com",
44 },
45 },
46 },
47 ],
48 },
49 );
50 }
51 }
52 return Effect.fn(`AWS.Location.StartJob(${executionRole.LogicalId})`)(
53 function* (request: StartJobRequest) {
54 return yield* startJob({
55 ...request,
56 ExecutionRoleArn: yield* RoleArn,
57 });
58 },
59 );
60 });
61 }),
62);