1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as Core from "@/Test/Core";
4import * as servicedirectory from "@distilled.cloud/gcp/servicedirectory_v1";
5import { describe, expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { dockerAvailable, expectProbe } from "../bindingHost.ts";
8import ServiceDirectoryBindingsHost, {
9 Api,
10 ENDPOINT_ADDRESS,
11 ENDPOINT_PORT,
12 Https,
13 Registry,
14} from "./fixtures/bindings-host.ts";
15
16const testOptions = { providers: GCP.providers() };
17const { test, beforeAll, afterAll } = Test.make(testOptions);
18const sharedStack = Core.scratchStack(testOptions, "ServiceDirectoryBindings");
19
20let baseUrl: string;
21let member: string;
22let serviceName: string;
23let endpointName: string;
24let namespaceName: string;
25
26/** Roles the host's service account holds on the bound service. */
27const hostRolesOnService = Effect.gen(function* () {
28 const policy =
29 yield* servicedirectory.getIamPolicyProjectsLocationsNamespacesServices({
30 resource: serviceName,
31 body: { options: { requestedPolicyVersion: 3 } },
32 });
33 return (policy.bindings ?? [])
34 .filter((binding) => (binding.members ?? []).includes(member))
35 .map((binding) => binding.role)
36 .sort();
37});
38
39/** Roles the host's service account holds on the parent namespace. */
40const hostRolesOnNamespace = Effect.gen(function* () {
41 const policy =
42 yield* servicedirectory.getIamPolicyProjectsLocationsNamespaces({
43 resource: namespaceName,
44 body: { options: { requestedPolicyVersion: 3 } },
45 });
46 return (policy.bindings ?? [])
47 .filter((binding) => (binding.members ?? []).includes(member))
48 .map((binding) => binding.role);
49});
50
51describe.skipIf(!dockerAvailable)(
52 "ServiceDirectory Bindings",
53 {
54 tags: [
55 "provider:gcp",
56 "provider:gcp:servicedirectory",
57 "provider:gcp:run",
58 "live",
59 ],
60 },
61 () => {
62 beforeAll(
63 Effect.gen(function* () {
64 yield* sharedStack.destroy();
65 const out = yield* sharedStack.deploy(
66 Effect.gen(function* () {
67 const host = yield* ServiceDirectoryBindingsHost;
68 return {
69 uri: host.uri,
70 serviceAccount: host.serviceAccount,
71 namespace: (yield* Registry).name,
72 service: (yield* Api).name,
73 endpoint: (yield* Https).name,
74 };
75 }),
76 );
77 baseUrl = out.uri!;
78 member = `serviceAccount:${out.serviceAccount!}`;
79 namespaceName = out.namespace;
80 serviceName = out.service;
81 endpointName = out.endpoint;
82 }),
83 { timeout: 900_000 },
84 );
85
86 afterAll(sharedStack.destroy(), { timeout: 600_000 });
87
88 describe("Resolve", () => {
89 test.provider(
90 "resolves the service with its endpoints, granted viewer on the service only",
91 (_stack) =>
92 Effect.gen(function* () {
93 const out =
94 yield* expectProbe<servicedirectory.ResolveServiceResponse>(
95 baseUrl,
96 "resolve",
97 );
98 expect(out.service?.name).toEqual(serviceName);
99 expect(
100 (out.service?.endpoints ?? []).map((endpoint) => ({
101 name: endpoint.name,
102 address: endpoint.address,
103 port: endpoint.port,
104 })),
105 ).toEqual([
106 {
107 name: endpointName,
108 address: ENDPOINT_ADDRESS,
109 port: ENDPOINT_PORT,
110 },
111 ]);
112
113 expect(yield* hostRolesOnService).toEqual([
114 "roles/servicedirectory.viewer",
115 ]);
116 expect(yield* hostRolesOnNamespace).toEqual([]);
117 }),
118 {
119 tags: ["provider:gcp", "provider:gcp:servicedirectory", "live"],
120 timeout: 600_000,
121 },
122 );
123 });
124
125 describe("GetEndpoint", () => {
126 test.provider(
127 "reads the endpoint, granted viewer on its service only",
128 (_stack) =>
129 Effect.gen(function* () {
130 const live = yield* expectProbe<servicedirectory.Endpoint>(
131 baseUrl,
132 "getEndpoint",
133 );
134 expect(live.name).toEqual(endpointName);
135 expect(live.address).toEqual(ENDPOINT_ADDRESS);
136 expect(live.port).toEqual(ENDPOINT_PORT);
137
138 // Out of band: the deployer sees the same endpoint.
139 const direct =
140 yield* servicedirectory.getProjectsLocationsNamespacesServicesEndpoints(
141 { name: endpointName },
142 );
143 expect(direct.uid).toEqual(live.uid);
144
145 expect(yield* hostRolesOnService).toEqual([
146 "roles/servicedirectory.viewer",
147 ]);
148 }),
149 {
150 tags: ["provider:gcp", "provider:gcp:servicedirectory", "live"],
151 timeout: 600_000,
152 },
153 );
154 });
155 },
156);