1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as Core from "@/Test/Core";
4import * as container from "@distilled.cloud/gcp/container_v1";
5import * as crm from "@distilled.cloud/gcp/cloudresourcemanager_v3";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { dockerAvailable, expectProbe } from "../bindingHost.ts";
9import ContainerBindingsHost, {
10 App,
11 slow,
12 Workers,
13 ZonalWorkers,
14} from "./fixtures/bindings-host.ts";
15
16const testOptions = { providers: GCP.providers() };
17const { test, beforeAll, afterAll } = Test.make(testOptions);
18const sharedStack = Core.scratchStack(testOptions, "ContainerBindings");
19
20let baseUrl: string;
21let hostAccount: string;
22let project: string;
23let cluster: { name: string; clusterId: string };
24let pool: { name: string; nodePoolId: string };
25let zonalPool: { nodePoolId: string };
26
27/**
28 * GKE has no per-cluster IAM policy: every Get binding grants
29 * `roles/container.clusterViewer` on the project.
30 */
31const expectProjectGrants = Effect.gen(function* () {
32 const policy = yield* crm.getIamPolicyProjects({
33 resource: `projects/${project}`,
34 body: { options: { requestedPolicyVersion: 3 } },
35 });
36 const roles = (policy.bindings ?? [])
37 .filter((binding) =>
38 (binding.members ?? []).includes(`serviceAccount:${hostAccount}`),
39 )
40 .map((binding) => ({ role: binding.role, condition: binding.condition }));
41 expect(roles).toEqual([
42 { role: "roles/container.clusterViewer", condition: undefined },
43 ]);
44});
45
46describe.skipIf(!dockerAvailable || !slow)(
47 "Container Bindings",
48 {
49 tags: [
50 "provider:gcp",
51 "provider:gcp:container",
52 "provider:gcp:run",
53 "live",
54 ],
55 },
56 () => {
57 beforeAll(
58 Effect.gen(function* () {
59 yield* sharedStack.destroy();
60 const out = yield* sharedStack.deploy(
61 Effect.gen(function* () {
62 const host = yield* ContainerBindingsHost;
63 const app = yield* App;
64 const workers = yield* Workers;
65 const zonal = yield* ZonalWorkers;
66 return {
67 uri: host.uri,
68 serviceAccount: host.serviceAccount,
69 project: app.project,
70 cluster: { name: app.name, clusterId: app.clusterId },
71 pool: { name: workers.name, nodePoolId: workers.nodePoolId },
72 zonalPool: { nodePoolId: zonal.nodePoolId },
73 };
74 }),
75 );
76 baseUrl = out.uri!;
77 hostAccount = out.serviceAccount!;
78 project = out.project;
79 cluster = out.cluster;
80 pool = out.pool;
81 zonalPool = out.zonalPool;
82 }),
83 { timeout: 2_400_000 },
84 );
85
86 afterAll(sharedStack.destroy(), { timeout: 1_800_000 });
87
88 describe("GetCluster", () => {
89 test.provider(
90 "reads the cluster as the host's service account",
91 (_stack) =>
92 Effect.gen(function* () {
93 const live = yield* expectProbe<container.Cluster>(
94 baseUrl,
95 "getCluster",
96 );
97 const actual = yield* container.getProjectsLocationsClusters({
98 name: cluster.name,
99 });
100 expect(live.name).toEqual(cluster.clusterId);
101 expect(live.status).toEqual("RUNNING");
102 expect(live.endpoint).toEqual(actual.endpoint);
103 yield* expectProjectGrants;
104 }),
105 {
106 tags: ["provider:gcp", "provider:gcp:container", "live"],
107 timeout: 600_000,
108 },
109 );
110 });
111
112 describe("GetNodePool", () => {
113 test.provider(
114 "reads the node pool as the host's service account",
115 (_stack) =>
116 Effect.gen(function* () {
117 const live = yield* expectProbe<container.NodePool>(
118 baseUrl,
119 "getNodePool",
120 );
121 expect(live.name).toEqual(pool.nodePoolId);
122 expect(live.config?.machineType).toEqual("e2-medium");
123 expect(live.config?.spot).toEqual(true);
124 yield* expectProjectGrants;
125 }),
126 {
127 tags: ["provider:gcp", "provider:gcp:container", "live"],
128 timeout: 600_000,
129 },
130 );
131 });
132
133 describe("GetClustersNodePool", () => {
134 test.provider(
135 "reads the zonal node pool as the host's service account",
136 (_stack) =>
137 Effect.gen(function* () {
138 const live = yield* expectProbe<container.NodePool>(
139 baseUrl,
140 "getClustersNodePool",
141 );
142 expect(live.name).toEqual(zonalPool.nodePoolId);
143 expect(live.config?.machineType).toEqual("e2-medium");
144 yield* expectProjectGrants;
145 }),
146 {
147 tags: ["provider:gcp", "provider:gcp:container", "live"],
148 timeout: 600_000,
149 },
150 );
151 });
152 },
153);