@microagi/alchemy-gcp 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/lib/Container/NodePool.d.ts +10 -0
  2. package/lib/Container/NodePool.d.ts.map +1 -1
  3. package/lib/Container/NodePool.js +4 -0
  4. package/lib/Container/NodePool.js.map +1 -1
  5. package/lib/Iam/ServiceAccount.d.ts.map +1 -1
  6. package/lib/Iam/ServiceAccount.js +21 -2
  7. package/lib/Iam/ServiceAccount.js.map +1 -1
  8. package/lib/Iam/ServiceAccountKey.d.ts +72 -0
  9. package/lib/Iam/ServiceAccountKey.d.ts.map +1 -0
  10. package/lib/Iam/ServiceAccountKey.js +164 -0
  11. package/lib/Iam/ServiceAccountKey.js.map +1 -0
  12. package/lib/Iam/index.d.ts +2 -0
  13. package/lib/Iam/index.d.ts.map +1 -1
  14. package/lib/Iam/index.js +1 -0
  15. package/lib/Iam/index.js.map +1 -1
  16. package/lib/Kubernetes/Helm.d.ts +132 -0
  17. package/lib/Kubernetes/Helm.d.ts.map +1 -0
  18. package/lib/Kubernetes/Helm.js +314 -0
  19. package/lib/Kubernetes/Helm.js.map +1 -0
  20. package/lib/Kubernetes/index.d.ts +1 -0
  21. package/lib/Kubernetes/index.d.ts.map +1 -1
  22. package/lib/Kubernetes/index.js +1 -0
  23. package/lib/Kubernetes/index.js.map +1 -1
  24. package/lib/Providers.d.ts +1 -1
  25. package/lib/Providers.d.ts.map +1 -1
  26. package/lib/Providers.js +7 -1
  27. package/lib/Providers.js.map +1 -1
  28. package/lib/Storage/Bucket.d.ts +117 -0
  29. package/lib/Storage/Bucket.d.ts.map +1 -0
  30. package/lib/Storage/Bucket.js +239 -0
  31. package/lib/Storage/Bucket.js.map +1 -0
  32. package/lib/Storage/BucketIamMember.d.ts +29 -0
  33. package/lib/Storage/BucketIamMember.d.ts.map +1 -0
  34. package/lib/Storage/BucketIamMember.js +26 -0
  35. package/lib/Storage/BucketIamMember.js.map +1 -0
  36. package/lib/Storage/index.d.ts +4 -0
  37. package/lib/Storage/index.d.ts.map +1 -0
  38. package/lib/Storage/index.js +3 -0
  39. package/lib/Storage/index.js.map +1 -0
  40. package/lib/index.d.ts +1 -0
  41. package/lib/index.d.ts.map +1 -1
  42. package/lib/index.js +1 -0
  43. package/lib/index.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/Container/NodePool.ts +12 -0
  46. package/src/Iam/ServiceAccount.ts +21 -2
  47. package/src/Iam/ServiceAccountKey.ts +284 -0
  48. package/src/Iam/index.ts +5 -0
  49. package/src/Kubernetes/Helm.ts +536 -0
  50. package/src/Kubernetes/index.ts +1 -0
  51. package/src/Providers.ts +18 -0
  52. package/src/Storage/Bucket.ts +439 -0
  53. package/src/Storage/BucketIamMember.ts +33 -0
  54. package/src/Storage/index.ts +10 -0
  55. package/src/index.ts +1 -0
@@ -0,0 +1,536 @@
1
+ import { Credentials } from "@distilled.cloud/gcp";
2
+ import { Resource } from "alchemy";
3
+ import { Unowned } from "alchemy/AdoptPolicy";
4
+ import { isResolved, somePropsAreDifferent } from "alchemy/Diff";
5
+ import * as Provider from "alchemy/Provider";
6
+ import * as Data from "effect/Data";
7
+ import * as Effect from "effect/Effect";
8
+ import * as FileSystem from "effect/FileSystem";
9
+ import * as Path from "effect/Path";
10
+ import * as Redacted from "effect/Redacted";
11
+ import * as Stream from "effect/Stream";
12
+ import { ChildProcess } from "effect/unstable/process";
13
+ import type * as GCP from "../Providers.ts";
14
+ import { gcpInternalLabels, hasAlchemyLabels } from "../Tags.ts";
15
+
16
+ /**
17
+ * Install or upgrade a **Helm release** in a GKE cluster.
18
+ *
19
+ * Shells out to the `helm` CLI (v3.8+, required for OCI chart support) with a
20
+ * temporary kubeconfig minted from the GKE cluster's endpoint + CA + an ADC
21
+ * bearer token. The release is branded with alchemy-internal labels
22
+ * (`alchemy_app` / `alchemy_stage` / `alchemy_id`) via `--labels`, so adoption
23
+ * gating works the same as labelled GCP resources and K8s manifests — a
24
+ * release created out of band reads back as {@link Unowned} until adopted.
25
+ *
26
+ * **Reconcile** runs `helm upgrade --install` (idempotent for both first
27
+ * install and subsequent upgrades). **Delete** runs `helm uninstall`. **Read**
28
+ * runs `helm status -o json` and checks the release's labels for ownership.
29
+ *
30
+ * Both OCI charts (`oci://…`) and repository charts (`repo/chart` with an
31
+ * optional `repoUrl`) are supported. Inline `values` are serialized to a
32
+ * temporary JSON file and passed via `--values`.
33
+ *
34
+ * @section Installing a chart
35
+ * @example OCI chart with inline values
36
+ * ```typescript
37
+ * yield* GCP.HelmRelease("ArchilCsi", {
38
+ * endpoint: cluster.endpoint,
39
+ * caCertificate: cluster.clusterCaCertificate,
40
+ * name: "archil-csi-driver",
41
+ * namespace: "archil-system",
42
+ * chart: "oci://registry-1.docker.io/archildata/csi-driver-chart",
43
+ * createNamespace: true,
44
+ * values: {
45
+ * tolerations: [
46
+ * { key: "nvidia.com/gpu", operator: "Equal", value: "present", effect: "NoSchedule" },
47
+ * ],
48
+ * },
49
+ * });
50
+ * ```
51
+ */
52
+ export type HelmReleaseProps = {
53
+ /**
54
+ * GKE control-plane endpoint (IP or hostname, no scheme) — typically
55
+ * `cluster.endpoint`. A change points at a different cluster → replace.
56
+ */
57
+ endpoint: string;
58
+ /**
59
+ * Base64-encoded cluster CA certificate (PEM) — typically
60
+ * `cluster.clusterCaCertificate`. Mutable (can rotate on the same cluster).
61
+ */
62
+ caCertificate: string;
63
+ /** Release name (helm `--name`). Immutable — replace if changed. */
64
+ name: string;
65
+ /** Target namespace. Immutable — replace if changed. */
66
+ namespace: string;
67
+ /**
68
+ * Chart reference as you'd pass to `helm install`: an OCI URL
69
+ * (`oci://registry…/chart`), a `repo/chart` pair, or a local path.
70
+ * Immutable — replace if changed.
71
+ */
72
+ chart: string;
73
+ /**
74
+ * Chart version to pin. Mutable (upgrading the chart version is an
75
+ * in-place `helm upgrade`).
76
+ */
77
+ version?: string;
78
+ /**
79
+ * Inline Helm values (serialized to a temp JSON file and passed via
80
+ * `--values`). Mutable.
81
+ */
82
+ values?: Record<string, unknown>;
83
+ /**
84
+ * Create the namespace if it doesn't exist (`--create-namespace`).
85
+ * @default false
86
+ */
87
+ createNamespace?: boolean;
88
+ /**
89
+ * Helm repository URL to add before install (`helm repo add`). Only used
90
+ * when `chart` is a `repo/chart` reference (not OCI). The repo name is
91
+ * derived from the first segment of `chart`.
92
+ */
93
+ repoUrl?: string;
94
+ /**
95
+ * Wait for all Kubernetes resources to be ready before returning
96
+ * (`--wait --atomic`). Rolls back on failure.
97
+ * @default false
98
+ */
99
+ wait?: boolean;
100
+ /**
101
+ * Timeout for `--wait` (Helm duration format, e.g. `"5m"`, `"300s"`).
102
+ * @default "5m"
103
+ */
104
+ timeout?: string;
105
+ /** Extra labels (alchemy ownership labels merge on top). Mutable. */
106
+ labels?: Record<string, string>;
107
+ };
108
+
109
+ export type HelmReleaseAttributes = {
110
+ /** Release name. */
111
+ name: string;
112
+ /** Namespace. */
113
+ namespace: string;
114
+ /** Chart reference (as declared). */
115
+ chart: string;
116
+ /** Resolved chart version (from `helm status`). */
117
+ version: string | undefined;
118
+ /** Release status (`"deployed"`, `"failed"`, `"pending-rollout"`, …). */
119
+ status: string | undefined;
120
+ /** Release revision number (increments on each upgrade). */
121
+ revision: number | undefined;
122
+ /** Control-plane endpoint, threaded through for delete/read. */
123
+ endpoint: string;
124
+ /** Cluster CA certificate, threaded through for delete/read. */
125
+ caCertificate: string;
126
+ };
127
+
128
+ export type HelmRelease = Resource<
129
+ "GCP.HelmRelease",
130
+ HelmReleaseProps,
131
+ HelmReleaseAttributes,
132
+ never,
133
+ GCP.Providers
134
+ >;
135
+ export const HelmRelease = Resource<HelmRelease>(
136
+ "GCP.HelmRelease",
137
+ );
138
+
139
+ /** Error from a `helm` CLI invocation. */
140
+ export class HelmError extends Data.TaggedError("HelmError")<{
141
+ command: string;
142
+ exitCode: number;
143
+ stderr: string;
144
+ stdout: string;
145
+ }> {}
146
+
147
+ /** Shape of `helm status -o json` output (only the fields we read). */
148
+ interface HelmReleaseStatus {
149
+ name: string;
150
+ namespace: string;
151
+ revision: number;
152
+ info: {
153
+ status: string;
154
+ first_deployed?: string;
155
+ last_deployed?: string;
156
+ };
157
+ chart: {
158
+ metadata: {
159
+ name: string;
160
+ version: string;
161
+ };
162
+ };
163
+ labels?: Record<string, string>;
164
+ }
165
+
166
+ const toAttributes = (
167
+ status: HelmReleaseStatus | undefined,
168
+ parent: {
169
+ name: string;
170
+ namespace: string;
171
+ chart: string;
172
+ endpoint: string;
173
+ caCertificate: string;
174
+ },
175
+ ): HelmReleaseAttributes => ({
176
+ name: status?.name ?? parent.name,
177
+ namespace: status?.namespace ?? parent.namespace,
178
+ chart: parent.chart,
179
+ version: status?.chart?.metadata?.version,
180
+ status: status?.info?.status,
181
+ revision: status?.revision,
182
+ endpoint: parent.endpoint,
183
+ caCertificate: parent.caCertificate,
184
+ });
185
+
186
+ /** Mint a fresh GKE connection (bearer token) from props + ADC credentials. */
187
+ const connect = Effect.fn("helm.connect")(function* (props: {
188
+ endpoint: string;
189
+ caCertificate: string;
190
+ }) {
191
+ const { accessToken } = yield* yield* Credentials;
192
+ return {
193
+ endpoint: props.endpoint,
194
+ caCertificate: props.caCertificate,
195
+ token: Redacted.value(accessToken),
196
+ };
197
+ });
198
+
199
+ export const HelmReleaseProvider = () =>
200
+ Provider.effect(
201
+ HelmRelease,
202
+ Effect.gen(function* () {
203
+ const fs = yield* FileSystem.FileSystem;
204
+ const path = yield* Path.Path;
205
+
206
+ /**
207
+ * Write a temp kubeconfig (and optional values file) to a temp dir,
208
+ * returning the paths. The temp dir is tied to a Scope via
209
+ * `acquireRelease` — if any step after `makeTempDirectory` fails, the
210
+ * dir (which contains the sensitive kubeconfig with an ADC bearer
211
+ * token) is removed immediately. Callers wrap their body in
212
+ * `Effect.scoped` so the dir also cleans up on success/interruption.
213
+ */
214
+ const prepare = (props: {
215
+ endpoint: string;
216
+ caCertificate: string;
217
+ values?: Record<string, unknown>;
218
+ }) =>
219
+ Effect.acquireRelease(
220
+ Effect.gen(function* () {
221
+ const connection = yield* connect(props);
222
+ const dir = yield* fs.makeTempDirectory({
223
+ prefix: "helm-alchemy-",
224
+ });
225
+ const kubeconfigPath = path.join(dir, "kubeconfig.json");
226
+ const valuesPath = props.values
227
+ ? path.join(dir, "values.json")
228
+ : undefined;
229
+
230
+ // Kubeconfig with the cluster's CA + the ADC bearer token. The CA
231
+ // is already base64-encoded (as GKE returns it), so it goes
232
+ // straight into `certificate-authority-data`.
233
+ const kubeconfig = JSON.stringify({
234
+ apiVersion: "v1",
235
+ kind: "Config",
236
+ clusters: [
237
+ {
238
+ name: "gke",
239
+ cluster: {
240
+ "certificate-authority-data": props.caCertificate,
241
+ server: `https://${props.endpoint}`,
242
+ },
243
+ },
244
+ ],
245
+ contexts: [
246
+ {
247
+ name: "gke",
248
+ context: { cluster: "gke", user: "gke-user" },
249
+ },
250
+ ],
251
+ "current-context": "gke",
252
+ users: [
253
+ {
254
+ name: "gke-user",
255
+ user: { token: connection.token },
256
+ },
257
+ ],
258
+ });
259
+ yield* fs.writeFileString(kubeconfigPath, kubeconfig);
260
+
261
+ if (valuesPath && props.values) {
262
+ // JSON is valid YAML — helm accepts it for --values.
263
+ yield* fs.writeFileString(valuesPath, JSON.stringify(props.values));
264
+ }
265
+
266
+ return { dir, kubeconfigPath, valuesPath, connection };
267
+ }),
268
+ // Release: always remove the temp dir, even on success.
269
+ ({ dir }) => fs.remove(dir, { recursive: true }).pipe(Effect.ignore),
270
+ );
271
+
272
+ /**
273
+ * Run a `helm` command with `KUBECONFIG` pointing at the temp file and
274
+ * `HELM_CONFIG_HOME`/`HELM_CACHE_HOME` isolated to the temp dir so
275
+ * `helm repo add`/`helm repo update` don't mutate the user's global
276
+ * Helm config. Returns stdout/stderr/exitCode. Scoped so the child
277
+ * process is cleaned up even on interruption.
278
+ */
279
+ const runHelm = Effect.fn("helm.run")(function* (args: {
280
+ kubeconfigPath: string;
281
+ dir: string;
282
+ helmArgs: string[];
283
+ }) {
284
+ const cmd = ChildProcess.make("helm", args.helmArgs, {
285
+ shell: false,
286
+ env: {
287
+ ...process.env,
288
+ KUBECONFIG: args.kubeconfigPath,
289
+ // Isolate helm config/cache to the temp dir so parallel
290
+ // reconciles or CI jobs don't race on the shared repository
291
+ // index (~/.config/helm/repositories.yaml).
292
+ HELM_CONFIG_HOME: args.dir,
293
+ HELM_CACHE_HOME: args.dir,
294
+ },
295
+ });
296
+ const handle = yield* cmd;
297
+ const [exitCode, stdout, stderr] = yield* Effect.all(
298
+ [
299
+ handle.exitCode,
300
+ Stream.mkString(Stream.decodeText(handle.stdout)),
301
+ Stream.mkString(Stream.decodeText(handle.stderr)),
302
+ ] as const,
303
+ { concurrency: 3 },
304
+ );
305
+ return { exitCode, stdout, stderr };
306
+ });
307
+
308
+ /** Run a helm command, mapping a non-zero exit to a HelmError. */
309
+ const runHelmOrFail = Effect.fn("helm.runOrFail")(function* (args: {
310
+ kubeconfigPath: string;
311
+ dir: string;
312
+ helmArgs: string[];
313
+ }) {
314
+ const result = yield* runHelm(args);
315
+ if (result.exitCode !== 0) {
316
+ return yield* Effect.fail(
317
+ new HelmError({
318
+ command: `helm ${args.helmArgs.join(" ")}`,
319
+ exitCode: result.exitCode,
320
+ stderr: result.stderr,
321
+ stdout: result.stdout,
322
+ }),
323
+ );
324
+ }
325
+ return result;
326
+ });
327
+
328
+ return {
329
+ // Identity (name/namespace/chart) and endpoint are unchanged by an
330
+ // in-place upgrade. revision increments on every upgrade — it is NOT
331
+ // stable (an update must persist the new revision).
332
+ stables: ["name", "namespace", "chart", "endpoint"],
333
+ diff: Effect.fn(function* ({ news, olds = {} }) {
334
+ if (!isResolved(news)) return undefined;
335
+ const o = olds as HelmReleaseProps;
336
+ // endpoint points at a specific cluster, and name/namespace/chart
337
+ // are the release's identity — any change orphans the old release.
338
+ if (
339
+ somePropsAreDifferent(o, news, [
340
+ "name",
341
+ "namespace",
342
+ "chart",
343
+ "endpoint",
344
+ ])
345
+ ) {
346
+ return { action: "replace" } as const;
347
+ }
348
+ return undefined;
349
+ }),
350
+ reconcile: Effect.fn(function* ({ id, news, session }) {
351
+ return yield* Effect.scoped(
352
+ Effect.gen(function* () {
353
+ const { dir, kubeconfigPath, valuesPath } = yield* prepare(news);
354
+
355
+ // Add helm repo if needed (non-OCI charts with repoUrl).
356
+ if (news.repoUrl && !news.chart.startsWith("oci://")) {
357
+ const repoName = news.chart.split("/")[0];
358
+ yield* session.note(`Adding helm repo: ${repoName}`);
359
+ yield* runHelmOrFail({
360
+ dir,
361
+ kubeconfigPath,
362
+ helmArgs: [
363
+ "repo",
364
+ "add",
365
+ repoName,
366
+ news.repoUrl,
367
+ "--force-update",
368
+ ],
369
+ });
370
+ yield* runHelmOrFail({
371
+ dir,
372
+ kubeconfigPath,
373
+ helmArgs: ["repo", "update"],
374
+ });
375
+ }
376
+
377
+ // Build alchemy ownership labels.
378
+ const labels = {
379
+ ...(news.labels ?? {}),
380
+ ...(yield* gcpInternalLabels(id)),
381
+ };
382
+ const labelsFlag = Object.entries(labels)
383
+ .map(([k, v]) => `${k}=${v}`)
384
+ .join(",");
385
+
386
+ // helm upgrade --install is idempotent for both first install
387
+ // and subsequent upgrades. --reset-values makes the declared
388
+ // `values` authoritative: if a key is removed from `values`,
389
+ // the next upgrade reverts it to the chart default rather than
390
+ // silently retaining the prior release's value.
391
+ const helmArgs = [
392
+ "upgrade",
393
+ "--install",
394
+ "--reset-values",
395
+ news.name,
396
+ news.chart,
397
+ "--namespace",
398
+ news.namespace,
399
+ ...(news.createNamespace ? ["--create-namespace"] : []),
400
+ ...(news.version ? ["--version", news.version] : []),
401
+ ...(valuesPath ? ["--values", valuesPath] : []),
402
+ ...(labelsFlag ? ["--labels", labelsFlag] : []),
403
+ ...(news.wait
404
+ ? ["--wait", "--atomic", "--timeout", news.timeout ?? "5m"]
405
+ : []),
406
+ ];
407
+
408
+ yield* session.note(
409
+ `Running helm upgrade --install ${news.name} ${news.chart}`,
410
+ );
411
+ yield* runHelmOrFail({ dir, kubeconfigPath, helmArgs });
412
+
413
+ // Read the release status to return fresh attributes.
414
+ const statusResult = yield* runHelmOrFail({
415
+ dir,
416
+ kubeconfigPath,
417
+ helmArgs: [
418
+ "status",
419
+ news.name,
420
+ "--namespace",
421
+ news.namespace,
422
+ "-o",
423
+ "json",
424
+ ],
425
+ });
426
+ const status = JSON.parse(
427
+ statusResult.stdout,
428
+ ) as HelmReleaseStatus;
429
+
430
+ return toAttributes(status, {
431
+ name: news.name,
432
+ namespace: news.namespace,
433
+ chart: news.chart,
434
+ endpoint: news.endpoint,
435
+ caCertificate: news.caCertificate,
436
+ });
437
+ }),
438
+ );
439
+ }),
440
+ delete: Effect.fn(function* ({ output }) {
441
+ yield* Effect.scoped(
442
+ Effect.gen(function* () {
443
+ const { dir, kubeconfigPath } = yield* prepare(output);
444
+
445
+ const result = yield* runHelm({
446
+ dir,
447
+ kubeconfigPath,
448
+ helmArgs: [
449
+ "uninstall",
450
+ output.name,
451
+ "--namespace",
452
+ output.namespace,
453
+ ],
454
+ });
455
+ // "release: not found" is success (idempotent teardown).
456
+ if (
457
+ result.exitCode !== 0 &&
458
+ !/not found/i.test(result.stderr)
459
+ ) {
460
+ return yield* Effect.fail(
461
+ new HelmError({
462
+ command: `helm uninstall ${output.name}`,
463
+ exitCode: result.exitCode,
464
+ stderr: result.stderr,
465
+ stdout: result.stdout,
466
+ }),
467
+ );
468
+ }
469
+ }),
470
+ );
471
+ }),
472
+ read: Effect.fn(function* ({ id, output, olds }) {
473
+ const endpoint = output?.endpoint ?? olds?.endpoint;
474
+ const caCertificate = output?.caCertificate ?? olds?.caCertificate;
475
+ const name = output?.name ?? olds?.name;
476
+ const namespace = output?.namespace ?? olds?.namespace;
477
+ const chart = output?.chart ?? olds?.chart;
478
+ if (!endpoint || !caCertificate || !name || !namespace || !chart) {
479
+ return undefined;
480
+ }
481
+
482
+ return yield* Effect.scoped(
483
+ Effect.gen(function* () {
484
+ const { dir, kubeconfigPath } = yield* prepare({
485
+ endpoint,
486
+ caCertificate,
487
+ });
488
+
489
+ const result = yield* runHelm({
490
+ dir,
491
+ kubeconfigPath,
492
+ helmArgs: [
493
+ "status",
494
+ name,
495
+ "--namespace",
496
+ namespace,
497
+ "-o",
498
+ "json",
499
+ ],
500
+ });
501
+
502
+ // Release not found → absent.
503
+ if (
504
+ result.exitCode !== 0 &&
505
+ /not found/i.test(result.stderr)
506
+ ) {
507
+ return undefined;
508
+ }
509
+ if (result.exitCode !== 0) {
510
+ return yield* Effect.fail(
511
+ new HelmError({
512
+ command: `helm status ${name}`,
513
+ exitCode: result.exitCode,
514
+ stderr: result.stderr,
515
+ stdout: result.stdout,
516
+ }),
517
+ );
518
+ }
519
+
520
+ const status = JSON.parse(result.stdout) as HelmReleaseStatus;
521
+ const attrs = toAttributes(status, {
522
+ name,
523
+ namespace,
524
+ chart,
525
+ endpoint,
526
+ caCertificate,
527
+ });
528
+ return (yield* hasAlchemyLabels(id, status.labels))
529
+ ? attrs
530
+ : Unowned(attrs);
531
+ }),
532
+ );
533
+ }),
534
+ };
535
+ }),
536
+ );
@@ -1,2 +1,3 @@
1
+ export * from "./Helm.ts";
1
2
  export * from "./KubernetesManifest.ts";
2
3
  export * from "./Secret.ts";
package/src/Providers.ts CHANGED
@@ -27,6 +27,14 @@ import {
27
27
  ServiceAccount,
28
28
  ServiceAccountProvider,
29
29
  } from "./Iam/ServiceAccount.ts";
30
+ import {
31
+ ServiceAccountKey,
32
+ ServiceAccountKeyProvider,
33
+ } from "./Iam/ServiceAccountKey.ts";
34
+ import {
35
+ HelmRelease,
36
+ HelmReleaseProvider,
37
+ } from "./Kubernetes/Helm.ts";
30
38
  import {
31
39
  KubernetesManifest,
32
40
  KubernetesManifestProvider,
@@ -45,6 +53,10 @@ import {
45
53
  PsaConnection,
46
54
  PsaConnectionProvider,
47
55
  } from "./ServiceNetworking/PsaConnection.ts";
56
+ import {
57
+ StorageBucket,
58
+ StorageBucketProvider,
59
+ } from "./Storage/Bucket.ts";
48
60
  import { ApiEnable, ApiEnableProvider } from "./ServiceUsage/ApiEnable.ts";
49
61
  import { SqlDatabase, SqlDatabaseProvider } from "./Sqladmin/Database.ts";
50
62
  import { SqlInstance, SqlInstanceProvider } from "./Sqladmin/Instance.ts";
@@ -87,6 +99,7 @@ export const providers = () =>
87
99
  SharedVpcServiceProject,
88
100
  PsaConnection,
89
101
  ManagedLustreInstance,
102
+ StorageBucket,
90
103
  Service,
91
104
  Job,
92
105
  SqlInstance,
@@ -94,8 +107,10 @@ export const providers = () =>
94
107
  SqlUser,
95
108
  ArtifactRegistryRepository,
96
109
  ServiceAccount,
110
+ ServiceAccountKey,
97
111
  KubernetesSecret,
98
112
  KubernetesManifest,
113
+ HelmRelease,
99
114
  ]),
100
115
  ).pipe(
101
116
  Layer.provide(
@@ -114,6 +129,7 @@ export const providers = () =>
114
129
  SharedVpcServiceProjectProvider(),
115
130
  PsaConnectionProvider(),
116
131
  ManagedLustreInstanceProvider(),
132
+ StorageBucketProvider(),
117
133
  ServiceProvider(),
118
134
  JobProvider(),
119
135
  SqlInstanceProvider(),
@@ -121,8 +137,10 @@ export const providers = () =>
121
137
  SqlUserProvider(),
122
138
  ArtifactRegistryRepositoryProvider(),
123
139
  ServiceAccountProvider(),
140
+ ServiceAccountKeyProvider(),
124
141
  KubernetesSecretProvider(),
125
142
  KubernetesManifestProvider(),
143
+ HelmReleaseProvider(),
126
144
  ),
127
145
  ),
128
146
  Layer.provideMerge(fromAuthProvider()),