1import * as AWS from "@/AWS";
2import { RegistryPolicy } from "@/AWS/ECR";
3import type { PolicyDocument } from "@/AWS/IAM/Policy.ts";
4import { normalizePolicyDocument } from "@/AWS/IAM/Policy.ts";
5import * as Test from "@/Test/Alchemy";
6import * as ecr from "@distilled.cloud/aws/ecr";
7import { Region } from "@distilled.cloud/aws/Region";
8import * as sts from "@distilled.cloud/aws/sts";
9import { expect } from "alchemy-test";
10import * as Effect from "effect/Effect";
11
12const { test } = Test.make({ providers: AWS.providers() });
13
14const readRegistryPolicy = ecr.getRegistryPolicy({}).pipe(
15 Effect.map((response) => response.policyText),
16 Effect.catchTag("RegistryPolicyNotFoundException", () =>
17 Effect.succeed(undefined),
18 ),
19);
20
21// The registry policy is an account/region SINGLETON — capture any
22// pre-existing policy up front and restore it after the test so a shared
23// testing account is left exactly as we found it.
24test.provider(
25 "PolicyDocument-valued registry policy deploys and re-deploys clean",
26 (stack) =>
27 Effect.gen(function* () {
28 const prior = yield* readRegistryPolicy;
29
30 const identity = yield* sts.getCallerIdentity({});
31 const accountId = identity.Account!;
32 const region = yield* yield* Region;
33
34 const registryPolicy: PolicyDocument = {
35 Version: "2012-10-17",
36 Statement: [
37 {
38 Sid: "AlchemyTestReplication",
39 Effect: "Allow",
40 Principal: { AWS: `arn:aws:iam::${accountId}:root` },
41 Action: ["ecr:ReplicateImage"],
42 Resource: `arn:aws:ecr:${region}:${accountId}:repository/*`,
43 },
44 ],
45 };
46
47 const run = Effect.gen(function* () {
48 yield* stack.destroy();
49
50 const deployPolicy = () =>
51 stack.deploy(
52 Effect.gen(function* () {
53 return yield* RegistryPolicy("TestRegistryPolicy", {
54 policy: registryPolicy,
55 });
56 }),
57 );
58
59 const deployed = yield* deployPolicy();
60 expect(deployed.registryId).toBe(accountId);
61
62 // Out-of-band: the applied policy is equivalent to the structured
63 // document (normalized comparison).
64 const observed = yield* readRegistryPolicy;
65 expect(observed).toBeDefined();
66 expect(normalizePolicyDocument(observed!)).toBe(
67 normalizePolicyDocument(registryPolicy),
68 );
69
70 // Re-deploy the identical PolicyDocument — must converge cleanly
71 // (normalized observed vs desired no-ops the put).
72 const again = yield* deployPolicy();
73 expect(again.registryId).toBe(accountId);
74 const observedAgain = yield* readRegistryPolicy;
75 expect(normalizePolicyDocument(observedAgain!)).toBe(
76 normalizePolicyDocument(registryPolicy),
77 );
78
79 yield* stack.destroy();
80 expect(yield* readRegistryPolicy).toBeUndefined();
81 });
82
83 // Restore whatever registry policy existed before the test ran,
84 // whether the body succeeded or failed.
85 yield* run.pipe(
86 Effect.ensuring(
87 prior === undefined
88 ? Effect.void
89 : ecr.putRegistryPolicy({ policyText: prior }).pipe(Effect.ignore),
90 ),
91 );
92 }),
93 {
94 tags: ["provider:aws", "provider:aws:ecr", "provider:aws:iam", "live"],
95 timeout: 120_000,
96 },
97);