From d34cb74ed9a0a1bd7ba1ab68603fa250856a9c4c Mon Sep 17 00:00:00 2001 From: gnaveen-netapp Date: Wed, 13 May 2026 15:51:55 +0530 Subject: [PATCH] Add ONTAP mode support with preflight validation and audit logging --- .gitignore | 3 +- .prettierignore | 1 + GEMINI.md | 301 +- eslint.config.mjs | 1 + package-lock.json | 95 +- package.json | 3 +- src/registry/register-tools.test.ts | 2 +- src/registry/register-tools.ts | 65 + src/resources/ontap-api-index.json | 3592 +++++++++++++++++ src/tools/active-directory-tools.ts | 13 +- src/tools/backup-policy-tools.ts | 4 +- src/tools/backup-tools.ts | 4 +- src/tools/backup-vault-tools.ts | 6 +- .../handlers/active-directory-handler.test.ts | 4 +- .../handlers/active-directory-handler.ts | 2 +- src/tools/handlers/backup-handler.test.ts | 4 +- src/tools/handlers/backup-handler.ts | 2 +- .../handlers/backup-policy-handler.test.ts | 16 +- src/tools/handlers/backup-policy-handler.ts | 6 +- .../handlers/backup-vault-handler.test.ts | 8 +- src/tools/handlers/backup-vault-handler.ts | 4 +- src/tools/handlers/host-group-handler.test.ts | 2 +- src/tools/handlers/host-group-handler.ts | 2 +- src/tools/handlers/kms-config-handler.test.ts | 4 +- src/tools/handlers/kms-config-handler.ts | 2 +- .../handlers/ontap-audit-log-handler.test.ts | 85 + src/tools/handlers/ontap-audit-log-handler.ts | 95 + .../handlers/ontap-discover-handler.test.ts | 260 ++ src/tools/handlers/ontap-discover-handler.ts | 272 ++ .../handlers/ontap-execute-handler.test.ts | 511 +++ src/tools/handlers/ontap-execute-handler.ts | 312 ++ src/tools/handlers/ontap-handler.test.ts | 298 ++ src/tools/handlers/ontap-handler.ts | 273 ++ src/tools/handlers/operation-handler.ts | 12 +- src/tools/handlers/quota-rule-handler.test.ts | 4 +- src/tools/handlers/quota-rule-handler.ts | 2 +- .../handlers/replication-handler.test.ts | 8 +- src/tools/handlers/replication-handler.ts | 6 +- src/tools/handlers/snapshot-handler.test.ts | 2 +- src/tools/handlers/snapshot-handler.ts | 2 +- .../handlers/storage-pool-handler.test.ts | 77 +- src/tools/handlers/storage-pool-handler.ts | 78 +- src/tools/handlers/volume-handler.test.ts | 4 +- src/tools/handlers/volume-handler.ts | 2 +- src/tools/host-group-tools.ts | 4 +- src/tools/kms-config-tools.ts | 4 +- src/tools/ontap-audit-log-tool.ts | 31 + src/tools/ontap-discover-tool.ts | 48 + src/tools/ontap-execute-tool.ts | 67 + src/tools/ontap-tools.ts | 264 ++ src/tools/quota-rule-tools.ts | 4 +- src/tools/replication-tools.ts | 4 +- src/tools/snapshot-tools.ts | 20 +- src/tools/storage-pool-tools.test.ts | 5 + src/tools/storage-pool-tools.ts | 21 +- src/tools/volume-tools.ts | 14 +- src/types/tool.ts | 5 + src/utils/ontap-audit-logger.test.ts | 367 ++ src/utils/ontap-audit-logger.ts | 405 ++ src/utils/ontap-http-client.test.ts | 260 ++ src/utils/ontap-http-client.ts | 192 + src/utils/ontap-index-loader.test.ts | 34 + src/utils/ontap-index-loader.ts | 145 + src/utils/ontap-preflight-validator.test.ts | 288 ++ src/utils/ontap-preflight-validator.ts | 181 + src/utils/ontap-response-utils.ts | 123 + src/utils/scope-denied-envelope.test.ts | 73 + src/utils/scope-denied-envelope.ts | 62 + src/utils/sleep.ts | 2 + 69 files changed, 8894 insertions(+), 178 deletions(-) create mode 100644 .prettierignore create mode 100644 src/resources/ontap-api-index.json create mode 100644 src/tools/handlers/ontap-audit-log-handler.test.ts create mode 100644 src/tools/handlers/ontap-audit-log-handler.ts create mode 100644 src/tools/handlers/ontap-discover-handler.test.ts create mode 100644 src/tools/handlers/ontap-discover-handler.ts create mode 100644 src/tools/handlers/ontap-execute-handler.test.ts create mode 100644 src/tools/handlers/ontap-execute-handler.ts create mode 100644 src/tools/handlers/ontap-handler.test.ts create mode 100644 src/tools/handlers/ontap-handler.ts create mode 100644 src/tools/ontap-audit-log-tool.ts create mode 100644 src/tools/ontap-discover-tool.ts create mode 100644 src/tools/ontap-execute-tool.ts create mode 100644 src/tools/ontap-tools.ts create mode 100644 src/utils/ontap-audit-logger.test.ts create mode 100644 src/utils/ontap-audit-logger.ts create mode 100644 src/utils/ontap-http-client.test.ts create mode 100644 src/utils/ontap-http-client.ts create mode 100644 src/utils/ontap-index-loader.test.ts create mode 100644 src/utils/ontap-index-loader.ts create mode 100644 src/utils/ontap-preflight-validator.test.ts create mode 100644 src/utils/ontap-preflight-validator.ts create mode 100644 src/utils/ontap-response-utils.ts create mode 100644 src/utils/scope-denied-envelope.test.ts create mode 100644 src/utils/scope-denied-envelope.ts create mode 100644 src/utils/sleep.ts diff --git a/.gitignore b/.gitignore index 4461b1d..7aff3dd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ .env .vscode/ .npm-cache/ -.vitest-coverage/ \ No newline at end of file +.vitest-coverage/ +/logs/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6d9594a --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +/logs/ diff --git a/GEMINI.md b/GEMINI.md index 576986b..baef940 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -24,6 +24,7 @@ You are an expert helper for managing Google Cloud NetApp Volumes (GCNV) using t ``` gcloud auth application-default login ``` +- **ONTAP Expert Mode:** The MCP server handles ONTAP proxy authentication and URL construction automatically. No additional configuration is needed beyond standard Google Application Default Credentials. - **Safety:** All create, update, delete, revert, and replication-mutation operations are considered disruptive. - Ask for explicit confirmation before invoking tools. - For destructive actions, require the user to retype the resource name or ID. @@ -39,11 +40,41 @@ Use this link to explain billing and estimate pricing (pair with the Google Clou --- +## Session startup — ONTAP audit logging (MUST DO) + +**Before you run the very first ONTAP tool call in a session** (`ontap_discover`, `ontap_execute`, `ontap_svm_list`, `ontap_volume_*`, `ontap_snapshot_*`, `ontap_lun_*`, `ontap_job_get`, or any other `ontap_*` tool), you **must** ask the user: + +> "Would you like me to log ONTAP operations to a local file for this session?" + +- If the user says **yes** → call `ontap_audit_log` with `action="enable"` (no `outputDir` needed — logs are saved automatically to the `logs/` folder in the project directory), then proceed with the requested operation. Tell the user the full log file path returned by the tool. +- If **no** → proceed without logging. +- Do **not** ask again in the same session. +- At session end, call `ontap_audit_log` with `action="disable"` to finalize the log. + +### userIntent parameter (when audit logging is enabled) + +When audit logging is active, **always populate the `userIntent` parameter** on every ONTAP tool call (`ontap_*`), **except `ontap_audit_log` itself**. This field should be a brief, plain-English description of what the user asked for that led to this tool call. Examples: + +- "User asked to list all volumes in the pool" +- "User wants to create a volume for NFS workloads" +- "User requested snapshot deletion to reclaim space" +- "Follow-up: user asked to check the async job status after volume creation" + +Guidelines: + +- Keep it to one sentence (under 150 characters). +- Include the user's stated goal or reason when available. +- For follow-up operations (e.g. polling a job), note that it is a follow-up. +- When audit logging is **not** enabled, omit `userIntent` to avoid unnecessary overhead. + +--- + # Operating Principles ### Input discipline - Never guess resource identifiers. Always collect `projectId`, `location`, and resource-specific IDs explicitly. +- For `gcnv_storage_pool_create`: always ask for `mode` if not provided. Present options as `DEFAULT` (standard) or `ONTAP` (expert mode). Never assume a default silently. - Validate numeric fields (e.g., `capacityGib`) and repeat key parameters back to the user before running a tool. - Validate `location` format: - **Region or zone are both valid.** The API accepts either a **region** (e.g., `us-central1`, `us-west1`) or a **zone** (e.g., `us-central1-a`, `us-west1-b`). Use whichever the user specified—e.g. if they say "list my volumes in us-west1-b", pass `location: "us-west1-b"`; do not correct or reject zones. @@ -57,13 +88,118 @@ Use this link to explain billing and estimate pricing (pair with the Google Clou ### Operation handling -- Long-running actions return `structuredContent.operationId`. +- **GCNV control-plane operations:** Long-running actions return `structuredContent.operationId`. - Always surface the operation ID. - Suggest using `gcnv_operation_get` to monitor. - Use `gcnv_operation_cancel` only when the user insists. +- **ONTAP async jobs:** Mutating ONTAP operations (create, delete, update via dedicated tools or `ontap_execute` with POST/PATCH/DELETE) return an async job UUID. + - Always surface the job UUID to the user. + - Suggest polling with `ontap_job_get` until `state` is `"success"` or `"failure"`. + - Do not assume an operation succeeded just because the tool returned without error — the response contains a job reference, not a completion status. - List operations may return `nextPageToken`. - Surface it clearly so the user can request additional pages. - - Always echo `operationId` and `nextPageToken` in responses so users can continue or paginate. + - Always echo `operationId` (GCNV) or `jobUuid` (ONTAP) and `nextPageToken` in responses so users can continue or paginate. + +### Scope-boundary denials (terminal — never retry) + +This is the **single canonical rule** for `scope_denied` envelopes. Other sections of this doc cross-reference back here; do not interpret those cross-references as separate rules. + +When **any** ONTAP tool returns a JSON error of this shape: + +``` +{ + "error": "scope_denied", + "retryability": false, + "source": "preflight" | "proxy" | "ontap", + "reason": "", + "suggested_tool": "" +} +``` + +the denial is **terminal for that sub-task**. The `source` tells you who said no: + +- `preflight` — this MCP rejected the call before sending it (e.g. an `/api/private/cli/*` path, or an endpoint marked as denied by the bundled proxy/RBAC index). +- `proxy` — the vsa-control-plane proxy rejected the call. +- `ontap` — ONTAP itself rejected the call (typically RBAC). + +You **must**: + +1. Stop the operation. Do **not** retry the same call. +2. Do **not** try a sibling endpoint, a different HTTP method, or an `/api/private/cli/...` variant — they are out of scope too. +3. Do **not** call `ontap_discover` looking for an "alternative" endpoint to bypass the denial. +4. Echo `reason` to the user verbatim and stop work on that branch. +5. If `suggested_tool` is present, you may invoke it **once** as a follow-up. +6. If the user insists, surface the denial again and explain that this MCP does not expose that operation. + +`retryability: false` is terminal — never silently retry. This supersedes the generic "retry once silently" guidance in "Error recovery" below. + +**Out-of-scope paths:** Any path under `/api/private/cli/` is rejected at preflight, regardless of the proxy/RBAC outcome. Use a public `/api/...` endpoint or a dedicated `gcnv_*` / `ontap_*` tool instead. + +### Error recovery (mandatory) + +When any tool returns an error: + +1. Read the error message and follow the `suggestion` field if present. +2. If the response is a `scope_denied` envelope (see "Scope-boundary denials" above), **do not retry** -- treat it as terminal. +3. If the fix only involves correcting a **technical** parameter you chose (e.g. a typo in a UUID, wrong API path), you may retry **once** silently. +4. If the fix requires changing a parameter **the user explicitly specified** (size, name, region, tier, protocol, etc.), you **must not** auto-correct. Instead, report the error to the user, explain the constraint, and ask how they want to proceed. See "Parameter change approval" below. +5. If the error says to use a different tool, switch to that tool. +6. If the error persists after one retry, report the full error to the user and ask for guidance. +7. For `ontap_execute` errors: check whether the error says `retryable: true` or `retryable: false`. Only retry mutating operations (POST/PATCH/DELETE) if the error explicitly says `retryable: true`. GET requests are always safe to retry. + +**Never** read or inspect the MCP server's internal source code to debug tool errors. The error messages are self-contained and actionable. If the message does not help, escalate to the user. + +### Operation safety (mandatory) + +These rules prevent autonomous actions that could cause unintended cost, data loss, or configuration changes. + +#### Delete operations (preview-first pattern) + +DELETE operations through `ontap_execute` use a **preview-first** pattern: + +1. **First call** (without `confirmDelete`): The server returns a preview showing the resource path and pool that will be affected. This is **not an error** -- it is a deliberate confirmation step. +2. **You MUST show this preview to the user** and ask: "Do you want to proceed with deleting [resource] on pool [pool]?" +3. **Only after the user explicitly says YES**, call `ontap_execute` again with the same parameters plus `confirmDelete=true`. +4. **If the user says NO or does not confirm**, do not proceed. Inform them the delete was cancelled. + +Rules: + +- **NEVER** set `confirmDelete=true` without first showing the delete preview to the user and receiving their explicit confirmation. +- **NEVER** decide on your own that a resource should be deleted as part of a retry or recovery flow. If a workflow fails and you think deleting and recreating a resource might help, **ask the user first**. +- This applies to ALL delete operations: volumes, cluster peers, snapmirror relationships, export policies, LUNs, etc. + +#### Parameter change approval + +When an operation fails because a **user-specified parameter** does not meet a system requirement, you must **inform the user and get approval** before retrying with a different value. Never silently change a value the user chose. + +This applies to: + +- **Size changes** -- e.g. user requests a size that ONTAP rejects as below the minimum. Report the exact error from ONTAP, show the user's requested size, and ask how they want to proceed. +- **Service level / tier changes** -- e.g. user asks for Standard but the operation requires Premium. Ask before upgrading -- tier changes affect billing. +- **Region / location changes** -- never switch regions without consent. Compliance and latency implications. +- **Protocol changes** -- e.g. user asks for NFS but you think SMB is needed. Ask first. +- **Name changes** -- if the requested name is invalid or taken, propose an alternative and ask. +- **Replication or backup configuration** -- never change RPO, schedule, or destination without consent. + +General rule: **if the user explicitly stated a value and the system rejects it, report the conflict and let the user decide.** Do not auto-correct and retry. + +#### Autonomous resource creation + +Do not create resources the user did not explicitly ask for without informing them first. Examples: + +- User asks to create a LUN. Do not silently create an igroup and LUN mapping on top of it -- inform the user these are needed and ask if they want to proceed. +- User asks to set up SnapMirror. Do not silently create cluster peers, SVM peers, or destination volumes without explaining each step and confirming. +- User asks to create a volume. Do not silently attach snapshot policies, QoS policies, or export policies unless the user asked for them. + +Exception: if a tool's design **always** creates a prerequisite (e.g. SVM/aggregate auto-resolution during volume create), that is expected and does not need confirmation. The distinction is: auto-resolution of existing resources is fine; **creating new billable or policy-affecting resources** requires consent. + +#### Multi-step workflow guardrail + +For complex workflows (SnapMirror setup, FlexCache creation, CIFS share setup, etc.) that involve multiple tool calls: + +1. **Outline the plan first**: Before executing, list all the steps you intend to perform and ask the user to confirm. +2. **Pause on failure**: If any step fails, report the failure and current state to the user before deciding on next steps. Do not auto-recover by deleting and recreating resources. +3. **Pause on parameter revision**: If any step requires changing a parameter from what the user specified, pause and ask (see "Parameter change approval" above). ### Interaction style @@ -72,12 +208,12 @@ Use this link to explain billing and estimate pricing (pair with the Google Clou ### List response formatting -- **Always beautify list tool responses in a tabular format.** When you receive results from any `gcnv_*_list` tool (e.g. `gcnv_storage_pool_list`, `gcnv_volume_list`, `gcnv_backup_vault_list`, `gcnv_host_group_list`, etc.), present the data to the user as a **markdown table** instead of raw JSON. +- **Always beautify list tool responses in a tabular format.** When you receive results from any list tool — GCNV (`gcnv_storage_pool_list`, `gcnv_volume_list`, etc.) or ONTAP (`ontap_volume_list`, `ontap_discover`, etc.) — present the data to the user as a **markdown table** instead of raw JSON. - **Table structure:** - Use a header row with column names derived from the list item fields (e.g. Name, ID, State, Location, Capacity, Service Level, Create Time—pick the most relevant fields for that resource type). - **Include details that vary between resources.** Add columns for fields that differ across items (e.g. state, capacity, service level, location, protocol, backup state) so the table is informative and each row is distinguishable. Avoid a one-size-fits-all set of columns—tailor columns to the resource type and to what actually varies in the response. - **Include these bare-minimum columns when present in the response** (so the table is always useful). Add any additional columns that vary and are relevant. - - **Storage pools:** name/ID, state, serviceLevel, capacityGib, **volumeCapacityGib**, **volumeCount** (or volumecount), **encryptionType**, **allowAutoTiering**, **totalThroughputMibps**, qosType, zone, replicaZone, createTime. + - **Storage pools:** name/ID, state, serviceLevel, capacityGib, **volumeCapacityGib**, **volumeCount** (or volumecount), **mode** (DEFAULT or ONTAP), **encryptionType**, **allowAutoTiering**, **totalThroughputMibps**, qosType, zone, replicaZone, createTime. - **Volumes:** name/volumeId, state, capacityGib, usedGib, protocols, serviceLevel, **encryptionType**, **throughputMibps** (or availableThroughputMibps), **coldTierSizeGib**, **hotTierSizeUsedGib** (or hotTierSizeGib), tieringPolicy, createTime, storagePool. - **Snapshots:** name/snapshotId, volumeId, state, createTime, description. - **Backups:** backupId, backupVaultId, state, sourceVolume, backupType, volumeUsagebytes, chainStoragebytes, createTime, retentionDays. @@ -89,6 +225,11 @@ Use this link to explain billing and estimate pricing (pair with the Google Clou - **Quota rules:** quotaRuleId, target, quotaType, diskLimitMib, state, createTime. - **Operations:** name (or operationId), done, success, target, verb, createTime, statusMessage. - **Active directories:** activeDirectoryId, domain, site, state, createTime. + - **ONTAP volumes** (from `ontap_volume_list`): name, uuid, size, style, state, svm. + - **ONTAP snapshots** (from `ontap_snapshot_list`): name, uuid, create_time, state. + - **ONTAP LUNs** (from `ontap_lun_list`): name, uuid, os_type, space (size/used), state. + - **ONTAP discover categories** (from `ontap_discover` with no args): resource name, endpoint count. + - **ONTAP discover endpoints** (from `ontap_discover` with resource/search): method, path, description, hint (if present); include `decision` and `decisionReason` when `decision` is `denied` so the user sees out-of-scope APIs before execute. - One row per item; keep cells concise (e.g. short IDs, not full resource names unless needed). - If the list is empty, say so clearly (e.g. "No storage pools found.") instead of showing an empty table. - **State column: mark states with icons.** In the State (or equivalent) column, prefix or replace raw state values with a short, clear icon so status is scannable at a glance. Examples: @@ -131,6 +272,14 @@ Notes: - StoragePoolType: - Users can optionally provide `storagePoolType` (`FILE`, `UNIFIED`, `UNIFIED_LARGE_CAPACITY`). - `UNIFIED` and `UNIFIED_LARGE_CAPACITY` are only supported for **FLEX** service level. +- ONTAP Expert Mode (`mode` field): + - `mode` is an input parameter on `gcnv_storage_pool_create` and is returned in `gcnv_storage_pool_get` / `gcnv_storage_pool_list` responses. + - **Always ask the user for `mode` if it is not stated in their request.** Do not silently default — present the two options explicitly: + - `DEFAULT` — standard pool. ONTAP tools (`ontap_*`) are not available. + - `ONTAP` — expert mode. Enables `ontap_*` tools for ONTAP volumes, snapshots, LUNs, QoS policies, SnapMirror, SnapLock, EBR, litigations, export policies, CIFS, and more. + - Example prompt: _"Should this pool be in standard mode (`DEFAULT`) or ONTAP expert mode (`ONTAP`)?"_ + - Only skip asking if the user has already specified `mode` in their request. + - The server accepts `mode` case-insensitively (`"ontap"` and `"ONTAP"` both work). - In simple terms: - **FLEX** is the newer service level focused on flexibility (smaller minimum sizes and, in some regions, more independent performance scaling). It is also available in many more regions. - **STANDARD / PREMIUM / EXTREME** are the classic tiers; Premium and Extreme are higher-performance tiers than Standard. @@ -269,6 +418,134 @@ Notes: - `gcnv_quota_rule_list` - `gcnv_quota_rule_update` +### ONTAP Expert Mode + +ONTAP Expert Mode is enabled by creating a storage pool with `mode: ONTAP`. Once a pool is in ONTAP mode, it exposes the full ONTAP REST API surface through the MCP server. + +**Common parameters for all ONTAP tools** (always required, **except** `ontap_audit_log` which only takes `action` and optionally `outputDir`): + +- `projectId` — GCP project ID or numeric project number (e.g. `"my-project"` or `"123456789"`). Both forms are accepted. +- `locationId` — GCP region/location (e.g. `"us-east1"`). +- `storagePoolId` — GCP storage pool resource name ID (e.g. `"my-pool"`). + +#### Tool Selection — Which Tool to Use + +Choose the right tool based on what the user is asking to do: + +| User wants to... | Use this tool | Why | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | +| List SVMs, create/list/get/delete ONTAP volumes | Dedicated tools (`ontap_volume_*`, `ontap_svm_list`) | Simpler interface, auto-resolves SVM/aggregate names | +| Create/list/delete ONTAP snapshots | Dedicated tools (`ontap_snapshot_*`) | Simpler interface | +| Create/list/get/delete LUNs | Dedicated tools (`ontap_lun_*`) | Simpler interface, auto-resolves SVM name | +| Check async job status | `ontap_job_get` | Purpose-built for job polling | +| Manage QoS policies, SnapMirror, export policies, CIFS shares, igroups, snapshot policies, SnapLock, EBR, litigations, schedules, or any other ONTAP resource | `ontap_discover` → `ontap_execute` | Generic workflow for the full API surface | +| Not sure what ONTAP APIs are available | `ontap_discover` (no arguments) | Lists all resource categories | + +**Rule of thumb:** If a dedicated `ontap_*` tool exists for the operation, prefer it. Otherwise, use discover + execute. **Exception:** If a resource was created via `ontap_execute` (e.g. FlexCache, SnapMirror, QoS policy), always use `ontap_discover` + `ontap_execute` to manage and delete it — the dedicated volume/snapshot tools use generic ONTAP endpoints that may not work for specialized resource types. + +#### Dedicated Convenience Tools + +These tools provide a simpler interface for common operations and auto-resolve SVM/aggregate names. + +**SVMs:** + +- `ontap_svm_list` — List SVMs on the pool. Each ONTAP pool has one SVM and one aggregate. Returns the SVM name and aggregate name, which are required for volume and LUN creation. **Call this first** if the user hasn't provided SVM or aggregate names. + +**Volumes:** + +- `ontap_volume_create` — Create a volume. Required: `name`, `size`. Optional: `svmName`, `aggregateName` (auto-resolved via `ontap_svm_list` if omitted), `nasPath`. + - **Naming**: letters, numbers, and underscores only (e.g. `"my_volume"`). ONTAP does not permit hyphens in volume names. + - **Size**: string with unit suffix (e.g. `"5GB"`, `"500MB"`, `"1TB"`). + - **`nasPath`**: junction path in the SVM namespace (e.g. `"/my_volume"`). Required for NAS/SMB access — a CIFS share cannot be created without it. Must be set at creation time; cannot be changed afterwards. +- `ontap_volume_list` — List volumes. Optional: `maxRecords`. +- `ontap_volume_get` — Get volume details by UUID. Required: `volumeUuid`. +- `ontap_volume_delete` — Delete volume by UUID. Required: `volumeUuid`. Returns an async job — poll with `ontap_job_get`. + +**SMB/CIFS Volume Workflow:** + +To create an SMB-accessible volume and share, follow this sequence: + +1. `ontap_svm_list` — retrieve the SVM name and aggregate name. +2. `ontap_volume_create` with `nasPath` set (e.g. `nasPath: "/my_volume"`) — mounts the volume in the SVM namespace at creation time. **Do not skip `nasPath`** — the CIFS share creation will fail if the volume has no junction path. +3. `ontap_job_get` — poll until `state: "success"`. +4. `ontap_discover` with `resource="cifs_share"` — find the POST endpoint and body format. +5. `ontap_execute` `POST /api/protocols/cifs/shares` with body: `{"name":"","path":"","svm":{"name":""}}`. +6. `ontap_job_get` — poll the CIFS share creation job until `state: "success"`. + +**Snapshots:** + +- `ontap_snapshot_create` — Create a snapshot. Required: `volumeUuid`, `name`. Returns an async job. +- `ontap_snapshot_list` — List snapshots for a volume. Required: `volumeUuid`. Optional: `maxRecords`. +- `ontap_snapshot_delete` — Delete a snapshot. Required: `volumeUuid`, `snapshotUuid`. Returns an async job. + +**LUNs:** + +- `ontap_lun_create` — Create a LUN. Required: `name` (full path, e.g. `"/vol/vol1/lun1"`), `volumeName`, `size` (e.g. `"1GB"`), `osType` (`linux`, `windows`, `vmware`, `aix`, `hpux`, `solaris`, `xen`). Optional: `svmName` (auto-resolved if omitted). +- `ontap_lun_list` — List LUNs. Optional: `maxRecords`. +- `ontap_lun_get` — Get LUN details by UUID. Required: `lunUuid`. +- `ontap_lun_delete` — Delete LUN by UUID. Required: `lunUuid`. + +**Jobs:** + +- `ontap_job_get` — Get async job status by UUID. Required: `jobUuid`. Poll until `state` is `"success"` or `"failure"`. Always recommend this after any mutating operation. + +#### Generic Discovery + Execution + +For any ONTAP REST API operation **not covered by a dedicated tool** (QoS policies, SnapMirror, export policies, CIFS shares, igroups, SnapLock, EBR, litigations, snapshot policies, schedules, etc.), follow this two-step workflow: + +**Step 1 — Discover the endpoint:** + +Call `ontap_discover` to find the correct API path, method, and body format. + +- No arguments → lists all resource categories with endpoint counts. Present as a table. +- `resource="qos_policy"` → returns all endpoints for that category (GET, POST, PATCH, DELETE) with paths, descriptions, and body hints. +- `search="legal hold"` → keyword search across all resources (matches names, descriptions, paths, and aliases). +- Some endpoints include `decision: "denied"` and `decisionReason`. Do **not** call `ontap_execute` on those paths — they fail at preflight with `scope_denied`. Prefer another method or path in the same category, or the GCNV tool named in `suggestedTool` or `scopeBoundaryNote` when present. +- If `scopeBoundaryNote` is present on the response, treat it as binding for that discover result. + +Available resource categories: `svm`, `volume`, `lun`, `qtree`, `snapshot`, `qos_policy`, `snapshot_policy`, `flexcache`, `quota_rule`, `snaplock`, `ebr_policy`, `ebr_operation`, `litigation`, `job`, `schedule`, `snapmirror`, `snapmirror_policy`, `export_policy`, `cifs_share`, `cifs_service`, `igroup`, `ip_interface`, `cluster_peer`, `svm_peer`, `svm_peer_permission`. + +> **Out of scope:** `/api/private/cli/*` paths are rejected at preflight with a `scope_denied` envelope. See "Scope-boundary denials" above for the full rule. + +**Step 2 — Execute the call:** + +Call `ontap_execute` with the endpoint info from Step 1. + +| Parameter | Required | Description | +| --------------- | -------------- | -------------------------------------------------------------------------------------- | +| `projectId` | Yes | GCP project ID or number | +| `locationId` | Yes | GCP region (e.g. `"us-east1"`) | +| `storagePoolId` | Yes | Pool resource name ID | +| `method` | Yes | `GET`, `POST`, `PATCH`, or `DELETE` | +| `ontapApiPath` | Yes | Path starting with `/api/` from discover results | +| `body` | For POST/PATCH | **JSON string** of the request body. The server auto-wraps it in a `body:{}` envelope. | +| `queryParams` | Optional | **JSON string** of query parameters | +| `confirmDelete` | For DELETE | Must be `true` to prevent accidental deletion | + +**Critical:** `body` and `queryParams` must be **JSON strings**, not objects. Build the object, then serialize it: + +- Correct: `body: '{"name":"my-policy","fixed":{"max_throughput_iops":"1000"}}'` +- Wrong: `body: {"name":"my-policy","fixed":{"max_throughput_iops":"1000"}}` + +GET requests default to `max_records=20`. Pass a higher value in `queryParams` to retrieve more: `queryParams: '{"max_records":"100"}'`. + +**Step 3 — Poll the job (for mutating operations):** + +POST, PATCH, and DELETE responses include async job info. Extract the job UUID and poll with `ontap_job_get` until `state` is `"success"` or `"failure"`. Do not assume the operation succeeded from the initial response alone. + +**Example — Create a QoS policy:** + +1. Discover: `ontap_discover` with `resource="qos_policy"` → find the POST endpoint path and body hint. +2. Execute: `ontap_execute` with `method: "POST"`, `ontapApiPath: "/api/storage/qos/policies"`, `body: '{"name":"my-policy","svm":{"name":"vs0"},"fixed":{"max_throughput_iops":"1000"}}'`. +3. Poll: Extract `job.uuid` from response → `ontap_job_get` with `jobUuid`. + +#### ONTAP Safety and Error Handling + +- **Confirmation:** All ONTAP create, update, and delete operations are disruptive. Ask the user for explicit confirmation before executing. For DELETE, `confirmDelete: true` is enforced by the tool. +- **Blocked operations:** Some operations (e.g. `PATCH`/`DELETE` on `/api/storage/volumes`, anything under `/api/private/cli/`) are rejected by preflight, the proxy, or ONTAP RBAC and surface as a `scope_denied` envelope. Treat it as terminal (full rule: "Scope-boundary denials" above) and, if the envelope's `suggested_tool` is missing, fall back to suggesting the equivalent GCNV control-plane tool (e.g. `gcnv_volume_update`, `gcnv_volume_delete`). +- **Error responses:** `ontap_execute` returns structured ONTAP error details including HTTP status code, error message, and a `suggestion` field with actionable guidance. Surface all three to the user. +- **Path validation:** `ontapApiPath` must start with `/api/`. If the user provides a path without this prefix, prepend it or ask them to correct it. + ### Operations (Monitoring) - `gcnv_operation_get` @@ -296,6 +573,18 @@ Notes: - Highlight the failing parameter. - Provide actionable guidance (fix invalid region, invalid ID, missing field, insufficient permission, etc.). +### ONTAP-Specific Errors + +- **`scope_denied` envelope** — terminal denial from preflight, the proxy, or ONTAP RBAC. **Do not retry.** Full rule and envelope shape: "Scope-boundary denials" above. +- **"blocked by proxy rule engine"** — legacy error text without a `scope_denied` envelope. Treat it the same way: terminal, and suggest the equivalent GCNV control-plane tool (e.g. `gcnv_volume_update` instead of `PATCH /api/storage/volumes`). +- **ONTAP 4xx/5xx errors** — `ontap_execute` returns the HTTP status code, ONTAP error message, and a `suggestion` field. Surface all three. Common causes: incorrect body structure (re-run `ontap_discover` to check the body hint), missing required fields, or invalid UUIDs. +- **"ontapApiPath must start with /api/"** — the path is malformed. Use `ontap_discover` to find the correct path. +- **"Invalid JSON string for parameter"** — the `body` or `queryParams` string is not valid JSON. Check for unescaped quotes, trailing commas, or missing braces. +- **Invalid `ontap_fields` / field rejected** — ONTAP REST API field names are strict and endpoint-specific. Do not guess field names. When a field is rejected: + 1. Remove the rejected field and retry with the remaining fields. + 2. If you are unsure which fields are valid, call the endpoint **without** `ontap_fields` first. The default response contains the base fields — inspect their names to discover valid field names for that endpoint. + 3. Never retry with the same rejected field name or a variation of it (e.g. do not try `creation_time` then `creationTime` then `created_time`). If a field does not exist, it does not exist. + --- # Model Expectations @@ -303,5 +592,7 @@ Notes: - Ask follow-up questions instead of assuming missing parameters. - Keep answers short unless the user asks for details. - When returning tool results, highlight only key fields unless the user requests full JSON. -- **For list tool results:** Always present the items in a **tabular (markdown table) format**—do not show raw JSON by default. +- **For list tool results:** Always present items in a **markdown table**—do not show raw JSON by default. This applies to all GCNV and ONTAP list responses, including `ontap_discover` results (categories as a table; endpoints as a table with body hints when the user needs them for an execute call). +- **For ONTAP job polling:** After any mutating ONTAP operation, always extract the job UUID and suggest the user poll with `ontap_job_get`. Report the final job state clearly (`"success"` or `"failure"` with the error message). - Maintain safety-first decision making for all operations. +- **ONTAP tool routing:** When a user asks about an ONTAP operation, first check if a dedicated tool exists (SVM, volume, snapshot, LUN, job). If yes, use it — unless the resource was created via `ontap_execute` (e.g. FlexCache, SnapMirror, QoS policy), in which case use `ontap_discover` + `ontap_execute` to manage and delete it. Never skip the discover step for advanced operations — the body hints are essential for constructing correct requests. diff --git a/eslint.config.mjs b/eslint.config.mjs index 2062014..414c4bc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -49,6 +49,7 @@ export default [ // Tests often use async callbacks without awaits, loose assertions, and string patterns. '@typescript-eslint/require-await': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/unbound-method': 'off', 'no-useless-escape': 'off', }, }, diff --git a/package-lock.json b/package-lock.json index a1c4b2b..16e006d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@google-cloud/local-auth": "3.0.1", - "@google-cloud/netapp": "^0.16.0", + "@google-cloud/netapp": "^0.18.0", "@modelcontextprotocol/sdk": "^1.20.1", "axios": "^1.6.3", "pino": "^9.5.0", @@ -825,9 +825,9 @@ } }, "node_modules/@google-cloud/netapp": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@google-cloud/netapp/-/netapp-0.16.0.tgz", - "integrity": "sha512-X+7mn/VE/TOmoJWFGN+lX7Wen8q8AYshCLBltFXe86XdlpO4WVYZjqvgTrkFryzpmZMK9Rj0tBOaPIOhYrcg8A==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@google-cloud/netapp/-/netapp-0.18.0.tgz", + "integrity": "sha512-Ih2UaLGUBiUXogeEU1467Q0tTLbGCZKpRES5qj/yVLDFPv0byKBwVtgnfWe7Kj/9agiUK6zs9aQhcqqIpJJMwQ==", "license": "Apache-2.0", "dependencies": { "google-gax": "^5.0.0" @@ -837,9 +837,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", - "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", @@ -1595,15 +1595,6 @@ "win32" ] }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -3757,29 +3748,16 @@ } }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/https-proxy-agent": { @@ -4817,9 +4795,9 @@ "license": "MIT" }, "node_modules/proto3-json-serializer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.3.tgz", - "integrity": "sha512-iUi7jGLuECChuoUwtvf6eXBDcFXTHAt5GM6ckvtD3RqD+j2wW0GW6WndPOu9IWeUk7n933lzrskcNMHJy2tFSw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", "license": "Apache-2.0", "dependencies": { "protobufjs": "^7.4.0" @@ -4829,9 +4807,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -5483,13 +5461,13 @@ } }, "node_modules/teeny-request": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.0.tgz", - "integrity": "sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.2.tgz", + "integrity": "sha512-Xj0ZAQ0CeuQn6UxCDPLbFRlgcSTUEyO3+wiepr2grjIjyL/lMMs1Z4OwXn8kLvn/V1OuaEP0UY7Na6UDNNsYrQ==", "license": "Apache-2.0", "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "stream-events": "^1.0.5" }, @@ -5497,31 +5475,6 @@ "node": ">=18" } }, - "node_modules/teeny-request/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/teeny-request/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", diff --git a/package.json b/package.json index 84e7c8e..4df996f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "files": [ "build", + "src/resources", "GEMINI.md", "gemini-extension.json", "README.md", @@ -67,7 +68,7 @@ "vitest": "^3.2.4" }, "dependencies": { - "@google-cloud/netapp": "^0.16.0", + "@google-cloud/netapp": "^0.18.0", "@google-cloud/local-auth": "3.0.1", "@modelcontextprotocol/sdk": "^1.20.1", "axios": "^1.6.3", diff --git a/src/registry/register-tools.test.ts b/src/registry/register-tools.test.ts index 392842f..841a65f 100644 --- a/src/registry/register-tools.test.ts +++ b/src/registry/register-tools.test.ts @@ -14,7 +14,7 @@ describe('registerAllTools', () => { registerAllTools(fakeMcpServer); // Keep this count in sync with register-tools.ts - expect(calls.length).toBe(69); + expect(calls.length).toBe(85); // Each registration uses the tool's name as the key for (const c of calls) { diff --git a/src/registry/register-tools.ts b/src/registry/register-tools.ts index 5c3231c..140e42e 100644 --- a/src/registry/register-tools.ts +++ b/src/registry/register-tools.ts @@ -184,6 +184,44 @@ import { listHostGroupsHandler, updateHostGroupHandler, } from '../tools/handlers/host-group-handler.js'; +import { + ontapSvmListTool, + ontapVolumeCreateTool, + ontapVolumeListTool, + ontapVolumeGetTool, + ontapVolumeDeleteTool, + ontapJobGetTool, + ontapSnapshotCreateTool, + ontapSnapshotListTool, + ontapSnapshotDeleteTool, + ontapLunCreateTool, + ontapLunListTool, + ontapLunGetTool, + ontapLunDeleteTool, +} from '../tools/ontap-tools.js'; +import { + ontapSvmListHandler, + ontapVolumeCreateHandler, + ontapVolumeListHandler, + ontapVolumeGetHandler, + ontapVolumeDeleteHandler, + ontapJobGetHandler, + ontapSnapshotCreateHandler, + ontapSnapshotListHandler, + ontapSnapshotDeleteHandler, + ontapLunCreateHandler, + ontapLunListHandler, + ontapLunGetHandler, + ontapLunDeleteHandler, +} from '../tools/handlers/ontap-handler.js'; +import { ontapDiscoverTool } from '../tools/ontap-discover-tool.js'; +import { ontapDiscoverHandler } from '../tools/handlers/ontap-discover-handler.js'; +import { ontapExecuteTool } from '../tools/ontap-execute-tool.js'; +import { ontapExecuteHandler } from '../tools/handlers/ontap-execute-handler.js'; +import { ontapAuditLogTool } from '../tools/ontap-audit-log-tool.js'; +import { ontapAuditLogHandler } from '../tools/handlers/ontap-audit-log-handler.js'; +import { withAuditLog } from '../utils/ontap-audit-logger.js'; +import { ToolConfig, ToolHandler } from '../types/tool.js'; /** * Register all tools and their handlers to the tool registry @@ -377,4 +415,31 @@ export function registerAllTools(mcpServer: McpServer) { mcpServer.registerTool(getHostGroupTool.name, getHostGroupTool, getHostGroupHandler); mcpServer.registerTool(listHostGroupsTool.name, listHostGroupsTool, listHostGroupsHandler); mcpServer.registerTool(updateHostGroupTool.name, updateHostGroupTool, updateHostGroupHandler); + + // Register ONTAP Expert Mode tools -- audit log control + mcpServer.registerTool(ontapAuditLogTool.name, ontapAuditLogTool, ontapAuditLogHandler); + + // Register ONTAP Expert Mode tools + // withAuditLog wraps each handler to record calls when logging is enabled. + const ontapTools: { tool: ToolConfig; handler: ToolHandler }[] = [ + { tool: ontapDiscoverTool, handler: ontapDiscoverHandler }, + { tool: ontapExecuteTool, handler: ontapExecuteHandler }, + { tool: ontapSvmListTool, handler: ontapSvmListHandler }, + { tool: ontapVolumeCreateTool, handler: ontapVolumeCreateHandler }, + { tool: ontapVolumeListTool, handler: ontapVolumeListHandler }, + { tool: ontapVolumeGetTool, handler: ontapVolumeGetHandler }, + { tool: ontapVolumeDeleteTool, handler: ontapVolumeDeleteHandler }, + { tool: ontapJobGetTool, handler: ontapJobGetHandler }, + { tool: ontapSnapshotCreateTool, handler: ontapSnapshotCreateHandler }, + { tool: ontapSnapshotListTool, handler: ontapSnapshotListHandler }, + { tool: ontapSnapshotDeleteTool, handler: ontapSnapshotDeleteHandler }, + { tool: ontapLunCreateTool, handler: ontapLunCreateHandler }, + { tool: ontapLunListTool, handler: ontapLunListHandler }, + { tool: ontapLunGetTool, handler: ontapLunGetHandler }, + { tool: ontapLunDeleteTool, handler: ontapLunDeleteHandler }, + ]; + + for (const { tool, handler } of ontapTools) { + mcpServer.registerTool(tool.name, tool, withAuditLog(handler, tool.name)); + } } diff --git a/src/resources/ontap-api-index.json b/src/resources/ontap-api-index.json new file mode 100644 index 0000000..8b5574b --- /dev/null +++ b/src/resources/ontap-api-index.json @@ -0,0 +1,3592 @@ +{ + "version": "1.1", + "note": "Auto-generated from ONTAP API definitions. Use ontap_discover to search. Use ontap_execute to call.", + "synonyms": { + "smb": ["cifs_share", "cifs_service"], + "cifs": ["cifs_share", "cifs_service"], + "nfs": ["export_policy"], + "protect": ["snapshot", "snapmirror", "snapshot_policy"], + "data protection": ["snapmirror", "snapshot"], + "backup": ["snapshot", "snapshot_policy"], + "replication": ["snapmirror", "snapmirror_policy"], + "mirror": ["snapmirror", "snapmirror_policy"], + "dr": ["snapmirror"], + "worm": ["snaplock", "litigation"], + "regulatory": ["snaplock", "litigation"], + "compliance": ["snaplock", "litigation"], + "block storage": ["lun", "igroup"], + "iscsi": ["lun", "igroup"], + "san": ["lun", "igroup"], + "fc": ["lun", "igroup"], + "host mapping": ["igroup"], + "lun map": ["igroup"], + "peer": ["cluster_peer", "svm_peer", "svm_peer_permission"], + "peering": ["cluster_peer", "svm_peer"], + "svm peer": ["svm_peer", "svm_peer_permission"], + "intercluster": ["cluster_peer", "ip_interface"], + "cross-cluster": ["cluster_peer", "snapmirror"], + "snap": ["snapshot", "snapshot_policy", "snaplock", "snapmirror"], + "retention": ["ebr_policy", "ebr_operation", "snaplock", "snapshot_policy"], + "ebr": ["ebr_policy", "ebr_operation"], + "event retention": ["ebr_policy", "ebr_operation"], + "vol": ["volume"], + "provision": ["volume", "lun"], + "cli": ["private_cli"], + "command line": ["private_cli"], + "dns": ["name_services_dns"], + "name service": ["name_services_dns", "name_services_ldap", "name_services_nis"], + "resolver": ["name_services_dns"], + "ldap": ["name_services_ldap", "cifs_service"], + "nis": ["name_services_nis"], + "unix user": ["name_services_unix_users"], + "unix group": ["name_services_unix_groups"], + "name mapping": ["name_services_name_mappings"], + "identity mapping": ["name_services_name_mappings"], + "hosts file": ["name_services_local_hosts"] + }, + "categories": [ + { + "resource": "cifs_service", + "keywords": [ + "SMB service", + "CIFS server", + "Active Directory", + "AD join", + "workgroup", + "windows server", + "domain join", + "AD domain", + "LDAP" + ], + "count": 5 + }, + { + "resource": "cifs_share", + "keywords": [ + "SMB share", + "Windows share", + "CIFS", + "file share", + "SMB", + "windows file", + "network share", + "shared folder", + "access control", + "ACL" + ], + "count": 5 + }, + { + "resource": "cluster", + "keywords": [ + "cluster identity", + "cluster name", + "cluster UUID", + "ONTAP version", + "system info", + "platform" + ], + "count": 1 + }, + { + "resource": "cluster_peer", + "keywords": [ + "cluster peering", + "remote cluster", + "intercluster", + "SnapMirror prerequisite", + "cross-cluster", + "trust relationship", + "partner cluster" + ], + "count": 5 + }, + { + "resource": "ebr_operation", + "keywords": [ + "EBR apply", + "retention operation", + "event retention apply", + "trigger retention" + ], + "count": 4 + }, + { + "resource": "ebr_policy", + "keywords": [ + "event based retention", + "EBR", + "retention policy", + "auto retain", + "event trigger", + "retain on event" + ], + "count": 5 + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "count": 10 + }, + { + "resource": "flexcache", + "keywords": [ + "cache", + "read acceleration", + "hot data", + "remote cache", + "caching volume", + "origin volume", + "low latency read" + ], + "count": 5 + }, + { + "resource": "igroup", + "keywords": [ + "initiator group", + "LUN mapping", + "SAN host", + "IQN", + "WWPN", + "host access", + "iscsi initiator", + "fibre channel initiator", + "host map", + "LUN mask" + ], + "count": 5 + }, + { + "resource": "ip_interface", + "keywords": [ + "network interface", + "IP address", + "LIF", + "data LIF", + "network endpoint", + "management LIF", + "intercluster LIF", + "subnet" + ], + "count": 2 + }, + { + "resource": "job", + "keywords": [ + "async job", + "operation status", + "poll", + "background job", + "progress", + "running", + "pending", + "completed", + "failed" + ], + "count": 2 + }, + { + "resource": "litigation", + "keywords": [ + "legal hold", + "WORM hold", + "compliance hold", + "e-discovery", + "litigation hold", + "preserve evidence", + "regulatory hold", + "audit trail" + ], + "count": 8 + }, + { + "resource": "lun", + "keywords": [ + "logical unit", + "block storage", + "san", + "iscsi", + "fibre channel", + "FC", + "block device", + "block volume", + "disk image", + "mapped", + "serial number" + ], + "count": 5 + }, + { + "resource": "name_services_dns", + "keywords": [ + "DNS server", + "DNS configuration", + "domain", + "resolver", + "name resolution", + "SVM DNS", + "DNS servers" + ], + "count": 5 + }, + { + "resource": "name_services_ldap", + "keywords": [ + "LDAP client", + "directory service", + "bind DN", + "base DN", + "LDAP server", + "LDAP configuration" + ], + "count": 5 + }, + { + "resource": "name_services_local_hosts", + "keywords": ["hosts file", "static host", "hostname resolution", "local host entry"], + "count": 5 + }, + { + "resource": "name_services_name_mappings", + "keywords": [ + "name mapping", + "identity mapping", + "UNIX to Windows", + "Windows to UNIX", + "user mapping" + ], + "count": 5 + }, + { + "resource": "name_services_nis", + "keywords": ["NIS", "yellow pages", "NIS domain", "NIS server"], + "count": 5 + }, + { + "resource": "name_services_unix_groups", + "keywords": ["local unix group", "GID", "group membership"], + "count": 5 + }, + { + "resource": "name_services_unix_users", + "keywords": ["local unix user", "UID", "passwd", "unix account"], + "count": 5 + }, + { + "resource": "private_cli", + "keywords": [ + "CLI command", + "ONTAP shell", + "diag", + "diagnostic", + "console", + "privilege", + "system shell" + ], + "count": 1 + }, + { + "resource": "qos_policy", + "keywords": [ + "quality of service", + "throughput", + "IOPS", + "bandwidth", + "rate limit", + "performance", + "throttle", + "latency", + "SLA", + "service level", + "max throughput", + "min throughput" + ], + "count": 5 + }, + { + "resource": "qtree", + "keywords": ["directory quota", "subdirectory", "security style", "unix permissions"], + "count": 5 + }, + { + "resource": "quota_rule", + "keywords": [ + "disk quota", + "space limit", + "file limit", + "usage limit", + "hard limit", + "soft limit", + "user quota", + "group quota", + "tree quota" + ], + "count": 5 + }, + { + "resource": "schedule", + "keywords": [ + "cron", + "interval", + "recurring", + "timer", + "periodic", + "frequency", + "automation", + "time-based" + ], + "count": 5 + }, + { + "resource": "snaplock", + "keywords": [ + "WORM", + "compliance", + "immutable", + "retention", + "tamper proof", + "write once read many", + "regulatory", + "compliance clock", + "enterprise lock", + "archive" + ], + "count": 3 + }, + { + "resource": "snapmirror", + "keywords": [ + "replication", + "mirror", + "disaster recovery", + "DR", + "replicate", + "data protection", + "DP", + "async mirror", + "cross-region", + "failover", + "resync", + "transfer", + "vault", + "baseline" + ], + "count": 5 + }, + { + "resource": "snapmirror_policy", + "keywords": [ + "replication policy", + "mirror policy", + "transfer schedule", + "async policy", + "sync policy", + "continuous policy", + "RPO" + ], + "count": 5 + }, + { + "resource": "snapshot", + "keywords": [ + "point in time", + "restore", + "rollback", + "recovery point", + "backup copy", + "snap", + "clone source", + "revert" + ], + "count": 4 + }, + { + "resource": "snapshot_policy", + "keywords": [ + "snapshot schedule", + "automated snapshot", + "scheduled backup", + "retention policy", + "auto snap", + "copies", + "daily", + "hourly", + "weekly" + ], + "count": 5 + }, + { + "resource": "svm", + "keywords": [ + "storage virtual machine", + "vserver", + "data SVM", + "tenant", + "namespace", + "aggregate" + ], + "count": 1 + }, + { + "resource": "svm_peer", + "keywords": [ + "SVM peering", + "vserver peer", + "SnapMirror prerequisite", + "cross-SVM", + "cross-vserver", + "SVM relationship" + ], + "count": 5 + }, + { + "resource": "svm_peer_permission", + "keywords": [ + "SVM peer permission", + "peer authorization", + "SnapMirror prerequisite", + "peer auth", + "authorize peering" + ], + "count": 5 + }, + { + "resource": "volume", + "keywords": [ + "flexvol", + "nas", + "san", + "disk", + "capacity", + "resize", + "grow", + "shrink", + "data store", + "provision", + "junction path", + "mount", + "tiering", + "efficiency", + "dedup", + "compression" + ], + "count": 5 + } + ], + "endpoints": [ + { + "resource": "cluster", + "keywords": [ + "cluster identity", + "cluster name", + "cluster UUID", + "ONTAP version", + "system info", + "platform" + ], + "method": "GET", + "path": "/api/cluster", + "pathParams": [], + "description": "Get cluster identity: name, UUID, version, location, contact. Required before cross-cluster SnapMirror setup — call on both source and destination pools to obtain cluster names/UUIDs.", + "hint": null, + "body": null + }, + { + "resource": "job", + "keywords": [ + "async job", + "operation status", + "poll", + "background job", + "progress", + "running", + "pending", + "completed", + "failed" + ], + "method": "GET", + "path": "/api/cluster/jobs", + "pathParams": [], + "description": "List all async jobs.", + "hint": null, + "body": null + }, + { + "resource": "job", + "keywords": [ + "async job", + "operation status", + "poll", + "background job", + "progress", + "running", + "pending", + "completed", + "failed" + ], + "method": "GET", + "path": "/api/cluster/jobs/{uuid}", + "pathParams": ["uuid"], + "description": "Get a job by UUID. Poll until state is success or failure.", + "hint": "Poll until state is 'success' or 'failure'.", + "body": null + }, + { + "resource": "cluster_peer", + "keywords": [ + "cluster peering", + "remote cluster", + "intercluster", + "SnapMirror prerequisite", + "cross-cluster", + "trust relationship", + "partner cluster" + ], + "method": "GET", + "path": "/api/cluster/peers", + "pathParams": [], + "description": "List all cluster peer relationships.", + "hint": "Check existing peering before creating SnapMirror relationships.", + "body": null + }, + { + "resource": "cluster_peer", + "keywords": [ + "cluster peering", + "remote cluster", + "intercluster", + "SnapMirror prerequisite", + "cross-cluster", + "trust relationship", + "partner cluster" + ], + "method": "POST", + "path": "/api/cluster/peers", + "pathParams": [], + "description": "Create a cluster peer relationship. Required before SVM peering and SnapMirror.", + "hint": "Must be done on both source and destination clusters (pools). Use the remote cluster's intercluster IP addresses.", + "body": { + "remote": { + "ip_addresses": [""] + }, + "authentication": { + "passphrase": "" + } + } + }, + { + "resource": "cluster_peer", + "keywords": [ + "cluster peering", + "remote cluster", + "intercluster", + "SnapMirror prerequisite", + "cross-cluster", + "trust relationship", + "partner cluster" + ], + "method": "GET", + "path": "/api/cluster/peers/{uuid}", + "pathParams": ["uuid"], + "description": "Get a cluster peer relationship by UUID.", + "hint": null, + "body": null + }, + { + "resource": "cluster_peer", + "keywords": [ + "cluster peering", + "remote cluster", + "intercluster", + "SnapMirror prerequisite", + "cross-cluster", + "trust relationship", + "partner cluster" + ], + "method": "PATCH", + "path": "/api/cluster/peers/{uuid}", + "pathParams": ["uuid"], + "description": "Update a cluster peer relationship.", + "hint": null, + "body": {} + }, + { + "resource": "cluster_peer", + "keywords": [ + "cluster peering", + "remote cluster", + "intercluster", + "SnapMirror prerequisite", + "cross-cluster", + "trust relationship", + "partner cluster" + ], + "method": "DELETE", + "path": "/api/cluster/peers/{uuid}", + "pathParams": ["uuid"], + "description": "Delete a cluster peer relationship.", + "hint": null, + "body": null + }, + { + "resource": "schedule", + "keywords": [ + "cron", + "interval", + "recurring", + "timer", + "periodic", + "frequency", + "automation", + "time-based" + ], + "method": "GET", + "path": "/api/cluster/schedules", + "pathParams": [], + "description": "List all job schedules.", + "hint": null, + "body": null + }, + { + "resource": "schedule", + "keywords": [ + "cron", + "interval", + "recurring", + "timer", + "periodic", + "frequency", + "automation", + "time-based" + ], + "method": "POST", + "path": "/api/cluster/schedules", + "pathParams": [], + "description": "Create a schedule. Body requires \"name\" and EITHER \"cron\" (object with minutes/hours/weekdays/months/days arrays) OR \"interval\" (ISO 8601 duration like \"PT1H\"), not both.", + "hint": "Provide EITHER 'cron' (object with minutes/hours/weekdays/months/days arrays) OR 'interval' (ISO 8601 duration like 'PT1H'), not both.", + "body": { + "name": "my_schedule", + "cron": { + "minutes": [0], + "hours": [0] + } + } + }, + { + "resource": "schedule", + "keywords": [ + "cron", + "interval", + "recurring", + "timer", + "periodic", + "frequency", + "automation", + "time-based" + ], + "method": "GET", + "path": "/api/cluster/schedules/{uuid}", + "pathParams": ["uuid"], + "description": "Get a schedule by UUID.", + "hint": null, + "body": null + }, + { + "resource": "schedule", + "keywords": [ + "cron", + "interval", + "recurring", + "timer", + "periodic", + "frequency", + "automation", + "time-based" + ], + "method": "PATCH", + "path": "/api/cluster/schedules/{uuid}", + "pathParams": ["uuid"], + "description": "Update a schedule.", + "hint": null, + "body": {} + }, + { + "resource": "schedule", + "keywords": [ + "cron", + "interval", + "recurring", + "timer", + "periodic", + "frequency", + "automation", + "time-based" + ], + "method": "DELETE", + "path": "/api/cluster/schedules/{uuid}", + "pathParams": ["uuid"], + "description": "Delete a schedule.", + "hint": null, + "body": null + }, + { + "resource": "name_services_dns", + "keywords": [ + "DNS server", + "DNS configuration", + "domain", + "resolver", + "name resolution", + "SVM DNS", + "DNS servers" + ], + "method": "GET", + "path": "/api/name-services/dns", + "pathParams": [], + "description": "List DNS configurations for SVMs.", + "hint": null, + "body": null + }, + { + "resource": "name_services_dns", + "keywords": [ + "DNS server", + "DNS configuration", + "domain", + "resolver", + "name resolution", + "SVM DNS", + "DNS servers" + ], + "method": "POST", + "path": "/api/name-services/dns", + "pathParams": [], + "description": "Create a DNS configuration for an SVM. Requires svm.uuid, domains, and servers.", + "hint": null, + "body": { + "svm": { + "uuid": "" + }, + "domains": ["example.com"], + "servers": ["10.0.0.1"] + } + }, + { + "resource": "name_services_dns", + "keywords": [ + "DNS server", + "DNS configuration", + "domain", + "resolver", + "name resolution", + "SVM DNS", + "DNS servers" + ], + "method": "GET", + "path": "/api/name-services/dns/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Get DNS configuration for a specific SVM.", + "hint": null, + "body": null + }, + { + "resource": "name_services_dns", + "keywords": [ + "DNS server", + "DNS configuration", + "domain", + "resolver", + "name resolution", + "SVM DNS", + "DNS servers" + ], + "method": "PATCH", + "path": "/api/name-services/dns/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Update DNS configuration for an SVM.", + "hint": null, + "body": { + "domains": ["example.com"], + "servers": ["10.0.0.1", "10.0.0.2"] + } + }, + { + "resource": "name_services_dns", + "keywords": [ + "DNS server", + "DNS configuration", + "domain", + "resolver", + "name resolution", + "SVM DNS", + "DNS servers" + ], + "method": "DELETE", + "path": "/api/name-services/dns/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Delete DNS configuration for an SVM.", + "hint": null, + "body": null + }, + { + "resource": "name_services_ldap", + "keywords": [ + "LDAP client", + "directory service", + "bind DN", + "base DN", + "LDAP server", + "LDAP configuration" + ], + "method": "GET", + "path": "/api/name-services/ldap", + "pathParams": [], + "description": "List LDAP client configurations for SVMs.", + "hint": null, + "body": null + }, + { + "resource": "name_services_ldap", + "keywords": [ + "LDAP client", + "directory service", + "bind DN", + "base DN", + "LDAP server", + "LDAP configuration" + ], + "method": "POST", + "path": "/api/name-services/ldap", + "pathParams": [], + "description": "Create an LDAP client configuration for an SVM. Requires svm.uuid, servers, and base_dn.", + "hint": null, + "body": { + "svm": { + "uuid": "" + }, + "servers": ["10.0.0.1"], + "base_dn": "dc=example,dc=com" + } + }, + { + "resource": "name_services_ldap", + "keywords": [ + "LDAP client", + "directory service", + "bind DN", + "base DN", + "LDAP server", + "LDAP configuration" + ], + "method": "GET", + "path": "/api/name-services/ldap/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Get LDAP configuration for a specific SVM.", + "hint": null, + "body": null + }, + { + "resource": "name_services_ldap", + "keywords": [ + "LDAP client", + "directory service", + "bind DN", + "base DN", + "LDAP server", + "LDAP configuration" + ], + "method": "PATCH", + "path": "/api/name-services/ldap/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Update LDAP configuration for an SVM.", + "hint": null, + "body": {} + }, + { + "resource": "name_services_ldap", + "keywords": [ + "LDAP client", + "directory service", + "bind DN", + "base DN", + "LDAP server", + "LDAP configuration" + ], + "method": "DELETE", + "path": "/api/name-services/ldap/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Delete LDAP configuration for an SVM.", + "hint": null, + "body": null + }, + { + "resource": "name_services_local_hosts", + "keywords": ["hosts file", "static host", "hostname resolution", "local host entry"], + "method": "GET", + "path": "/api/name-services/local-hosts", + "pathParams": [], + "description": "List local host entries (hosts file).", + "hint": null, + "body": null + }, + { + "resource": "name_services_local_hosts", + "keywords": ["hosts file", "static host", "hostname resolution", "local host entry"], + "method": "POST", + "path": "/api/name-services/local-hosts", + "pathParams": [], + "description": "Create a local host entry. Requires owner.uuid, address, and hostname.", + "hint": null, + "body": { + "owner": { + "uuid": "" + }, + "address": "10.0.0.1", + "hostname": "host1.example.com" + } + }, + { + "resource": "name_services_local_hosts", + "keywords": ["hosts file", "static host", "hostname resolution", "local host entry"], + "method": "GET", + "path": "/api/name-services/local-hosts/{owner.uuid}/{address}", + "pathParams": ["owner.uuid", "address"], + "description": "Get a local host entry by SVM and address.", + "hint": null, + "body": null + }, + { + "resource": "name_services_local_hosts", + "keywords": ["hosts file", "static host", "hostname resolution", "local host entry"], + "method": "PATCH", + "path": "/api/name-services/local-hosts/{owner.uuid}/{address}", + "pathParams": ["owner.uuid", "address"], + "description": "Update a local host entry.", + "hint": null, + "body": {} + }, + { + "resource": "name_services_local_hosts", + "keywords": ["hosts file", "static host", "hostname resolution", "local host entry"], + "method": "DELETE", + "path": "/api/name-services/local-hosts/{owner.uuid}/{address}", + "pathParams": ["owner.uuid", "address"], + "description": "Delete a local host entry.", + "hint": null, + "body": null + }, + { + "resource": "name_services_name_mappings", + "keywords": [ + "name mapping", + "identity mapping", + "UNIX to Windows", + "Windows to UNIX", + "user mapping" + ], + "method": "GET", + "path": "/api/name-services/name-mappings", + "pathParams": [], + "description": "List name mapping rules (UNIX-to-Windows or Windows-to-UNIX).", + "hint": null, + "body": null + }, + { + "resource": "name_services_name_mappings", + "keywords": [ + "name mapping", + "identity mapping", + "UNIX to Windows", + "Windows to UNIX", + "user mapping" + ], + "method": "POST", + "path": "/api/name-services/name-mappings", + "pathParams": [], + "description": "Create a name mapping rule. Requires svm.uuid, direction (win_unix or unix_win), index, pattern, and replacement.", + "hint": null, + "body": { + "svm": { + "uuid": "" + }, + "direction": "win_unix", + "index": 1, + "pattern": "DOMAIN\\\\(.+)", + "replacement": "\\1" + } + }, + { + "resource": "name_services_name_mappings", + "keywords": [ + "name mapping", + "identity mapping", + "UNIX to Windows", + "Windows to UNIX", + "user mapping" + ], + "method": "GET", + "path": "/api/name-services/name-mappings/{svm.uuid}/{direction}/{index}", + "pathParams": ["svm.uuid", "direction", "index"], + "description": "Get a specific name mapping rule.", + "hint": null, + "body": null + }, + { + "resource": "name_services_name_mappings", + "keywords": [ + "name mapping", + "identity mapping", + "UNIX to Windows", + "Windows to UNIX", + "user mapping" + ], + "method": "PATCH", + "path": "/api/name-services/name-mappings/{svm.uuid}/{direction}/{index}", + "pathParams": ["svm.uuid", "direction", "index"], + "description": "Update a name mapping rule.", + "hint": null, + "body": {} + }, + { + "resource": "name_services_name_mappings", + "keywords": [ + "name mapping", + "identity mapping", + "UNIX to Windows", + "Windows to UNIX", + "user mapping" + ], + "method": "DELETE", + "path": "/api/name-services/name-mappings/{svm.uuid}/{direction}/{index}", + "pathParams": ["svm.uuid", "direction", "index"], + "description": "Delete a name mapping rule.", + "hint": null, + "body": null + }, + { + "resource": "name_services_nis", + "keywords": ["NIS", "yellow pages", "NIS domain", "NIS server"], + "method": "GET", + "path": "/api/name-services/nis", + "pathParams": [], + "description": "List NIS configurations for SVMs.", + "hint": null, + "body": null + }, + { + "resource": "name_services_nis", + "keywords": ["NIS", "yellow pages", "NIS domain", "NIS server"], + "method": "POST", + "path": "/api/name-services/nis", + "pathParams": [], + "description": "Create a NIS configuration for an SVM. Requires svm.uuid, domain, and servers.", + "hint": null, + "body": { + "svm": { + "uuid": "" + }, + "domain": "example.com", + "servers": ["10.0.0.1"] + } + }, + { + "resource": "name_services_nis", + "keywords": ["NIS", "yellow pages", "NIS domain", "NIS server"], + "method": "GET", + "path": "/api/name-services/nis/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Get NIS configuration for a specific SVM.", + "hint": null, + "body": null + }, + { + "resource": "name_services_nis", + "keywords": ["NIS", "yellow pages", "NIS domain", "NIS server"], + "method": "PATCH", + "path": "/api/name-services/nis/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Update NIS configuration for an SVM.", + "hint": null, + "body": {} + }, + { + "resource": "name_services_nis", + "keywords": ["NIS", "yellow pages", "NIS domain", "NIS server"], + "method": "DELETE", + "path": "/api/name-services/nis/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Delete NIS configuration for an SVM.", + "hint": null, + "body": null + }, + { + "resource": "name_services_unix_groups", + "keywords": ["local unix group", "GID", "group membership"], + "method": "GET", + "path": "/api/name-services/unix-groups", + "pathParams": [], + "description": "List local UNIX groups.", + "hint": null, + "body": null + }, + { + "resource": "name_services_unix_groups", + "keywords": ["local unix group", "GID", "group membership"], + "method": "POST", + "path": "/api/name-services/unix-groups", + "pathParams": [], + "description": "Create a local UNIX group. Requires svm.uuid, name, and id (GID).", + "hint": null, + "body": { + "svm": { + "uuid": "" + }, + "name": "group1", + "id": 1001 + } + }, + { + "resource": "name_services_unix_groups", + "keywords": ["local unix group", "GID", "group membership"], + "method": "GET", + "path": "/api/name-services/unix-groups/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Get a local UNIX group by SVM and name.", + "hint": null, + "body": null + }, + { + "resource": "name_services_unix_groups", + "keywords": ["local unix group", "GID", "group membership"], + "method": "PATCH", + "path": "/api/name-services/unix-groups/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Update a local UNIX group.", + "hint": null, + "body": {} + }, + { + "resource": "name_services_unix_groups", + "keywords": ["local unix group", "GID", "group membership"], + "method": "DELETE", + "path": "/api/name-services/unix-groups/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Delete a local UNIX group.", + "hint": null, + "body": null + }, + { + "resource": "name_services_unix_users", + "keywords": ["local unix user", "UID", "passwd", "unix account"], + "method": "GET", + "path": "/api/name-services/unix-users", + "pathParams": [], + "description": "List local UNIX users.", + "hint": null, + "body": null + }, + { + "resource": "name_services_unix_users", + "keywords": ["local unix user", "UID", "passwd", "unix account"], + "method": "POST", + "path": "/api/name-services/unix-users", + "pathParams": [], + "description": "Create a local UNIX user. Requires svm.uuid, name, and id (UID).", + "hint": null, + "body": { + "svm": { + "uuid": "" + }, + "name": "user1", + "id": 1001 + } + }, + { + "resource": "name_services_unix_users", + "keywords": ["local unix user", "UID", "passwd", "unix account"], + "method": "GET", + "path": "/api/name-services/unix-users/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Get a local UNIX user by SVM and name.", + "hint": null, + "body": null + }, + { + "resource": "name_services_unix_users", + "keywords": ["local unix user", "UID", "passwd", "unix account"], + "method": "PATCH", + "path": "/api/name-services/unix-users/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Update a local UNIX user.", + "hint": null, + "body": {} + }, + { + "resource": "name_services_unix_users", + "keywords": ["local unix user", "UID", "passwd", "unix account"], + "method": "DELETE", + "path": "/api/name-services/unix-users/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Delete a local UNIX user.", + "hint": null, + "body": null + }, + { + "resource": "ip_interface", + "keywords": [ + "network interface", + "IP address", + "LIF", + "data LIF", + "network endpoint", + "management LIF", + "intercluster LIF", + "subnet" + ], + "method": "GET", + "path": "/api/network/ip/interfaces", + "pathParams": [], + "description": "List all IP interfaces (LIFs).", + "hint": null, + "body": null + }, + { + "resource": "ip_interface", + "keywords": [ + "network interface", + "IP address", + "LIF", + "data LIF", + "network endpoint", + "management LIF", + "intercluster LIF", + "subnet" + ], + "method": "GET", + "path": "/api/network/ip/interfaces/{uuid}", + "pathParams": ["uuid"], + "description": "Get an IP interface by UUID.", + "hint": null, + "body": null + }, + { + "resource": "private_cli", + "keywords": [ + "CLI command", + "ONTAP shell", + "diag", + "diagnostic", + "console", + "privilege", + "system shell" + ], + "method": "POST", + "path": "/api/private/cli", + "pathParams": [], + "description": "Execute an ONTAP CLI command.", + "hint": "Commands are validated by the ONTAP proxy rule engine. Requires ontapModeAdmin or ontapModeRead permission.", + "body": { + "input": "volume show", + "privilege": "admin" + } + }, + { + "resource": "cifs_service", + "keywords": [ + "SMB service", + "CIFS server", + "Active Directory", + "AD join", + "workgroup", + "windows server", + "domain join", + "AD domain", + "LDAP" + ], + "method": "GET", + "path": "/api/protocols/cifs/services", + "pathParams": [], + "description": "List all CIFS/SMB services.", + "hint": null, + "body": null + }, + { + "resource": "cifs_service", + "keywords": [ + "SMB service", + "CIFS server", + "Active Directory", + "AD join", + "workgroup", + "windows server", + "domain join", + "AD domain", + "LDAP" + ], + "method": "POST", + "path": "/api/protocols/cifs/services", + "pathParams": [], + "description": "Create a CIFS/SMB service. For AD-joined: requires svm, name, ad_domain (with fqdn, user, password). For workgroup mode: requires svm, name, workgroup.", + "hint": "For AD-joined: requires svm, name, ad_domain.fqdn, ad_domain.user, ad_domain.password. For workgroup: requires svm, name, workgroup.", + "body": { + "svm": { + "name": "" + }, + "name": "", + "ad_domain": { + "fqdn": "", + "user": "", + "password": "" + } + } + }, + { + "resource": "cifs_service", + "keywords": [ + "SMB service", + "CIFS server", + "Active Directory", + "AD join", + "workgroup", + "windows server", + "domain join", + "AD domain", + "LDAP" + ], + "method": "GET", + "path": "/api/protocols/cifs/services/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Get a CIFS service by SVM UUID.", + "hint": null, + "body": null + }, + { + "resource": "cifs_service", + "keywords": [ + "SMB service", + "CIFS server", + "Active Directory", + "AD join", + "workgroup", + "windows server", + "domain join", + "AD domain", + "LDAP" + ], + "method": "PATCH", + "path": "/api/protocols/cifs/services/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Update a CIFS service.", + "hint": null, + "body": {} + }, + { + "resource": "cifs_service", + "keywords": [ + "SMB service", + "CIFS server", + "Active Directory", + "AD join", + "workgroup", + "windows server", + "domain join", + "AD domain", + "LDAP" + ], + "method": "DELETE", + "path": "/api/protocols/cifs/services/{svm.uuid}", + "pathParams": ["svm.uuid"], + "description": "Delete a CIFS service.", + "hint": null, + "body": null + }, + { + "resource": "cifs_share", + "keywords": [ + "SMB share", + "Windows share", + "CIFS", + "file share", + "SMB", + "windows file", + "network share", + "shared folder", + "access control", + "ACL" + ], + "method": "GET", + "path": "/api/protocols/cifs/shares", + "pathParams": [], + "description": "List all CIFS/SMB shares.", + "hint": null, + "body": null + }, + { + "resource": "cifs_share", + "keywords": [ + "SMB share", + "Windows share", + "CIFS", + "file share", + "SMB", + "windows file", + "network share", + "shared folder", + "access control", + "ACL" + ], + "method": "POST", + "path": "/api/protocols/cifs/shares", + "pathParams": [], + "description": "Create a CIFS/SMB share.", + "hint": null, + "body": { + "name": "share1", + "svm": { + "name": "" + }, + "path": "/" + } + }, + { + "resource": "cifs_share", + "keywords": [ + "SMB share", + "Windows share", + "CIFS", + "file share", + "SMB", + "windows file", + "network share", + "shared folder", + "access control", + "ACL" + ], + "method": "GET", + "path": "/api/protocols/cifs/shares/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Get a CIFS share by SVM UUID and name.", + "hint": null, + "body": null + }, + { + "resource": "cifs_share", + "keywords": [ + "SMB share", + "Windows share", + "CIFS", + "file share", + "SMB", + "windows file", + "network share", + "shared folder", + "access control", + "ACL" + ], + "method": "PATCH", + "path": "/api/protocols/cifs/shares/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Update a CIFS share.", + "hint": null, + "body": {} + }, + { + "resource": "cifs_share", + "keywords": [ + "SMB share", + "Windows share", + "CIFS", + "file share", + "SMB", + "windows file", + "network share", + "shared folder", + "access control", + "ACL" + ], + "method": "DELETE", + "path": "/api/protocols/cifs/shares/{svm.uuid}/{name}", + "pathParams": ["svm.uuid", "name"], + "description": "Delete a CIFS share.", + "hint": null, + "body": null + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "GET", + "path": "/api/protocols/nfs/export-policies", + "pathParams": [], + "description": "List all NFS export policies.", + "hint": null, + "body": null + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "POST", + "path": "/api/protocols/nfs/export-policies", + "pathParams": [], + "description": "Create an NFS export policy.", + "hint": null, + "body": { + "name": "export1", + "svm": { + "name": "" + } + } + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "GET", + "path": "/api/protocols/nfs/export-policies/{id}", + "pathParams": ["id"], + "description": "Get an export policy by ID.", + "hint": null, + "body": null + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "PATCH", + "path": "/api/protocols/nfs/export-policies/{id}", + "pathParams": ["id"], + "description": "Update an export policy.", + "hint": null, + "body": {} + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "DELETE", + "path": "/api/protocols/nfs/export-policies/{id}", + "pathParams": ["id"], + "description": "Delete an export policy.", + "hint": null, + "body": null + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "GET", + "path": "/api/protocols/nfs/export-policies/{policy.id}/rules", + "pathParams": ["policy.id"], + "description": "List export policy rules.", + "hint": null, + "body": null + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "POST", + "path": "/api/protocols/nfs/export-policies/{policy.id}/rules", + "pathParams": ["policy.id"], + "description": "Create an export policy rule. Client match field is \"match\" (NOT \"address\" or \"host\"). Example clients entry: { \"match\": \"0.0.0.0/0\" }. ro_rule/rw_rule/superuser are arrays of security types (e.g. [\"any\"], [\"sys\"], [\"krb5\"]).", + "hint": "Client match field is 'match' (NOT 'address' or 'host'). Example: { \"clients\": [{ \"match\": \"0.0.0.0/0\" }], \"ro_rule\": [\"any\"], \"rw_rule\": [\"any\"] }. ro_rule/rw_rule/superuser are arrays of security types.", + "body": { + "clients": [ + { + "match": "0.0.0.0/0" + } + ], + "ro_rule": ["any"], + "rw_rule": ["any"], + "superuser": ["any"] + } + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "GET", + "path": "/api/protocols/nfs/export-policies/{policy.id}/rules/{index}", + "pathParams": ["policy.id", "index"], + "description": "Get an export policy rule.", + "hint": null, + "body": null + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "PATCH", + "path": "/api/protocols/nfs/export-policies/{policy.id}/rules/{index}", + "pathParams": ["policy.id", "index"], + "description": "Update an export policy rule.", + "hint": null, + "body": {} + }, + { + "resource": "export_policy", + "keywords": [ + "NFS export", + "NFS access", + "export rules", + "NFS share", + "unix share", + "linux share", + "network file system", + "export rule", + "client match", + "ro rule", + "rw rule", + "superuser", + "kerberos" + ], + "method": "DELETE", + "path": "/api/protocols/nfs/export-policies/{policy.id}/rules/{index}", + "pathParams": ["policy.id", "index"], + "description": "Delete an export policy rule.", + "hint": null, + "body": null + }, + { + "resource": "igroup", + "keywords": [ + "initiator group", + "LUN mapping", + "SAN host", + "IQN", + "WWPN", + "host access", + "iscsi initiator", + "fibre channel initiator", + "host map", + "LUN mask" + ], + "method": "GET", + "path": "/api/protocols/san/igroups", + "pathParams": [], + "description": "List all initiator groups.", + "hint": null, + "body": null + }, + { + "resource": "igroup", + "keywords": [ + "initiator group", + "LUN mapping", + "SAN host", + "IQN", + "WWPN", + "host access", + "iscsi initiator", + "fibre channel initiator", + "host map", + "LUN mask" + ], + "method": "POST", + "path": "/api/protocols/san/igroups", + "pathParams": [], + "description": "Create an initiator group. Requires name, svm, os_type (linux/windows/vmware). initiators[].name takes IQN (iSCSI) or WWPN (FC).", + "hint": "os_type is required (linux, windows, vmware, etc.). initiators[].name takes IQN (iSCSI) or WWPN (FC) values.", + "body": { + "name": "igroup1", + "svm": { + "name": "" + }, + "os_type": "linux", + "protocol": "iscsi", + "initiators": [ + { + "name": "" + } + ] + } + }, + { + "resource": "igroup", + "keywords": [ + "initiator group", + "LUN mapping", + "SAN host", + "IQN", + "WWPN", + "host access", + "iscsi initiator", + "fibre channel initiator", + "host map", + "LUN mask" + ], + "method": "GET", + "path": "/api/protocols/san/igroups/{uuid}", + "pathParams": ["uuid"], + "description": "Get an igroup by UUID.", + "hint": null, + "body": null + }, + { + "resource": "igroup", + "keywords": [ + "initiator group", + "LUN mapping", + "SAN host", + "IQN", + "WWPN", + "host access", + "iscsi initiator", + "fibre channel initiator", + "host map", + "LUN mask" + ], + "method": "PATCH", + "path": "/api/protocols/san/igroups/{uuid}", + "pathParams": ["uuid"], + "description": "Update an igroup.", + "hint": null, + "body": {} + }, + { + "resource": "igroup", + "keywords": [ + "initiator group", + "LUN mapping", + "SAN host", + "IQN", + "WWPN", + "host access", + "iscsi initiator", + "fibre channel initiator", + "host map", + "LUN mask" + ], + "method": "DELETE", + "path": "/api/protocols/san/igroups/{uuid}", + "pathParams": ["uuid"], + "description": "Delete an igroup.", + "hint": null, + "body": null + }, + { + "resource": "snapmirror_policy", + "keywords": [ + "replication policy", + "mirror policy", + "transfer schedule", + "async policy", + "sync policy", + "continuous policy", + "RPO" + ], + "method": "GET", + "path": "/api/snapmirror/policies", + "pathParams": [], + "description": "List all SnapMirror policies.", + "hint": null, + "body": null + }, + { + "resource": "snapmirror_policy", + "keywords": [ + "replication policy", + "mirror policy", + "transfer schedule", + "async policy", + "sync policy", + "continuous policy", + "RPO" + ], + "method": "POST", + "path": "/api/snapmirror/policies", + "pathParams": [], + "description": "Create a SnapMirror policy. Body uses \"retention\" array (NOT \"rules\"). Each retention entry: { \"label\": \"