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