1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as clouddeploy from "@distilled.cloud/gcp/clouddeploy_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const waitUntilGone = (name: string) =>
18 clouddeploy.getProjectsLocationsDeliveryPipelinesAutomations({ name }).pipe(
19 Effect.as("found" as const),
20 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
21 Effect.repeat({
22 schedule: Schedule.spaced("2 seconds"),
23 until: (status) => status === "gone",
24 times: 10,
25 }),
26 );
27
28test.provider(
29 "getProjectsLocationsDeliveryPipelinesAutomations on a missing automation fails with a typed tag",
30 (stack) =>
31 Effect.gen(function* () {
32 const { project } = yield* GcpEnvironment.current;
33
34 yield* stack.destroy();
35
36 const error = yield* Effect.flip(
37 clouddeploy.getProjectsLocationsDeliveryPipelinesAutomations({
38 name: `projects/${project}/locations/us-central1/deliveryPipelines/alchemy-missing-pipeline/automations/alchemy-missing-automation`,
39 }),
40 );
41 expect(error._tag).toEqual("NotFound");
42
43 yield* stack.destroy();
44 }).pipe(logLevel),
45 {
46 tags: ["provider:gcp", "provider:gcp:clouddeploy", "live"],
47 timeout: 90_000,
48 },
49);
50
51test.provider(
52 "create, update, and delete a delivery pipeline automation",
53 (stack) =>
54 Effect.gen(function* () {
55 const { project } = yield* GcpEnvironment.current;
56 const serviceAccount =
57 process.env.GCP_TEST_CLOUDDEPLOY_SA ??
58 `alchemy-testing@${project}.iam.gserviceaccount.com`;
59 const runLocation = `projects/${project}/locations/us-central1`;
60
61 yield* stack.destroy();
62
63 const created = yield* stack.deploy(
64 Effect.gen(function* () {
65 const target = yield* GCP.CloudDeploy.Target("Staging", {
66 run: { location: runLocation },
67 labels: { env: "test" },
68 });
69 const pipeline = yield* GCP.CloudDeploy.DeliveryPipeline("App", {
70 serialPipeline: { stages: [{ targetId: target.targetId }] },
71 description: "alchemy-test-automation-pipeline",
72 labels: { env: "test" },
73 });
74 const automation = yield* GCP.CloudDeploy.DeliveryPipelinesAutomation(
75 "Promote",
76 {
77 deliveryPipeline: pipeline.name,
78 serviceAccount,
79 selector: { targets: [{ id: "*" }] },
80 rules: [{ promoteReleaseRule: { id: "promote-release" } }],
81 description: "alchemy-test-automation",
82 labels: { env: "test" },
83 },
84 );
85 return { target, pipeline, automation };
86 }),
87 );
88
89 expect(created.automation.name).toContain("/automations/");
90 expect(created.automation.automationId).toEqual(expect.any(String));
91 expect(created.automation.deliveryPipeline).toEqual(
92 created.pipeline.name,
93 );
94 expect(created.automation.location).toEqual("us-central1");
95 expect(created.automation.description).toEqual("alchemy-test-automation");
96 expect(created.automation.serviceAccount).toEqual(serviceAccount);
97 expect(created.automation.selector?.targets?.[0]?.id).toEqual("*");
98 expect(created.automation.rules[0]?.promoteReleaseRule?.id).toEqual(
99 "promote-release",
100 );
101 expect(created.automation.labels).toMatchObject({ env: "test" });
102
103 const fetched =
104 yield* clouddeploy.getProjectsLocationsDeliveryPipelinesAutomations({
105 name: created.automation.name,
106 });
107 expect(fetched.name).toEqual(created.automation.name);
108 expect(fetched.description).toEqual("alchemy-test-automation");
109 expect(fetched.labels?.env).toEqual("test");
110
111 const updated = yield* stack.deploy(
112 Effect.gen(function* () {
113 const target = yield* GCP.CloudDeploy.Target("Staging", {
114 targetId: created.target.targetId,
115 run: { location: runLocation },
116 labels: { env: "test" },
117 });
118 const pipeline = yield* GCP.CloudDeploy.DeliveryPipeline("App", {
119 deliveryPipelineId: created.pipeline.deliveryPipelineId,
120 serialPipeline: { stages: [{ targetId: target.targetId }] },
121 description: "alchemy-test-automation-pipeline",
122 labels: { env: "test" },
123 });
124 const automation = yield* GCP.CloudDeploy.DeliveryPipelinesAutomation(
125 "Promote",
126 {
127 automationId: created.automation.automationId,
128 deliveryPipeline: pipeline.name,
129 serviceAccount,
130 selector: { targets: [{ id: "*" }] },
131 rules: [{ promoteReleaseRule: { id: "promote-release" } }],
132 description: "alchemy-prod-automation",
133 labels: { env: "prod", role: "promote" },
134 },
135 );
136 return automation;
137 }),
138 );
139
140 expect(updated.name).toEqual(created.automation.name);
141 expect(updated.description).toEqual("alchemy-prod-automation");
142 expect(updated.labels).toMatchObject({ env: "prod", role: "promote" });
143
144 yield* stack.destroy();
145
146 const gone = yield* waitUntilGone(created.automation.name);
147 expect(gone).toEqual("gone");
148 }).pipe(logLevel),
149 {
150 tags: ["provider:gcp", "provider:gcp:clouddeploy", "live"],
151 timeout: 120_000,
152 },
153);