1import * as oracle from "@distilled.cloud/gcp/oracledatabase_v1";
2import * as Effect from "effect/Effect";
3import { Unowned } from "../../AdoptPolicy.ts";
4import { isResolved } from "../../Diff.ts";
5import * as Provider from "../../Provider.ts";
6import { Resource } from "../../Resource.ts";
7import { tagRecord } from "../../Tags.ts";
8import { GcpEnvironment } from "../Environment.ts";
9import { createInternalLabels, hasAlchemyLabels, toLabels } from "../Labels.ts";
10import type { Providers } from "../Providers.ts";
11import {
12 OracleDatabaseNotResolved,
13 collectPages,
14 hasAlchemyLabelMap,
15 listAtLocation,
16 normalizeLocation,
17 parentOf,
18 parseName,
19 replaceOnIdentity,
20 resourceNameOf,
21 retryConflict,
22 retryQuota,
23 toPhysicalId,
24 userLabels,
25 waitUntilExists,
26 waitUntilGone,
27 waitUntilReady,
28} from "./internal.ts";
29import { waitForOperation } from "./operations.ts";
30
31const COLLECTION = "goldengateConnectionAssignments";
32const FALLBACK_ID = "ggassign";
33
34export type GoldengateConnectionAssignmentPropertiesInput = {
35 /**
36 * GoldenGate connection
37 * (`projects/{project}/locations/{location}/goldengateConnections/{id}`).
38 * Required on create. Immutable.
39 */
40 goldengateConnection?: string;
41 /**
42 * GoldenGate deployment
43 * (`projects/{project}/locations/{location}/goldengateDeployments/{id}`).
44 * Required on create. Immutable.
45 */
46 goldengateDeployment?: string;
47};
48
49export type GoldengateConnectionAssignmentProps = {
50 /**
51 * Assignment id. If omitted, a unique RFC1035 name is generated.
52 * Immutable.
53 */
54 goldengateConnectionAssignmentId?: string;
55 /**
56 * Region. Immutable.
57 * @default the stack's GCP region (`GCP.Region`, else the profile region, else `us-central1`)
58 */
59 location?: string;
60 /**
61 * Display name.
62 */
63 displayName?: string;
64 /**
65 * User labels. Alchemy ownership labels are merged in automatically.
66 */
67 labels?: Record<string, string>;
68 /**
69 * Assignment properties.
70 */
71 properties?: GoldengateConnectionAssignmentPropertiesInput;
72 /** Connection name. Convenience alias. */
73 goldengateConnection?: string;
74 /** Deployment name. Convenience alias. */
75 goldengateDeployment?: string;
76};
77
78export type GoldengateConnectionAssignment = Resource<
79 "GCP.OracleDatabase.GoldengateConnectionAssignment",
80 GoldengateConnectionAssignmentProps,
81 {
82 /** Full resource name. */
83 name: string;
84 /** Assignment id. */
85 goldengateConnectionAssignmentId: string;
86 /** Project id. */
87 project: string;
88 /** Location id. */
89 location: string;
90 /** Display name. */
91 displayName: string | undefined;
92 /** User labels (Alchemy ownership labels stripped). */
93 labels: Record<string, string>;
94 /** Entitlement id. */
95 entitlementId: string | undefined;
96 /** Assigned connection. */
97 goldengateConnection: string | undefined;
98 /** Assigned deployment. */
99 goldengateDeployment: string | undefined;
100 /** Lifecycle state. */
101 state: string | undefined;
102 /** Credential store alias. */
103 alias: string | undefined;
104 /** OCID. */
105 ocid: string | undefined;
106 /** RFC3339 creation timestamp. */
107 createTime: string | undefined;
108 },
109 never,
110 Providers
111>;
112
113/**
114 * An Oracle GoldenGate connection assignment on Google Cloud.
115 *
116 * Changing `goldengateConnectionAssignmentId`, `location`, the
117 * connection, or the deployment replaces the assignment. There is no
118 * patch API in the distilled SDK, so labels are applied at create.
119 *
120 * ### Creating an assignment
121 * **Example:** Bind a connection to a deployment
122 * ```typescript
123 * const assignment = yield* GCP.OracleDatabase.GoldengateConnectionAssignment(
124 * "Assign",
125 * {
126 * goldengateConnection: connection.name,
127 * goldengateDeployment: deployment.name,
128 * displayName: "assign",
129 * },
130 * );
131 * ```
132 *
133 * @resource
134 * @category OracleDatabase
135 */
136export const GoldengateConnectionAssignment =
137 Resource<GoldengateConnectionAssignment>(
138 "GCP.OracleDatabase.GoldengateConnectionAssignment",
139 );
140
141const mergedProperties = (
142 news: GoldengateConnectionAssignmentProps,
143): GoldengateConnectionAssignmentPropertiesInput => ({
144 ...(news.properties ?? {}),
145 goldengateConnection:
146 news.goldengateConnection ?? news.properties?.goldengateConnection,
147 goldengateDeployment:
148 news.goldengateDeployment ?? news.properties?.goldengateDeployment,
149});
150
151const toCreateBody = (
152 news: GoldengateConnectionAssignmentProps,
153 desiredLabels: Record<string, string>,
154): oracle.GoldengateConnectionAssignment => {
155 const props = mergedProperties(news);
156 const properties: oracle.GoldengateConnectionAssignmentProperties = {};
157 if (props.goldengateConnection !== undefined) {
158 properties.goldengateConnection = props.goldengateConnection;
159 }
160 if (props.goldengateDeployment !== undefined) {
161 properties.goldengateDeployment = props.goldengateDeployment;
162 }
163 const body: oracle.GoldengateConnectionAssignment = {
164 labels: desiredLabels,
165 properties,
166 };
167 if (news.displayName !== undefined) body.displayName = news.displayName;
168 return body;
169};
170
171const toAttrs = (
172 assignment: oracle.GoldengateConnectionAssignment,
173 project: string,
174) => {
175 const name = assignment.name ?? "";
176 const parsed = parseName(name, COLLECTION);
177 return {
178 name,
179 goldengateConnectionAssignmentId: parsed.id,
180 project: parsed.project || project,
181 location: parsed.location,
182 displayName: assignment.displayName,
183 labels: userLabels(assignment.labels),
184 entitlementId: assignment.entitlementId,
185 goldengateConnection: assignment.properties?.goldengateConnection,
186 goldengateDeployment: assignment.properties?.goldengateDeployment,
187 state: assignment.properties?.state,
188 alias: assignment.properties?.alias,
189 ocid: assignment.properties?.ocid,
190 createTime: assignment.createTime,
191 };
192};
193
194const getByName = (name: string) =>
195 retryQuota(
196 oracle.getProjectsLocationsGoldengateConnectionAssignments({ name }),
197 ).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
198
199const listAssignments = (project: string, region: string) => {
200 const collect = (parent: string) =>
201 collectPages(
202 oracle.listProjectsLocationsGoldengateConnectionAssignments.pages({
203 parent,
204 pageSize: 1000,
205 }),
206 (page) => page.goldengateConnectionAssignments,
207 ).pipe(
208 Effect.map((items) =>
209 items.filter((item) => hasAlchemyLabelMap(item.labels)),
210 ),
211 );
212 return listAtLocation(project, region, collect).pipe(
213 Effect.catchTag("NotFound", () => Effect.succeed([])),
214 );
215};
216
217export const GoldengateConnectionAssignmentProvider = () =>
218 Provider.succeed(GoldengateConnectionAssignment, {
219 stables: [
220 "name",
221 "goldengateConnectionAssignmentId",
222 "project",
223 "location",
224 "createTime",
225 "ocid",
226 ],
227
228 diff: Effect.fn(function* ({ news, olds, output }) {
229 if (!isResolved(news)) return undefined;
230 const env = yield* GcpEnvironment.current;
231 const previousConn =
232 olds?.goldengateConnection ??
233 olds?.properties?.goldengateConnection ??
234 output?.goldengateConnection ??
235 "";
236 const nextConn =
237 news.goldengateConnection ??
238 news.properties?.goldengateConnection ??
239 previousConn;
240 const previousDep =
241 olds?.goldengateDeployment ??
242 olds?.properties?.goldengateDeployment ??
243 output?.goldengateDeployment ??
244 "";
245 const nextDep =
246 news.goldengateDeployment ??
247 news.properties?.goldengateDeployment ??
248 previousDep;
249 return replaceOnIdentity({
250 previousId:
251 olds?.goldengateConnectionAssignmentId ??
252 output?.goldengateConnectionAssignmentId,
253 nextId:
254 news.goldengateConnectionAssignmentId ??
255 olds?.goldengateConnectionAssignmentId ??
256 output?.goldengateConnectionAssignmentId,
257 previousLocation: normalizeLocation(
258 olds?.location ?? output?.location ?? env.region,
259 ),
260 nextLocation: normalizeLocation(
261 news.location ?? olds?.location ?? output?.location ?? env.region,
262 ),
263 extra: nextConn !== previousConn || nextDep !== previousDep,
264 });
265 }),
266
267 read: Effect.fn(function* ({ id, olds, output }) {
268 const env = yield* GcpEnvironment.current;
269 const goldengateConnectionAssignmentId = yield* toPhysicalId(
270 id,
271 olds?.goldengateConnectionAssignmentId,
272 output?.goldengateConnectionAssignmentId,
273 FALLBACK_ID,
274 );
275 const location = normalizeLocation(
276 olds?.location ?? output?.location ?? env.region,
277 );
278 const name =
279 output?.name ??
280 resourceNameOf(
281 env.project,
282 location,
283 COLLECTION,
284 goldengateConnectionAssignmentId,
285 );
286 const existing = yield* getByName(name);
287 if (existing === undefined) return undefined;
288 const attrs = toAttrs(existing, env.project);
289 return (yield* hasAlchemyLabels(id, tagRecord(existing.labels)))
290 ? attrs
291 : Unowned(attrs);
292 }),
293
294 list: () =>
295 Effect.gen(function* () {
296 const env = yield* GcpEnvironment.current;
297 const items = yield* listAssignments(env.project, env.region);
298 return items.map((item) => toAttrs(item, env.project));
299 }),
300
301 reconcile: Effect.fn(function* ({ id, news, output }) {
302 const env = yield* GcpEnvironment.current;
303 const goldengateConnectionAssignmentId = yield* toPhysicalId(
304 id,
305 news.goldengateConnectionAssignmentId,
306 output?.goldengateConnectionAssignmentId,
307 FALLBACK_ID,
308 );
309 const location = normalizeLocation(
310 news.location ?? output?.location ?? env.region,
311 );
312 const name = resourceNameOf(
313 env.project,
314 location,
315 COLLECTION,
316 goldengateConnectionAssignmentId,
317 );
318 const desiredLabels = {
319 ...toLabels(news.labels),
320 ...(yield* createInternalLabels(id)),
321 };
322
323 let current = yield* getByName(output?.name ?? name);
324
325 if (current === undefined) {
326 const created = yield* oracle
327 .createProjectsLocationsGoldengateConnectionAssignments({
328 parent: parentOf(env.project, location),
329 goldengateConnectionAssignmentId,
330 body: toCreateBody(news, desiredLabels),
331 })
332 .pipe(
333 retryQuota,
334 Effect.catchTag("Conflict", () => Effect.succeed(undefined)),
335 );
336 if (created !== undefined) {
337 yield* waitForOperation(created);
338 }
339 current = yield* waitUntilExists(getByName(name), name);
340 }
341
342 if (current === undefined) {
343 return yield* new OracleDatabaseNotResolved({ name });
344 }
345
346 const ready = yield* waitUntilReady(
347 getByName(current.name ?? name),
348 current.name ?? name,
349 (value) => value.properties?.state,
350 );
351
352 return toAttrs(ready, env.project);
353 }),
354
355 delete: Effect.fn(function* ({ output }) {
356 const operation = yield* oracle
357 .deleteProjectsLocationsGoldengateConnectionAssignments({
358 name: output.name,
359 })
360 .pipe(
361 retryConflict,
362 Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
363 );
364 if (operation !== undefined) {
365 yield* waitForOperation(operation, { notFoundOk: true });
366 }
367 yield* waitUntilGone(getByName(output.name), output.name);
368 }),
369 });