1import * as netapp from "@distilled.cloud/gcp/netapp_v1";
2import * as Effect from "effect/Effect";
3import * as Schedule from "effect/Schedule";
4import { Unowned } from "../../AdoptPolicy.ts";
5import { isResolved } from "../../Diff.ts";
6import * as Provider from "../../Provider.ts";
7import { Resource } from "../../Resource.ts";
8import { tagRecord } from "../../Tags.ts";
9import { GcpEnvironment } from "../Environment.ts";
10import {
11 createInternalLabels,
12 diffLabels,
13 hasAlchemyLabels,
14 toLabels,
15} from "../Labels.ts";
16import type { Providers } from "../Providers.ts";
17import {
18 expandParent,
19 fieldMask,
20 fingerprint,
21 gibOf,
22 lastSegment,
23 listAtLocation,
24 listLabeledPages,
25 normalizeLocation,
26 parentOf,
27 parseName,
28 replaceOnIdentity,
29 ResourceNotResolved,
30 sameStringList,
31 toPhysicalId,
32 userLabels,
33 waitForDeleteOperation,
34 waitForOperation,
35 waitUntilExists,
36 waitUntilGone,
37 waitUntilReady,
38} from "./internal.ts";
39
40const DEFAULT_PROTOCOL: netapp.VolumeProtocolsItemEnum = "NFSV3";
41const DEFAULT_CAPACITY_GIB = 100;
42
43export type ExportPolicyRule = {
44 /** Access type (`READ_ONLY`, `READ_WRITE`, `READ_NONE`). */
45 accessType?: netapp.SimpleExportPolicyRuleAccessTypeEnum | (string & {});
46 /** Squash mode. Takes precedence over `hasRootAccess`. */
47 squashMode?: netapp.SimpleExportPolicyRuleSquashModeEnum | (string & {});
48 /** Comma-separated allowed client IPs or CIDRs. */
49 allowedClients?: string;
50 /** Enable NFSv3. */
51 nfsv3?: boolean;
52 /** Enable NFSv4. */
53 nfsv4?: boolean;
54 /** Unix root access (`true` / `false`). */
55 hasRootAccess?: string;
56 /** Anonymous user id when squash is ROOT_SQUASH or ALL_SQUASH. */
57 anonUid?: string;
58 kerberos5ReadOnly?: boolean;
59 kerberos5ReadWrite?: boolean;
60 kerberos5iReadOnly?: boolean;
61 kerberos5iReadWrite?: boolean;
62 kerberos5pReadOnly?: boolean;
63 kerberos5pReadWrite?: boolean;
64};
65
66export type ExportPolicy = {
67 /** Export policy rules. */
68 rules?: ExportPolicyRule[];
69};
70
71export type SnapshotSchedule = {
72 snapshotsToKeep?: number;
73 minute?: number;
74 hour?: number;
75 day?: string;
76 daysOfMonth?: string;
77};
78
79export type SnapshotPolicy = {
80 enabled?: boolean;
81 hourlySchedule?: SnapshotSchedule;
82 dailySchedule?: SnapshotSchedule;
83 weeklySchedule?: SnapshotSchedule;
84 monthlySchedule?: SnapshotSchedule;
85};
86
87export type BackupConfig = {
88 /** Backup vault name or id. */
89 backupVault?: string;
90 /** Backup policy names. */
91 backupPolicies?: string[];
92 /** Enable scheduled backups. */
93 scheduledBackupEnabled?: boolean;
94};
95
96export type TieringPolicy = {
97 tierAction?: netapp.TieringPolicyTierActionEnum | (string & {});
98 coolingThresholdDays?: number;
99 hotTierBypassModeEnabled?: boolean;
100};
101
102export type RestoreParameters = {
103 sourceSnapshot?: string;
104 sourceBackup?: string;
105};
106
107export type BlockDevice = {
108 osType?: netapp.BlockDeviceOsTypeEnum | (string & {});
109 name?: string;
110 sizeGib?: number | string;
111 hostGroups?: string[];
112};
113
114export type VolumeProps = {
115 /**
116 * Volume id (the `{volume}` segment of
117 * `projects/{project}/locations/{location}/volumes/{volume}`).
118 * If omitted, a unique RFC1035 name is generated from the stack, stage,
119 * and logical id. Immutable — changing it replaces the volume.
120 */
121 volumeId?: string;
122 /**
123 * Region (`us-central1`, `us-east1`, …). Immutable — changing it
124 * replaces the volume. `US-CENTRAL1` is accepted and normalized to
125 * `us-central1`.
126 * @default the stack's GCP region (`GCP.Region`, profile region, `us-central1`)
127 */
128 location?: string;
129 /**
130 * Storage pool name or id. Immutable — changing it replaces the volume.
131 */
132 storagePool: string;
133 /**
134 * NFS/SMB share name. Defaults to the volume id. Immutable — changing
135 * it replaces the volume.
136 */
137 shareName?: string;
138 /**
139 * Protocols. Immutable — changing them replaces the volume.
140 * @default ["NFSV3"]
141 */
142 protocols?: Array<netapp.VolumeProtocolsItemEnum | (string & {})>;
143 /**
144 * Capacity in GiB.
145 * @default 100
146 */
147 capacityGib?: number | string;
148 /**
149 * Unix permissions on the mount point (NFS).
150 */
151 unixPermissions?: string;
152 /**
153 * Security style. Immutable — changing it replaces the volume.
154 */
155 securityStyle?: netapp.VolumeSecurityStyleEnum | (string & {});
156 /**
157 * Percentage of capacity reserved for snapshots.
158 */
159 snapReserve?: number;
160 /**
161 * Expose a read-only `.snapshot` directory.
162 */
163 snapshotDirectory?: boolean;
164 /**
165 * Throughput in MiB/s.
166 */
167 throughputMibps?: number;
168 /**
169 * Enable Kerberos. Immutable — changing it replaces the volume.
170 */
171 kerberosEnabled?: boolean;
172 /**
173 * SMB share settings.
174 */
175 smbSettings?: Array<netapp.VolumeSmbSettingsItemEnum | (string & {})>;
176 /**
177 * Restricted actions (`DELETE`, …).
178 */
179 restrictedActions?: Array<
180 netapp.VolumeRestrictedActionsItemEnum | (string & {})
181 >;
182 /**
183 * NFS export policy.
184 */
185 exportPolicy?: ExportPolicy;
186 /**
187 * Snapshot schedule.
188 */
189 snapshotPolicy?: SnapshotPolicy;
190 /**
191 * Backup configuration.
192 */
193 backupConfig?: BackupConfig;
194 /**
195 * Auto-tiering policy.
196 */
197 tieringPolicy?: TieringPolicy;
198 /**
199 * Restore from a snapshot or backup. Immutable.
200 */
201 restoreParameters?: RestoreParameters;
202 /**
203 * Block devices (unified pools). Currently one device per volume.
204 */
205 blockDevices?: BlockDevice[];
206 /**
207 * Large-capacity volume (legacy FILE pools). Immutable.
208 */
209 largeCapacity?: boolean;
210 /**
211 * Large-capacity constituent config (unified pools). Immutable.
212 */
213 largeCapacityConfig?: { constituentCount?: number };
214 /**
215 * One IP per node for large-capacity volumes. Immutable.
216 */
217 multipleEndpoints?: boolean;
218 /**
219 * Human-readable description.
220 */
221 description?: string;
222 /**
223 * User labels. Alchemy ownership labels are merged in automatically.
224 */
225 labels?: Record<string, string>;
226};
227
228export type MountOption = {
229 export: string | undefined;
230 exportFull: string | undefined;
231 protocol: string | undefined;
232 instructions: string | undefined;
233 ipAddress: string | undefined;
234};
235
236export type Volume = Resource<
237 "GCP.NetApp.Volume",
238 VolumeProps,
239 {
240 /** Full resource name. */
241 name: string;
242 /** Volume id (last path segment). */
243 volumeId: string;
244 /** Project id. */
245 project: string;
246 /** Location id. */
247 location: string;
248 /** Storage pool name. */
249 storagePool: string | undefined;
250 /** Share name. */
251 shareName: string | undefined;
252 /** Protocols. */
253 protocols: string[];
254 /** Capacity in GiB. */
255 capacityGib: string | undefined;
256 /** Used capacity in GiB. */
257 usedGib: string | undefined;
258 /** VPC network path. */
259 network: string | undefined;
260 /** Service level inherited from the pool. */
261 serviceLevel: string | undefined;
262 /** Unix permissions. */
263 unixPermissions: string | undefined;
264 /** Security style. */
265 securityStyle: string | undefined;
266 /** Snapshot reserve percent. */
267 snapReserve: number | undefined;
268 /** Whether `.snapshot` is exposed. */
269 snapshotDirectory: boolean | undefined;
270 /** Throughput in MiB/s. */
271 throughputMibps: number | undefined;
272 /** Whether Kerberos is enabled. */
273 kerberosEnabled: boolean | undefined;
274 /** SMB settings. */
275 smbSettings: string[];
276 /** Restricted actions. */
277 restrictedActions: string[];
278 /** Export policy. */
279 exportPolicy: ExportPolicy | undefined;
280 /** Snapshot policy. */
281 snapshotPolicy: SnapshotPolicy | undefined;
282 /** Backup configuration. */
283 backupConfig: BackupConfig | undefined;
284 /** Tiering policy. */
285 tieringPolicy: TieringPolicy | undefined;
286 /** Mount options. */
287 mountOptions: MountOption[];
288 /** Active zone. */
289 zone: string | undefined;
290 /** Replica zone. */
291 replicaZone: string | undefined;
292 /** KMS config name. */
293 kmsConfig: string | undefined;
294 /** Active Directory name. */
295 activeDirectory: string | undefined;
296 /** Whether NFS LDAP is enabled. */
297 ldapEnabled: boolean | undefined;
298 /** Whether the volume is in a replication. */
299 hasReplication: boolean | undefined;
300 /** Encryption key source. */
301 encryptionType: string | undefined;
302 /** Cold-tier usage in GiB. */
303 coldTierSizeGib: string | undefined;
304 /** Hot-tier usage in GiB. */
305 hotTierSizeUsedGib: string | undefined;
306 /** Human-readable description. */
307 description: string | undefined;
308 /** User labels (Alchemy ownership labels stripped). */
309 labels: Record<string, string>;
310 /** Server-reported state. */
311 state: string | undefined;
312 /** State details. */
313 stateDetails: string | undefined;
314 /** RFC3339 creation timestamp. */
315 createTime: string | undefined;
316 },
317 never,
318 Providers
319>;
320
321/**
322 * A Cloud NetApp Volumes file or block volume hosted in a storage pool.
323 *
324 * Changing `volumeId`, `location`, `storagePool`, `shareName`,
325 * `protocols`, `securityStyle`, `kerberosEnabled`, `restoreParameters`,
326 * or large-capacity flags replaces the volume. Capacity, export policy,
327 * snapshots, backups, description, and labels update in place.
328 *
329 * ### Creating a Volume
330 * **Example:** NFS volume
331 * ```typescript
332 * const volume = yield* GCP.NetApp.Volume("Share", {
333 * storagePool: pool.name,
334 * protocols: ["NFSV3"],
335 * capacityGib: 100,
336 * });
337 * ```
338 *
339 * **Example:** Explicit share name and export policy
340 * ```typescript
341 * const volume = yield* GCP.NetApp.Volume("Share", {
342 * storagePool: pool.name,
343 * shareName: "app",
344 * protocols: ["NFSV3"],
345 * capacityGib: 200,
346 * unixPermissions: "0770",
347 * exportPolicy: {
348 * rules: [{ allowedClients: "10.0.0.0/24", accessType: "READ_WRITE" }],
349 * },
350 * labels: { env: "prod" },
351 * });
352 * ```
353 *
354 * ### Updating a Volume
355 * **Example:** Capacity and labels
356 * ```typescript
357 * const volume = yield* GCP.NetApp.Volume("Share", {
358 * storagePool: pool.name,
359 * capacityGib: 300,
360 * description: "app share v2",
361 * labels: { env: "prod", team: "storage" },
362 * });
363 * ```
364 *
365 * @resource
366 * @category NetApp
367 */
368export const Volume = Resource<Volume>("GCP.NetApp.Volume");
369
370const resourceName = (project: string, location: string, volumeId: string) =>
371 `projects/${project}/locations/${location}/volumes/${volumeId}`;
372
373const toBackupConfig = (
374 config: netapp.BackupConfig | undefined,
375): BackupConfig | undefined =>
376 config === undefined
377 ? undefined
378 : {
379 backupVault: config.backupVault,
380 backupPolicies: config.backupPolicies,
381 scheduledBackupEnabled: config.scheduledBackupEnabled,
382 };
383
384const toExportPolicy = (
385 policy: netapp.ExportPolicy | undefined,
386): ExportPolicy | undefined =>
387 policy === undefined
388 ? undefined
389 : {
390 rules: policy.rules?.map((rule) => ({
391 accessType: rule.accessType,
392 squashMode: rule.squashMode,
393 allowedClients: rule.allowedClients,
394 nfsv3: rule.nfsv3,
395 nfsv4: rule.nfsv4,
396 hasRootAccess: rule.hasRootAccess,
397 anonUid: rule.anonUid,
398 kerberos5ReadOnly: rule.kerberos5ReadOnly,
399 kerberos5ReadWrite: rule.kerberos5ReadWrite,
400 kerberos5iReadOnly: rule.kerberos5iReadOnly,
401 kerberos5iReadWrite: rule.kerberos5iReadWrite,
402 kerberos5pReadOnly: rule.kerberos5pReadOnly,
403 kerberos5pReadWrite: rule.kerberos5pReadWrite,
404 })),
405 };
406
407const toSnapshotPolicy = (
408 policy: netapp.SnapshotPolicy | undefined,
409): SnapshotPolicy | undefined =>
410 policy === undefined
411 ? undefined
412 : {
413 enabled: policy.enabled,
414 hourlySchedule: policy.hourlySchedule,
415 dailySchedule: policy.dailySchedule,
416 weeklySchedule: policy.weeklySchedule,
417 monthlySchedule: policy.monthlySchedule,
418 };
419
420const toTieringPolicy = (
421 policy: netapp.TieringPolicy | undefined,
422): TieringPolicy | undefined =>
423 policy === undefined
424 ? undefined
425 : {
426 tierAction: policy.tierAction,
427 coolingThresholdDays: policy.coolingThresholdDays,
428 hotTierBypassModeEnabled: policy.hotTierBypassModeEnabled,
429 };
430
431const toAttrs = (item: netapp.Volume, project: string, region: string) => {
432 const name = item.name ?? "";
433 const parsed = parseName(name, "volumes", region);
434 return {
435 name,
436 volumeId: parsed.id,
437 project: parsed.project || project,
438 location: parsed.location,
439 storagePool: item.storagePool,
440 shareName: item.shareName,
441 protocols: item.protocols ?? [],
442 capacityGib: item.capacityGib,
443 usedGib: item.usedGib,
444 network: item.network,
445 serviceLevel: item.serviceLevel,
446 unixPermissions: item.unixPermissions,
447 securityStyle: item.securityStyle,
448 snapReserve: item.snapReserve,
449 snapshotDirectory: item.snapshotDirectory,
450 throughputMibps: item.throughputMibps,
451 kerberosEnabled: item.kerberosEnabled,
452 smbSettings: item.smbSettings ?? [],
453 restrictedActions: item.restrictedActions ?? [],
454 exportPolicy: toExportPolicy(item.exportPolicy),
455 snapshotPolicy: toSnapshotPolicy(item.snapshotPolicy),
456 backupConfig: toBackupConfig(item.backupConfig),
457 tieringPolicy: toTieringPolicy(item.tieringPolicy),
458 mountOptions: (item.mountOptions ?? []).map((option) => ({
459 export: option.export,
460 exportFull: option.exportFull,
461 protocol: option.protocol,
462 instructions: option.instructions,
463 ipAddress: option.ipAddress,
464 })),
465 zone: item.zone,
466 replicaZone: item.replicaZone,
467 kmsConfig: item.kmsConfig,
468 activeDirectory: item.activeDirectory,
469 ldapEnabled: item.ldapEnabled,
470 hasReplication: item.hasReplication,
471 encryptionType: item.encryptionType,
472 coldTierSizeGib: item.coldTierSizeGib,
473 hotTierSizeUsedGib: item.hotTierSizeUsedGib,
474 description: item.description,
475 labels: userLabels(item.labels),
476 state: item.state,
477 stateDetails: item.stateDetails,
478 createTime: item.createTime,
479 };
480};
481
482const getByName = (name: string) =>
483 netapp
484 .getProjectsLocationsVolumes({ name })
485 .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
486
487const listOwned = (project: string) =>
488 listAtLocation(project, (parent) =>
489 listLabeledPages(
490 netapp.listProjectsLocationsVolumes.pages({
491 parent,
492 pageSize: 1000,
493 }),
494 (page) => page.volumes,
495 (item) => item.labels,
496 ),
497 );
498
499const desiredBackupConfig = (
500 project: string,
501 location: string,
502 config: BackupConfig | undefined,
503): netapp.BackupConfig | undefined => {
504 if (config === undefined) return undefined;
505 return {
506 backupVault:
507 config.backupVault === undefined
508 ? undefined
509 : expandParent(config.backupVault, project, location, "backupVaults"),
510 backupPolicies: config.backupPolicies?.map((policy) =>
511 expandParent(policy, project, location, "backupPolicies"),
512 ),
513 scheduledBackupEnabled: config.scheduledBackupEnabled,
514 };
515};
516
517export const VolumeProvider = () =>
518 Provider.succeed(Volume, {
519 stables: ["name", "volumeId", "project", "location", "createTime"],
520
521 diff: Effect.fn(function* ({ news, olds, output }) {
522 if (!isResolved(news)) return undefined;
523 const env = yield* GcpEnvironment.current;
524 const previousPool = olds?.storagePool ?? output?.storagePool;
525 const previousShare = olds?.shareName ?? output?.shareName;
526 const previousProtocols = olds?.protocols ?? output?.protocols;
527 const previousStyle = olds?.securityStyle ?? output?.securityStyle;
528 const previousKerberos = olds?.kerberosEnabled ?? output?.kerberosEnabled;
529 const nextProtocols = news.protocols ?? [DEFAULT_PROTOCOL];
530 return replaceOnIdentity({
531 previousId: olds?.volumeId ?? output?.volumeId,
532 nextId: news.volumeId ?? olds?.volumeId ?? output?.volumeId,
533 previousLocation: normalizeLocation(
534 olds?.location ?? output?.location,
535 env.region,
536 ),
537 nextLocation: normalizeLocation(
538 news.location ?? olds?.location ?? output?.location,
539 env.region,
540 ),
541 extra:
542 (previousPool !== undefined &&
543 news.storagePool !== previousPool &&
544 !previousPool.endsWith(`/${news.storagePool}`)) ||
545 (previousShare !== undefined &&
546 news.shareName !== undefined &&
547 news.shareName !== previousShare) ||
548 (previousProtocols !== undefined &&
549 !sameStringList(previousProtocols, nextProtocols)) ||
550 (previousStyle !== undefined &&
551 news.securityStyle !== undefined &&
552 news.securityStyle !== previousStyle) ||
553 (previousKerberos !== undefined &&
554 news.kerberosEnabled !== undefined &&
555 news.kerberosEnabled !== previousKerberos) ||
556 (olds?.restoreParameters !== undefined &&
557 news.restoreParameters !== undefined &&
558 fingerprint(olds.restoreParameters) !==
559 fingerprint(news.restoreParameters)) ||
560 (olds?.largeCapacity !== undefined &&
561 news.largeCapacity !== undefined &&
562 news.largeCapacity !== olds.largeCapacity) ||
563 (olds?.multipleEndpoints !== undefined &&
564 news.multipleEndpoints !== undefined &&
565 news.multipleEndpoints !== olds.multipleEndpoints),
566 });
567 }),
568
569 read: Effect.fn(function* ({ id, olds, output }) {
570 const env = yield* GcpEnvironment.current;
571 const volumeId = yield* toPhysicalId(
572 id,
573 olds?.volumeId,
574 output?.volumeId,
575 "volume",
576 );
577 const location = normalizeLocation(
578 olds?.location ?? output?.location,
579 env.region,
580 );
581 const name =
582 output?.name ?? resourceName(env.project, location, volumeId);
583 const existing = yield* getByName(name);
584 if (existing === undefined) return undefined;
585 const attrs = toAttrs(existing, env.project, env.region);
586 return (yield* hasAlchemyLabels(id, tagRecord(existing.labels)))
587 ? attrs
588 : Unowned(attrs);
589 }),
590
591 list: () =>
592 Effect.gen(function* () {
593 const env = yield* GcpEnvironment.current;
594 const items = yield* listOwned(env.project);
595 return items.map((item) => toAttrs(item, env.project, env.region));
596 }),
597
598 reconcile: Effect.fn(function* ({ id, news, output }) {
599 const env = yield* GcpEnvironment.current;
600 const volumeId = yield* toPhysicalId(
601 id,
602 news.volumeId,
603 output?.volumeId,
604 "volume",
605 );
606 const location = normalizeLocation(
607 news.location ?? output?.location,
608 env.region,
609 );
610 const name = resourceName(env.project, location, volumeId);
611 // Volumes reference their pool by id; a full resource name is rejected
612 // with `BadRequest: specified storage pool not found`.
613 const storagePool = lastSegment(news.storagePool);
614 const shareName = news.shareName ?? volumeId;
615 const protocols = news.protocols ?? [DEFAULT_PROTOCOL];
616 const capacityGib = gibOf(news.capacityGib ?? DEFAULT_CAPACITY_GIB);
617 const desiredLabels = {
618 ...toLabels(news.labels),
619 ...(yield* createInternalLabels(id)),
620 };
621 const backupConfig = desiredBackupConfig(
622 env.project,
623 location,
624 news.backupConfig,
625 );
626 const blockDevices = news.blockDevices?.map((device) => ({
627 osType: device.osType,
628 name: device.name,
629 sizeGib: gibOf(device.sizeGib),
630 hostGroups: device.hostGroups?.map((group) =>
631 expandParent(group, env.project, location, "hostGroups"),
632 ),
633 }));
634
635 let current = yield* getByName(output?.name ?? name);
636
637 if (current === undefined) {
638 const created = yield* netapp
639 .createProjectsLocationsVolumes({
640 parent: parentOf(env.project, location),
641 volumeId,
642 body: {
643 storagePool,
644 shareName,
645 protocols,
646 capacityGib,
647 unixPermissions: news.unixPermissions,
648 securityStyle: news.securityStyle,
649 snapReserve: news.snapReserve,
650 snapshotDirectory: news.snapshotDirectory,
651 throughputMibps: news.throughputMibps,
652 kerberosEnabled: news.kerberosEnabled,
653 smbSettings: news.smbSettings,
654 restrictedActions: news.restrictedActions,
655 exportPolicy: news.exportPolicy,
656 snapshotPolicy: news.snapshotPolicy,
657 backupConfig,
658 tieringPolicy: news.tieringPolicy,
659 restoreParameters: news.restoreParameters,
660 blockDevices,
661 largeCapacity: news.largeCapacity,
662 largeCapacityConfig: news.largeCapacityConfig,
663 multipleEndpoints: news.multipleEndpoints,
664 description: news.description,
665 labels: desiredLabels,
666 },
667 })
668 .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
669 if (created !== undefined) {
670 yield* waitForOperation(created);
671 }
672 current = yield* waitUntilExists(getByName(name), name);
673 }
674
675 if (current === undefined) {
676 return yield* new ResourceNotResolved({ name });
677 }
678
679 current = yield* waitUntilReady(
680 getByName(current.name ?? name),
681 current.name ?? name,
682 (item) => item.state,
683 (item) => item.stateDetails,
684 );
685
686 const observedLabels = tagRecord(current.labels);
687 const { upsert, removed } = diffLabels(observedLabels, desiredLabels);
688 const mask = fieldMask([
689 (upsert.length > 0 || removed.length > 0) && "labels",
690 (current.description ?? "") !== (news.description ?? "") &&
691 "description",
692 (current.capacityGib ?? "") !== (capacityGib ?? "") && "capacityGib",
693 news.unixPermissions !== undefined &&
694 (current.unixPermissions ?? "") !== news.unixPermissions &&
695 "unixPermissions",
696 news.snapReserve !== undefined &&
697 (current.snapReserve ?? 0) !== news.snapReserve &&
698 "snapReserve",
699 news.snapshotDirectory !== undefined &&
700 (current.snapshotDirectory ?? false) !== news.snapshotDirectory &&
701 "snapshotDirectory",
702 news.throughputMibps !== undefined &&
703 (current.throughputMibps ?? 0) !== news.throughputMibps &&
704 "throughputMibps",
705 news.smbSettings !== undefined &&
706 !sameStringList(current.smbSettings, news.smbSettings) &&
707 "smbSettings",
708 news.restrictedActions !== undefined &&
709 !sameStringList(current.restrictedActions, news.restrictedActions) &&
710 "restrictedActions",
711 news.exportPolicy !== undefined &&
712 fingerprint(toExportPolicy(current.exportPolicy)) !==
713 fingerprint(news.exportPolicy) &&
714 "exportPolicy",
715 news.snapshotPolicy !== undefined &&
716 fingerprint(toSnapshotPolicy(current.snapshotPolicy)) !==
717 fingerprint(news.snapshotPolicy) &&
718 "snapshotPolicy",
719 news.backupConfig !== undefined &&
720 fingerprint(toBackupConfig(current.backupConfig)) !==
721 fingerprint(backupConfig) &&
722 "backupConfig",
723 news.tieringPolicy !== undefined &&
724 fingerprint(toTieringPolicy(current.tieringPolicy)) !==
725 fingerprint(news.tieringPolicy) &&
726 "tieringPolicy",
727 news.blockDevices !== undefined &&
728 fingerprint(current.blockDevices) !== fingerprint(blockDevices) &&
729 "blockDevices",
730 ]);
731
732 if (mask.length > 0) {
733 const operation = yield* netapp.patchProjectsLocationsVolumes({
734 name: current.name ?? name,
735 updateMask: mask,
736 body: {
737 name: current.name ?? name,
738 labels: desiredLabels,
739 description: news.description,
740 capacityGib,
741 unixPermissions: news.unixPermissions,
742 snapReserve: news.snapReserve,
743 snapshotDirectory: news.snapshotDirectory,
744 throughputMibps: news.throughputMibps,
745 smbSettings: news.smbSettings,
746 restrictedActions: news.restrictedActions,
747 exportPolicy: news.exportPolicy,
748 snapshotPolicy: news.snapshotPolicy,
749 backupConfig,
750 tieringPolicy: news.tieringPolicy,
751 blockDevices,
752 },
753 });
754 yield* waitForOperation(operation);
755 current = yield* waitUntilReady(
756 getByName(current.name ?? name),
757 current.name ?? name,
758 (item) => item.state,
759 (item) => item.stateDetails,
760 );
761 }
762
763 return toAttrs(current, env.project, env.region);
764 }),
765
766 delete: Effect.fn(function* ({ output }) {
767 const operation = yield* netapp
768 .deleteProjectsLocationsVolumes({ name: output.name })
769 .pipe(
770 Effect.retry({
771 while: (error) => error._tag === "Conflict",
772 times: 8,
773 schedule: Schedule.spaced("2 seconds"),
774 }),
775 Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
776 );
777 if (operation !== undefined) {
778 yield* waitForDeleteOperation(operation);
779 }
780 yield* waitUntilGone(getByName(output.name), output.name);
781 }),
782 });