1import * as AWS from "@/AWS";
2import { Domain, EnvironmentBlueprintConfiguration } from "@/AWS/DataZone";
3import * as IAM from "@/AWS/IAM";
4import * as Test from "@/Test/Alchemy";
5import * as datazone from "@distilled.cloud/aws/datazone";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11const findConfiguration = (domainId: string, blueprintId: string) =>
12 datazone
13 .getEnvironmentBlueprintConfiguration({
14 domainIdentifier: domainId,
15 environmentBlueprintIdentifier: blueprintId,
16 })
17 .pipe(
18 Effect.catchTag("ResourceNotFoundException", () =>
19 Effect.succeed(undefined),
20 ),
21 // once the domain is deleted, DataZone reports AccessDenied (auth is
22 // checked before existence) — also "absent".
23 Effect.catchTag("AccessDeniedException", () => Effect.succeed(undefined)),
24 );
25
26test.provider(
27 "enable the DefaultDataLake blueprint in a domain, then remove it",
28 (stack) =>
29 Effect.gen(function* () {
30 yield* stack.destroy();
31
32 // 1. Deploy a domain, the provisioning/manage-access roles, and the
33 // blueprint configuration.
34 const result = yield* stack.deploy(
35 Effect.gen(function* () {
36 const domain = yield* Domain("BlueprintTestDomain", {
37 description: "alchemy datazone blueprint config test",
38 });
39 const provisioningRole = yield* IAM.Role("DataZoneProvisioningRole", {
40 assumeRolePolicyDocument: {
41 Version: "2012-10-17",
42 Statement: [
43 {
44 Effect: "Allow",
45 Principal: { Service: "cloudformation.amazonaws.com" },
46 Action: ["sts:AssumeRole"],
47 },
48 ],
49 },
50 managedPolicyArns: [
51 "arn:aws:iam::aws:policy/AmazonDataZoneRedshiftGlueProvisioningPolicy",
52 ],
53 });
54 const manageAccessRole = yield* IAM.Role("DataZoneManageAccessRole", {
55 assumeRolePolicyDocument: {
56 Version: "2012-10-17",
57 Statement: [
58 {
59 Effect: "Allow",
60 Principal: { Service: "datazone.amazonaws.com" },
61 Action: ["sts:AssumeRole", "sts:TagSession"],
62 },
63 ],
64 },
65 managedPolicyArns: [
66 "arn:aws:iam::aws:policy/service-role/AmazonDataZoneGlueManageAccessRolePolicy",
67 ],
68 });
69 const config = yield* EnvironmentBlueprintConfiguration(
70 "DataLakeBlueprint",
71 {
72 domainId: domain.domainId,
73 environmentBlueprint: "DefaultDataLake",
74 enabledRegions: ["us-west-2"],
75 provisioningRoleArn: provisioningRole.roleArn,
76 manageAccessRoleArn: manageAccessRole.roleArn,
77 },
78 );
79 return {
80 domainId: domain.domainId,
81 environmentBlueprintId: config.environmentBlueprintId,
82 environmentBlueprintName: config.environmentBlueprintName,
83 enabledRegions: config.enabledRegions,
84 provisioningRoleArn: config.provisioningRoleArn,
85 manageAccessRoleArn: config.manageAccessRoleArn,
86 };
87 }),
88 );
89
90 expect(result.environmentBlueprintName).toBe("DefaultDataLake");
91 expect(result.environmentBlueprintId).toBeDefined();
92 expect(result.enabledRegions).toEqual(["us-west-2"]);
93
94 // out-of-band: the configuration exists with the requested roles.
95 const created = yield* findConfiguration(
96 result.domainId,
97 result.environmentBlueprintId,
98 );
99 expect(created).toBeDefined();
100 expect(created!.enabledRegions).toEqual(["us-west-2"]);
101 expect(created!.provisioningRoleArn).toBe(result.provisioningRoleArn);
102 expect(created!.manageAccessRoleArn).toBe(result.manageAccessRoleArn);
103
104 // 2. Update — the PUT converges regional parameters in place.
105 yield* stack.deploy(
106 Effect.gen(function* () {
107 const domain = yield* Domain("BlueprintTestDomain", {
108 description: "alchemy datazone blueprint config test",
109 });
110 const provisioningRole = yield* IAM.Role("DataZoneProvisioningRole", {
111 assumeRolePolicyDocument: {
112 Version: "2012-10-17",
113 Statement: [
114 {
115 Effect: "Allow",
116 Principal: { Service: "cloudformation.amazonaws.com" },
117 Action: ["sts:AssumeRole"],
118 },
119 ],
120 },
121 managedPolicyArns: [
122 "arn:aws:iam::aws:policy/AmazonDataZoneRedshiftGlueProvisioningPolicy",
123 ],
124 });
125 const manageAccessRole = yield* IAM.Role("DataZoneManageAccessRole", {
126 assumeRolePolicyDocument: {
127 Version: "2012-10-17",
128 Statement: [
129 {
130 Effect: "Allow",
131 Principal: { Service: "datazone.amazonaws.com" },
132 Action: ["sts:AssumeRole", "sts:TagSession"],
133 },
134 ],
135 },
136 managedPolicyArns: [
137 "arn:aws:iam::aws:policy/service-role/AmazonDataZoneGlueManageAccessRolePolicy",
138 ],
139 });
140 yield* EnvironmentBlueprintConfiguration("DataLakeBlueprint", {
141 domainId: domain.domainId,
142 environmentBlueprint: "DefaultDataLake",
143 enabledRegions: ["us-west-2"],
144 provisioningRoleArn: provisioningRole.roleArn,
145 manageAccessRoleArn: manageAccessRole.roleArn,
146 regionalParameters: {
147 "us-west-2": { S3Location: "s3://alchemy-datazone-test" },
148 },
149 });
150 }),
151 );
152
153 const updated = yield* findConfiguration(
154 result.domainId,
155 result.environmentBlueprintId,
156 );
157 expect(updated!.regionalParameters?.["us-west-2"]?.S3Location).toBe(
158 "s3://alchemy-datazone-test",
159 );
160
161 // 3. Destroy — the configuration is deleted (before its domain). The
162 // domain delete waits until the domain is gone, so the config
163 // lookup must observe "absent" immediately after destroy.
164 yield* stack.destroy();
165 const gone = yield* findConfiguration(
166 result.domainId,
167 result.environmentBlueprintId,
168 );
169 expect(gone).toBeUndefined();
170 }),
171 {
172 tags: ["provider:aws", "provider:aws:datazone", "provider:aws:iam", "live"],
173 timeout: 480_000,
174 },
175);