@microagi/alchemy-gcp 0.11.5 → 0.11.7

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 (180) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/lib/ArtifactRegistry/Operations.d.ts +2 -1
  3. package/lib/ArtifactRegistry/Operations.d.ts.map +1 -1
  4. package/lib/ArtifactRegistry/Operations.js +9 -2
  5. package/lib/ArtifactRegistry/Operations.js.map +1 -1
  6. package/lib/ArtifactRegistry/Repository.d.ts.map +1 -1
  7. package/lib/ArtifactRegistry/Repository.js +6 -1
  8. package/lib/ArtifactRegistry/Repository.js.map +1 -1
  9. package/lib/ArtifactRegistry/RepositoryIamMember.d.ts.map +1 -1
  10. package/lib/ArtifactRegistry/RepositoryIamMember.js +6 -1
  11. package/lib/ArtifactRegistry/RepositoryIamMember.js.map +1 -1
  12. package/lib/CloudResourceManager/Project.d.ts.map +1 -1
  13. package/lib/CloudResourceManager/Project.js +10 -2
  14. package/lib/CloudResourceManager/Project.js.map +1 -1
  15. package/lib/Compute/Address.d.ts +1 -1
  16. package/lib/Compute/Address.d.ts.map +1 -1
  17. package/lib/Compute/Address.js +5 -2
  18. package/lib/Compute/Address.js.map +1 -1
  19. package/lib/Compute/Firewall.d.ts +1 -1
  20. package/lib/Compute/Firewall.d.ts.map +1 -1
  21. package/lib/Compute/Firewall.js +5 -2
  22. package/lib/Compute/Firewall.js.map +1 -1
  23. package/lib/Compute/ForwardingRule.d.ts +1 -1
  24. package/lib/Compute/ForwardingRule.d.ts.map +1 -1
  25. package/lib/Compute/ForwardingRule.js +5 -2
  26. package/lib/Compute/ForwardingRule.js.map +1 -1
  27. package/lib/Compute/GlobalAddress.d.ts +1 -1
  28. package/lib/Compute/GlobalAddress.d.ts.map +1 -1
  29. package/lib/Compute/GlobalAddress.js +5 -2
  30. package/lib/Compute/GlobalAddress.js.map +1 -1
  31. package/lib/Compute/Network.d.ts +1 -1
  32. package/lib/Compute/Network.d.ts.map +1 -1
  33. package/lib/Compute/Network.js +5 -2
  34. package/lib/Compute/Network.js.map +1 -1
  35. package/lib/Compute/Operations.d.ts +7 -2
  36. package/lib/Compute/Operations.d.ts.map +1 -1
  37. package/lib/Compute/Operations.js +10 -5
  38. package/lib/Compute/Operations.js.map +1 -1
  39. package/lib/Compute/ProjectDefaultNetworkTier.d.ts +1 -1
  40. package/lib/Compute/ProjectDefaultNetworkTier.d.ts.map +1 -1
  41. package/lib/Compute/ProjectDefaultNetworkTier.js +2 -0
  42. package/lib/Compute/ProjectDefaultNetworkTier.js.map +1 -1
  43. package/lib/Compute/SharedVpcHost.d.ts +1 -1
  44. package/lib/Compute/SharedVpcHost.d.ts.map +1 -1
  45. package/lib/Compute/SharedVpcHost.js +7 -2
  46. package/lib/Compute/SharedVpcHost.js.map +1 -1
  47. package/lib/Compute/SharedVpcServiceProject.d.ts +1 -1
  48. package/lib/Compute/SharedVpcServiceProject.d.ts.map +1 -1
  49. package/lib/Compute/SharedVpcServiceProject.js +5 -2
  50. package/lib/Compute/SharedVpcServiceProject.js.map +1 -1
  51. package/lib/Compute/Subnetwork.d.ts +1 -1
  52. package/lib/Compute/Subnetwork.d.ts.map +1 -1
  53. package/lib/Compute/Subnetwork.js +9 -3
  54. package/lib/Compute/Subnetwork.js.map +1 -1
  55. package/lib/Container/Cluster.d.ts.map +1 -1
  56. package/lib/Container/Cluster.js +6 -2
  57. package/lib/Container/Cluster.js.map +1 -1
  58. package/lib/Container/NodePool.d.ts.map +1 -1
  59. package/lib/Container/NodePool.js +2 -0
  60. package/lib/Container/NodePool.js.map +1 -1
  61. package/lib/Container/Operations.d.ts +2 -2
  62. package/lib/Container/Operations.d.ts.map +1 -1
  63. package/lib/Container/Operations.js +9 -3
  64. package/lib/Container/Operations.js.map +1 -1
  65. package/lib/Iam/Operations.d.ts +42 -0
  66. package/lib/Iam/Operations.d.ts.map +1 -0
  67. package/lib/Iam/Operations.js +48 -0
  68. package/lib/Iam/Operations.js.map +1 -0
  69. package/lib/Iam/ServiceAccount.d.ts.map +1 -1
  70. package/lib/Iam/ServiceAccount.js +6 -1
  71. package/lib/Iam/ServiceAccount.js.map +1 -1
  72. package/lib/Iam/ServiceAccountKey.d.ts.map +1 -1
  73. package/lib/Iam/ServiceAccountKey.js +2 -0
  74. package/lib/Iam/ServiceAccountKey.js.map +1 -1
  75. package/lib/Iam/WorkloadIdentityPool.d.ts +100 -0
  76. package/lib/Iam/WorkloadIdentityPool.d.ts.map +1 -0
  77. package/lib/Iam/WorkloadIdentityPool.js +175 -0
  78. package/lib/Iam/WorkloadIdentityPool.js.map +1 -0
  79. package/lib/Iam/WorkloadIdentityPoolProvider.d.ts +124 -0
  80. package/lib/Iam/WorkloadIdentityPoolProvider.d.ts.map +1 -0
  81. package/lib/Iam/WorkloadIdentityPoolProvider.js +222 -0
  82. package/lib/Iam/WorkloadIdentityPoolProvider.js.map +1 -0
  83. package/lib/Iam/index.d.ts +4 -0
  84. package/lib/Iam/index.d.ts.map +1 -1
  85. package/lib/Iam/index.js +2 -0
  86. package/lib/Iam/index.js.map +1 -1
  87. package/lib/Kubernetes/Helm.d.ts.map +1 -1
  88. package/lib/Kubernetes/Helm.js +2 -0
  89. package/lib/Kubernetes/Helm.js.map +1 -1
  90. package/lib/Kubernetes/KubernetesManifest.d.ts.map +1 -1
  91. package/lib/Kubernetes/KubernetesManifest.js +2 -0
  92. package/lib/Kubernetes/KubernetesManifest.js.map +1 -1
  93. package/lib/Kubernetes/Secret.d.ts.map +1 -1
  94. package/lib/Kubernetes/Secret.js +2 -0
  95. package/lib/Kubernetes/Secret.js.map +1 -1
  96. package/lib/ManagedLustre/Instance.d.ts.map +1 -1
  97. package/lib/ManagedLustre/Instance.js +11 -4
  98. package/lib/ManagedLustre/Instance.js.map +1 -1
  99. package/lib/Providers.d.ts +1 -1
  100. package/lib/Providers.d.ts.map +1 -1
  101. package/lib/Providers.js +5 -1
  102. package/lib/Providers.js.map +1 -1
  103. package/lib/Run/IamSync.d.ts.map +1 -1
  104. package/lib/Run/IamSync.js +4 -1
  105. package/lib/Run/IamSync.js.map +1 -1
  106. package/lib/Run/Job.d.ts.map +1 -1
  107. package/lib/Run/Job.js +6 -1
  108. package/lib/Run/Job.js.map +1 -1
  109. package/lib/Run/Operations.d.ts +2 -2
  110. package/lib/Run/Operations.d.ts.map +1 -1
  111. package/lib/Run/Operations.js +9 -3
  112. package/lib/Run/Operations.js.map +1 -1
  113. package/lib/Run/Service.d.ts.map +1 -1
  114. package/lib/Run/Service.js +6 -1
  115. package/lib/Run/Service.js.map +1 -1
  116. package/lib/ServiceNetworking/PsaConnection.d.ts.map +1 -1
  117. package/lib/ServiceNetworking/PsaConnection.js +11 -4
  118. package/lib/ServiceNetworking/PsaConnection.js.map +1 -1
  119. package/lib/ServiceUsage/ApiEnable.d.ts.map +1 -1
  120. package/lib/ServiceUsage/ApiEnable.js +9 -1
  121. package/lib/ServiceUsage/ApiEnable.js.map +1 -1
  122. package/lib/Sqladmin/Database.d.ts.map +1 -1
  123. package/lib/Sqladmin/Database.js +2 -0
  124. package/lib/Sqladmin/Database.js.map +1 -1
  125. package/lib/Sqladmin/Instance.d.ts.map +1 -1
  126. package/lib/Sqladmin/Instance.js +6 -1
  127. package/lib/Sqladmin/Instance.js.map +1 -1
  128. package/lib/Sqladmin/Operations.d.ts.map +1 -1
  129. package/lib/Sqladmin/Operations.js +7 -1
  130. package/lib/Sqladmin/Operations.js.map +1 -1
  131. package/lib/Sqladmin/User.d.ts.map +1 -1
  132. package/lib/Sqladmin/User.js +2 -0
  133. package/lib/Sqladmin/User.js.map +1 -1
  134. package/lib/Storage/Bucket.d.ts.map +1 -1
  135. package/lib/Storage/Bucket.js +6 -1
  136. package/lib/Storage/Bucket.js.map +1 -1
  137. package/lib/Storage/StorageBucketIamMember.d.ts.map +1 -1
  138. package/lib/Storage/StorageBucketIamMember.js +6 -1
  139. package/lib/Storage/StorageBucketIamMember.js.map +1 -1
  140. package/package.json +7 -7
  141. package/src/ArtifactRegistry/Operations.ts +10 -5
  142. package/src/ArtifactRegistry/Repository.ts +7 -3
  143. package/src/ArtifactRegistry/RepositoryIamMember.ts +6 -3
  144. package/src/CloudResourceManager/Project.ts +11 -6
  145. package/src/Compute/Address.ts +5 -1
  146. package/src/Compute/Firewall.ts +5 -1
  147. package/src/Compute/ForwardingRule.ts +5 -1
  148. package/src/Compute/GlobalAddress.ts +5 -1
  149. package/src/Compute/Network.ts +5 -1
  150. package/src/Compute/Operations.ts +8 -4
  151. package/src/Compute/ProjectDefaultNetworkTier.ts +2 -0
  152. package/src/Compute/SharedVpcHost.ts +7 -1
  153. package/src/Compute/SharedVpcServiceProject.ts +5 -1
  154. package/src/Compute/Subnetwork.ts +9 -4
  155. package/src/Container/Cluster.ts +7 -3
  156. package/src/Container/NodePool.ts +2 -0
  157. package/src/Container/Operations.ts +10 -6
  158. package/src/Iam/Operations.ts +81 -0
  159. package/src/Iam/ServiceAccount.ts +6 -3
  160. package/src/Iam/ServiceAccountKey.ts +2 -0
  161. package/src/Iam/WorkloadIdentityPool.ts +309 -0
  162. package/src/Iam/WorkloadIdentityPoolProvider.ts +425 -0
  163. package/src/Iam/index.ts +10 -0
  164. package/src/Kubernetes/Helm.ts +2 -0
  165. package/src/Kubernetes/KubernetesManifest.ts +2 -0
  166. package/src/Kubernetes/Secret.ts +2 -0
  167. package/src/ManagedLustre/Instance.ts +10 -4
  168. package/src/Providers.ts +12 -0
  169. package/src/Run/IamSync.ts +4 -3
  170. package/src/Run/Job.ts +7 -3
  171. package/src/Run/Operations.ts +10 -6
  172. package/src/Run/Service.ts +7 -3
  173. package/src/ServiceNetworking/PsaConnection.ts +10 -4
  174. package/src/ServiceUsage/ApiEnable.ts +10 -4
  175. package/src/Sqladmin/Database.ts +2 -0
  176. package/src/Sqladmin/Instance.ts +7 -3
  177. package/src/Sqladmin/Operations.ts +8 -4
  178. package/src/Sqladmin/User.ts +2 -0
  179. package/src/Storage/Bucket.ts +6 -3
  180. package/src/Storage/StorageBucketIamMember.ts +6 -3
@@ -76,9 +76,10 @@ export const ArtifactRegistryRepositoryIamMemberProvider = () =>
76
76
  effect.pipe(
77
77
  Effect.retry({
78
78
  while: (error) => (error as { _tag?: string })?._tag === "Conflict",
79
- schedule: Schedule.exponential(Duration.millis(250)).pipe(
80
- Schedule.both(Schedule.recurs(8)),
81
- ),
79
+ schedule: Schedule.max([
80
+ Schedule.exponential(Duration.millis(250)),
81
+ Schedule.recurs(8),
82
+ ]),
82
83
  }),
83
84
  );
84
85
 
@@ -89,6 +90,8 @@ export const ArtifactRegistryRepositoryIamMemberProvider = () =>
89
90
  });
90
91
 
91
92
  return {
93
+ nuke: { skip: true },
94
+ list: () => Effect.succeed([]),
92
95
  stables: ["repository", "role", "member"],
93
96
  diff: Effect.fn(function* ({ news, olds = {}, output }) {
94
97
  if (!isResolved(news)) return undefined;
@@ -242,9 +242,11 @@ export const ProjectProvider = () =>
242
242
  ),
243
243
  Effect.retry({
244
244
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
245
- schedule: Schedule.exponential(Duration.seconds(1), 1.5).pipe(
246
- Schedule.both(Schedule.recurs(60)),
247
- Schedule.tapOutput(() =>
245
+ schedule: Schedule.max([
246
+ Schedule.exponential(Duration.seconds(1), 1.5),
247
+ Schedule.recurs(60),
248
+ ]).pipe(
249
+ Schedule.tap(() =>
248
250
  session.note(`Waiting for GCP operation ${operationName}…`),
249
251
  ),
250
252
  ),
@@ -467,13 +469,16 @@ export const ProjectProvider = () =>
467
469
  });
468
470
  }).pipe(
469
471
  Effect.retry({
470
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
471
- Schedule.both(Schedule.recurs(8)),
472
- ),
472
+ schedule: Schedule.max([
473
+ Schedule.exponential(Duration.seconds(2)),
474
+ Schedule.recurs(8),
475
+ ]),
473
476
  }),
474
477
  );
475
478
 
476
479
  return {
480
+ nuke: { skip: true },
481
+ list: () => Effect.succeed([]),
477
482
  stables: ["projectId", "projectNumber", "name"],
478
483
  diff: Effect.fn(function* ({ id, news, olds = {}, output }) {
479
484
  if (!isResolved(news)) return undefined;
@@ -172,6 +172,8 @@ export const AddressProvider = () =>
172
172
  });
173
173
 
174
174
  return {
175
+ nuke: { skip: true },
176
+ list: () => Effect.succeed([]),
175
177
  stables: ["name", "project", "region", "selfLink", "id", "address"],
176
178
  diff: Effect.fn(function* ({ news, olds = {} }) {
177
179
  if (!isResolved(news)) return undefined;
@@ -250,7 +252,7 @@ export const AddressProvider = () =>
250
252
  });
251
253
  }),
252
254
  delete: Effect.fn(function* ({ output, session }) {
253
- yield* deleteAddresses({
255
+ const deletion = deleteAddresses({
254
256
  project: output.project,
255
257
  region: output.region,
256
258
  address: output.name,
@@ -260,6 +262,8 @@ export const AddressProvider = () =>
260
262
  ? awaitOp(output.project, output.region, op.name, session)
261
263
  : Effect.succeed(op),
262
264
  ),
265
+ );
266
+ yield* deletion.pipe(
263
267
  Effect.catchTag("NotFound", () => Effect.void),
264
268
  // 400 "is being used by ..." comes back while a forwarding
265
269
  // rule still references the address. The rule must be
@@ -199,6 +199,8 @@ export const FirewallProvider = () =>
199
199
  );
200
200
 
201
201
  return {
202
+ nuke: { skip: true },
203
+ list: () => Effect.succeed([]),
202
204
  stables: ["name", "project", "selfLink", "id", "network"],
203
205
  diff: Effect.fn(function* ({ news, olds = {} }) {
204
206
  if (!isResolved(news)) return undefined;
@@ -297,7 +299,7 @@ export const FirewallProvider = () =>
297
299
  return toFirewallAttributes(final, { project: news.project });
298
300
  }),
299
301
  delete: Effect.fn(function* ({ output, session }) {
300
- yield* deleteFirewalls({
302
+ const deletion = deleteFirewalls({
301
303
  project: output.project,
302
304
  firewall: output.name,
303
305
  }).pipe(
@@ -306,6 +308,8 @@ export const FirewallProvider = () =>
306
308
  ? awaitOp(output.project, op.name, session)
307
309
  : Effect.succeed(op),
308
310
  ),
311
+ );
312
+ yield* deletion.pipe(
309
313
  Effect.catchTag("NotFound", () => Effect.void),
310
314
  );
311
315
  }),
@@ -182,6 +182,8 @@ export const ForwardingRuleProvider = () =>
182
182
  });
183
183
 
184
184
  return {
185
+ nuke: { skip: true },
186
+ list: () => Effect.succeed([]),
185
187
  stables: ["name", "project", "region", "selfLink", "id"],
186
188
  diff: Effect.fn(function* ({ news, olds = {} }) {
187
189
  if (!isResolved(news)) return undefined;
@@ -265,7 +267,7 @@ export const ForwardingRuleProvider = () =>
265
267
  });
266
268
  }),
267
269
  delete: Effect.fn(function* ({ output, session }) {
268
- yield* deleteForwardingRules({
270
+ const deletion = deleteForwardingRules({
269
271
  project: output.project,
270
272
  region: output.region,
271
273
  forwardingRule: output.name,
@@ -275,6 +277,8 @@ export const ForwardingRuleProvider = () =>
275
277
  ? awaitOp(output.project, output.region, op.name, session)
276
278
  : Effect.succeed(op),
277
279
  ),
280
+ );
281
+ yield* deletion.pipe(
278
282
  Effect.catchTag("NotFound", () => Effect.void),
279
283
  );
280
284
  }),
@@ -176,6 +176,8 @@ export const GlobalAddressProvider = () =>
176
176
  });
177
177
 
178
178
  return {
179
+ nuke: { skip: true },
180
+ list: () => Effect.succeed([]),
179
181
  stables: ["name", "project", "selfLink", "id", "address"],
180
182
  diff: Effect.fn(function* ({ news, olds = {} }) {
181
183
  if (!isResolved(news)) return undefined;
@@ -248,7 +250,7 @@ export const GlobalAddressProvider = () =>
248
250
  return toGlobalAddressAttributes(final, { project: news.project });
249
251
  }),
250
252
  delete: Effect.fn(function* ({ output, session }) {
251
- yield* deleteGlobalAddresses({
253
+ const deletion = deleteGlobalAddresses({
252
254
  project: output.project,
253
255
  address: output.name,
254
256
  }).pipe(
@@ -257,6 +259,8 @@ export const GlobalAddressProvider = () =>
257
259
  ? awaitOp(output.project, op.name, session)
258
260
  : Effect.succeed(op),
259
261
  ),
262
+ );
263
+ yield* deletion.pipe(
260
264
  Effect.catchTag("NotFound", () => Effect.void),
261
265
  // 400 with "is being used by ..." comes back when a PSA
262
266
  // connection still references the range. The user has to
@@ -252,6 +252,8 @@ export const NetworkProvider = () =>
252
252
  });
253
253
 
254
254
  return {
255
+ nuke: { skip: true },
256
+ list: () => Effect.succeed([]),
255
257
  stables: ["name", "project", "selfLink", "id"],
256
258
  diff: Effect.fn(function* ({ news, olds = {} }) {
257
259
  if (!isResolved(news)) return undefined;
@@ -326,7 +328,7 @@ export const NetworkProvider = () =>
326
328
  return toNetworkAttributes(final, { project: news.project });
327
329
  }),
328
330
  delete: Effect.fn(function* ({ output, session }) {
329
- yield* deleteNetworks({
331
+ const deletion = deleteNetworks({
330
332
  project: output.project,
331
333
  network: output.name,
332
334
  }).pipe(
@@ -335,6 +337,8 @@ export const NetworkProvider = () =>
335
337
  ? awaitOp(output.project, op.name, session)
336
338
  : Effect.succeed(op),
337
339
  ),
340
+ );
341
+ yield* deletion.pipe(
338
342
  Effect.catchTag("NotFound", () => Effect.void),
339
343
  );
340
344
  }),
@@ -30,13 +30,17 @@ const formatOperationError = (op: compute.Operation): string => {
30
30
  const isDone = (op: compute.Operation): boolean => op.status === "DONE";
31
31
 
32
32
  const pollSchedule = (label: string, session: ScopedPlanStatusSession) =>
33
- Schedule.exponential(Duration.seconds(2), 1.5).pipe(
34
- Schedule.either(Schedule.spaced(Duration.seconds(15))),
33
+ Schedule.max([
34
+ Schedule.min([
35
+ Schedule.exponential(Duration.seconds(2), 1.5),
36
+ Schedule.spaced(Duration.seconds(15)),
37
+ ]),
35
38
  // 80 × ≤15s ≈ 20 min — long enough for a network create (~1 min),
36
39
  // a parallelstore-adjacent global op, or a 10-minute parallelstore
37
40
  // delete (longest LRO observed in this provider's surface).
38
- Schedule.both(Schedule.recurs(80)),
39
- Schedule.tapOutput(() =>
41
+ Schedule.recurs(80),
42
+ ]).pipe(
43
+ Schedule.tap(() =>
40
44
  session.note(`Waiting for Compute operation ${label}…`),
41
45
  ),
42
46
  );
@@ -55,6 +55,8 @@ export const ProjectDefaultNetworkTierProvider = () =>
55
55
  );
56
56
 
57
57
  return {
58
+ nuke: { singleton: true },
59
+ list: () => Effect.succeed([]),
58
60
  stables: ["project"],
59
61
  diff: Effect.fn(function* ({ news, olds = {} }) {
60
62
  if (!isResolved(news)) return undefined;
@@ -75,6 +75,8 @@ export const SharedVpcHostProvider = () =>
75
75
  );
76
76
 
77
77
  return {
78
+ nuke: { singleton: true },
79
+ list: () => Effect.succeed([]),
78
80
  stables: ["project"],
79
81
  diff: Effect.fn(function* ({ news, olds = {} }) {
80
82
  if (!isResolved(news)) return undefined;
@@ -104,12 +106,16 @@ export const SharedVpcHostProvider = () =>
104
106
  return { project: news.project, xpnProjectStatus: "HOST" };
105
107
  }),
106
108
  delete: Effect.fn(function* ({ output, session }) {
107
- yield* disableXpnHostProjects({ project: output.project }).pipe(
109
+ const disabling = disableXpnHostProjects({
110
+ project: output.project,
111
+ }).pipe(
108
112
  Effect.flatMap((op) =>
109
113
  op.name
110
114
  ? awaitOp(output.project, op.name, session)
111
115
  : Effect.succeed(op),
112
116
  ),
117
+ );
118
+ yield* disabling.pipe(
113
119
  Effect.catchTag("NotFound", () => Effect.void),
114
120
  // `BadRequest` is returned when:
115
121
  // 1. The project isn't a host (idempotent disable — fine).
@@ -125,6 +125,8 @@ export const SharedVpcServiceProjectProvider = () =>
125
125
  });
126
126
 
127
127
  return {
128
+ nuke: { skip: true },
129
+ list: () => Effect.succeed([]),
128
130
  stables: ["hostProject", "serviceProject"],
129
131
  diff: Effect.fn(function* ({ news, olds = {} }) {
130
132
  if (!isResolved(news)) return undefined;
@@ -153,7 +155,7 @@ export const SharedVpcServiceProjectProvider = () =>
153
155
  };
154
156
  }),
155
157
  delete: Effect.fn(function* ({ output, session }) {
156
- yield* disableXpnResource({
158
+ const disabling = disableXpnResource({
157
159
  project: output.hostProject,
158
160
  body: {
159
161
  xpnResource: { type: "PROJECT", id: output.serviceProject },
@@ -164,6 +166,8 @@ export const SharedVpcServiceProjectProvider = () =>
164
166
  ? awaitOp(output.hostProject, op.name, session)
165
167
  : Effect.succeed(op),
166
168
  ),
169
+ );
170
+ yield* disabling.pipe(
167
171
  Effect.catchTag("NotFound", () => Effect.void),
168
172
  // `BadRequest` covers "not attached to this host" (idempotent
169
173
  // detach); other 400s (resources still using shared subnets)
@@ -254,9 +254,10 @@ export const SubnetworkProvider = () =>
254
254
  // multiple bindings in parallel, races are common. Cap at
255
255
  // ~1.5 min total to surface real failures.
256
256
  Effect.retry({
257
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
258
- Schedule.both(Schedule.recurs(8)),
259
- ),
257
+ schedule: Schedule.max([
258
+ Schedule.exponential(Duration.seconds(2)),
259
+ Schedule.recurs(8),
260
+ ]),
260
261
  }),
261
262
  );
262
263
 
@@ -344,6 +345,8 @@ export const SubnetworkProvider = () =>
344
345
  });
345
346
 
346
347
  return {
348
+ nuke: { skip: true },
349
+ list: () => Effect.succeed([]),
347
350
  stables: ["name", "project", "region", "selfLink", "id"],
348
351
  diff: Effect.fn(function* ({ news, olds = {} }) {
349
352
  if (!isResolved(news)) return undefined;
@@ -445,7 +448,7 @@ export const SubnetworkProvider = () =>
445
448
  });
446
449
  }),
447
450
  delete: Effect.fn(function* ({ output, session }) {
448
- yield* deleteSubnetworks({
451
+ const deletion = deleteSubnetworks({
449
452
  project: output.project,
450
453
  region: output.region,
451
454
  subnetwork: output.name,
@@ -455,6 +458,8 @@ export const SubnetworkProvider = () =>
455
458
  ? awaitOp(output.project, output.region, op.name, session)
456
459
  : Effect.succeed(op),
457
460
  ),
461
+ );
462
+ yield* deletion.pipe(
458
463
  Effect.catchTag("NotFound", () => Effect.void),
459
464
  );
460
465
  }),
@@ -505,6 +505,8 @@ export const ClusterProvider = () =>
505
505
  });
506
506
 
507
507
  return {
508
+ nuke: { skip: true },
509
+ list: () => Effect.succeed([]),
508
510
  stables: [
509
511
  "name",
510
512
  "selfLink",
@@ -595,9 +597,11 @@ export const ClusterProvider = () =>
595
597
  while: (e: { _tag?: string; message?: string }) =>
596
598
  e?._tag === "Forbidden" &&
597
599
  /enabled this API recently|has not been used/i.test(e.message ?? ""),
598
- schedule: Schedule.spaced(Duration.seconds(15)).pipe(
599
- Schedule.both(Schedule.recurs(20)), // 20 × 15s ≈ 5 min
600
- Schedule.tapOutput(() =>
600
+ schedule: Schedule.max([
601
+ Schedule.spaced(Duration.seconds(15)),
602
+ Schedule.recurs(20), // 20 × 15s ≈ 5 min
603
+ ]).pipe(
604
+ Schedule.tap(() =>
601
605
  session.note(
602
606
  "Waiting for Container API enablement to propagate…",
603
607
  ),
@@ -546,6 +546,8 @@ export const NodePoolProvider = () =>
546
546
  });
547
547
 
548
548
  return {
549
+ nuke: { skip: true },
550
+ list: () => Effect.succeed([]),
549
551
  stables: ["name", "selfLink", "project", "location", "clusterName"],
550
552
  diff: Effect.fn(function* ({ news, olds = {} }) {
551
553
  if (!isResolved(news)) return undefined;
@@ -46,8 +46,8 @@ type GetOperations = Effect.Success<typeof cont.getProjectsLocationsOperations>;
46
46
  * The doneness predicate compares `.status === "DONE"` accordingly.
47
47
  *
48
48
  * Schedule: exponential 2s → 1.5× growth, capped per-poll at 15s via
49
- * `Schedule.either`, then a hard count cap of 80 retries via
50
- * `Schedule.both(Schedule.recurs(80))` → ~20 min wall ceiling, well
49
+ * `Schedule.min`, then a hard count cap of 80 retries via
50
+ * `Schedule.max` → ~20 min wall ceiling, well
51
51
  * above the worst observed GKE op time (cluster create runs 5–10 min).
52
52
  *
53
53
  * The helper is a factory — the caller resolves
@@ -68,10 +68,14 @@ export const makeAwaitOperation = (getOperations: GetOperations) =>
68
68
  ),
69
69
  Effect.retry({
70
70
  while: (e: { _tag?: string }) => e?._tag === "OperationPending",
71
- schedule: Schedule.exponential(Duration.seconds(2), 1.5).pipe(
72
- Schedule.either(Schedule.spaced(Duration.seconds(15))),
73
- Schedule.both(Schedule.recurs(80)),
74
- Schedule.tapOutput(() =>
71
+ schedule: Schedule.max([
72
+ Schedule.min([
73
+ Schedule.exponential(Duration.seconds(2), 1.5),
74
+ Schedule.spaced(Duration.seconds(15)),
75
+ ]),
76
+ Schedule.recurs(80),
77
+ ]).pipe(
78
+ Schedule.tap(() =>
75
79
  session.note(`Waiting for GKE operation ${operationName}…`),
76
80
  ),
77
81
  ),
@@ -0,0 +1,81 @@
1
+ import { ConfigError } from "@distilled.cloud/gcp";
2
+ import type * as iam from "@distilled.cloud/gcp/unstable/iam-v1";
3
+ import type { ScopedPlanStatusSession } from "alchemy/Cli/Cli";
4
+ import * as Duration from "effect/Duration";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Schedule from "effect/Schedule";
7
+
8
+ /**
9
+ * Resolved callable signature of an `iam-v1` operations getter.
10
+ *
11
+ * Workload Identity pools and providers each have their own operations
12
+ * collection (`…WorkloadIdentityPoolsOperations`,
13
+ * `…WorkloadIdentityPoolsProvidersOperations`) rather than one shared
14
+ * endpoint, so this is generic over the getter and each resource passes its
15
+ * own — the same factory shape `ArtifactRegistry/Operations.ts` uses.
16
+ */
17
+ type GetOperations = Effect.Success<
18
+ typeof iam.getProjectsLocationsWorkloadIdentityPoolsOperations
19
+ >;
20
+
21
+ /**
22
+ * Build the IAM long-running-operation polling helper.
23
+ *
24
+ * `iam-v1` operations use the standard `google.longrunning.Operation` shape —
25
+ * doneness is `.done === true` and failure is a populated `.error` (NOT
26
+ * Container's `.status === "DONE"`, and NOT Sqladmin's tri-state `.status`).
27
+ * Verified against the `Operation` interface in
28
+ * `vendor/distilled/packages/gcp/src/unstable-services/iam-v1.ts`.
29
+ *
30
+ * Operation names returned by pool/provider create and delete are already
31
+ * fully qualified (`projects/{p}/locations/global/workloadIdentityPools/{id}/operations/{op}`),
32
+ * so callers pass them straight through — no qualify dance.
33
+ *
34
+ * Schedule: exponential 1s → 1.5× growth, capped per-poll at 10s via
35
+ * `Schedule.min`, then a hard count cap of 60 retries via `Schedule.max`
36
+ * → ~10 min wall ceiling. Pool and provider CRUD normally settles in
37
+ * seconds; the ceiling exists so a stuck operation surfaces as an error
38
+ * rather than hanging a deploy indefinitely.
39
+ *
40
+ * The helper is a factory — the caller resolves the operations getter once at
41
+ * provider construction and passes the resulting callable here, so we never
42
+ * re-resolve services on each await.
43
+ */
44
+ export const makeAwaitOperation = (
45
+ getOperations: GetOperations,
46
+ label: string,
47
+ ) =>
48
+ Effect.fn(function* (
49
+ operationName: string,
50
+ session: ScopedPlanStatusSession,
51
+ ) {
52
+ const op = yield* getOperations({ name: operationName }).pipe(
53
+ Effect.flatMap((current) =>
54
+ current.done === true
55
+ ? Effect.succeed(current)
56
+ : Effect.fail({ _tag: "OperationPending" as const }),
57
+ ),
58
+ Effect.retry({
59
+ while: (e: { _tag?: string }) => e?._tag === "OperationPending",
60
+ schedule: Schedule.max([
61
+ Schedule.min([
62
+ Schedule.exponential(Duration.seconds(1), 1.5),
63
+ Schedule.spaced(Duration.seconds(10)),
64
+ ]),
65
+ Schedule.recurs(60),
66
+ ]).pipe(
67
+ Schedule.tap(() =>
68
+ session.note(`Waiting for ${label} operation ${operationName}…`),
69
+ ),
70
+ ),
71
+ }),
72
+ );
73
+ if (op.error) {
74
+ return yield* new ConfigError({
75
+ message: `${label} operation ${operationName} failed: ${
76
+ op.error.message ?? JSON.stringify(op.error)
77
+ }`,
78
+ });
79
+ }
80
+ return op;
81
+ });
@@ -330,9 +330,10 @@ export const ServiceAccountProvider = () =>
330
330
  });
331
331
  }).pipe(
332
332
  Effect.retry({
333
- schedule: Schedule.exponential(Duration.seconds(2)).pipe(
334
- Schedule.both(Schedule.recurs(8)),
335
- ),
333
+ schedule: Schedule.max([
334
+ Schedule.exponential(Duration.seconds(2)),
335
+ Schedule.recurs(8),
336
+ ]),
336
337
  }),
337
338
  );
338
339
 
@@ -340,6 +341,8 @@ export const ServiceAccountProvider = () =>
340
341
  // Stable identity: accountId + project; both immutable; either
341
342
  // change → replace. The server-assigned uniqueId is also stable
342
343
  // for the lifetime of the GSA (re-create gets a different one).
344
+ nuke: { skip: true },
345
+ list: () => Effect.succeed([]),
343
346
  stables: ["accountId", "projectId", "uniqueId", "name", "email"],
344
347
  diff: Effect.fn(function* ({ id, news, olds = {}, output }) {
345
348
  if (!isResolved(news)) return undefined;
@@ -153,6 +153,8 @@ export const ServiceAccountKeyProvider = () =>
153
153
  // privateKeyData is stable: it's set on create and never
154
154
  // changes. The engine preserves it across reconciles and
155
155
  // does not use it for drift detection.
156
+ nuke: { skip: true },
157
+ list: () => Effect.succeed([]),
156
158
  stables: ["name", "privateKeyData", "keyAlgorithm", "keyType"],
157
159
  diff: Effect.fn(function* ({ news, olds = {} }) {
158
160
  if (!isResolved(news)) return undefined;