1import * as backup from "@distilled.cloud/aws/backup";
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 {
9 StartRestoreJob,
10 type StartRestoreJobRequest,
11} from "./StartRestoreJob.ts";
12
13export const StartRestoreJobHttp = Layer.effect(
14 StartRestoreJob,
15 Effect.gen(function* () {
16 const startRestoreJob = yield* backup.startRestoreJob;
17
18 return Effect.fn(function* <R extends Role>(restoreRole: R) {
19 const RoleArn = yield* restoreRole.roleArn;
20 if (!globalThis.__ALCHEMY_RUNTIME__) {
21 const host = yield* Binding.Host;
22 if (isBindingHost(host)) {
23 yield* host.bind`Allow(${host}, AWS.Backup.StartRestoreJob(${restoreRole}))`(
24 {
25 policyStatements: [
26 // StartRestoreJob authorizes on the recovery point's
27 // underlying resource ARN (a snapshot ARN unknowable at
28 // deploy time), so the grant is on `*`.
29 {
30 Effect: "Allow",
31 Action: ["backup:StartRestoreJob"],
32 Resource: ["*"],
33 },
34 // CRITICAL: without iam:PassRole on the restore role,
35 // StartRestoreJob fails only at runtime with an AccessDenied.
36 {
37 Effect: "Allow",
38 Action: ["iam:PassRole"],
39 Resource: [Output.interpolate`${restoreRole.roleArn}`],
40 Condition: {
41 StringEquals: {
42 "iam:PassedToService": "backup.amazonaws.com",
43 },
44 },
45 },
46 ],
47 },
48 );
49 }
50 }
51 return Effect.fn(`AWS.Backup.StartRestoreJob(${restoreRole.LogicalId})`)(
52 function* (request: StartRestoreJobRequest) {
53 return yield* startRestoreJob({
54 ...request,
55 IamRoleArn: request.IamRoleArn ?? (yield* RoleArn),
56 });
57 },
58 );
59 });
60 }),
61);