diff --git a/.changeset/0010-v2-grammar.md b/.changeset/0010-v2-grammar.md new file mode 100644 index 0000000..d9380c0 --- /dev/null +++ b/.changeset/0010-v2-grammar.md @@ -0,0 +1,5 @@ +--- +"pan-scm-cli": major +--- + +**2.0: one CLI grammar, everywhere (breaking).** Object names are now positional (`scm set object tag prod --color Red`, not `--name prod`). Every containerized set/delete/show accepts `--folder`, `--snippet`, and `--device` with exactly-one enforcement (previously ~20 types were folder-only). All `load` commands support `--dry-run`; all `backup` commands support `--file` (including SASE). Flag unification: tags are always `--tags` (repeatable) — CSV `--tag` variants removed; `vlan-interface --tag` renamed `--vlan-id`; job ids are always `--id` (operations `--job-id` removed); dead `--list` flags removed from identity shows; dead `--mock` params removed from insights (use `SCM_MOCK=1`). Show commands gained `--max-results`. There are no deprecation aliases — update scripts to the new grammar (see the migration table in the release notes). diff --git a/AGENTS.md b/AGENTS.md index 3ffdfd2..9d48e02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ `pan-scm-cli` is a CLI tool for managing Palo Alto Networks Strata Cloud Manager (SCM) configurations. It manages objects, network configs, security policy, identity profiles, mobile agent settings, setup containers, and SASE/deployment resources through a consistent verb-based interface, plus monitoring (insights, incidents), device operations, local config retrieval, jobs, and firewall posture (BPA) assessment. -- **Package:** `pan-scm-cli` (version `1.5.0`), console script `scm`. +- **Package:** `pan-scm-cli` (version `2.0.0`), console script `scm`. - **Runtime:** Python `>=3.10,<3.14`, built with Typer + Click, validated with Pydantic v2. - **SDK dependency:** `pan-scm-sdk ^0.15.0` (i.e. `>=0.15.0,<0.16.0`) — verify compatibility when bumping. - **Packaging/build:** Poetry (`pyproject.toml`). @@ -65,13 +65,15 @@ The command reference under `docs-site/docs/cli/` is the source of truth for the For configuration verbs the CLI follows: ``` -scm [options] +scm [NAME] [options] ``` **Actions:** `set`, `delete`, `show`, `load`, `backup`, `move` **Categories:** `object`, `network`, `security`, `identity`, `mobile-agent`, `setup`, `sase` **Standalone top-level commands:** `commit`, `context`, `jobs`, `insights`, `incidents`, `local`, `operations`, `posture` +The object name is a positional `NAME` argument (required for `set`/`delete`/`move`, optional for `show` — omit it to list all). Exceptions: quarantined-device uses `--host-id`, network-location uses `--value`, and singletons (sase bgp-routing, mobile-agent global-setting) take no name. + ### Command Modules (`src/scm_cli/commands/`) - `objects.py` — object category: address, address group, application, application group, application filter, auto-tag-action, dynamic user group, external dynamic list, HIP object, HIP profile, HTTP server profile, log forwarding profile, quarantined-device, region, schedule, service, service group, syslog server profile, tag @@ -159,14 +161,16 @@ Lint config: ruff `line-length = 192`, `target-version = py310`, rules `E,F,I,B, ## Quick Reference ``` -scm [options] +scm [NAME] [options] ``` **Actions:** `set`, `delete`, `show`, `load`, `backup`, `move` **Categories:** `object`, `network`, `security`, `identity`, `mobile-agent`, `setup`, `sase` **Standalone:** `scm commit`, `scm context`, `scm jobs`, `scm insights`, `scm incidents`, `scm local`, `scm operations`, `scm posture` -Global options: `--version`/`-V`, `--region ` (override SCM API region for the invocation). +The object name is a positional `NAME` argument: required for `set`/`delete`/`move`, optional for `show` (omit it to list all — there is no `--list` flag). Every `show` supports `--output table|json|yaml` and `--max-results N`; every `load` supports `--dry-run`; every `backup` supports `--file`. Multi-value flags are repeatable (e.g. `--tags a --tags b`, `--members m1 --members m2`), not comma-separated. + +Global options: `--version`/`-V`, `--region ` (override SCM API region for the invocation), `--debug`. --- @@ -196,7 +200,7 @@ scm context current **Environment variable overrides:** `SCM_CLIENT_ID`, `SCM_CLIENT_SECRET`, `SCM_TSG_ID` (useful for CI/CD). -**Mock mode:** Set `SCM_MOCK=1` (or use `--mock` where available) to test without API credentials. Missing credentials without explicit mock mode fail with exit code 1 — there is no silent fallback. +**Mock mode:** Set `SCM_MOCK=1` to test without API credentials (`scm context test` also accepts `--mock`; other commands have no `--mock` flag). Missing credentials without explicit mock mode fail with exit code 1 — there is no silent fallback. **Note:** Legacy config files (`~/.scm-cli/config.yaml`, `.secrets.yaml`) are no longer supported — use contexts. @@ -204,7 +208,7 @@ scm context current ## Container Locations -Most commands require exactly ONE container location: +`set`/`delete`/`show` for object, network, security, and identity types require exactly ONE container location: | Flag | Description | |------|-------------| @@ -212,6 +216,8 @@ Most commands require exactly ONE container location: | `--snippet ` | Shared configuration snippet | | `--device ` | Device-level configuration | +Exceptions: `sase` types, `setup` folder/label/snippet/device, and quarantined-device take no container; `mobile-agent` is folder-only (SDK limitation). + --- ## Object Management (`scm ... object ...`) @@ -220,19 +226,19 @@ Most commands require exactly ONE container location: ```bash # IP netmask -scm set object address --folder Texas --name webserver --ip-netmask 10.1.1.10/32 --description "Web server" --tags web prod +scm set object address webserver --folder Texas --ip-netmask 10.1.1.10/32 --description "Web server" --tags web --tags prod # IP range -scm set object address --folder Texas --name dhcp-pool --ip-range 10.1.3.1-10.1.3.10 +scm set object address dhcp-pool --folder Texas --ip-range 10.1.3.1-10.1.3.10 # FQDN -scm set object address --folder Texas --name google-dns --fqdn dns.google.com +scm set object address google-dns --folder Texas --fqdn dns.google.com # IP wildcard -scm set object address --folder Texas --name wildcard --ip-wildcard 10.20.0.0/0.0.255.255 +scm set object address wildcard --folder Texas --ip-wildcard 10.20.0.0/0.0.255.255 ``` ```bash -scm show object address --folder Texas # list all -scm show object address --folder Texas --name webserver # show one -scm delete object address --folder Texas --name webserver [--force] +scm show object address --folder Texas # list all +scm show object address webserver --folder Texas # show one +scm delete object address webserver --folder Texas [--force] ``` **Constraints:** Name 1-63 chars. Exactly ONE address type required. @@ -240,8 +246,8 @@ scm delete object address --folder Texas --name webserver [--force] ### Address Groups ```bash -scm set object address-group --folder Texas --name web-servers --type static --members webserver1 webserver2 -scm set object address-group --folder Texas --name tagged-web --type dynamic --filter "'web' and 'prod'" +scm set object address-group web-servers --folder Texas --type static --members webserver1 --members webserver2 +scm set object address-group tagged-web --folder Texas --type dynamic --filter "'web' and 'prod'" ``` **Constraints:** Static requires `--members` (min 1). Dynamic uses tag-based filter expressions. @@ -249,9 +255,9 @@ scm set object address-group --folder Texas --name tagged-web --type dynamic --f ### Applications ```bash -scm set object application --folder Texas --name custom-app \ +scm set object application custom-app --folder Texas \ --category business-systems --subcategory erp --technology client-server --risk 3 \ - --ports tcp/8080 tcp/8443 --transfers-files --has-known-vulnerabilities + --ports tcp/8080 --ports tcp/8443 --transfers-files --has-known-vulnerabilities ``` **Risk levels:** 1-5. Boolean flags: `--evasive`, `--pervasive`, `--excessive-bandwidth-use`, `--used-by-malware`, `--transfers-files`, `--has-known-vulnerabilities`, `--tunnels-other-apps`, `--prone-to-misuse`, `--no-certifications`. @@ -259,20 +265,20 @@ scm set object application --folder Texas --name custom-app \ ### Application Groups ```bash -scm set object application-group --folder Texas --name web-apps --members web-browsing ssl http +scm set object application-group web-apps --folder Texas --members web-browsing --members ssl --members http ``` ### Application Filters ```bash -scm set object application-filter --folder Texas --name high-risk \ - --category business-systems --subcategory erp --technology client-server --risk 4 5 +scm set object application-filter high-risk --folder Texas \ + --category business-systems --subcategory erp --technology client-server --risk 4 --risk 5 ``` ### Tags ```bash -scm set object tag --folder Texas --name production --color Red --comments "Production environment" +scm set object tag production --folder Texas --color Red --comments "Production environment" ``` **42 valid colors:** Red, Green, Blue, Yellow, Copper, Orange, Purple, Gray, Light Green, Cyan, Light Gray, Blue Gray, Lime, Black, Gold, Brown, Olive, Maroon, Red-Orange, Yellow-Orange, Forest Green, Turquoise Blue, Azure Blue, Cerulean Blue, Midnight Blue, Medium Blue, Cobalt Blue, Violet Blue, Blue Violet, Medium Violet, Medium Rose, Lavender, Orchid, Thistle, Peach, Salmon, Magenta, Red Violet, Mahogany, Burnt Sienna, Chestnut. Color names are case-insensitive in the CLI validator but case-sensitive in the API. @@ -280,10 +286,10 @@ scm set object tag --folder Texas --name production --color Red --comments "Prod ### Services ```bash -scm set object service --folder Texas --name web-http --protocol tcp --port 80 -scm set object service --folder Texas --name web-https --protocol tcp --port 443 --timeout 3600 -scm set object service --folder Texas --name multi-port --protocol tcp --port 80,443,8080 -scm set object service --folder Texas --name port-range --protocol tcp --port 8000-8999 +scm set object service web-http --folder Texas --protocol tcp --port 80 +scm set object service web-https --folder Texas --protocol tcp --port 443 --timeout 3600 +scm set object service multi-port --folder Texas --protocol tcp --port 80,443,8080 +scm set object service port-range --folder Texas --protocol tcp --port 8000-8999 ``` **Constraints:** Protocol `tcp` or `udp`. Port: single, range (80-443), or comma-separated (80,443,8080). Service tags must reference existing tag objects. @@ -291,7 +297,7 @@ scm set object service --folder Texas --name port-range --protocol tcp --port 80 ### Service Groups ```bash -scm set object service-group --folder Texas --name web-services --members web-http web-https +scm set object service-group web-services --folder Texas --members web-http --members web-https ``` **Constraints:** Members must be unique; can reference services or other service groups (nested allowed). @@ -299,7 +305,7 @@ scm set object service-group --folder Texas --name web-services --members web-ht ### Dynamic User Groups ```bash -scm set object dynamic-user-group --folder Texas --name risky-users \ +scm set object dynamic-user-group risky-users --folder Texas \ --filter "'high-risk' and 'external'" --description "High risk external users" ``` @@ -308,11 +314,11 @@ scm set object dynamic-user-group --folder Texas --name risky-users \ ### External Dynamic Lists ```bash -scm set object external-dynamic-list --folder Texas --name bulletproof-ips \ +scm set object external-dynamic-list bulletproof-ips --folder Texas \ --type predefined_ip --url panw-bulletproof-ip-list -scm set object external-dynamic-list --folder Texas --name threat-ips \ +scm set object external-dynamic-list threat-ips --folder Texas \ --type ip --url https://example.com/threats.txt --recurring daily --hour 03 -scm set object external-dynamic-list --folder Texas --name blocked-domains \ +scm set object external-dynamic-list blocked-domains --folder Texas \ --type domain --url https://example.com/domains.txt --recurring hourly \ --username api_user --password secret --expand-domain ``` @@ -324,9 +330,9 @@ Predefined EDLs use short names (e.g. `panw-bulletproof-ip-list`) not full URLs. ### HIP Objects ```bash -scm set object hip-object --folder Texas --name corporate-host \ - --host-info-os Microsoft --host-info-managed true \ - --disk-encryption-enabled true +scm set object hip-object corporate-host --folder Texas \ + --host-info-os Microsoft --host-info-os-value All --host-info-managed \ + --disk-encryption-enabled ``` Criteria types: host info, network info, patch management, disk encryption, mobile device, certificate. HIP objects use a flattened field structure in validators (converted to nested SDK format). See `examples/hip-objects.yml`. @@ -334,7 +340,7 @@ Criteria types: host info, network info, patch management, disk encryption, mobi ### HIP Profiles ```bash -scm set object hip-profile --folder Texas --name corp-compliance --match "corporate-host is" +scm set object hip-profile corp-compliance --folder Texas --match "corporate-host is" ``` Reference HIP objects through match criteria with boolean operators (is/is-not). @@ -342,9 +348,8 @@ Reference HIP objects through match criteria with boolean operators (is/is-not). ### HTTP Server Profiles ```bash -scm set object http-server-profile --folder Texas --name webhook-profile \ - --server-name srv1 --server-address 192.168.1.1 --server-protocol HTTP \ - --server-port 8080 --server-http-method POST +scm set object http-server-profile webhook-profile --folder Texas \ + --servers '[{"name": "srv1", "address": "192.168.1.1", "protocol": "HTTP", "port": 8080, "http_method": "POST"}]' ``` **Important:** `http_method` is required for all server configs. The `server` field is singular from the API but YAML uses plural `servers` for consistency. @@ -352,7 +357,7 @@ scm set object http-server-profile --folder Texas --name webhook-profile \ ### Log Forwarding Profiles ```bash -scm set object log-forwarding-profile --folder Texas --name forward-all --enhanced-application-logging +scm set object log-forwarding-profile forward-all --folder Texas --enhanced-application-logging ``` **Important:** the `filter` field is required in match list entries despite SDK docs showing it optional. Match lists support traffic, threat, wildfire, url, data, tunnel, auth, decryption, dns-security log types. @@ -360,9 +365,9 @@ scm set object log-forwarding-profile --folder Texas --name forward-all --enhanc ### Syslog Server Profiles ```bash -scm set object syslog-server-profile --folder Texas --name syslog-central \ +scm set object syslog-server-profile syslog-central --folder Texas \ --server-name syslog1 --server-address 192.168.1.100 \ - --server-transport UDP --server-port 514 --server-format BSD --server-facility LOG_USER + --transport UDP --port 514 --format BSD --facility LOG_USER ``` **Transport:** UDP, TCP (SSL not supported by SDK). **Format:** BSD, IETF. **Facilities:** LOG_USER, LOG_LOCAL0–LOG_LOCAL7. Uses `fetch()` (not `get()`) in the SDK client for retrieval. @@ -370,29 +375,29 @@ scm set object syslog-server-profile --folder Texas --name syslog-central \ ### Schedules ```bash -scm set object schedule --folder Texas --name maintenance-window \ - --saturday 02:00-06:00 --sunday 02:00-06:00 +scm set object schedule maintenance-window --folder Texas \ + --schedule-type recurring-weekly --saturday 02:00-06:00 --sunday 02:00-06:00 ``` ### Regions ```bash -scm set object region --folder Texas --name us-east --address 10.0.0.0/8 172.16.0.0/12 +scm set object region us-east --folder Texas --address 10.0.0.0/8 --address 172.16.0.0/12 ``` ### Auto Tag Actions ```bash -scm set object auto-tag-action --folder Texas --name auto-tag-threats +scm set object auto-tag-action auto-tag-threats --folder Texas ``` ### Quarantined Devices ```bash -scm show object quarantined-device --folder Texas +scm show object quarantined-device [--host-id ] [--serial-number ] ``` -Manage quarantined devices (list/set/delete) — see `docs-site/docs/cli/objects/quarantined-device.md`. +Manage quarantined devices (list/set/delete) — identified by `--host-id`, no positional name and no container flags. See `docs-site/docs/cli/objects/quarantined-device.md`. --- @@ -401,9 +406,9 @@ Manage quarantined devices (list/set/delete) — see `docs-site/docs/cli/objects ### Security Zones ```bash -scm set network zone --folder Texas --name trust --interfaces ethernet1/1 ethernet1/2 +scm set network zone trust --folder Texas --mode layer3 --interfaces ethernet1/1 --interfaces ethernet1/2 scm show network zone --folder Texas -scm delete network zone --folder Texas --name trust [--force] +scm delete network zone trust --folder Texas [--force] ``` ### Other Network Objects @@ -446,19 +451,20 @@ All follow `scm network `: ### Security Rules ```bash -scm set security rule --folder Texas --name allow-web \ +scm set security rule allow-web --folder Texas \ --source-zones trust --destination-zones untrust \ --source-addresses any --destination-addresses web-servers \ - --applications web-browsing ssl --services application-default \ + --applications web-browsing --applications ssl --services application-default \ --action allow --log-end --description "Allow web traffic" --rulebase pre scm show security rule --folder Texas -scm show security rule --folder Texas --name allow-web -scm delete security rule --folder Texas --name allow-web [--force] +scm show security rule allow-web --folder Texas +scm delete security rule allow-web --folder Texas [--force] # Move (reorder) -scm move security rule --folder Texas --name allow-web --insert before --reference deny-all --rulebase pre -scm move security rule --folder Texas --name allow-web --insert after --reference allow-dns --rulebase pre +scm move security rule allow-web --folder Texas --destination top --rulebase pre +scm move security rule allow-web --folder Texas --destination before --destination-rule --rulebase pre +scm move security rule allow-web --folder Texas --destination after --destination-rule --rulebase pre ``` **Options:** `--source-zones`/`--destination-zones` (names or `any`); `--source-addresses`/`--destination-addresses`; `--applications`; `--services` (names, `application-default`, or `any`); `--action` (`allow`/`deny`/`drop`); `--enabled`/`--disabled`; `--log-start`/`--log-end`; `--log-setting`; `--rulebase` (`pre` default, `post`, `default`); `--tags`. @@ -500,10 +506,10 @@ scm move security rule --folder Texas --name allow-web --insert after --referenc | `agent-version` | Agent versions (show only, read-only) | | `auth-setting` | GlobalProtect auth settings | -Also includes agent/forwarding/tunnel profiles and global/infrastructure settings — see `docs-site/docs/cli/mobile-agent/`. +Also includes agent/forwarding/tunnel profiles and global/infrastructure settings — see `docs-site/docs/cli/mobile-agent/`. Mobile-agent commands are folder-only (no `--snippet`/`--device`, SDK limitation); global-setting is a singleton and takes no name. ```bash -scm set mobile-agent auth-setting --folder Texas --name gp-auth \ +scm set mobile-agent auth-setting gp-auth --folder "Mobile Users" \ --authentication-profile corp-auth --os Any ``` @@ -514,27 +520,27 @@ scm set mobile-agent auth-setting --folder Texas --name gp-auth \ ### Folders ```bash -scm set setup folder --name "Branch Office" --parent "All Firewalls" --description "Branch config" +scm set setup folder "Branch Office" --parent "All Firewalls" --description "Branch config" scm show setup folder -scm delete setup folder --name "Branch Office" [--force] +scm delete setup folder "Branch Office" [--force] ``` ### Labels ```bash -scm set setup label --name production --description "Production environment" +scm set setup label production --description "Production environment" ``` ### Snippets ```bash -scm set setup snippet --name shared-config --description "Shared configuration" +scm set setup snippet shared-config --description "Shared configuration" ``` ### Variables ```bash -scm set setup variable --folder Texas --name '$dns-server' --type ip-netmask --value 8.8.8.8/32 +scm set setup variable '$dns-server' --folder Texas --type ip-netmask --value 8.8.8.8/32 ``` **Variable types:** `percent`, `count`, `ip-netmask`, `zone`, `ip-range`, `ip-wildcard`, `fqdn`, `port`, `egress-max`, and more. @@ -560,10 +566,12 @@ Manage device records — see `docs-site/docs/cli/setup/device.md`. | `service-connection` | Service connections | ```bash -scm set sase bandwidth-allocation --folder Texas --name site-bw --bandwidth 1000 -scm show sase bandwidth-allocation --folder Texas +scm set sase bandwidth-allocation site-bw --bandwidth 1000 --spn-name-list spn1,spn2 +scm show sase bandwidth-allocation ``` +SASE resources are global — no `--folder`/`--snippet`/`--device` flags. `bgp-routing` is a singleton and takes no name. + --- ## Bulk Operations via YAML (`load` / `backup`) @@ -729,7 +737,7 @@ scm insights service-connections --list [--health healthy] [--metrics] scm insights tunnels --list [--status up] [--stats] ``` -All insights commands support: `--export json|csv`, `--output `, `--max-results `, `--folder `, `--mock`. +All insights commands support: `--export json|csv`, `--output `, `--max-results `, `--folder `. There is no `--mock` flag on insights commands — set `SCM_MOCK=1` to test without credentials. --- @@ -771,7 +779,7 @@ scm operations interfaces --device 007951000123456 scm operations device-rules --device 007951000123456 scm operations bgp-export --device 007951000123456 scm operations logging-status --device 007951000123456 -scm operations status --job-id abc-123 # check a dispatched async job +scm operations status --id abc-123 # check a dispatched async job ``` --- @@ -802,7 +810,7 @@ Env vars: `PANOS_HOST`, `PANOS_USER`, `PANOS_PASSWORD`. 1. **Always commit after changes.** `set`, `delete`, and `load` stage changes; nothing applies until `scm commit`. 2. **One address type per address object.** Exactly one of `--ip-netmask`, `--ip-range`, `--ip-wildcard`, `--fqdn`. -3. **One container per command.** Exactly one of `--folder`, `--snippet`, `--device`. +3. **One container per command.** Object, network, security, and identity commands require exactly one of `--folder`, `--snippet`, `--device`. Exceptions: sase, setup folder/label/snippet/device, and quarantined-device take no container; mobile-agent is folder-only. 4. **Boolean fields:** omit false booleans from YAML/requests to avoid API validation errors. 5. **Tag references must exist** before objects reference them. 6. **SDK service names are singular** (e.g. `application_filter`, `external_dynamic_list`, `hip_object`, `service`, `tag`). @@ -810,7 +818,7 @@ Env vars: `PANOS_HOST`, `PANOS_USER`, `PANOS_PASSWORD`. 8. **HTTP server profiles require `http_method`.** 9. **Log forwarding profile match lists require `filter`** despite SDK docs. 10. **`--force` skips confirmation prompts** — use on `delete`/`commit` when non-interactive. -11. **Mock mode is explicit:** set `SCM_MOCK=1` (or `--mock` where available) to test without credentials; missing credentials otherwise exit 1. +11. **Mock mode is explicit:** set `SCM_MOCK=1` to test without credentials (only `scm context test` still has a `--mock` flag); missing credentials otherwise exit 1. 12. **Security rule ordering matters** — use `scm move security rule` to reorder. 13. **Rulebase options:** `pre` (before default), `post` (after), `default` (the default rulebase). 14. **Color names are case-sensitive in the API** but case-insensitive in the CLI validator. diff --git a/CLAUDE.md b/CLAUDE.md index 22a66ae..7525570 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co `pan-scm-cli` is a CLI tool for managing Palo Alto Networks Strata Cloud Manager (SCM) configurations. It manages objects, network configs, security policy, identity profiles, mobile agent settings, setup containers, and SASE/deployment resources through a consistent verb-based interface, plus monitoring (insights, incidents), device operations, local config retrieval, jobs, and firewall posture (BPA) assessment. -- **Package:** `pan-scm-cli` (version `1.5.0`), console script `scm`. +- **Package:** `pan-scm-cli` (version `2.0.0`), console script `scm`. - **Runtime:** Python `>=3.10,<3.14`, built with Typer + Click, validated with Pydantic v2. - **SDK dependency:** `pan-scm-sdk ^0.15.0` (i.e. `>=0.15.0,<0.16.0`) — verify compatibility when bumping. - **Packaging/build:** Poetry (`pyproject.toml`). @@ -65,13 +65,15 @@ The command reference under `docs-site/docs/cli/` is the source of truth for the For configuration verbs the CLI follows: ``` -scm [options] +scm [NAME] [options] ``` **Actions:** `set`, `delete`, `show`, `load`, `backup`, `move` **Categories:** `object`, `network`, `security`, `identity`, `mobile-agent`, `setup`, `sase` **Standalone top-level commands:** `commit`, `context`, `jobs`, `insights`, `incidents`, `local`, `operations`, `posture` +The object name is a positional `NAME` argument (required for `set`/`delete`/`move`, optional for `show` — omit it to list all). Exceptions: quarantined-device uses `--host-id`, network-location uses `--value`, and singletons (sase bgp-routing, mobile-agent global-setting) take no name. + ### Command Modules (`src/scm_cli/commands/`) - `objects.py` — object category: address, address group, application, application group, application filter, auto-tag-action, dynamic user group, external dynamic list, HIP object, HIP profile, HTTP server profile, log forwarding profile, quarantined-device, region, schedule, service, service group, syslog server profile, tag @@ -159,14 +161,16 @@ Lint config: ruff `line-length = 192`, `target-version = py310`, rules `E,F,I,B, ## Quick Reference ``` -scm [options] +scm [NAME] [options] ``` **Actions:** `set`, `delete`, `show`, `load`, `backup`, `move` **Categories:** `object`, `network`, `security`, `identity`, `mobile-agent`, `setup`, `sase` **Standalone:** `scm commit`, `scm context`, `scm jobs`, `scm insights`, `scm incidents`, `scm local`, `scm operations`, `scm posture` -Global options: `--version`/`-V`, `--region ` (override SCM API region for the invocation). +The object name is a positional `NAME` argument: required for `set`/`delete`/`move`, optional for `show` (omit it to list all — there is no `--list` flag). Every `show` supports `--output table|json|yaml` and `--max-results N`; every `load` supports `--dry-run`; every `backup` supports `--file`. Multi-value flags are repeatable (e.g. `--tags a --tags b`, `--members m1 --members m2`), not comma-separated. + +Global options: `--version`/`-V`, `--region ` (override SCM API region for the invocation), `--debug`. --- @@ -196,7 +200,7 @@ scm context current **Environment variable overrides:** `SCM_CLIENT_ID`, `SCM_CLIENT_SECRET`, `SCM_TSG_ID` (useful for CI/CD). -**Mock mode:** Set `SCM_MOCK=1` (or use `--mock` where available) to test without API credentials. Missing credentials without explicit mock mode fail with exit code 1 — there is no silent fallback. +**Mock mode:** Set `SCM_MOCK=1` to test without API credentials (`scm context test` also accepts `--mock`; other commands have no `--mock` flag). Missing credentials without explicit mock mode fail with exit code 1 — there is no silent fallback. **Note:** Legacy config files (`~/.scm-cli/config.yaml`, `.secrets.yaml`) are no longer supported — use contexts. @@ -204,7 +208,7 @@ scm context current ## Container Locations -Most commands require exactly ONE container location: +`set`/`delete`/`show` for object, network, security, and identity types require exactly ONE container location: | Flag | Description | |------|-------------| @@ -212,6 +216,8 @@ Most commands require exactly ONE container location: | `--snippet ` | Shared configuration snippet | | `--device ` | Device-level configuration | +Exceptions: `sase` types, `setup` folder/label/snippet/device, and quarantined-device take no container; `mobile-agent` is folder-only (SDK limitation). + --- ## Object Management (`scm ... object ...`) @@ -220,19 +226,19 @@ Most commands require exactly ONE container location: ```bash # IP netmask -scm set object address --folder Texas --name webserver --ip-netmask 10.1.1.10/32 --description "Web server" --tags web prod +scm set object address webserver --folder Texas --ip-netmask 10.1.1.10/32 --description "Web server" --tags web --tags prod # IP range -scm set object address --folder Texas --name dhcp-pool --ip-range 10.1.3.1-10.1.3.10 +scm set object address dhcp-pool --folder Texas --ip-range 10.1.3.1-10.1.3.10 # FQDN -scm set object address --folder Texas --name google-dns --fqdn dns.google.com +scm set object address google-dns --folder Texas --fqdn dns.google.com # IP wildcard -scm set object address --folder Texas --name wildcard --ip-wildcard 10.20.0.0/0.0.255.255 +scm set object address wildcard --folder Texas --ip-wildcard 10.20.0.0/0.0.255.255 ``` ```bash -scm show object address --folder Texas # list all -scm show object address --folder Texas --name webserver # show one -scm delete object address --folder Texas --name webserver [--force] +scm show object address --folder Texas # list all +scm show object address webserver --folder Texas # show one +scm delete object address webserver --folder Texas [--force] ``` **Constraints:** Name 1-63 chars. Exactly ONE address type required. @@ -240,8 +246,8 @@ scm delete object address --folder Texas --name webserver [--force] ### Address Groups ```bash -scm set object address-group --folder Texas --name web-servers --type static --members webserver1 webserver2 -scm set object address-group --folder Texas --name tagged-web --type dynamic --filter "'web' and 'prod'" +scm set object address-group web-servers --folder Texas --type static --members webserver1 --members webserver2 +scm set object address-group tagged-web --folder Texas --type dynamic --filter "'web' and 'prod'" ``` **Constraints:** Static requires `--members` (min 1). Dynamic uses tag-based filter expressions. @@ -249,9 +255,9 @@ scm set object address-group --folder Texas --name tagged-web --type dynamic --f ### Applications ```bash -scm set object application --folder Texas --name custom-app \ +scm set object application custom-app --folder Texas \ --category business-systems --subcategory erp --technology client-server --risk 3 \ - --ports tcp/8080 tcp/8443 --transfers-files --has-known-vulnerabilities + --ports tcp/8080 --ports tcp/8443 --transfers-files --has-known-vulnerabilities ``` **Risk levels:** 1-5. Boolean flags: `--evasive`, `--pervasive`, `--excessive-bandwidth-use`, `--used-by-malware`, `--transfers-files`, `--has-known-vulnerabilities`, `--tunnels-other-apps`, `--prone-to-misuse`, `--no-certifications`. @@ -259,20 +265,20 @@ scm set object application --folder Texas --name custom-app \ ### Application Groups ```bash -scm set object application-group --folder Texas --name web-apps --members web-browsing ssl http +scm set object application-group web-apps --folder Texas --members web-browsing --members ssl --members http ``` ### Application Filters ```bash -scm set object application-filter --folder Texas --name high-risk \ - --category business-systems --subcategory erp --technology client-server --risk 4 5 +scm set object application-filter high-risk --folder Texas \ + --category business-systems --subcategory erp --technology client-server --risk 4 --risk 5 ``` ### Tags ```bash -scm set object tag --folder Texas --name production --color Red --comments "Production environment" +scm set object tag production --folder Texas --color Red --comments "Production environment" ``` **42 valid colors:** Red, Green, Blue, Yellow, Copper, Orange, Purple, Gray, Light Green, Cyan, Light Gray, Blue Gray, Lime, Black, Gold, Brown, Olive, Maroon, Red-Orange, Yellow-Orange, Forest Green, Turquoise Blue, Azure Blue, Cerulean Blue, Midnight Blue, Medium Blue, Cobalt Blue, Violet Blue, Blue Violet, Medium Violet, Medium Rose, Lavender, Orchid, Thistle, Peach, Salmon, Magenta, Red Violet, Mahogany, Burnt Sienna, Chestnut. Color names are case-insensitive in the CLI validator but case-sensitive in the API. @@ -280,10 +286,10 @@ scm set object tag --folder Texas --name production --color Red --comments "Prod ### Services ```bash -scm set object service --folder Texas --name web-http --protocol tcp --port 80 -scm set object service --folder Texas --name web-https --protocol tcp --port 443 --timeout 3600 -scm set object service --folder Texas --name multi-port --protocol tcp --port 80,443,8080 -scm set object service --folder Texas --name port-range --protocol tcp --port 8000-8999 +scm set object service web-http --folder Texas --protocol tcp --port 80 +scm set object service web-https --folder Texas --protocol tcp --port 443 --timeout 3600 +scm set object service multi-port --folder Texas --protocol tcp --port 80,443,8080 +scm set object service port-range --folder Texas --protocol tcp --port 8000-8999 ``` **Constraints:** Protocol `tcp` or `udp`. Port: single, range (80-443), or comma-separated (80,443,8080). Service tags must reference existing tag objects. @@ -291,7 +297,7 @@ scm set object service --folder Texas --name port-range --protocol tcp --port 80 ### Service Groups ```bash -scm set object service-group --folder Texas --name web-services --members web-http web-https +scm set object service-group web-services --folder Texas --members web-http --members web-https ``` **Constraints:** Members must be unique; can reference services or other service groups (nested allowed). @@ -299,7 +305,7 @@ scm set object service-group --folder Texas --name web-services --members web-ht ### Dynamic User Groups ```bash -scm set object dynamic-user-group --folder Texas --name risky-users \ +scm set object dynamic-user-group risky-users --folder Texas \ --filter "'high-risk' and 'external'" --description "High risk external users" ``` @@ -308,11 +314,11 @@ scm set object dynamic-user-group --folder Texas --name risky-users \ ### External Dynamic Lists ```bash -scm set object external-dynamic-list --folder Texas --name bulletproof-ips \ +scm set object external-dynamic-list bulletproof-ips --folder Texas \ --type predefined_ip --url panw-bulletproof-ip-list -scm set object external-dynamic-list --folder Texas --name threat-ips \ +scm set object external-dynamic-list threat-ips --folder Texas \ --type ip --url https://example.com/threats.txt --recurring daily --hour 03 -scm set object external-dynamic-list --folder Texas --name blocked-domains \ +scm set object external-dynamic-list blocked-domains --folder Texas \ --type domain --url https://example.com/domains.txt --recurring hourly \ --username api_user --password secret --expand-domain ``` @@ -324,9 +330,9 @@ Predefined EDLs use short names (e.g. `panw-bulletproof-ip-list`) not full URLs. ### HIP Objects ```bash -scm set object hip-object --folder Texas --name corporate-host \ - --host-info-os Microsoft --host-info-managed true \ - --disk-encryption-enabled true +scm set object hip-object corporate-host --folder Texas \ + --host-info-os Microsoft --host-info-os-value All --host-info-managed \ + --disk-encryption-enabled ``` Criteria types: host info, network info, patch management, disk encryption, mobile device, certificate. HIP objects use a flattened field structure in validators (converted to nested SDK format). See `examples/hip-objects.yml`. @@ -334,7 +340,7 @@ Criteria types: host info, network info, patch management, disk encryption, mobi ### HIP Profiles ```bash -scm set object hip-profile --folder Texas --name corp-compliance --match "corporate-host is" +scm set object hip-profile corp-compliance --folder Texas --match "corporate-host is" ``` Reference HIP objects through match criteria with boolean operators (is/is-not). @@ -342,9 +348,8 @@ Reference HIP objects through match criteria with boolean operators (is/is-not). ### HTTP Server Profiles ```bash -scm set object http-server-profile --folder Texas --name webhook-profile \ - --server-name srv1 --server-address 192.168.1.1 --server-protocol HTTP \ - --server-port 8080 --server-http-method POST +scm set object http-server-profile webhook-profile --folder Texas \ + --servers '[{"name": "srv1", "address": "192.168.1.1", "protocol": "HTTP", "port": 8080, "http_method": "POST"}]' ``` **Important:** `http_method` is required for all server configs. The `server` field is singular from the API but YAML uses plural `servers` for consistency. @@ -352,7 +357,7 @@ scm set object http-server-profile --folder Texas --name webhook-profile \ ### Log Forwarding Profiles ```bash -scm set object log-forwarding-profile --folder Texas --name forward-all --enhanced-application-logging +scm set object log-forwarding-profile forward-all --folder Texas --enhanced-application-logging ``` **Important:** the `filter` field is required in match list entries despite SDK docs showing it optional. Match lists support traffic, threat, wildfire, url, data, tunnel, auth, decryption, dns-security log types. @@ -360,9 +365,9 @@ scm set object log-forwarding-profile --folder Texas --name forward-all --enhanc ### Syslog Server Profiles ```bash -scm set object syslog-server-profile --folder Texas --name syslog-central \ +scm set object syslog-server-profile syslog-central --folder Texas \ --server-name syslog1 --server-address 192.168.1.100 \ - --server-transport UDP --server-port 514 --server-format BSD --server-facility LOG_USER + --transport UDP --port 514 --format BSD --facility LOG_USER ``` **Transport:** UDP, TCP (SSL not supported by SDK). **Format:** BSD, IETF. **Facilities:** LOG_USER, LOG_LOCAL0–LOG_LOCAL7. Uses `fetch()` (not `get()`) in the SDK client for retrieval. @@ -370,29 +375,29 @@ scm set object syslog-server-profile --folder Texas --name syslog-central \ ### Schedules ```bash -scm set object schedule --folder Texas --name maintenance-window \ - --saturday 02:00-06:00 --sunday 02:00-06:00 +scm set object schedule maintenance-window --folder Texas \ + --schedule-type recurring-weekly --saturday 02:00-06:00 --sunday 02:00-06:00 ``` ### Regions ```bash -scm set object region --folder Texas --name us-east --address 10.0.0.0/8 172.16.0.0/12 +scm set object region us-east --folder Texas --address 10.0.0.0/8 --address 172.16.0.0/12 ``` ### Auto Tag Actions ```bash -scm set object auto-tag-action --folder Texas --name auto-tag-threats +scm set object auto-tag-action auto-tag-threats --folder Texas ``` ### Quarantined Devices ```bash -scm show object quarantined-device --folder Texas +scm show object quarantined-device [--host-id ] [--serial-number ] ``` -Manage quarantined devices (list/set/delete) — see `docs-site/docs/cli/objects/quarantined-device.md`. +Manage quarantined devices (list/set/delete) — identified by `--host-id`, no positional name and no container flags. See `docs-site/docs/cli/objects/quarantined-device.md`. --- @@ -401,9 +406,9 @@ Manage quarantined devices (list/set/delete) — see `docs-site/docs/cli/objects ### Security Zones ```bash -scm set network zone --folder Texas --name trust --interfaces ethernet1/1 ethernet1/2 +scm set network zone trust --folder Texas --mode layer3 --interfaces ethernet1/1 --interfaces ethernet1/2 scm show network zone --folder Texas -scm delete network zone --folder Texas --name trust [--force] +scm delete network zone trust --folder Texas [--force] ``` ### Other Network Objects @@ -446,19 +451,20 @@ All follow `scm network `: ### Security Rules ```bash -scm set security rule --folder Texas --name allow-web \ +scm set security rule allow-web --folder Texas \ --source-zones trust --destination-zones untrust \ --source-addresses any --destination-addresses web-servers \ - --applications web-browsing ssl --services application-default \ + --applications web-browsing --applications ssl --services application-default \ --action allow --log-end --description "Allow web traffic" --rulebase pre scm show security rule --folder Texas -scm show security rule --folder Texas --name allow-web -scm delete security rule --folder Texas --name allow-web [--force] +scm show security rule allow-web --folder Texas +scm delete security rule allow-web --folder Texas [--force] # Move (reorder) -scm move security rule --folder Texas --name allow-web --insert before --reference deny-all --rulebase pre -scm move security rule --folder Texas --name allow-web --insert after --reference allow-dns --rulebase pre +scm move security rule allow-web --folder Texas --destination top --rulebase pre +scm move security rule allow-web --folder Texas --destination before --destination-rule --rulebase pre +scm move security rule allow-web --folder Texas --destination after --destination-rule --rulebase pre ``` **Options:** `--source-zones`/`--destination-zones` (names or `any`); `--source-addresses`/`--destination-addresses`; `--applications`; `--services` (names, `application-default`, or `any`); `--action` (`allow`/`deny`/`drop`); `--enabled`/`--disabled`; `--log-start`/`--log-end`; `--log-setting`; `--rulebase` (`pre` default, `post`, `default`); `--tags`. @@ -500,10 +506,10 @@ scm move security rule --folder Texas --name allow-web --insert after --referenc | `agent-version` | Agent versions (show only, read-only) | | `auth-setting` | GlobalProtect auth settings | -Also includes agent/forwarding/tunnel profiles and global/infrastructure settings — see `docs-site/docs/cli/mobile-agent/`. +Also includes agent/forwarding/tunnel profiles and global/infrastructure settings — see `docs-site/docs/cli/mobile-agent/`. Mobile-agent commands are folder-only (no `--snippet`/`--device`, SDK limitation); global-setting is a singleton and takes no name. ```bash -scm set mobile-agent auth-setting --folder Texas --name gp-auth \ +scm set mobile-agent auth-setting gp-auth --folder "Mobile Users" \ --authentication-profile corp-auth --os Any ``` @@ -514,27 +520,27 @@ scm set mobile-agent auth-setting --folder Texas --name gp-auth \ ### Folders ```bash -scm set setup folder --name "Branch Office" --parent "All Firewalls" --description "Branch config" +scm set setup folder "Branch Office" --parent "All Firewalls" --description "Branch config" scm show setup folder -scm delete setup folder --name "Branch Office" [--force] +scm delete setup folder "Branch Office" [--force] ``` ### Labels ```bash -scm set setup label --name production --description "Production environment" +scm set setup label production --description "Production environment" ``` ### Snippets ```bash -scm set setup snippet --name shared-config --description "Shared configuration" +scm set setup snippet shared-config --description "Shared configuration" ``` ### Variables ```bash -scm set setup variable --folder Texas --name '$dns-server' --type ip-netmask --value 8.8.8.8/32 +scm set setup variable '$dns-server' --folder Texas --type ip-netmask --value 8.8.8.8/32 ``` **Variable types:** `percent`, `count`, `ip-netmask`, `zone`, `ip-range`, `ip-wildcard`, `fqdn`, `port`, `egress-max`, and more. @@ -560,10 +566,12 @@ Manage device records — see `docs-site/docs/cli/setup/device.md`. | `service-connection` | Service connections | ```bash -scm set sase bandwidth-allocation --folder Texas --name site-bw --bandwidth 1000 -scm show sase bandwidth-allocation --folder Texas +scm set sase bandwidth-allocation site-bw --bandwidth 1000 --spn-name-list spn1,spn2 +scm show sase bandwidth-allocation ``` +SASE resources are global — no `--folder`/`--snippet`/`--device` flags. `bgp-routing` is a singleton and takes no name. + --- ## Bulk Operations via YAML (`load` / `backup`) @@ -729,7 +737,7 @@ scm insights service-connections --list [--health healthy] [--metrics] scm insights tunnels --list [--status up] [--stats] ``` -All insights commands support: `--export json|csv`, `--output `, `--max-results `, `--folder `, `--mock`. +All insights commands support: `--export json|csv`, `--output `, `--max-results `, `--folder `. There is no `--mock` flag on insights commands — set `SCM_MOCK=1` to test without credentials. --- @@ -771,7 +779,7 @@ scm operations interfaces --device 007951000123456 scm operations device-rules --device 007951000123456 scm operations bgp-export --device 007951000123456 scm operations logging-status --device 007951000123456 -scm operations status --job-id abc-123 # check a dispatched async job +scm operations status --id abc-123 # check a dispatched async job ``` --- @@ -802,7 +810,7 @@ Env vars: `PANOS_HOST`, `PANOS_USER`, `PANOS_PASSWORD`. 1. **Always commit after changes.** `set`, `delete`, and `load` stage changes; nothing applies until `scm commit`. 2. **One address type per address object.** Exactly one of `--ip-netmask`, `--ip-range`, `--ip-wildcard`, `--fqdn`. -3. **One container per command.** Exactly one of `--folder`, `--snippet`, `--device`. +3. **One container per command.** Object, network, security, and identity commands require exactly one of `--folder`, `--snippet`, `--device`. Exceptions: sase, setup folder/label/snippet/device, and quarantined-device take no container; mobile-agent is folder-only. 4. **Boolean fields:** omit false booleans from YAML/requests to avoid API validation errors. 5. **Tag references must exist** before objects reference them. 6. **SDK service names are singular** (e.g. `application_filter`, `external_dynamic_list`, `hip_object`, `service`, `tag`). @@ -810,7 +818,7 @@ Env vars: `PANOS_HOST`, `PANOS_USER`, `PANOS_PASSWORD`. 8. **HTTP server profiles require `http_method`.** 9. **Log forwarding profile match lists require `filter`** despite SDK docs. 10. **`--force` skips confirmation prompts** — use on `delete`/`commit` when non-interactive. -11. **Mock mode is explicit:** set `SCM_MOCK=1` (or `--mock` where available) to test without credentials; missing credentials otherwise exit 1. +11. **Mock mode is explicit:** set `SCM_MOCK=1` to test without credentials (only `scm context test` still has a `--mock` flag); missing credentials otherwise exit 1. 12. **Security rule ordering matters** — use `scm move security rule` to reorder. 13. **Rulebase options:** `pre` (before default), `post` (after), `default` (the default rulebase). 14. **Color names are case-sensitive in the API** but case-insensitive in the CLI validator. diff --git a/README.md b/README.md index 285e86d..c86e10a 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ scm context create myenv \ scm show object address --folder Texas # create an address -scm set object address --folder Texas --name webserver --ip-netmask 10.1.1.100/32 +scm set object address webserver --folder Texas --ip-netmask 10.1.1.100/32 # backup to YAML scm backup object address --folder Texas diff --git a/docs-site/docs/about/getting-started.md b/docs-site/docs/about/getting-started.md index 5e0c527..e07f1ec 100644 --- a/docs-site/docs/about/getting-started.md +++ b/docs-site/docs/about/getting-started.md @@ -88,15 +88,16 @@ variables for secure credential management. Regularly rotate your credentials. All commands follow this pattern: ```bash -scm [options] +scm [name] [options] ``` | Component | Description | Examples | | --- | --- | --- | -| `` | Operation to perform | `set`, `delete`, `load`, `show`, `backup` | +| `` | Operation to perform | `set`, `delete`, `load`, `show`, `backup`, `move` | | `` | Category of resource | `object`, `network`, `security`, `sase` | -| `` | Specific resource type | `address`, `address-group`, `security-zone` | -| `[options]` | Resource-specific parameters | `--folder`, `--name`, `--ip-netmask` | +| `` | Specific resource type | `address`, `address-group`, `zone` | +| `[name]` | Positional resource name (required for `set`/`delete`/`move`; optional for `show`) | `webserver` | +| `[options]` | Resource-specific parameters | `--folder`, `--ip-netmask` | ## Basic Usage Examples @@ -115,39 +116,51 @@ Options: --help Show this message and exit. Commands: - backup Backup configurations to YAML files - delete Remove configurations - load Load configurations from YAML files - set Create or update configurations - show Display configurations + backup Backup configurations to YAML files + commit Commit staged configuration changes + context Manage authentication contexts + delete Remove configurations + incidents Search and view security incidents + insights Query monitoring insights + jobs Manage SCM jobs + load Load configurations from YAML files + local Retrieve local device configurations + move Move rules to a new position + operations Run device operations + posture Firewall posture / BPA assessment + set Create or update configurations + show Display configurations ``` Command-specific help: ```bash $ scm set object address --help -Usage: scm set object address [OPTIONS] +Usage: scm set object address [OPTIONS] NAME - Create or update an address object in SCM. + Create or update an address object. + +Arguments: + NAME Name of the address [required] Options: - --folder TEXT Folder for the address object [required] - --name TEXT Name of the address object [required] - --description TEXT Description for the address - --tags LIST List of tags to apply to the address - --ip-netmask TEXT Address in CIDR notation (e.g., 192.168.1.0/24) - --ip-range TEXT Address range (e.g., 192.168.1.1-192.168.1.10) - --ip-wildcard TEXT Address with wildcard mask (e.g., 10.20.1.0/0.0.248.255) - --fqdn TEXT Fully qualified domain name (e.g., example.com) + --folder TEXT Folder location + --snippet TEXT Snippet location + --device TEXT Device location + --description TEXT Description of the address + --tags TEXT Tags (repeat for multiple) + --ip-netmask TEXT IP address with CIDR notation (e.g. 192.168.1.0/24) + --ip-range TEXT IP address range (e.g. 192.168.1.1-192.168.1.10) + --ip-wildcard TEXT IP wildcard mask (e.g. 10.20.1.0/0.0.248.255) + --fqdn TEXT Fully qualified domain name (e.g. example.com) --help Show this message and exit. ``` ### Creating an Address Object ```bash -$ scm set object address \ +$ scm set object address webserver \ --folder Texas \ - --name webserver \ --ip-netmask 192.168.1.100/32 \ --description "Web server" ---> 100% @@ -157,9 +170,8 @@ Created address: webserver in folder Texas ### Creating an Address with FQDN ```bash -$ scm set object address \ +$ scm set object address company-website \ --folder Texas \ - --name company-website \ --fqdn example.com \ --description "Company website" ---> 100% @@ -186,7 +198,7 @@ Name: company-website ### Deleting an Address Object ```bash -$ scm delete object address --folder Texas --name webserver +$ scm delete object address webserver --folder Texas ---> 100% Deleted address: webserver from folder Texas ``` @@ -241,30 +253,27 @@ Successfully loaded 3 out of 3 addresses from 'addresses.yml' ### Dry Run Mode -Preview changes without applying them: +Preview bulk changes without applying them (available on every `load` command): ```bash -$ scm set object address \ - --folder Texas \ - --name webserver \ - --ip-netmask 192.168.1.100/32 \ - --dry-run +$ scm load object address --file addresses.yml --dry-run ---> 100% -[DRY RUN] Would create address: webserver in folder Texas +Dry run mode: would apply the following configurations: +- name: web-server-1 + folder: Texas + ip_netmask: 192.168.1.10/32 ``` ### Mock Mode -Run commands without connecting to the SCM API: +Set `SCM_MOCK=1` to run commands without connecting to the SCM API: ```bash -$ scm set object address \ +$ SCM_MOCK=1 scm set object address webserver \ --folder Texas \ - --name webserver \ - --ip-netmask 192.168.1.100/32 \ - --mock + --ip-netmask 192.168.1.100/32 ---> 100% -[MOCK] Created address: webserver in folder Texas +Created address: webserver in folder Texas ``` :::tip diff --git a/docs-site/docs/about/release-notes.md b/docs-site/docs/about/release-notes.md index 68318fb..597570a 100644 --- a/docs-site/docs/about/release-notes.md +++ b/docs-site/docs/about/release-notes.md @@ -2,6 +2,29 @@ This page contains the release history of the Strata Cloud Manager CLI, with the most recent releases at the top. +## Version 2.0.0 + +**Released:** July 2026 + +### Changed (BREAKING) + +- **Positional NAME argument**: `set`, `delete`, `show`, and `move` commands now take the resource name as a positional argument instead of `--name`. + - Old: `scm set object address --folder Texas --name web1 --ip-netmask 10.0.0.1/32` + - New: `scm set object address web1 --folder Texas --ip-netmask 10.0.0.1/32` + - `show` without a name lists all items. + - Exceptions: quarantined devices use `--host-id`; network locations use `--value`; `bgp-routing` and mobile-agent `global-setting` are singletons with no name. + - **Migration**: Move the `--name` value to the position immediately after the object type. +- **Containers everywhere**: `set`/`delete`/`show` for object, network, security, and identity types accept exactly one of `--folder`, `--snippet`, or `--device`. SASE resources and setup folder/label/snippet/device remain container-less; mobile-agent commands remain folder-only (SDK limitation). +- **Repeatable `--tags`**: Tags are always provided by repeating the flag (`--tags a --tags b`). Comma-separated `--tag a,b` variants have been removed. +- **Job IDs are always `--id`**: `scm operations status` now uses `--id` (previously `--job-id`), matching `scm jobs status` and `scm jobs wait`. +- **Insights `--mock` flags removed**: Use the `SCM_MOCK=1` environment variable instead (applies to all commands; `scm context test --mock` is unchanged). +- **Layer3 subinterface VLAN flag**: Renamed to `--vlan-tag`. + +### Improved + +- Every `show` command supports `--output table|json|yaml` and `--max-results N`. +- Every `load` command supports `--dry-run`; every `backup` command supports `--file`. + ## Version 1.3.3 **Released:** April 2026 diff --git a/docs-site/docs/about/troubleshooting.md b/docs-site/docs/about/troubleshooting.md index 4dd336e..9a36ef4 100644 --- a/docs-site/docs/about/troubleshooting.md +++ b/docs-site/docs/about/troubleshooting.md @@ -157,7 +157,7 @@ This format is produced by the `@handle_command_errors` decorator used across al :::tip Since errors go to stderr, you can separate them from normal output in scripts: ```bash -scm set object address --folder Texas --name test --ip-netmask 10.0.0.1/32 2>errors.log +scm set object address test --folder Texas --ip-netmask 10.0.0.1/32 2>errors.log ``` ::: diff --git a/docs-site/docs/cli/deployment/bandwidth.md b/docs-site/docs/cli/deployment/bandwidth.md index a7b3c6d..53908d4 100644 --- a/docs-site/docs/cli/deployment/bandwidth.md +++ b/docs-site/docs/cli/deployment/bandwidth.md @@ -19,14 +19,19 @@ Create or update a bandwidth allocation. ### Syntax ```bash -scm set sase bandwidth-allocation [OPTIONS] +scm set sase bandwidth-allocation NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the bandwidth allocation | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the bandwidth allocation | Yes | | `--bandwidth INT` | Bandwidth value in Mbps | Yes | | `--spn-name-list TEXT` | SPN names (comma-separated if multiple) | Yes | | `--description TEXT` | Description for the bandwidth allocation | No | @@ -41,8 +46,7 @@ Bandwidth allocations are global resources and do not require a `--folder` param #### Create a Basic Bandwidth Allocation ```bash -$ scm set sase bandwidth-allocation \ - --name Standard-Branch \ +$ scm set sase bandwidth-allocation Standard-Branch \ --bandwidth 100 \ --spn-name-list "branch-spn-1" \ --description "Standard bandwidth for branch offices" @@ -53,8 +57,7 @@ Created bandwidth allocation: Standard-Branch (100 Mbps) #### Create a Bandwidth Allocation with Multiple SPNs ```bash -$ scm set sase bandwidth-allocation \ - --name HQ-Bandwidth \ +$ scm set sase bandwidth-allocation HQ-Bandwidth \ --bandwidth 1000 \ --spn-name-list "HQ-SPN-1,HQ-SPN-2" \ --description "High bandwidth for headquarters" @@ -69,21 +72,26 @@ Delete a bandwidth allocation from SCM. ### Syntax ```bash -scm delete sase bandwidth-allocation [OPTIONS] +scm delete sase bandwidth-allocation NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the bandwidth allocation to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the bandwidth allocation to delete | Yes | | `--spn-name-list TEXT` | SPN names (comma-separated if multiple) | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete sase bandwidth-allocation --name Standard-Branch --spn-name-list "branch-spn-1" --force +$ scm delete sase bandwidth-allocation Standard-Branch --spn-name-list "branch-spn-1" --force ---> 100% Deleted bandwidth allocation: Standard-Branch ``` @@ -148,17 +156,24 @@ Display bandwidth allocation objects. ### Syntax ```bash -scm show sase bandwidth-allocation [OPTIONS] +scm show sase bandwidth-allocation [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the bandwidth allocation to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the bandwidth allocation to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -166,7 +181,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Bandwidth Allocation ```bash -$ scm show sase bandwidth-allocation --name HQ-Bandwidth +$ scm show sase bandwidth-allocation HQ-Bandwidth ---> 100% Bandwidth Allocation: HQ-Bandwidth Allocated Bandwidth: 1000 Mbps diff --git a/docs-site/docs/cli/deployment/bgp-routing.md b/docs-site/docs/cli/deployment/bgp-routing.md index 6bd3fac..e7ae913 100644 --- a/docs-site/docs/cli/deployment/bgp-routing.md +++ b/docs-site/docs/cli/deployment/bgp-routing.md @@ -25,7 +25,7 @@ scm set sase bgp-routing [OPTIONS] | Option | Description | Required | | --- | --- | --- | -| `--backbone-routing TEXT` | Backbone routing mode (no-asymmetric-routing, asymmetric-routing) | Yes | +| `--backbone-routing TEXT` | Backbone routing mode (no-asymmetric-routing, asymmetric-routing-only, asymmetric-routing-with-load-share) | Yes | | `--routing-preference TEXT` | Routing preference (default, hot_potato_routing) | No | | `--accept-route-over-sc` | Accept routes over service connections | No | | `--outbound-routes TEXT` | Comma-separated outbound routes for services | No | @@ -60,7 +60,7 @@ Updated BGP routing configuration ```bash $ scm set sase bgp-routing \ - --backbone-routing asymmetric-routing \ + --backbone-routing asymmetric-routing-only \ --outbound-routes "10.0.0.0/8,172.16.0.0/12" \ --withdraw-static-route ---> 100% @@ -98,9 +98,15 @@ Display the current BGP routing configuration. ### Syntax ```bash -scm show sase bgp-routing +scm show sase bgp-routing [OPTIONS] ``` +### Options + +| Option | Description | Required | +| --- | --- | --- | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | + ### Example ```bash diff --git a/docs-site/docs/cli/deployment/internal-dns-server.md b/docs-site/docs/cli/deployment/internal-dns-server.md index 9b0c822..b49c212 100644 --- a/docs-site/docs/cli/deployment/internal-dns-server.md +++ b/docs-site/docs/cli/deployment/internal-dns-server.md @@ -19,14 +19,19 @@ Create or update an internal DNS server configuration. ### Syntax ```bash -scm set sase internal-dns-server [OPTIONS] +scm set sase internal-dns-server NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the DNS server entry | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the DNS server entry | Yes | | `--domain-name TEXT` | Domain name(s), comma-separated | Yes | | `--primary TEXT` | Primary DNS server IP address | Yes | | `--secondary TEXT` | Secondary DNS server IP address | No | @@ -36,8 +41,7 @@ scm set sase internal-dns-server [OPTIONS] #### Create a DNS Server with Primary and Secondary ```bash -$ scm set sase internal-dns-server \ - --name corp-dns \ +$ scm set sase internal-dns-server corp-dns \ --domain-name corp.example.com \ --primary 10.0.0.1 \ --secondary 10.0.0.2 @@ -48,8 +52,7 @@ Created internal DNS server: corp-dns #### Create a DNS Server for Multiple Domains ```bash -$ scm set sase internal-dns-server \ - --name multi-domain-dns \ +$ scm set sase internal-dns-server multi-domain-dns \ --domain-name "internal.example.com,dev.example.com" \ --primary 10.0.1.1 ---> 100% @@ -63,20 +66,25 @@ Delete an internal DNS server configuration from SCM. ### Syntax ```bash -scm delete sase internal-dns-server [OPTIONS] +scm delete sase internal-dns-server NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the DNS server entry to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the DNS server entry to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete sase internal-dns-server --name corp-dns --force +$ scm delete sase internal-dns-server corp-dns --force ---> 100% Deleted internal DNS server: corp-dns ``` @@ -136,17 +144,24 @@ Display internal DNS server configurations. ### Syntax ```bash -scm show sase internal-dns-server [OPTIONS] +scm show sase internal-dns-server [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the DNS server entry to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the DNS server entry to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -154,7 +169,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Internal DNS Server ```bash -$ scm show sase internal-dns-server --name corp-dns +$ scm show sase internal-dns-server corp-dns ---> 100% Internal DNS Server: corp-dns Domain: corp.example.com diff --git a/docs-site/docs/cli/deployment/network-location.md b/docs-site/docs/cli/deployment/network-location.md index da59e31..cf89afe 100644 --- a/docs-site/docs/cli/deployment/network-location.md +++ b/docs-site/docs/cli/deployment/network-location.md @@ -17,6 +17,8 @@ scm show sase network-location [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--value TEXT` | System value of the location (e.g., us-west-1) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples diff --git a/docs-site/docs/cli/deployment/remote-network.md b/docs-site/docs/cli/deployment/remote-network.md index d2bce37..dc86583 100644 --- a/docs-site/docs/cli/deployment/remote-network.md +++ b/docs-site/docs/cli/deployment/remote-network.md @@ -30,18 +30,23 @@ Create or update a remote network configuration. ### Syntax ```bash -scm set sase remote-network [OPTIONS] +scm set sase remote-network NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the remote network | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the remote network | Yes | | `--region TEXT` | Region for the remote network | Yes | | `--license-type TEXT` | License type (default: FWAAS-AGGREGATE) | No | | `--description TEXT` | Description of the remote network | No | -| `--subnets LIST` | Comma-separated list of subnets | No | +| `--subnets TEXT` | Subnets (repeat for multiple) | No | | `--spn-name TEXT` | SPN name (required for FWAAS-AGGREGATE license) | No | | `--ecmp-load-balancing TEXT` | Enable or disable ECMP (default: disable) | No | | `--ipsec-tunnel TEXT` | IPsec tunnel (required when ECMP is disabled) | No | @@ -57,12 +62,11 @@ scm set sase remote-network [OPTIONS] #### Create a Basic Remote Network ```bash -$ scm set sase remote-network \ - --name branch-office-nyc \ +$ scm set sase remote-network branch-office-nyc \ --region us-east-1 \ --spn-name us-east-spn \ --ipsec-tunnel ipsec-tunnel-nyc \ - --subnets "10.1.0.0/24,10.1.1.0/24" \ + --subnets 10.1.0.0/24 --subnets 10.1.1.0/24 \ --description "New York branch office" ---> 100% Created remote network: branch-office-nyc @@ -71,12 +75,11 @@ Created remote network: branch-office-nyc #### Create a Remote Network with BGP ```bash -$ scm set sase remote-network \ - --name datacenter-west \ +$ scm set sase remote-network datacenter-west \ --region us-west-2 \ --spn-name us-west-spn \ --ipsec-tunnel ipsec-tunnel-dc-west \ - --subnets "172.16.0.0/16,172.17.0.0/16" \ + --subnets 172.16.0.0/16 --subnets 172.17.0.0/16 \ --bgp-enable \ --bgp-peer-as "65001" \ --bgp-peer-ip "192.168.1.1" \ @@ -89,12 +92,11 @@ Created remote network: datacenter-west #### Create a Remote Network with ECMP Load Balancing ```bash -$ scm set sase remote-network \ - --name hq-campus \ +$ scm set sase remote-network hq-campus \ --region eu-central-1 \ --spn-name eu-central-spn \ --ecmp-load-balancing enable \ - --subnets "10.0.0.0/8" \ + --subnets 10.0.0.0/8 \ --description "Headquarters campus with ECMP" ---> 100% Created remote network: hq-campus @@ -103,13 +105,12 @@ Created remote network: hq-campus #### Create a Remote Network with Redundant Tunnels ```bash -$ scm set sase remote-network \ - --name critical-site \ +$ scm set sase remote-network critical-site \ --region ap-southeast-1 \ --spn-name ap-southeast-spn \ --ipsec-tunnel ipsec-tunnel-primary \ --secondary-ipsec-tunnel ipsec-tunnel-secondary \ - --subnets "192.168.0.0/16" \ + --subnets 192.168.0.0/16 \ --description "Critical site with tunnel redundancy" ---> 100% Created remote network: critical-site @@ -122,20 +123,25 @@ Delete a remote network configuration from SCM. ### Syntax ```bash -scm delete sase remote-network [OPTIONS] +scm delete sase remote-network NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the remote network to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the remote network to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete sase remote-network --name branch-office-nyc --force +$ scm delete sase remote-network branch-office-nyc --force ---> 100% Deleted remote network: branch-office-nyc ``` @@ -231,17 +237,24 @@ Display remote network configurations. ### Syntax ```bash -scm show sase remote-network [OPTIONS] +scm show sase remote-network [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the remote network to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the remote network to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -249,7 +262,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Remote Network ```bash -$ scm show sase remote-network --name datacenter-central +$ scm show sase remote-network datacenter-central ---> 100% Remote Network: datacenter-central Folder: Remote Networks diff --git a/docs-site/docs/cli/deployment/service-connection.md b/docs-site/docs/cli/deployment/service-connection.md index 5fb87d9..3d6b04a 100644 --- a/docs-site/docs/cli/deployment/service-connection.md +++ b/docs-site/docs/cli/deployment/service-connection.md @@ -19,21 +19,26 @@ Create or update a service connection configuration. ### Syntax ```bash -scm set sase service-connection [OPTIONS] +scm set sase service-connection NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service connection | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the service connection | Yes | | `--ipsec-tunnel TEXT` | IPsec tunnel for the service connection | Yes | | `--region TEXT` | Region for the service connection | Yes | | `--onboarding-type TEXT` | Onboarding type (default: classic) | No | | `--backup-sc TEXT` | Backup service connection name | No | | `--nat-pool TEXT` | NAT pool for the service connection | No | | `--source-nat` | Enable source NAT | No | -| `--subnets LIST` | Comma-separated list of subnets | No | +| `--subnets TEXT` | Subnets (repeat for multiple) | No | | `--bgp-enable` | Enable BGP | No | | `--bgp-peer-as TEXT` | BGP peer AS number | No | | `--bgp-peer-ip TEXT` | BGP peer IP address | No | @@ -47,11 +52,10 @@ scm set sase service-connection [OPTIONS] #### Create a Basic Service Connection ```bash -$ scm set sase service-connection \ - --name branch-office-1 \ +$ scm set sase service-connection branch-office-1 \ --ipsec-tunnel ipsec-tunnel-branch-1 \ --region us-east-1 \ - --subnets "10.1.0.0/24,10.1.1.0/24" + --subnets 10.1.0.0/24 --subnets 10.1.1.0/24 ---> 100% Created service connection: branch-office-1 ``` @@ -59,11 +63,10 @@ Created service connection: branch-office-1 #### Create a Service Connection with BGP ```bash -$ scm set sase service-connection \ - --name hq-connection \ +$ scm set sase service-connection hq-connection \ --ipsec-tunnel ipsec-tunnel-hq \ --region us-west-2 \ - --subnets "172.16.0.0/16" \ + --subnets 172.16.0.0/16 \ --bgp-enable \ --bgp-peer-as "65001" \ --bgp-peer-ip "192.168.1.1" \ @@ -76,12 +79,11 @@ Created service connection: hq-connection #### Create a Service Connection with High Availability ```bash -$ scm set sase service-connection \ - --name critical-site \ +$ scm set sase service-connection critical-site \ --ipsec-tunnel ipsec-tunnel-primary \ --region eu-central-1 \ --backup-sc backup-connection \ - --subnets "10.10.0.0/16" \ + --subnets 10.10.0.0/16 \ --source-nat \ --qos-enable \ --qos-profile business-critical @@ -96,20 +98,25 @@ Delete a service connection configuration from SCM. ### Syntax ```bash -scm delete sase service-connection [OPTIONS] +scm delete sase service-connection NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service connection to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the service connection to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete sase service-connection --name branch-office-1 --force +$ scm delete sase service-connection branch-office-1 --force ---> 100% Deleted service connection: branch-office-1 ``` @@ -193,17 +200,24 @@ Display service connection configurations. ### Syntax ```bash -scm show sase service-connection [OPTIONS] +scm show sase service-connection [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service connection to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the service connection to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -211,7 +225,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Service Connection ```bash -$ scm show sase service-connection --name branch-office-east +$ scm show sase service-connection branch-office-east ---> 100% Service Connection: branch-office-east IPsec Tunnel: ipsec-tunnel-east diff --git a/docs-site/docs/cli/identity/authentication-profile.md b/docs-site/docs/cli/identity/authentication-profile.md index 1156e35..77c096c 100644 --- a/docs-site/docs/cli/identity/authentication-profile.md +++ b/docs-site/docs/cli/identity/authentication-profile.md @@ -19,14 +19,19 @@ Create or update an authentication profile. ### Syntax ```bash -scm set identity authentication-profile [OPTIONS] +scm set identity authentication-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -45,9 +50,8 @@ scm set identity authentication-profile [OPTIONS] #### Create LDAP Authentication Profile ```bash -$ scm set identity authentication-profile \ +$ scm set identity authentication-profile corp-ldap-auth \ --folder Texas \ - --name corp-ldap-auth \ --method '{"ldap": {"server_profile": "corp-ldap", "login_attribute": "sAMAccountName"}}' \ --user-domain "example.com" ---> 100% @@ -57,9 +61,8 @@ Created authentication-profile: corp-ldap-auth in folder Texas #### Create RADIUS Authentication Profile with Lockout ```bash -$ scm set identity authentication-profile \ +$ scm set identity authentication-profile corp-radius-auth \ --folder Texas \ - --name corp-radius-auth \ --method '{"radius": {"server_profile": "corp-radius"}}' \ --lockout '{"failed_attempts": 5, "lockout_time": 30}' ---> 100% @@ -69,9 +72,8 @@ Created authentication-profile: corp-radius-auth in folder Texas #### Create SAML Profile with MFA ```bash -$ scm set identity authentication-profile \ +$ scm set identity authentication-profile corp-saml-mfa \ --folder Texas \ - --name corp-saml-mfa \ --method '{"saml_idp": {"server_profile": "corp-saml"}}' \ --multi-factor-auth '{"mfa_enable": true, "factors": ["okta-otp"]}' \ --single-sign-on '{"realm": "example.com"}' @@ -86,14 +88,19 @@ Delete an authentication profile from SCM. ### Syntax ```bash -scm delete identity authentication-profile [OPTIONS] +scm delete identity authentication-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -104,9 +111,8 @@ scm delete identity authentication-profile [OPTIONS] ### Example ```bash -$ scm delete identity authentication-profile \ +$ scm delete identity authentication-profile corp-ldap-auth \ --folder Texas \ - --name corp-ldap-auth \ --force ---> 100% Deleted authentication-profile: corp-ldap-auth from folder Texas @@ -194,22 +200,29 @@ Display authentication profile objects. ### Syntax ```bash -scm show identity authentication-profile [OPTIONS] +scm show identity authentication-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | No | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -217,9 +230,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Authentication Profile ```bash -$ scm show identity authentication-profile \ - --folder Texas \ - --name corp-ldap-auth +$ scm show identity authentication-profile corp-ldap-auth \ + --folder Texas ---> 100% Authentication Profile: corp-ldap-auth Location: Folder 'Texas' diff --git a/docs-site/docs/cli/identity/kerberos-server-profile.md b/docs-site/docs/cli/identity/kerberos-server-profile.md index 6c87471..48a189b 100644 --- a/docs-site/docs/cli/identity/kerberos-server-profile.md +++ b/docs-site/docs/cli/identity/kerberos-server-profile.md @@ -19,14 +19,19 @@ Create or update a Kerberos server profile. ### Syntax ```bash -scm set identity kerberos-server-profile [OPTIONS] +scm set identity kerberos-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -39,9 +44,8 @@ scm set identity kerberos-server-profile [OPTIONS] #### Create Kerberos Server Profile ```bash -$ scm set identity kerberos-server-profile \ +$ scm set identity kerberos-server-profile corp-kerberos \ --folder Texas \ - --name corp-kerberos \ --servers '[{"name": "kdc1", "host": "kdc1.example.com", "port": 88}]' ---> 100% Created kerberos-server-profile: corp-kerberos in folder Texas @@ -50,9 +54,8 @@ Created kerberos-server-profile: corp-kerberos in folder Texas #### Create Profile with Multiple KDC Servers ```bash -$ scm set identity kerberos-server-profile \ +$ scm set identity kerberos-server-profile corp-kerberos-ha \ --folder Texas \ - --name corp-kerberos-ha \ --servers '[{"name": "kdc1", "host": "kdc1.example.com", "port": 88}, {"name": "kdc2", "host": "kdc2.example.com", "port": 88}]' ---> 100% Created kerberos-server-profile: corp-kerberos-ha in folder Texas @@ -65,14 +68,19 @@ Delete a Kerberos server profile from SCM. ### Syntax ```bash -scm delete identity kerberos-server-profile [OPTIONS] +scm delete identity kerberos-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -83,9 +91,8 @@ scm delete identity kerberos-server-profile [OPTIONS] ### Example ```bash -$ scm delete identity kerberos-server-profile \ +$ scm delete identity kerberos-server-profile corp-kerberos \ --folder Texas \ - --name corp-kerberos \ --force ---> 100% Deleted kerberos-server-profile: corp-kerberos from folder Texas @@ -173,22 +180,29 @@ Display Kerberos server profile objects. ### Syntax ```bash -scm show identity kerberos-server-profile [OPTIONS] +scm show identity kerberos-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | No | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -196,9 +210,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Kerberos Server Profile ```bash -$ scm show identity kerberos-server-profile \ - --folder Texas \ - --name corp-kerberos +$ scm show identity kerberos-server-profile corp-kerberos \ + --folder Texas ---> 100% Kerberos Server Profile: corp-kerberos Location: Folder 'Texas' diff --git a/docs-site/docs/cli/identity/ldap-server-profile.md b/docs-site/docs/cli/identity/ldap-server-profile.md index aaa6a5d..ea2cc74 100644 --- a/docs-site/docs/cli/identity/ldap-server-profile.md +++ b/docs-site/docs/cli/identity/ldap-server-profile.md @@ -28,14 +28,19 @@ Create or update an LDAP server profile. ### Syntax ```bash -scm set identity ldap-server-profile [OPTIONS] +scm set identity ldap-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -53,9 +58,8 @@ scm set identity ldap-server-profile [OPTIONS] #### Create Active Directory Profile ```bash -$ scm set identity ldap-server-profile \ +$ scm set identity ldap-server-profile corp-ldap \ --folder Texas \ - --name corp-ldap \ --servers '[{"name": "ldap1", "address": "ldap.example.com", "port": 389}]' \ --base "dc=example,dc=com" \ --ldap-type active-directory @@ -66,9 +70,8 @@ Created ldap-server-profile: corp-ldap in folder Texas #### Create LDAP Profile with SSL and Bind Credentials ```bash -$ scm set identity ldap-server-profile \ +$ scm set identity ldap-server-profile secure-ldap \ --folder Texas \ - --name secure-ldap \ --servers '[{"name": "ldaps1", "address": "ldaps.example.com", "port": 636}]' \ --base "dc=example,dc=com" \ --bind-dn "cn=admin,dc=example,dc=com" \ @@ -82,9 +85,8 @@ Created ldap-server-profile: secure-ldap in folder Texas #### Create Profile with Multiple Servers ```bash -$ scm set identity ldap-server-profile \ +$ scm set identity ldap-server-profile corp-ldap-ha \ --folder Texas \ - --name corp-ldap-ha \ --servers '[{"name": "ldap1", "address": "ldap1.example.com", "port": 389}, {"name": "ldap2", "address": "ldap2.example.com", "port": 389}]' \ --base "dc=example,dc=com" \ --ldap-type active-directory @@ -99,14 +101,19 @@ Delete an LDAP server profile from SCM. ### Syntax ```bash -scm delete identity ldap-server-profile [OPTIONS] +scm delete identity ldap-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -117,9 +124,8 @@ scm delete identity ldap-server-profile [OPTIONS] ### Example ```bash -$ scm delete identity ldap-server-profile \ +$ scm delete identity ldap-server-profile corp-ldap \ --folder Texas \ - --name corp-ldap \ --force ---> 100% Deleted ldap-server-profile: corp-ldap from folder Texas @@ -211,22 +217,29 @@ Display LDAP server profile objects. ### Syntax ```bash -scm show identity ldap-server-profile [OPTIONS] +scm show identity ldap-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | No | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -234,9 +247,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific LDAP Server Profile ```bash -$ scm show identity ldap-server-profile \ - --folder Texas \ - --name corp-ldap +$ scm show identity ldap-server-profile corp-ldap \ + --folder Texas ---> 100% LDAP Server Profile: corp-ldap Location: Folder 'Texas' diff --git a/docs-site/docs/cli/identity/radius-server-profile.md b/docs-site/docs/cli/identity/radius-server-profile.md index 8ab2bc4..fd929a8 100644 --- a/docs-site/docs/cli/identity/radius-server-profile.md +++ b/docs-site/docs/cli/identity/radius-server-profile.md @@ -19,14 +19,19 @@ Create or update a RADIUS server profile. ### Syntax ```bash -scm set identity radius-server-profile [OPTIONS] +scm set identity radius-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -42,9 +47,8 @@ scm set identity radius-server-profile [OPTIONS] #### Create RADIUS Server Profile ```bash -$ scm set identity radius-server-profile \ +$ scm set identity radius-server-profile corp-radius \ --folder Texas \ - --name corp-radius \ --servers '[{"name": "rad1", "ip_address": "10.0.0.1", "port": 1812, "secret": "s3cret"}]' \ --protocol '{"CHAP": {}}' \ --timeout 5 \ @@ -56,9 +60,8 @@ Created radius-server-profile: corp-radius in folder Texas #### Create Profile with Multiple Servers ```bash -$ scm set identity radius-server-profile \ +$ scm set identity radius-server-profile corp-radius-ha \ --folder Texas \ - --name corp-radius-ha \ --servers '[{"name": "rad1", "ip_address": "10.0.0.1", "port": 1812, "secret": "s3cret"}, {"name": "rad2", "ip_address": "10.0.0.2", "port": 1812, "secret": "s3cret"}]' \ --timeout 3 \ --retries 3 @@ -69,9 +72,8 @@ Created radius-server-profile: corp-radius-ha in folder Texas #### Create Profile with PAP Protocol ```bash -$ scm set identity radius-server-profile \ +$ scm set identity radius-server-profile vpn-radius \ --folder Texas \ - --name vpn-radius \ --servers '[{"name": "rad-vpn", "ip_address": "10.0.1.1", "port": 1812, "secret": "vpn-s3cret"}]' \ --protocol '{"PAP": {}}' \ --timeout 10 @@ -86,14 +88,19 @@ Delete a RADIUS server profile from SCM. ### Syntax ```bash -scm delete identity radius-server-profile [OPTIONS] +scm delete identity radius-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -104,9 +111,8 @@ scm delete identity radius-server-profile [OPTIONS] ### Example ```bash -$ scm delete identity radius-server-profile \ +$ scm delete identity radius-server-profile corp-radius \ --folder Texas \ - --name corp-radius \ --force ---> 100% Deleted radius-server-profile: corp-radius from folder Texas @@ -200,22 +206,29 @@ Display RADIUS server profile objects. ### Syntax ```bash -scm show identity radius-server-profile [OPTIONS] +scm show identity radius-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | No | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -223,9 +236,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific RADIUS Server Profile ```bash -$ scm show identity radius-server-profile \ - --folder Texas \ - --name corp-radius +$ scm show identity radius-server-profile corp-radius \ + --folder Texas ---> 100% RADIUS Server Profile: corp-radius Location: Folder 'Texas' diff --git a/docs-site/docs/cli/identity/saml-server-profile.md b/docs-site/docs/cli/identity/saml-server-profile.md index 06b5f4a..28c01e6 100644 --- a/docs-site/docs/cli/identity/saml-server-profile.md +++ b/docs-site/docs/cli/identity/saml-server-profile.md @@ -19,14 +19,19 @@ Create or update a SAML server profile. ### Syntax ```bash -scm set identity saml-server-profile [OPTIONS] +scm set identity saml-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -46,9 +51,8 @@ scm set identity saml-server-profile [OPTIONS] #### Create Basic SAML Server Profile ```bash -$ scm set identity saml-server-profile \ +$ scm set identity saml-server-profile corp-saml \ --folder Texas \ - --name corp-saml \ --entity-id "https://idp.example.com" \ --certificate idp-cert \ --sso-url "https://idp.example.com/sso" \ @@ -60,9 +64,8 @@ Created saml-server-profile: corp-saml in folder Texas #### Create SAML Profile with SLO and Certificate Validation ```bash -$ scm set identity saml-server-profile \ +$ scm set identity saml-server-profile secure-saml \ --folder Texas \ - --name secure-saml \ --entity-id "https://idp.example.com" \ --certificate idp-cert \ --sso-url "https://idp.example.com/sso" \ @@ -78,9 +81,8 @@ Created saml-server-profile: secure-saml in folder Texas #### Create SAML Profile with Redirect Binding ```bash -$ scm set identity saml-server-profile \ +$ scm set identity saml-server-profile redirect-saml \ --folder Texas \ - --name redirect-saml \ --entity-id "https://idp.example.com/redirect" \ --certificate idp-redirect-cert \ --sso-url "https://idp.example.com/sso/redirect" \ @@ -96,14 +98,19 @@ Delete a SAML server profile from SCM. ### Syntax ```bash -scm delete identity saml-server-profile [OPTIONS] +scm delete identity saml-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -114,9 +121,8 @@ scm delete identity saml-server-profile [OPTIONS] ### Example ```bash -$ scm delete identity saml-server-profile \ +$ scm delete identity saml-server-profile corp-saml \ --folder Texas \ - --name corp-saml \ --force ---> 100% Deleted saml-server-profile: corp-saml from folder Texas @@ -205,22 +211,29 @@ Display SAML server profile objects. ### Syntax ```bash -scm show identity saml-server-profile [OPTIONS] +scm show identity saml-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | No | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -228,9 +241,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific SAML Server Profile ```bash -$ scm show identity saml-server-profile \ - --folder Texas \ - --name corp-saml +$ scm show identity saml-server-profile corp-saml \ + --folder Texas ---> 100% SAML Server Profile: corp-saml Location: Folder 'Texas' diff --git a/docs-site/docs/cli/identity/tacacs-server-profile.md b/docs-site/docs/cli/identity/tacacs-server-profile.md index e7f2ad3..810005a 100644 --- a/docs-site/docs/cli/identity/tacacs-server-profile.md +++ b/docs-site/docs/cli/identity/tacacs-server-profile.md @@ -19,14 +19,19 @@ Create or update a TACACS+ server profile. ### Syntax ```bash -scm set identity tacacs-server-profile [OPTIONS] +scm set identity tacacs-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -42,9 +47,8 @@ scm set identity tacacs-server-profile [OPTIONS] #### Create TACACS+ Server Profile ```bash -$ scm set identity tacacs-server-profile \ +$ scm set identity tacacs-server-profile corp-tacacs \ --folder Texas \ - --name corp-tacacs \ --servers '[{"name": "tac1", "address": "10.0.0.1", "port": 49, "secret": "s3cret"}]' \ --protocol CHAP \ --timeout 5 @@ -55,9 +59,8 @@ Created tacacs-server-profile: corp-tacacs in folder Texas #### Create Profile with Multiple Servers and Single Connection ```bash -$ scm set identity tacacs-server-profile \ +$ scm set identity tacacs-server-profile corp-tacacs-ha \ --folder Texas \ - --name corp-tacacs-ha \ --servers '[{"name": "tac1", "address": "10.0.0.1", "port": 49, "secret": "s3cret"}, {"name": "tac2", "address": "10.0.0.2", "port": 49, "secret": "s3cret"}]' \ --protocol CHAP \ --timeout 3 \ @@ -69,9 +72,8 @@ Created tacacs-server-profile: corp-tacacs-ha in folder Texas #### Create Profile with PAP Protocol ```bash -$ scm set identity tacacs-server-profile \ +$ scm set identity tacacs-server-profile legacy-tacacs \ --folder Texas \ - --name legacy-tacacs \ --servers '[{"name": "tac-legacy", "address": "10.0.1.1", "port": 49, "secret": "legacy-s3cret"}]' \ --protocol PAP \ --timeout 10 @@ -86,14 +88,19 @@ Delete a TACACS+ server profile from SCM. ### Syntax ```bash -scm delete identity tacacs-server-profile [OPTIONS] +scm delete identity tacacs-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | @@ -104,9 +111,8 @@ scm delete identity tacacs-server-profile [OPTIONS] ### Example ```bash -$ scm delete identity tacacs-server-profile \ +$ scm delete identity tacacs-server-profile corp-tacacs \ --folder Texas \ - --name corp-tacacs \ --force ---> 100% Deleted tacacs-server-profile: corp-tacacs from folder Texas @@ -197,22 +203,29 @@ Display TACACS+ server profile objects. ### Syntax ```bash -scm show identity tacacs-server-profile [OPTIONS] +scm show identity tacacs-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | No | | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -220,9 +233,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific TACACS+ Server Profile ```bash -$ scm show identity tacacs-server-profile \ - --folder Texas \ - --name corp-tacacs +$ scm show identity tacacs-server-profile corp-tacacs \ + --folder Texas ---> 100% TACACS+ Server Profile: corp-tacacs Location: Folder 'Texas' diff --git a/docs-site/docs/cli/index.md b/docs-site/docs/cli/index.md index feb663c..8ff3092 100644 --- a/docs-site/docs/cli/index.md +++ b/docs-site/docs/cli/index.md @@ -7,18 +7,19 @@ The `pan-scm-cli` command-line interface provides a structured set of commands f All commands follow this pattern: ```bash -scm [options] +scm [name] [options] ``` | Component | Description | Examples | | --- | --- | --- | -| `` | Operation to perform | `set`, `delete`, `load`, `show`, `backup` | +| `` | Operation to perform | `set`, `delete`, `load`, `show`, `backup`, `move` | | `` | Category of resource | `object`, `network`, `security`, `sase` | -| `` | Specific resource type | `address`, `security-zone`, `rule` | -| `[options]` | Resource-specific parameters | `--folder`, `--name`, `--file` | +| `` | Specific resource type | `address`, `zone`, `rule` | +| `[name]` | Positional resource name (required for `set`/`delete`/`move`; optional for `show`) | `web-server`, `Allow-Web` | +| `[options]` | Resource-specific parameters | `--folder`, `--file` | :::note -All `show` commands default to listing all items when no `--name` parameter +All `show` commands default to listing all items when no `NAME` argument is provided. ::: diff --git a/docs-site/docs/cli/insights.md b/docs-site/docs/cli/insights.md index 933d121..8003084 100644 --- a/docs-site/docs/cli/insights.md +++ b/docs-site/docs/cli/insights.md @@ -37,7 +37,6 @@ View and export security and system alerts from your Prisma Access environment. | `--max-results INT` | Maximum number of results to return (default: 10) | No | | `--export TEXT` | Export format (json, csv) | No | | `--output TEXT` | Output file path for export | No | -| `--mock` | Run in mock mode | No | \* One of --list, --id, or --real-time is required. @@ -126,7 +125,6 @@ Monitor mobile user connections and activity. | `--max-results INT` | Maximum number of results to return (default: 100) | No | | `--export TEXT` | Export format (json, csv) | No | | `--output TEXT` | Output file path for export | No | -| `--mock` | Run in mock mode | No | \* One of --list or --id is required. @@ -187,7 +185,6 @@ View location-based metrics and capacity information. | `--max-results INT` | Maximum number of results to return (default: 100) | No | | `--export TEXT` | Export format (json, csv) | No | | `--output TEXT` | Output file path for export | No | -| `--mock` | Run in mock mode | No | \* One of --list or --id is required. @@ -239,7 +236,6 @@ Monitor remote network connectivity and performance. | `--max-results INT` | Maximum number of results to return (default: 100) | No | | `--export TEXT` | Export format (json, csv) | No | | `--output TEXT` | Output file path for export | No | -| `--mock` | Run in mock mode | No | \* One of --list or --id is required. @@ -304,7 +300,6 @@ Monitor cloud service connections and their health status. | `--max-results INT` | Maximum number of results to return (default: 100) | No | | `--export TEXT` | Export format (json, csv) | No | | `--output TEXT` | Output file path for export | No | -| `--mock` | Run in mock mode | No | \* One of --list or --id is required. @@ -358,7 +353,6 @@ Monitor IPSec and SSL tunnel status and performance. | `--max-results INT` | Maximum number of results to return (default: 100) | No | | `--export TEXT` | Export format (json, csv) | No | | `--output TEXT` | Output file path for export | No | -| `--mock` | Run in mock mode | No | \* One of --list or --id is required. @@ -418,7 +412,6 @@ All insights commands support these common options: | `--max-results INT` | Limit the number of results (default varies by command) | No | | `--export TEXT` | Export format (json or csv) | No | | `--output TEXT` | Output file path for exports | No | -| `--mock` | Run in mock mode for testing | No | ## Export Formats @@ -456,4 +449,4 @@ alert-001,Critical CPU Usage,critical,active,2026-03-08T10:30:00Z,CPU usage exce - Export operations respect the `--max-results` limit - Time filters accept ISO timestamps (e.g., `2026-03-01T00:00:00`) - Alerts default to the last 7 days when no `--start` time is specified -- All commands support `--mock` for testing without API credentials +- Set the `SCM_MOCK=1` environment variable to test without API credentials diff --git a/docs-site/docs/cli/mobile-agent/agent-profile.md b/docs-site/docs/cli/mobile-agent/agent-profile.md index fd19ead..a8ac534 100644 --- a/docs-site/docs/cli/mobile-agent/agent-profile.md +++ b/docs-site/docs/cli/mobile-agent/agent-profile.md @@ -23,15 +23,20 @@ Create or update an agent profile. ### Syntax ```bash -scm set mobile-agent agent-profile [OPTIONS] +scm set mobile-agent agent-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the agent profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (defaults to `Mobile Users`) | No | -| `--name TEXT` | Name of the agent profile | Yes | | `--connect-method TEXT` | Connect method (`user-logon`, `pre-logon`, `on-demand`, `pre-logon-then-on-demand`) | No | | `--tunnel-mtu INT` | GlobalProtect connection MTU in bytes (1000-1420) | No | | `--os TEXT` | Operating system, repeatable (`Android`, `Chrome`, `IoT`, `Linux`, `Mac`, `Windows`, `WindowsUWP`, `iOS`) | No | @@ -46,9 +51,8 @@ Nested settings (agent UI, gateways, HIP collection, authentication override, ce #### Create Agent Profile ```bash -$ scm set mobile-agent agent-profile \ +$ scm set mobile-agent agent-profile "corp-app-settings" \ --folder "Mobile Users" \ - --name "corp-app-settings" \ --connect-method user-logon \ --tunnel-mtu 1400 \ --os Windows --os Mac @@ -58,8 +62,7 @@ Created agent profile: corp-app-settings in folder Mobile Users #### Update Save Credentials Behavior ```bash -$ scm set mobile-agent agent-profile \ - --name "corp-app-settings" \ +$ scm set mobile-agent agent-profile "corp-app-settings" \ --save-user-credentials 3 Updated agent profile: corp-app-settings in folder Mobile Users ``` @@ -71,15 +74,22 @@ Display one or all agent profiles. ### Syntax ```bash -scm show mobile-agent agent-profile [OPTIONS] +scm show mobile-agent agent-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the agent profile to show (lists all when omitted); omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (defaults to `Mobile Users`) | No | -| `--name TEXT` | Name of the agent profile to show (lists all when omitted) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -88,7 +98,7 @@ scm show mobile-agent agent-profile [OPTIONS] $ scm show mobile-agent agent-profile # Show a specific agent profile -$ scm show mobile-agent agent-profile --name "corp-app-settings" +$ scm show mobile-agent agent-profile "corp-app-settings" ``` ## Delete Agent Profile @@ -98,21 +108,26 @@ Remove an agent profile. ### Syntax ```bash -scm delete mobile-agent agent-profile [OPTIONS] +scm delete mobile-agent agent-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the agent profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (defaults to `Mobile Users`) | No | -| `--name TEXT` | Name of the agent profile to delete | Yes | | `--force` | Skip the confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent agent-profile --name "corp-app-settings" --force +$ scm delete mobile-agent agent-profile "corp-app-settings" --force Deleted agent profile: corp-app-settings from folder Mobile Users ``` diff --git a/docs-site/docs/cli/mobile-agent/agent-version.md b/docs-site/docs/cli/mobile-agent/agent-version.md index c930379..9ed60b6 100644 --- a/docs-site/docs/cli/mobile-agent/agent-version.md +++ b/docs-site/docs/cli/mobile-agent/agent-version.md @@ -9,18 +9,25 @@ Display agent version objects. ### Syntax ```bash -scm show mobile-agent agent-version [OPTIONS] +scm show mobile-agent agent-version [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of a specific agent version; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | Yes | -| `--name TEXT` | Name of a specific agent version | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all agent versions are listed by default. +When no `NAME` is specified, all agent versions are listed by default. ::: ### Examples @@ -28,9 +35,8 @@ When no `--name` is specified, all agent versions are listed by default. #### Show Specific Agent Version ```bash -$ scm show mobile-agent agent-version \ - --folder "Mobile Users" \ - --name "5.2.0" +$ scm show mobile-agent agent-version "5.2.0" \ + --folder "Mobile Users" ---> 100% Agent Version: 5.2.0 Location: Folder 'Mobile Users' diff --git a/docs-site/docs/cli/mobile-agent/auth-setting.md b/docs-site/docs/cli/mobile-agent/auth-setting.md index 44156de..05e6b52 100644 --- a/docs-site/docs/cli/mobile-agent/auth-setting.md +++ b/docs-site/docs/cli/mobile-agent/auth-setting.md @@ -29,15 +29,20 @@ Create or update an auth setting. ### Syntax ```bash -scm set mobile-agent auth-setting [OPTIONS] +scm set mobile-agent auth-setting NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the auth setting | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | Yes | -| `--name TEXT` | Name of the auth setting | Yes | | `--description TEXT` | Description | No | | `--auth-type TEXT` | Authentication type (saml, client-certificate, ldap) | No | | `--os TEXT` | Operating system (Any, Windows, macOS, Linux, iOS, Android, ChromeOS) | No | @@ -51,9 +56,8 @@ scm set mobile-agent auth-setting [OPTIONS] #### Create SAML Auth Setting ```bash -$ scm set mobile-agent auth-setting \ +$ scm set mobile-agent auth-setting "saml-auth" \ --folder "Mobile Users" \ - --name "saml-auth" \ --auth-type saml \ --saml-idp "okta-idp" \ --os Any @@ -64,9 +68,8 @@ Created auth setting: saml-auth in folder Mobile Users #### Create LDAP Auth Setting ```bash -$ scm set mobile-agent auth-setting \ +$ scm set mobile-agent auth-setting "ldap-auth" \ --folder "Mobile Users" \ - --name "ldap-auth" \ --auth-type ldap \ --ldap-profile "corp-ldap" \ --os Windows \ @@ -78,9 +81,8 @@ Created auth setting: ldap-auth in folder Mobile Users #### Create Certificate Auth Setting ```bash -$ scm set mobile-agent auth-setting \ +$ scm set mobile-agent auth-setting "cert-auth" \ --folder "Mobile Users" \ - --name "cert-auth" \ --auth-type client-certificate \ --certificate-profile "gp-cert-profile" \ --os macOS @@ -95,23 +97,27 @@ Delete an auth setting from SCM. ### Syntax ```bash -scm delete mobile-agent auth-setting [OPTIONS] +scm delete mobile-agent auth-setting NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the auth setting to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | Yes | -| `--name TEXT` | Name of the auth setting to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete mobile-agent auth-setting \ +$ scm delete mobile-agent auth-setting "saml-auth" \ --folder "Mobile Users" \ - --name "saml-auth" \ --force ---> 100% Deleted auth setting: saml-auth from folder Mobile Users @@ -195,18 +201,25 @@ Display auth setting objects. ### Syntax ```bash -scm show mobile-agent auth-setting [OPTIONS] +scm show mobile-agent auth-setting [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of a specific auth setting; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | Yes | -| `--name TEXT` | Name of a specific auth setting | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -214,9 +227,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Auth Setting ```bash -$ scm show mobile-agent auth-setting \ - --folder "Mobile Users" \ - --name "saml-auth" +$ scm show mobile-agent auth-setting "saml-auth" \ + --folder "Mobile Users" ---> 100% Auth Setting: saml-auth Location: Folder 'Mobile Users' diff --git a/docs-site/docs/cli/mobile-agent/forwarding-profile-destination.md b/docs-site/docs/cli/mobile-agent/forwarding-profile-destination.md index 9b42da3..7054f3e 100644 --- a/docs-site/docs/cli/mobile-agent/forwarding-profile-destination.md +++ b/docs-site/docs/cli/mobile-agent/forwarding-profile-destination.md @@ -14,7 +14,7 @@ The `forwarding-profile-destination` commands allow you to: - Export destinations for backup or migration :::note -Destinations only support the `Mobile Users` folder. The API addresses individual destinations by UUID; the CLI resolves `--name` to the UUID for you, or you can pass `--id` directly. +Destinations only support the `Mobile Users` folder. The API addresses individual destinations by UUID; the CLI resolves the `NAME` argument to the UUID for you, or you can pass `--id` directly. ::: ## Entry Formats @@ -33,15 +33,20 @@ Create or update a destination. ### Syntax ```bash -scm set mobile-agent forwarding-profile-destination [OPTIONS] +scm set mobile-agent forwarding-profile-destination NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the destination | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the destination | Yes | | `--description TEXT` | Description | No | | `--fqdn TEXT` | FQDN entry as `host[:port]` (repeatable) | No | | `--ip-address TEXT` | IP entry as `ip[/prefix][:port]` (repeatable) | No | @@ -49,9 +54,8 @@ scm set mobile-agent forwarding-profile-destination [OPTIONS] ### Examples ```bash -$ scm set mobile-agent forwarding-profile-destination \ +$ scm set mobile-agent forwarding-profile-destination "internal-apps" \ --folder "Mobile Users" \ - --name "internal-apps" \ --fqdn "*.example.com:8080" \ --fqdn "app.internal" \ --ip-address "10.0.0.0/8" @@ -65,16 +69,23 @@ Display destinations. ### Syntax ```bash -scm show mobile-agent forwarding-profile-destination [OPTIONS] +scm show mobile-agent forwarding-profile-destination [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the destination to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | No (defaults to `Mobile Users`) | -| `--name TEXT` | Name of the destination to show | No | | `--id TEXT` | UUID of the destination to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -83,7 +94,7 @@ scm show mobile-agent forwarding-profile-destination [OPTIONS] $ scm show mobile-agent forwarding-profile-destination --folder "Mobile Users" # Show by name -$ scm show mobile-agent forwarding-profile-destination --folder "Mobile Users" --name "internal-apps" +$ scm show mobile-agent forwarding-profile-destination "internal-apps" --folder "Mobile Users" # Show by UUID $ scm show mobile-agent forwarding-profile-destination --id "123e4567-e89b-12d3-a456-426655440000" @@ -96,22 +107,27 @@ Delete a destination by name or UUID. ### Syntax ```bash -scm delete mobile-agent forwarding-profile-destination [OPTIONS] +scm delete mobile-agent forwarding-profile-destination [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the destination to delete (omit when using `--id`) | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | No (defaults to `Mobile Users`) | -| `--name TEXT` | Name of the destination to delete | Yes (or `--id`) | | `--id TEXT` | UUID of the destination to delete | No | | `--force` | Skip confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent forwarding-profile-destination --folder "Mobile Users" --name "internal-apps" --force +$ scm delete mobile-agent forwarding-profile-destination "internal-apps" --folder "Mobile Users" --force Deleted forwarding profile destination: internal-apps ``` diff --git a/docs-site/docs/cli/mobile-agent/forwarding-profile-regional-and-custom-proxy.md b/docs-site/docs/cli/mobile-agent/forwarding-profile-regional-and-custom-proxy.md index c938015..0f2fdbc 100644 --- a/docs-site/docs/cli/mobile-agent/forwarding-profile-regional-and-custom-proxy.md +++ b/docs-site/docs/cli/mobile-agent/forwarding-profile-regional-and-custom-proxy.md @@ -35,15 +35,20 @@ Create or update a forwarding profile regional and custom proxy. ### Syntax ```bash -scm set mobile-agent forwarding-profile-regional-and-custom-proxy [OPTIONS] +scm set mobile-agent forwarding-profile-regional-and-custom-proxy NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the regional and custom proxy | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the regional and custom proxy | Yes | | `--type TEXT` | Proxy type (`gp-and-pac`, `ztna-agent`) | No | | `--proxy-1-fqdn TEXT` | Primary proxy server FQDN | No | | `--proxy-1-port INT` | Primary proxy server port (1-65535) | No | @@ -62,9 +67,8 @@ Nested `connectivity_preference` and `prisma_access_locations` entries are suppo ### Examples ```bash -$ scm set mobile-agent forwarding-profile-regional-and-custom-proxy \ +$ scm set mobile-agent forwarding-profile-regional-and-custom-proxy "emea-proxy" \ --folder "Mobile Users" \ - --name "emea-proxy" \ --type gp-and-pac \ --proxy-1-fqdn "proxy1.example.com" \ --proxy-1-port 8080 \ @@ -79,15 +83,22 @@ Display forwarding profile regional and custom proxies. ### Syntax ```bash -scm show mobile-agent forwarding-profile-regional-and-custom-proxy [OPTIONS] +scm show mobile-agent forwarding-profile-regional-and-custom-proxy [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the regional and custom proxy to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the regional and custom proxy to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -96,7 +107,7 @@ scm show mobile-agent forwarding-profile-regional-and-custom-proxy [OPTIONS] $ scm show mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" # Show a specific regional and custom proxy by name -$ scm show mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" --name "emea-proxy" +$ scm show mobile-agent forwarding-profile-regional-and-custom-proxy "emea-proxy" --folder "Mobile Users" ``` ## Delete Forwarding Profile Regional and Custom Proxy @@ -106,21 +117,26 @@ Remove a forwarding profile regional and custom proxy. ### Syntax ```bash -scm delete mobile-agent forwarding-profile-regional-and-custom-proxy [OPTIONS] +scm delete mobile-agent forwarding-profile-regional-and-custom-proxy NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the regional and custom proxy | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the regional and custom proxy | Yes | | `--force` | Skip confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" --name "emea-proxy" --force +$ scm delete mobile-agent forwarding-profile-regional-and-custom-proxy "emea-proxy" --folder "Mobile Users" --force Deleted forwarding profile regional and custom proxy: emea-proxy from folder Mobile Users ``` diff --git a/docs-site/docs/cli/mobile-agent/forwarding-profile-source-application.md b/docs-site/docs/cli/mobile-agent/forwarding-profile-source-application.md index f1a6a04..95d1523 100644 --- a/docs-site/docs/cli/mobile-agent/forwarding-profile-source-application.md +++ b/docs-site/docs/cli/mobile-agent/forwarding-profile-source-application.md @@ -23,24 +23,28 @@ Create or update a forwarding profile source application. ### Syntax ```bash -scm set mobile-agent forwarding-profile-source-application [OPTIONS] +scm set mobile-agent forwarding-profile-source-application NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the source application | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the source application | Yes | | `--application TEXT` | Application name (repeatable) | Yes | | `--description TEXT` | Description | No | ### Examples ```bash -$ scm set mobile-agent forwarding-profile-source-application \ +$ scm set mobile-agent forwarding-profile-source-application "office-apps" \ --folder "Mobile Users" \ - --name "office-apps" \ --application slack \ --application zoom \ --description "Collaboration applications" @@ -54,15 +58,22 @@ Display forwarding profile source applications. ### Syntax ```bash -scm show mobile-agent forwarding-profile-source-application [OPTIONS] +scm show mobile-agent forwarding-profile-source-application [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the source application to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the source application to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -71,7 +82,7 @@ scm show mobile-agent forwarding-profile-source-application [OPTIONS] $ scm show mobile-agent forwarding-profile-source-application --folder "Mobile Users" # Show a specific source application by name -$ scm show mobile-agent forwarding-profile-source-application --folder "Mobile Users" --name "office-apps" +$ scm show mobile-agent forwarding-profile-source-application "office-apps" --folder "Mobile Users" ``` ## Delete Forwarding Profile Source Application @@ -81,21 +92,26 @@ Remove a forwarding profile source application. ### Syntax ```bash -scm delete mobile-agent forwarding-profile-source-application [OPTIONS] +scm delete mobile-agent forwarding-profile-source-application NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the source application | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the source application | Yes | | `--force` | Skip confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent forwarding-profile-source-application --folder "Mobile Users" --name "office-apps" --force +$ scm delete mobile-agent forwarding-profile-source-application "office-apps" --folder "Mobile Users" --force Deleted forwarding profile source application: office-apps from folder Mobile Users ``` diff --git a/docs-site/docs/cli/mobile-agent/forwarding-profile-user-location.md b/docs-site/docs/cli/mobile-agent/forwarding-profile-user-location.md index c8409ce..baa7cbe 100644 --- a/docs-site/docs/cli/mobile-agent/forwarding-profile-user-location.md +++ b/docs-site/docs/cli/mobile-agent/forwarding-profile-user-location.md @@ -34,15 +34,20 @@ Create or update a forwarding profile user location. ### Syntax ```bash -scm set mobile-agent forwarding-profile-user-location [OPTIONS] +scm set mobile-agent forwarding-profile-user-location NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the user location | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the user location | Yes | | `--ip-address TEXT` | User location IP address (repeatable) | One criteria | | `--internal-host-ip TEXT` | Internal host detection IP address | One criteria | | `--internal-host-fqdn TEXT` | Internal host detection FQDN | One criteria | @@ -52,17 +57,15 @@ scm set mobile-agent forwarding-profile-user-location [OPTIONS] ```bash # IP address based location -$ scm set mobile-agent forwarding-profile-user-location \ +$ scm set mobile-agent forwarding-profile-user-location "branch-network" \ --folder "Mobile Users" \ - --name "branch-network" \ --ip-address "10.1.0.0/16" \ --ip-address "10.2.*.*" Created forwarding profile user location: branch-network in folder Mobile Users # Internal host detection based location -$ scm set mobile-agent forwarding-profile-user-location \ +$ scm set mobile-agent forwarding-profile-user-location "corp-office" \ --folder "Mobile Users" \ - --name "corp-office" \ --internal-host-ip "192.168.1.1" \ --internal-host-fqdn "intranet.example.com" Created forwarding profile user location: corp-office in folder Mobile Users @@ -75,15 +78,22 @@ Display forwarding profile user locations. ### Syntax ```bash -scm show mobile-agent forwarding-profile-user-location [OPTIONS] +scm show mobile-agent forwarding-profile-user-location [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the user location to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the user location to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -92,7 +102,7 @@ scm show mobile-agent forwarding-profile-user-location [OPTIONS] $ scm show mobile-agent forwarding-profile-user-location --folder "Mobile Users" # Show a specific user location by name -$ scm show mobile-agent forwarding-profile-user-location --folder "Mobile Users" --name "branch-network" +$ scm show mobile-agent forwarding-profile-user-location "branch-network" --folder "Mobile Users" ``` ## Delete Forwarding Profile User Location @@ -102,21 +112,26 @@ Remove a forwarding profile user location. ### Syntax ```bash -scm delete mobile-agent forwarding-profile-user-location [OPTIONS] +scm delete mobile-agent forwarding-profile-user-location NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the user location | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the user location | Yes | | `--force` | Skip confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent forwarding-profile-user-location --folder "Mobile Users" --name "branch-network" --force +$ scm delete mobile-agent forwarding-profile-user-location "branch-network" --folder "Mobile Users" --force Deleted forwarding profile user location: branch-network from folder Mobile Users ``` diff --git a/docs-site/docs/cli/mobile-agent/forwarding-profile.md b/docs-site/docs/cli/mobile-agent/forwarding-profile.md index 42971df..5ddf606 100644 --- a/docs-site/docs/cli/mobile-agent/forwarding-profile.md +++ b/docs-site/docs/cli/mobile-agent/forwarding-profile.md @@ -14,7 +14,7 @@ The `forwarding-profile` commands allow you to: - Export forwarding profiles for backup or migration :::note -Forwarding profiles only support the `Mobile Users` folder. The API addresses individual profiles by UUID; the CLI resolves `--name` to the UUID for you, or you can pass `--id` directly. +Forwarding profiles only support the `Mobile Users` folder. The API addresses individual profiles by UUID; the CLI resolves the `NAME` argument to the UUID for you, or you can pass `--id` directly. ::: ## Profile Types @@ -32,15 +32,20 @@ Create or update a forwarding profile. ### Syntax ```bash -scm set mobile-agent forwarding-profile [OPTIONS] +scm set mobile-agent forwarding-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the forwarding profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (must be `Mobile Users`) | Yes | -| `--name TEXT` | Name of the forwarding profile | Yes | | `--description TEXT` | Description | No | | `--definition-method TEXT` | How the profile is defined: `rules` or `pac-file` | No | | `--profile-type TEXT` | Profile type: `pac-file`, `global-protect-proxy`, or `ztna-agent` | No | @@ -53,9 +58,8 @@ Complex forwarding rules and block rules are not expressible as flags — use [L #### Create ZTNA Agent Profile ```bash -$ scm set mobile-agent forwarding-profile \ +$ scm set mobile-agent forwarding-profile "ztna-profile" \ --folder "Mobile Users" \ - --name "ztna-profile" \ --profile-type ztna-agent Created forwarding profile: ztna-profile ``` @@ -63,9 +67,8 @@ Created forwarding profile: ztna-profile #### Create GlobalProtect Proxy Profile with PAC Upload ```bash -$ scm set mobile-agent forwarding-profile \ +$ scm set mobile-agent forwarding-profile "proxy-profile" \ --folder "Mobile Users" \ - --name "proxy-profile" \ --profile-type global-protect-proxy \ --pac-upload Created forwarding profile: proxy-profile @@ -78,16 +81,23 @@ Display forwarding profiles. ### Syntax ```bash -scm show mobile-agent forwarding-profile [OPTIONS] +scm show mobile-agent forwarding-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | No (defaults to `Mobile Users`) | -| `--name TEXT` | Name of the profile to show | No | | `--id TEXT` | UUID of the profile to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -96,7 +106,7 @@ scm show mobile-agent forwarding-profile [OPTIONS] $ scm show mobile-agent forwarding-profile --folder "Mobile Users" # Show by name -$ scm show mobile-agent forwarding-profile --folder "Mobile Users" --name "ztna-profile" +$ scm show mobile-agent forwarding-profile "ztna-profile" --folder "Mobile Users" # Show by UUID $ scm show mobile-agent forwarding-profile --id "123e4567-e89b-12d3-a456-426655440000" @@ -109,22 +119,27 @@ Delete a forwarding profile by name or UUID. ### Syntax ```bash -scm delete mobile-agent forwarding-profile [OPTIONS] +scm delete mobile-agent forwarding-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the profile to delete (omit when using `--id`) | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location | No (defaults to `Mobile Users`) | -| `--name TEXT` | Name of the profile to delete | Yes (or `--id`) | | `--id TEXT` | UUID of the profile to delete | No | | `--force` | Skip confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent forwarding-profile --folder "Mobile Users" --name "ztna-profile" --force +$ scm delete mobile-agent forwarding-profile "ztna-profile" --folder "Mobile Users" --force Deleted forwarding profile: ztna-profile ``` diff --git a/docs-site/docs/cli/mobile-agent/global-setting.md b/docs-site/docs/cli/mobile-agent/global-setting.md index fa7773e..1d18715 100644 --- a/docs-site/docs/cli/mobile-agent/global-setting.md +++ b/docs-site/docs/cli/mobile-agent/global-setting.md @@ -10,7 +10,7 @@ The `global-setting` commands allow you to: - Update the agent version and manual gateway configuration :::note[Singleton semantics] -Global settings always exist for the tenant and are updated in place. There are no `delete`, `backup`, or `load` commands, and no `--folder` or `--name` options. +Global settings always exist for the tenant and are updated in place. There are no `delete`, `backup`, or `load` commands, no `--folder` option, and no `NAME` argument. ::: ## Show Global Setting diff --git a/docs-site/docs/cli/mobile-agent/infrastructure-setting.md b/docs-site/docs/cli/mobile-agent/infrastructure-setting.md index 584a97a..496a4ce 100644 --- a/docs-site/docs/cli/mobile-agent/infrastructure-setting.md +++ b/docs-site/docs/cli/mobile-agent/infrastructure-setting.md @@ -17,7 +17,7 @@ Infrastructure settings live only in the `Mobile Users` folder. The `--folder` o ::: :::note[Name required for show and backup] -The SCM API addresses this resource by name everywhere, including list. There is no list-all mode, so `show` and `backup` require `--name`. +The SCM API addresses this resource by name everywhere, including list. There is no list-all mode, so `show` requires the `NAME` argument and `backup` requires `--name`. ::: ## Set Infrastructure Setting @@ -27,14 +27,19 @@ Create or update an infrastructure setting. ### Syntax ```bash -scm set mobile-agent infrastructure-setting [OPTIONS] +scm set mobile-agent infrastructure-setting NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the infrastructure setting | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the infrastructure setting | Yes | | `--folder TEXT` | Folder location (must be `Mobile Users`, the default) | No | | `--dns-servers JSON` | DNS server entries as a JSON list | Yes | | `--ip-pools JSON` | IP pools as a JSON list | Yes | @@ -47,8 +52,7 @@ scm set mobile-agent infrastructure-setting [OPTIONS] ### Examples ```bash -$ scm set mobile-agent infrastructure-setting \ - --name "gp-infra" \ +$ scm set mobile-agent infrastructure-setting "gp-infra" \ --dns-servers '[{"name": "dns-1", "dns_suffix": ["example.com"], "primary_public_dns": {"dns_server": "8.8.8.8"}}]' \ --ip-pools '[{"name": "pool-1", "ip_pool": ["10.0.0.0/16"]}]' \ --portal-hostname '{"default_domain": {"hostname": "acme"}}' @@ -56,8 +60,7 @@ Created infrastructure setting: gp-infra in folder Mobile Users ``` ```bash -$ scm set mobile-agent infrastructure-setting \ - --name "gp-infra" \ +$ scm set mobile-agent infrastructure-setting "gp-infra" \ --dns-servers '[{"name": "dns-1", "dns_suffix": ["example.com"]}]' \ --ip-pools '[{"name": "pool-1", "ip_pool": ["10.0.0.0/16"]}]' \ --portal-hostname '{"custom_domain": {"hostname": "vpn.acme.com", "cname": "acme.gpcloudservice.com", "ssl_tls_service_profile": "acme-profile"}}' \ @@ -73,13 +76,13 @@ Display a named infrastructure setting. ### Syntax ```bash -scm show mobile-agent infrastructure-setting --name NAME +scm show mobile-agent infrastructure-setting NAME ``` ### Examples ```bash -$ scm show mobile-agent infrastructure-setting --name "gp-infra" +$ scm show mobile-agent infrastructure-setting "gp-infra" Infrastructure Setting: gp-infra ================================================================================ @@ -96,13 +99,13 @@ Delete an infrastructure setting. ### Syntax ```bash -scm delete mobile-agent infrastructure-setting --name NAME [--force] +scm delete mobile-agent infrastructure-setting NAME [--force] ``` ### Examples ```bash -$ scm delete mobile-agent infrastructure-setting --name "gp-infra" --force +$ scm delete mobile-agent infrastructure-setting "gp-infra" --force Deleted infrastructure setting: gp-infra from folder Mobile Users ``` diff --git a/docs-site/docs/cli/mobile-agent/tunnel-profile.md b/docs-site/docs/cli/mobile-agent/tunnel-profile.md index be9e73f..0ca08c0 100644 --- a/docs-site/docs/cli/mobile-agent/tunnel-profile.md +++ b/docs-site/docs/cli/mobile-agent/tunnel-profile.md @@ -23,15 +23,20 @@ Create or update a tunnel profile. ### Syntax ```bash -scm set mobile-agent tunnel-profile [OPTIONS] +scm set mobile-agent tunnel-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tunnel profile (max 31 chars) | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (defaults to `Mobile Users`) | No | -| `--name TEXT` | Name of the tunnel profile (max 31 chars) | Yes | | `--no-direct-access-to-local-network / --allow-direct-access-to-local-network` | Disable/allow direct access to the local network | No | | `--retrieve-framed-ip-address / --no-retrieve-framed-ip-address` | Retrieve the framed IP address from the authentication server | No | | `--os TEXT` | Operating system, repeatable (`Android`, `Chrome`, `IoT`, `Linux`, `Mac`, `Windows`, `WindowsUWP`, `iOS`) | No | @@ -48,9 +53,8 @@ Nested settings (authentication override cookies, source address, split tunnelin #### Create Tunnel Profile with Split Tunneling ```bash -$ scm set mobile-agent tunnel-profile \ +$ scm set mobile-agent tunnel-profile "corp-tunnel" \ --folder "Mobile Users" \ - --name "corp-tunnel" \ --access-route 10.0.0.0/8 \ --exclude-access-route 192.168.1.0/24 \ --no-direct-access-to-local-network @@ -64,15 +68,22 @@ Display one or all tunnel profiles. ### Syntax ```bash -scm show mobile-agent tunnel-profile [OPTIONS] +scm show mobile-agent tunnel-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tunnel profile to show (lists all when omitted); omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (defaults to `Mobile Users`) | No | -| `--name TEXT` | Name of the tunnel profile to show (lists all when omitted) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | ### Examples @@ -81,7 +92,7 @@ scm show mobile-agent tunnel-profile [OPTIONS] $ scm show mobile-agent tunnel-profile # Show a specific tunnel profile -$ scm show mobile-agent tunnel-profile --name "corp-tunnel" +$ scm show mobile-agent tunnel-profile "corp-tunnel" ``` ## Delete Tunnel Profile @@ -91,21 +102,26 @@ Remove a tunnel profile. ### Syntax ```bash -scm delete mobile-agent tunnel-profile [OPTIONS] +scm delete mobile-agent tunnel-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tunnel profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | | `--folder TEXT` | Folder location (defaults to `Mobile Users`) | No | -| `--name TEXT` | Name of the tunnel profile to delete | Yes | | `--force` | Skip the confirmation prompt | No | ### Examples ```bash -$ scm delete mobile-agent tunnel-profile --name "corp-tunnel" --force +$ scm delete mobile-agent tunnel-profile "corp-tunnel" --force Deleted tunnel profile: corp-tunnel from folder Mobile Users ``` diff --git a/docs-site/docs/cli/network/aggregate-interface.md b/docs-site/docs/cli/network/aggregate-interface.md index bddaf4e..41fbbd7 100644 --- a/docs-site/docs/cli/network/aggregate-interface.md +++ b/docs-site/docs/cli/network/aggregate-interface.md @@ -22,19 +22,24 @@ Create or update an aggregate interface. scm set network aggregate-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the aggregate interface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--comment TEXT` | Interface description | No | | `--layer2-json TEXT` | Layer2 config as JSON | No | | `--layer3-json TEXT` | Layer3 config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -70,17 +75,22 @@ Delete an aggregate interface from SCM. scm delete network aggregate-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the aggregate interface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -105,13 +115,11 @@ scm load network aggregate-interface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -170,22 +178,29 @@ Display aggregate interface objects. ### Syntax ```bash -scm show network aggregate-interface [OPTIONS] +scm show network aggregate-interface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the aggregate interface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific interface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -193,7 +208,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Aggregate Interface ```bash -$ scm show network aggregate-interface --folder Texas --name ae1 +$ scm show network aggregate-interface ae1 --folder Texas ---> 100% Aggregate Interface: ae1 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/bgp-address-family-profile.md b/docs-site/docs/cli/network/bgp-address-family-profile.md index f8dd862..7ee673b 100644 --- a/docs-site/docs/cli/network/bgp-address-family-profile.md +++ b/docs-site/docs/cli/network/bgp-address-family-profile.md @@ -22,17 +22,22 @@ Create or update a BGP address family profile. scm set network bgp-address-family-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP address family profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--ipv4-json TEXT` | IPv4 address family config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -66,17 +71,22 @@ Delete a BGP address family profile from SCM. scm delete network bgp-address-family-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP address family profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -101,13 +111,11 @@ scm load network bgp-address-family-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -163,22 +171,29 @@ Display BGP address family profile objects. ### Syntax ```bash -scm show network bgp-address-family-profile [OPTIONS] +scm show network bgp-address-family-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP address family profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -186,7 +201,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific BGP Address Family Profile ```bash -$ scm show network bgp-address-family-profile --folder Texas --name my-af-profile +$ scm show network bgp-address-family-profile my-af-profile --folder Texas ---> 100% BGP Address Family Profile: my-af-profile Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/bgp-auth-profile.md b/docs-site/docs/cli/network/bgp-auth-profile.md index 5756fa9..c1bd036 100644 --- a/docs-site/docs/cli/network/bgp-auth-profile.md +++ b/docs-site/docs/cli/network/bgp-auth-profile.md @@ -22,17 +22,22 @@ Create or update a BGP auth profile. scm set network bgp-auth-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP auth profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--secret TEXT` | BGP authentication key | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -66,17 +71,22 @@ Delete a BGP auth profile from SCM. scm delete network bgp-auth-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP auth profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -101,13 +111,11 @@ scm load network bgp-auth-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -159,22 +167,29 @@ Display BGP auth profile objects. ### Syntax ```bash -scm show network bgp-auth-profile [OPTIONS] +scm show network bgp-auth-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP auth profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -182,7 +197,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific BGP Auth Profile ```bash -$ scm show network bgp-auth-profile --folder Texas --name my-bgp-auth +$ scm show network bgp-auth-profile my-bgp-auth --folder Texas ---> 100% BGP Auth Profile: my-bgp-auth Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/bgp-filtering-profile.md b/docs-site/docs/cli/network/bgp-filtering-profile.md index 39fbbcf..660bca2 100644 --- a/docs-site/docs/cli/network/bgp-filtering-profile.md +++ b/docs-site/docs/cli/network/bgp-filtering-profile.md @@ -22,17 +22,22 @@ Create or update a BGP filtering profile. scm set network bgp-filtering-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP filtering profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--ipv4-json TEXT` | IPv4 filtering config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -66,17 +71,22 @@ Delete a BGP filtering profile from SCM. scm delete network bgp-filtering-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP filtering profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -101,13 +111,11 @@ scm load network bgp-filtering-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -163,22 +171,29 @@ Display BGP filtering profile objects. ### Syntax ```bash -scm show network bgp-filtering-profile [OPTIONS] +scm show network bgp-filtering-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP filtering profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -186,7 +201,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific BGP Filtering Profile ```bash -$ scm show network bgp-filtering-profile --folder Texas --name my-filter +$ scm show network bgp-filtering-profile my-filter --folder Texas ---> 100% BGP Filtering Profile: my-filter Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/bgp-redistribution-profile.md b/docs-site/docs/cli/network/bgp-redistribution-profile.md index 6c746b1..72e94a1 100644 --- a/docs-site/docs/cli/network/bgp-redistribution-profile.md +++ b/docs-site/docs/cli/network/bgp-redistribution-profile.md @@ -22,17 +22,22 @@ Create or update a BGP redistribution profile. scm set network bgp-redistribution-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP redistribution profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--ipv4-json TEXT` | IPv4 redistribution config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -66,17 +71,22 @@ Delete a BGP redistribution profile from SCM. scm delete network bgp-redistribution-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP redistribution profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -101,13 +111,11 @@ scm load network bgp-redistribution-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -165,22 +173,29 @@ Display BGP redistribution profile objects. ### Syntax ```bash -scm show network bgp-redistribution-profile [OPTIONS] +scm show network bgp-redistribution-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP redistribution profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -188,7 +203,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific BGP Redistribution Profile ```bash -$ scm show network bgp-redistribution-profile --folder Texas --name my-redist +$ scm show network bgp-redistribution-profile my-redist --folder Texas ---> 100% BGP Redistribution Profile: my-redist Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/bgp-route-map-redistribution.md b/docs-site/docs/cli/network/bgp-route-map-redistribution.md index e195f5f..91948bc 100644 --- a/docs-site/docs/cli/network/bgp-route-map-redistribution.md +++ b/docs-site/docs/cli/network/bgp-route-map-redistribution.md @@ -22,19 +22,24 @@ Create or update a BGP route map redistribution. scm set network bgp-route-map-redistribution NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP route map redistribution | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--bgp-json TEXT` | BGP source protocol config as JSON | No | | `--ospf-json TEXT` | OSPF source protocol config as JSON | No | | `--connected-static-json TEXT` | Connected/Static source config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -68,17 +73,22 @@ Delete a BGP route map redistribution from SCM. scm delete network bgp-route-map-redistribution NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP route map redistribution to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -103,13 +113,11 @@ scm load network bgp-route-map-redistribution [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -164,22 +172,29 @@ Display BGP route map redistribution objects. ### Syntax ```bash -scm show network bgp-route-map-redistribution [OPTIONS] +scm show network bgp-route-map-redistribution [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP route map redistribution to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific redistribution | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -187,7 +202,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific BGP Route Map Redistribution ```bash -$ scm show network bgp-route-map-redistribution --folder Texas --name my-redist-map +$ scm show network bgp-route-map-redistribution my-redist-map --folder Texas ---> 100% BGP Route Map Redistribution: my-redist-map Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/bgp-route-map.md b/docs-site/docs/cli/network/bgp-route-map.md index e090f45..48987b1 100644 --- a/docs-site/docs/cli/network/bgp-route-map.md +++ b/docs-site/docs/cli/network/bgp-route-map.md @@ -22,17 +22,22 @@ Create or update a BGP route map. scm set network bgp-route-map NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP route map | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Route map name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--route-map-json TEXT` | Route map entries as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -66,17 +71,22 @@ Delete a BGP route map from SCM. scm delete network bgp-route-map NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP route map to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Route map name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -101,13 +111,11 @@ scm load network bgp-route-map [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -167,22 +175,29 @@ Display BGP route map objects. ### Syntax ```bash -scm show network bgp-route-map [OPTIONS] +scm show network bgp-route-map [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the BGP route map to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific route map | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -190,7 +205,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific BGP Route Map ```bash -$ scm show network bgp-route-map --folder Texas --name my-route-map +$ scm show network bgp-route-map my-route-map --folder Texas ---> 100% BGP Route Map: my-route-map Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/dhcp-interface.md b/docs-site/docs/cli/network/dhcp-interface.md index 286ed04..3810a40 100644 --- a/docs-site/docs/cli/network/dhcp-interface.md +++ b/docs-site/docs/cli/network/dhcp-interface.md @@ -22,18 +22,23 @@ Create or update a DHCP interface. scm set network dhcp-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the DHCP interface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--server-json TEXT` | DHCP server config as JSON | No | | `--relay-json TEXT` | DHCP relay config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -67,17 +72,22 @@ Delete a DHCP interface from SCM. scm delete network dhcp-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the DHCP interface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -102,13 +112,11 @@ scm load network dhcp-interface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -167,22 +175,29 @@ Display DHCP interface objects. ### Syntax ```bash -scm show network dhcp-interface [OPTIONS] +scm show network dhcp-interface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the DHCP interface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific interface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -190,7 +205,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific DHCP Interface ```bash -$ scm show network dhcp-interface --folder Texas --name ethernet1/1 +$ scm show network dhcp-interface ethernet1/1 --folder Texas ---> 100% DHCP Interface: ethernet1/1 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/ethernet-interface.md b/docs-site/docs/cli/network/ethernet-interface.md index 49c1111..95dffff 100644 --- a/docs-site/docs/cli/network/ethernet-interface.md +++ b/docs-site/docs/cli/network/ethernet-interface.md @@ -22,14 +22,19 @@ Create or update an ethernet interface. scm set network ethernet-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the ethernet interface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--comment TEXT` | Interface description | No | | `--default-value TEXT` | Physical interface (e.g. ethernet1/1) | No | | `--link-speed TEXT` | Link speed (auto, 10, 100, 1000, 10000) | No | @@ -39,7 +44,7 @@ scm set network ethernet-interface NAME [OPTIONS] | `--layer3-json TEXT` | Layer3 config as JSON | No | | `--tap-json TEXT` | TAP config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -76,17 +81,22 @@ Delete an ethernet interface from SCM. scm delete network ethernet-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the ethernet interface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -111,13 +121,11 @@ scm load network ethernet-interface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -178,22 +186,29 @@ Display ethernet interface objects. ### Syntax ```bash -scm show network ethernet-interface [OPTIONS] +scm show network ethernet-interface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the ethernet interface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific interface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -201,7 +216,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Ethernet Interface ```bash -$ scm show network ethernet-interface --folder Texas --name ethernet1/1 +$ scm show network ethernet-interface ethernet1/1 --folder Texas ---> 100% Ethernet Interface: ethernet1/1 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/ike-crypto-profile.md b/docs-site/docs/cli/network/ike-crypto-profile.md index 9b20c7a..4cf3c4d 100644 --- a/docs-site/docs/cli/network/ike-crypto-profile.md +++ b/docs-site/docs/cli/network/ike-crypto-profile.md @@ -22,24 +22,29 @@ Create or update an IKE crypto profile. scm set network ike-crypto-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IKE crypto profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | | `--hash TEXT` | Hash algorithms (sha256, sha384, sha512, sha1, md5) | Yes | | `--dh-group TEXT` | DH groups (group1, group2, group5, group14, group19, group20) | Yes | | `--encryption TEXT` | Encryption algorithms (aes-256-cbc, aes-128-cbc, etc.) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--lifetime-seconds INT` | Lifetime in seconds (180-65535) | No | | `--lifetime-minutes INT` | Lifetime in minutes (3-65535) | No | | `--lifetime-hours INT` | Lifetime in hours (1-65535) | No | | `--lifetime-days INT` | Lifetime in days (1-365) | No | | `--authentication-multiple INT` | IKEv2 SA reauthentication interval (0-50) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -79,17 +84,22 @@ Delete an IKE crypto profile from SCM. scm delete network ike-crypto-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IKE crypto profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -114,13 +124,11 @@ scm load network ike-crypto-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -184,22 +192,29 @@ Display IKE crypto profile objects. ### Syntax ```bash -scm show network ike-crypto-profile [OPTIONS] +scm show network ike-crypto-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IKE crypto profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -207,7 +222,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific IKE Crypto Profile ```bash -$ scm show network ike-crypto-profile --folder Texas --name my-ike-profile +$ scm show network ike-crypto-profile my-ike-profile --folder Texas ---> 100% IKE Crypto Profile: my-ike-profile Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/ike-gateway.md b/docs-site/docs/cli/network/ike-gateway.md index eafee18..4676e65 100644 --- a/docs-site/docs/cli/network/ike-gateway.md +++ b/docs-site/docs/cli/network/ike-gateway.md @@ -22,14 +22,19 @@ Create or update an IKE gateway. scm set network ike-gateway NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IKE gateway | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Gateway name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--pre-shared-key TEXT` | Pre-shared key for authentication | No\*\* | | `--peer-address-ip TEXT` | Peer IP address | No\*\*\* | | `--peer-address-fqdn TEXT` | Peer FQDN | No\*\*\* | @@ -49,7 +54,7 @@ scm set network ike-gateway NAME [OPTIONS] | `--protocol-json TEXT` | Full protocol config as JSON | No | | `--protocol-common-json TEXT` | Full protocol_common config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. \*\* Either --pre-shared-key or --authentication-json is required. \*\*\* One of --peer-address-ip, --peer-address-fqdn, --peer-address-dynamic, or --peer-address-json is required. @@ -92,17 +97,22 @@ Delete an IKE gateway from SCM. scm delete network ike-gateway NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IKE gateway to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Gateway name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -127,13 +137,11 @@ scm load network ike-gateway [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -202,22 +210,29 @@ Display IKE gateway objects. ### Syntax ```bash -scm show network ike-gateway [OPTIONS] +scm show network ike-gateway [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IKE gateway to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific gateway | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -225,7 +240,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific IKE Gateway ```bash -$ scm show network ike-gateway --folder Texas --name my-gateway +$ scm show network ike-gateway my-gateway --folder Texas ---> 100% IKE Gateway: my-gateway Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/ipsec-crypto-profile.md b/docs-site/docs/cli/network/ipsec-crypto-profile.md index 9e227d4..95ccf1e 100644 --- a/docs-site/docs/cli/network/ipsec-crypto-profile.md +++ b/docs-site/docs/cli/network/ipsec-crypto-profile.md @@ -19,29 +19,37 @@ Create or update an IPsec crypto profile. ### Syntax ```bash -scm set network ipsec-crypto-profile [OPTIONS] +scm set network ipsec-crypto-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IPsec crypto profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | -| `--folder TEXT` | Folder location | Yes | -| `--esp-encryption TEXT` | ESP encryption algorithms (aes-256-cbc, aes-128-cbc, etc.) | Yes | -| `--esp-authentication TEXT` | ESP authentication algorithms (sha256, sha384, sha512, sha1, md5) | Yes | -| `--dh-group TEXT` | DH group for PFS (group14, group19, group20, no-pfs) | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--esp-encryption TEXT` | ESP encryption algorithms (aes-256-cbc, aes-128-cbc, etc.; default: `aes-256-cbc`) | No | +| `--esp-authentication TEXT` | ESP authentication algorithms (sha256, sha384, sha512, sha1, md5; default: `sha256`) | No | +| `--dh-group TEXT` | DH group for PFS (group14, group19, group20, no-pfs; default: `group14`) | No | | `--lifetime-seconds INT` | Lifetime in seconds | No | | `--lifetime-hours INT` | Lifetime in hours | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Examples #### Create an IPsec Crypto Profile ```bash -$ scm set network ipsec-crypto-profile \ +$ scm set network ipsec-crypto-profile my-ipsec-profile \ --folder Texas \ - --name my-ipsec-profile \ --esp-encryption aes-256-cbc \ --esp-authentication sha256 \ --dh-group group14 @@ -52,9 +60,8 @@ Created IPsec crypto profile: my-ipsec-profile in folder Texas #### Create a Profile with Custom Lifetime ```bash -$ scm set network ipsec-crypto-profile \ +$ scm set network ipsec-crypto-profile short-lived-profile \ --folder Texas \ - --name short-lived-profile \ --esp-encryption aes-256-cbc \ --esp-authentication sha384 \ --dh-group group19 \ @@ -70,21 +77,30 @@ Delete an IPsec crypto profile from SCM. ### Syntax ```bash -scm delete network ipsec-crypto-profile [OPTIONS] +scm delete network ipsec-crypto-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IPsec crypto profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Profile name | Yes | -| `--folder TEXT` | Folder location | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Example ```bash -$ scm delete network ipsec-crypto-profile --folder Texas --name my-ipsec-profile --force +$ scm delete network ipsec-crypto-profile my-ipsec-profile --folder Texas --force ---> 100% Deleted IPsec crypto profile: my-ipsec-profile from folder Texas ``` @@ -104,13 +120,11 @@ scm load network ipsec-crypto-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -174,22 +188,29 @@ Display IPsec crypto profile objects. ### Syntax ```bash -scm show network ipsec-crypto-profile [OPTIONS] +scm show network ipsec-crypto-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the IPsec crypto profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -197,7 +218,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific IPsec Crypto Profile ```bash -$ scm show network ipsec-crypto-profile --folder Texas --name my-ipsec-profile +$ scm show network ipsec-crypto-profile my-ipsec-profile --folder Texas ---> 100% IPsec Crypto Profile: my-ipsec-profile Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/layer2-subinterface.md b/docs-site/docs/cli/network/layer2-subinterface.md index 58b768a..3f51597 100644 --- a/docs-site/docs/cli/network/layer2-subinterface.md +++ b/docs-site/docs/cli/network/layer2-subinterface.md @@ -22,19 +22,24 @@ Create or update a layer2 subinterface. scm set network layer2-subinterface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the layer2 subinterface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Subinterface name (positional) | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--vlan-tag TEXT` | VLAN tag (1-4096) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | | `--parent-interface TEXT` | Parent interface name | No | | `--comment TEXT` | Interface description | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -71,17 +76,22 @@ Delete a layer2 subinterface from SCM. scm delete network layer2-subinterface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the layer2 subinterface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Subinterface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -106,13 +116,11 @@ scm load network layer2-subinterface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -167,22 +175,29 @@ Display layer2 subinterface objects. ### Syntax ```bash -scm show network layer2-subinterface [OPTIONS] +scm show network layer2-subinterface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the layer2 subinterface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific subinterface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -190,7 +205,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Layer2 Subinterface ```bash -$ scm show network layer2-subinterface --folder Texas --name ethernet1/1.100 +$ scm show network layer2-subinterface ethernet1/1.100 --folder Texas ---> 100% Layer2 Subinterface: ethernet1/1.100 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/layer3-subinterface.md b/docs-site/docs/cli/network/layer3-subinterface.md index 52d7d30..4fa3e66 100644 --- a/docs-site/docs/cli/network/layer3-subinterface.md +++ b/docs-site/docs/cli/network/layer3-subinterface.md @@ -22,22 +22,27 @@ Create or update a layer3 subinterface. scm set network layer3-subinterface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the layer3 subinterface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Subinterface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--tag INT` | VLAN tag (1-4096) | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--vlan-tag INTEGER` | VLAN tag (1-4096) | No | | `--parent-interface TEXT` | Parent interface name | No | | `--comment TEXT` | Interface description | No | -| `--mtu INT` | MTU (576-9216) | No | +| `--mtu INTEGER` | MTU (576-9216) | No | | `--ip-json TEXT` | Static IPs as JSON | No | | `--dhcp-client-json TEXT` | DHCP client config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -46,7 +51,7 @@ scm set network layer3-subinterface NAME [OPTIONS] ```bash $ scm set network layer3-subinterface ethernet1/1.200 \ --folder Texas \ - --tag 200 \ + --vlan-tag 200 \ --parent-interface ethernet1/1 \ --mtu 1500 \ --ip-json '[{"name": "10.0.2.1/24"}]' @@ -59,7 +64,7 @@ Created layer3 subinterface: ethernet1/1.200 in folder Texas ```bash $ scm set network layer3-subinterface ethernet1/2.300 \ --folder Texas \ - --tag 300 \ + --vlan-tag 300 \ --parent-interface ethernet1/2 \ --dhcp-client-json '{"enable": true}' \ --comment "DHCP-assigned VLAN" @@ -77,17 +82,22 @@ Delete a layer3 subinterface from SCM. scm delete network layer3-subinterface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the layer3 subinterface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Subinterface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -112,13 +122,11 @@ scm load network layer3-subinterface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -178,22 +186,29 @@ Display layer3 subinterface objects. ### Syntax ```bash -scm show network layer3-subinterface [OPTIONS] +scm show network layer3-subinterface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the layer3 subinterface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific subinterface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -201,7 +216,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Layer3 Subinterface ```bash -$ scm show network layer3-subinterface --folder Texas --name ethernet1/1.200 +$ scm show network layer3-subinterface ethernet1/1.200 --folder Texas ---> 100% Layer3 Subinterface: ethernet1/1.200 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/loopback-interface.md b/docs-site/docs/cli/network/loopback-interface.md index 6083abd..5cfed6f 100644 --- a/docs-site/docs/cli/network/loopback-interface.md +++ b/docs-site/docs/cli/network/loopback-interface.md @@ -22,21 +22,26 @@ Create or update a loopback interface. scm set network loopback-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the loopback interface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--comment TEXT` | Interface description | No | | `--default-value TEXT` | Default interface (e.g. loopback.1) | No | -| `--mtu INT` | MTU (576-9216) | No | +| `--mtu INTEGER` | MTU (576-9216) | No | | `--ip-json TEXT` | Static IPs as JSON | No | | `--ipv6-json TEXT` | IPv6 config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -73,17 +78,22 @@ Delete a loopback interface from SCM. scm delete network loopback-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the loopback interface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -108,13 +118,11 @@ scm load network loopback-interface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -170,22 +178,29 @@ Display loopback interface objects. ### Syntax ```bash -scm show network loopback-interface [OPTIONS] +scm show network loopback-interface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the loopback interface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific interface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -193,7 +208,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Loopback Interface ```bash -$ scm show network loopback-interface --folder Texas --name loopback.1 +$ scm show network loopback-interface loopback.1 --folder Texas ---> 100% Loopback Interface: loopback.1 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/nat-rule.md b/docs-site/docs/cli/network/nat-rule.md index 6929d4b..40c4eea 100644 --- a/docs-site/docs/cli/network/nat-rule.md +++ b/docs-site/docs/cli/network/nat-rule.md @@ -19,17 +19,24 @@ Create or update a NAT rule. ### Syntax ```bash -scm set network nat-rule [OPTIONS] +scm set network nat-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the NAT rule | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Rule name | Yes | -| `--folder TEXT` | Folder location | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Rule description | No | -| `--tag TEXT` | Tags | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | | `--disabled` | Disable the rule | No | | `--nat-type TEXT` | NAT type (ipv4, nat64, nptv6) | No | | `--from-zone TEXT` | Source zones | No | @@ -41,14 +48,15 @@ scm set network nat-rule [OPTIONS] | `--source-translation TEXT` | Source translation config as JSON | No | | `--destination-translation TEXT` | Destination translation config as JSON | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Examples #### Create an Outbound NAT Rule ```bash -$ scm set network nat-rule \ +$ scm set network nat-rule outbound-nat \ --folder Texas \ - --name outbound-nat \ --from-zone trust \ --to-zone untrust \ --source any \ @@ -61,9 +69,8 @@ Created NAT rule: outbound-nat in folder Texas #### Create a Destination NAT Rule ```bash -$ scm set network nat-rule \ +$ scm set network nat-rule inbound-web \ --folder Texas \ - --name inbound-web \ --from-zone untrust \ --to-zone dmz \ --destination 203.0.113.10 \ @@ -79,21 +86,30 @@ Delete a NAT rule from SCM. ### Syntax ```bash -scm delete network nat-rule [OPTIONS] +scm delete network nat-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the NAT rule to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Rule name | Yes | -| `--folder TEXT` | Folder location | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Example ```bash -$ scm delete network nat-rule --folder Texas --name outbound-nat --force +$ scm delete network nat-rule outbound-nat --folder Texas --force ---> 100% Deleted NAT rule: outbound-nat from folder Texas ``` @@ -113,13 +129,11 @@ scm load network nat-rule [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -191,22 +205,29 @@ Display NAT rule objects. ### Syntax ```bash -scm show network nat-rule [OPTIONS] +scm show network nat-rule [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the NAT rule to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific rule | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -214,7 +235,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific NAT Rule ```bash -$ scm show network nat-rule --folder Texas --name outbound-nat +$ scm show network nat-rule outbound-nat --folder Texas ---> 100% NAT Rule: outbound-nat Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/ospf-auth-profile.md b/docs-site/docs/cli/network/ospf-auth-profile.md index 66f7b2f..19ecd8a 100644 --- a/docs-site/docs/cli/network/ospf-auth-profile.md +++ b/docs-site/docs/cli/network/ospf-auth-profile.md @@ -22,18 +22,23 @@ Create or update an OSPF auth profile. scm set network ospf-auth-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the OSPF auth profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--password TEXT` | Simple password authentication | No | | `--md5-json TEXT` | MD5 authentication keys as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -67,17 +72,22 @@ Delete an OSPF auth profile from SCM. scm delete network ospf-auth-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the OSPF auth profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Profile name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -102,13 +112,11 @@ scm load network ospf-auth-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -162,22 +170,29 @@ Display OSPF auth profile objects. ### Syntax ```bash -scm show network ospf-auth-profile [OPTIONS] +scm show network ospf-auth-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the OSPF auth profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -185,7 +200,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific OSPF Auth Profile ```bash -$ scm show network ospf-auth-profile --folder Texas --name my-ospf-auth +$ scm show network ospf-auth-profile my-ospf-auth --folder Texas ---> 100% OSPF Auth Profile: my-ospf-auth Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/qos-profile.md b/docs-site/docs/cli/network/qos-profile.md index 080c86e..378cfb3 100644 --- a/docs-site/docs/cli/network/qos-profile.md +++ b/docs-site/docs/cli/network/qos-profile.md @@ -23,21 +23,26 @@ Create or update a QoS profile. ### Syntax ```bash -scm set network qos-profile [OPTIONS] NAME +scm set network qos-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the QoS profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the QoS profile | Yes | -| `--folder TEXT` | Folder location (must be "Remote Networks" or "Service Connections") | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location (must be "Remote Networks" or "Service Connections") | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--aggregate-bandwidth-json TEXT` | Aggregate bandwidth config as JSON | No | | `--class-bandwidth-type-json TEXT` | Class bandwidth type config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -68,20 +73,25 @@ Delete a QoS profile from SCM. ### Syntax ```bash -scm delete network qos-profile [OPTIONS] NAME +scm delete network qos-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the QoS profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the QoS profile to delete | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -106,9 +116,9 @@ scm load network qos-profile [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file containing QoS profile definitions | Yes | -| `--folder TEXT` | Override folder location | No | -| `--snippet TEXT` | Override snippet location | No | -| `--device TEXT` | Override device location | No | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | ### YAML File Format @@ -163,22 +173,29 @@ Display QoS profile details. ### Syntax ```bash -scm show network qos-profile [OPTIONS] +scm show network qos-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the QoS profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific QoS profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -186,7 +203,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific QoS Profile ```bash -$ scm show network qos-profile --folder "Remote Networks" --name my-qos +$ scm show network qos-profile my-qos --folder "Remote Networks" ---> 100% QoS Profile: my-qos ============================================================ diff --git a/docs-site/docs/cli/network/route-access-list.md b/docs-site/docs/cli/network/route-access-list.md index f6b62dd..b49145f 100644 --- a/docs-site/docs/cli/network/route-access-list.md +++ b/docs-site/docs/cli/network/route-access-list.md @@ -22,18 +22,23 @@ Create or update a route access list. scm set network route-access-list NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the route access list | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Access list name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Description | No | | `--type-json TEXT` | Access list type config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -67,17 +72,22 @@ Delete a route access list from SCM. scm delete network route-access-list NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the route access list to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Access list name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -102,13 +112,11 @@ scm load network route-access-list [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -174,22 +182,29 @@ Display route access list objects. ### Syntax ```bash -scm show network route-access-list [OPTIONS] +scm show network route-access-list [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the route access list to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific access list | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -197,7 +212,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Route Access List ```bash -$ scm show network route-access-list --folder Texas --name my-acl +$ scm show network route-access-list my-acl --folder Texas ---> 100% Route Access List: my-acl Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/route-prefix-list.md b/docs-site/docs/cli/network/route-prefix-list.md index de91d1b..bd09a61 100644 --- a/docs-site/docs/cli/network/route-prefix-list.md +++ b/docs-site/docs/cli/network/route-prefix-list.md @@ -22,18 +22,23 @@ Create or update a route prefix list. scm set network route-prefix-list NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the route prefix list | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Prefix list name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Description | No | | `--ipv4-json TEXT` | IPv4 prefix list config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -67,17 +72,22 @@ Delete a route prefix list from SCM. scm delete network route-prefix-list NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the route prefix list to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Prefix list name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -102,13 +112,11 @@ scm load network route-prefix-list [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -168,22 +176,29 @@ Display route prefix list objects. ### Syntax ```bash -scm show network route-prefix-list [OPTIONS] +scm show network route-prefix-list [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the route prefix list to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific prefix list | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -191,7 +206,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Route Prefix List ```bash -$ scm show network route-prefix-list --folder Texas --name my-prefix-list +$ scm show network route-prefix-list my-prefix-list --folder Texas ---> 100% Route Prefix List: my-prefix-list Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/security-zone.md b/docs-site/docs/cli/network/security-zone.md index 71b0043..42b3b89 100644 --- a/docs-site/docs/cli/network/security-zone.md +++ b/docs-site/docs/cli/network/security-zone.md @@ -18,7 +18,9 @@ The `zone` commands allow you to: | --- | --- | | `layer3` | Standard routed mode with IP addressing | | `layer2` | Switched mode for bridging traffic | +| `external` | Zone for traffic between virtual systems | | `virtual-wire` | Transparent inline mode between two interfaces | +| `tunnel` | Zone for tunnel interfaces | | `tap` | Passive monitoring mode for traffic analysis | ## Set Security Zone @@ -28,33 +30,37 @@ Create or update a security zone. ### Syntax ```bash -scm set network zone [OPTIONS] +scm set network zone NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security zone | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the security zone | Yes | -| `--folder TEXT` | Folder location | Yes | -| `--mode TEXT` | Zone protection mode (layer3, layer2, virtual-wire, tap) | Yes | -| `--description TEXT` | Description for the security zone | No | -| `--tags LIST` | List of tags to apply | No | -| `--enable-user-id BOOLEAN` | Enable User-ID for this zone | No | -| `--exclude-local-pan BOOLEAN` | Exclude local Panorama from User-ID distribution | No | -| `--log-setting TEXT` | Log forwarding profile | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--mode TEXT` | Zone mode (layer2, layer3, external, virtual-wire, tunnel, tap) | Yes | +| `--interfaces TEXT` | List of interfaces | No | +| `--enable-user-id` | Enable user identification | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Layer3 Security Zone ```bash -$ scm set network zone \ +$ scm set network zone Trust \ --folder Shared \ - --name Trust \ --mode layer3 \ - --enable-user-id true \ - --description "Internal trusted network zone" + --enable-user-id ---> 100% Created security zone: Trust in folder Shared ``` @@ -62,11 +68,9 @@ Created security zone: Trust in folder Shared #### Create a Virtual-Wire Security Zone ```bash -$ scm set network zone \ +$ scm set network zone DMZ \ --folder Shared \ - --name DMZ \ - --mode virtual-wire \ - --description "DMZ between trusted and untrusted networks" + --mode virtual-wire ---> 100% Created security zone: DMZ in folder Shared ``` @@ -78,21 +82,30 @@ Delete a security zone from SCM. ### Syntax ```bash -scm delete network zone [OPTIONS] +scm delete network zone NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security zone to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the security zone to delete | Yes | -| `--folder TEXT` | Folder location | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Example ```bash -$ scm delete network zone --folder Shared --name DMZ --force +$ scm delete network zone DMZ --folder Shared --force ---> 100% Deleted security zone: DMZ from folder Shared ``` @@ -112,13 +125,8 @@ scm load network zone [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file containing security zone definitions | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -153,7 +161,7 @@ security_zones: ### Examples -#### Load with Original Locations +#### Load Security Zones ```bash $ scm load network zone --file security-zones.yml @@ -165,22 +173,9 @@ $ scm load network zone --file security-zones.yml Successfully loaded 3 out of 3 security zones from 'security-zones.yml' ``` -#### Load with Folder Override - -```bash -$ scm load network zone --file security-zones.yml --folder Austin ----> 100% -✓ Loaded security zone: Trust -✓ Loaded security zone: Untrust -✓ Loaded security zone: DMZ - -Successfully loaded 3 out of 3 security zones from 'security-zones.yml' -``` - :::note -When using container override options (--folder, --snippet, --device), all security zones -will be loaded into the specified container, ignoring the container specified in the -YAML file. +Each security zone is loaded into the container (folder, snippet, or device) specified +in the YAML file. ::: ## Show Security Zone @@ -190,22 +185,29 @@ Display security zone objects. ### Syntax ```bash -scm show network zone [OPTIONS] +scm show network zone [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security zone to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific security zone | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -213,7 +215,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Security Zone ```bash -$ scm show network zone --folder Shared --name Trust +$ scm show network zone Trust --folder Shared ---> 100% Security Zone: Trust Location: Folder 'Shared' diff --git a/docs-site/docs/cli/network/tunnel-interface.md b/docs-site/docs/cli/network/tunnel-interface.md index 32297cc..c156a21 100644 --- a/docs-site/docs/cli/network/tunnel-interface.md +++ b/docs-site/docs/cli/network/tunnel-interface.md @@ -22,20 +22,25 @@ Create or update a tunnel interface. scm set network tunnel-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tunnel interface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--comment TEXT` | Interface description | No | | `--default-value TEXT` | Default interface (e.g. tunnel.1) | No | -| `--mtu INT` | MTU (576-9216) | No | +| `--mtu INTEGER` | MTU (576-9216) | No | | `--ip-json TEXT` | Static IPs as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -72,17 +77,22 @@ Delete a tunnel interface from SCM. scm delete network tunnel-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tunnel interface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -107,13 +117,11 @@ scm load network tunnel-interface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -170,22 +178,29 @@ Display tunnel interface objects. ### Syntax ```bash -scm show network tunnel-interface [OPTIONS] +scm show network tunnel-interface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tunnel interface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific interface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -193,7 +208,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Tunnel Interface ```bash -$ scm show network tunnel-interface --folder Texas --name tunnel.1 +$ scm show network tunnel-interface tunnel.1 --folder Texas ---> 100% Tunnel Interface: tunnel.1 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/network/vlan-interface.md b/docs-site/docs/cli/network/vlan-interface.md index 641bca1..26055c9 100644 --- a/docs-site/docs/cli/network/vlan-interface.md +++ b/docs-site/docs/cli/network/vlan-interface.md @@ -22,22 +22,27 @@ Create or update a VLAN interface. scm set network vlan-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the VLAN interface | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--comment TEXT` | Interface description | No | | `--default-value TEXT` | Default interface (e.g. vlan.100) | No | | `--vlan-tag TEXT` | VLAN tag (1-4096) | No | -| `--mtu INT` | MTU (576-9216) | No | +| `--mtu INTEGER` | MTU (576-9216) | No | | `--ip-json TEXT` | Static IPs as JSON | No | | `--dhcp-client-json TEXT` | DHCP client config as JSON | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -75,17 +80,22 @@ Delete a VLAN interface from SCM. scm delete network vlan-interface NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the VLAN interface to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Interface name (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -110,13 +120,11 @@ scm load network vlan-interface [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Override folder location for all objects | No | +| `--snippet TEXT` | Override snippet location for all objects | No | +| `--device TEXT` | Override device location for all objects | No | | `--dry-run` | Preview changes without applying | No | -\* One of --folder, --snippet, or --device is required. - ### YAML File Format ```yaml @@ -174,22 +182,29 @@ Display VLAN interface objects. ### Syntax ```bash -scm show network vlan-interface [OPTIONS] +scm show network vlan-interface [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the VLAN interface to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific interface | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -197,7 +212,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific VLAN Interface ```bash -$ scm show network vlan-interface --folder Texas --name vlan.100 +$ scm show network vlan-interface vlan.100 --folder Texas ---> 100% VLAN Interface: vlan.100 Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/address-group.md b/docs-site/docs/cli/objects/address-group.md index 85cc0d2..89fbf68 100644 --- a/docs-site/docs/cli/objects/address-group.md +++ b/docs-site/docs/cli/objects/address-group.md @@ -28,34 +28,39 @@ Create or update an address group. ### Syntax ```bash -scm set object address-group [OPTIONS] +scm set object address-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the address group | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the address group | Yes | -| `--name TEXT` | Name of the address group | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Description for the address group | No | -| `--tags LIST` | List of tags to apply to the address group | No | -| `--static` | Create a static address group | No\* | -| `--dynamic` | Create a dynamic address group | No\* | -| `--members LIST` | List of address objects for static groups | Only with `--static` | -| `--filter TEXT` | Tag-based filter expression for dynamic groups | Only with `--dynamic` | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | +| `--type TEXT` | Type of address group (`static` or `dynamic`) | Yes | +| `--members TEXT` | Address object for static groups (repeat for multiple) | Only with `--type static` | +| `--filter TEXT` | Tag-based filter expression for dynamic groups | Only with `--type dynamic` | -\* You must specify exactly one of `--static` or `--dynamic`. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Static Address Group ```bash -$ scm set object address-group \ +$ scm set object address-group web-servers \ --folder Shared \ - --name web-servers \ - --static \ - --members "web-server-1,web-server-2" + --type static \ + --members web-server-1 --members web-server-2 ---> 100% Created address group: web-servers in folder Shared ``` @@ -63,10 +68,9 @@ Created address group: web-servers in folder Shared #### Create a Dynamic Address Group ```bash -$ scm set object address-group \ +$ scm set object address-group trusted-endpoints \ --folder Shared \ - --name trusted-endpoints \ - --dynamic \ + --type dynamic \ --filter "'trusted-endpoint' and 'corporate-asset'" ---> 100% Created address group: trusted-endpoints in folder Shared @@ -79,21 +83,30 @@ Delete an address group from SCM. ### Syntax ```bash -scm delete object address-group [OPTIONS] +scm delete object address-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the address group to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the address group | Yes | -| `--name TEXT` | Name of the address group to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Example ```bash -$ scm delete object address-group --folder Shared --name web-servers --force +$ scm delete object address-group web-servers --folder Shared --force ---> 100% Deleted address group: web-servers from folder Shared ``` @@ -181,18 +194,29 @@ Display address group objects. ### Syntax ```bash -scm show object address-group [OPTIONS] +scm show object address-group [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the address group to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the address group | Yes | -| `--name TEXT` | Name of the address group to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -200,7 +224,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Address Group ```bash -$ scm show object address-group --folder Texas --name web-servers +$ scm show object address-group web-servers --folder Texas ---> 100% Address Group: web-servers Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/address.md b/docs-site/docs/cli/objects/address.md index 0d83971..14304d7 100644 --- a/docs-site/docs/cli/objects/address.md +++ b/docs-site/docs/cli/objects/address.md @@ -33,32 +33,40 @@ Create or update an address object. ### Syntax ```bash -scm set object address [OPTIONS] +scm set object address NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the address object | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the address object | Yes | -| `--name TEXT` | Name of the address object | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Description for the address | No | -| `--tags LIST` | List of tags to apply to the address | No | -| `--ip-netmask TEXT` | Address in CIDR notation | No\* | -| `--ip-range TEXT` | Address range | No\* | -| `--ip-wildcard TEXT` | Address with wildcard mask | No\* | -| `--fqdn TEXT` | Fully qualified domain name | No\* | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | +| `--ip-netmask TEXT` | Address in CIDR notation | No\*\* | +| `--ip-range TEXT` | Address range | No\*\* | +| `--ip-wildcard TEXT` | Address with wildcard mask | No\*\* | +| `--fqdn TEXT` | Fully qualified domain name | No\*\* | -\* You must specify exactly one of the address type options. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + +\*\* You must specify exactly one of the address type options. ### Examples #### Create an IP Netmask Address ```bash -$ scm set object address \ +$ scm set object address webserver \ --folder Texas \ - --name webserver \ --ip-netmask 192.168.1.100/32 \ --description "Web server" \ --tags server --tags web @@ -69,9 +77,8 @@ Created address: webserver in folder Texas #### Create an FQDN Address ```bash -$ scm set object address \ +$ scm set object address company-website \ --folder Texas \ - --name company-website \ --fqdn example.com \ --description "Company website" ---> 100% @@ -81,9 +88,8 @@ Created address: company-website in folder Texas #### Create an IP Range Address ```bash -$ scm set object address \ +$ scm set object address dhcp-pool \ --folder Texas \ - --name dhcp-pool \ --ip-range 192.168.1.100-192.168.1.200 \ --description "DHCP address pool" ---> 100% @@ -97,21 +103,30 @@ Delete an address object from SCM. ### Syntax ```bash -scm delete object address [OPTIONS] +scm delete object address NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the address object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the address object | Yes | -| `--name TEXT` | Name of the address object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + ### Example ```bash -$ scm delete object address --folder Texas --name webserver --force +$ scm delete object address webserver --folder Texas --force ---> 100% Deleted address: webserver from folder Texas ``` @@ -205,18 +220,29 @@ Display address objects. ### Syntax ```bash -scm show object address [OPTIONS] +scm show object address [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the address object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the address object | Yes | -| `--name TEXT` | Name of the address object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -224,7 +250,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Address ```bash -$ scm show object address --folder Texas --name webserver +$ scm show object address webserver --folder Texas ---> 100% Address: webserver Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/application-filter.md b/docs-site/docs/cli/objects/application-filter.md index d9b1629..014d1cd 100644 --- a/docs-site/docs/cli/objects/application-filter.md +++ b/docs-site/docs/cli/objects/application-filter.md @@ -19,22 +19,26 @@ Create or update an application filter object. ### Syntax ```bash -scm set object application-filter [OPTIONS] +scm set object application-filter NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application filter | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the application filter object | No\* | -| `--snippet TEXT` | Snippet for the application filter object | No\* | -| `--device TEXT` | Device for the application filter object | No\* | -| `--name TEXT` | Name of the application filter | Yes | -| `--category LIST` | List of application categories | No\*\* | -| `--subcategory LIST` | List of application subcategories | No\*\* | -| `--technology LIST` | List of technologies | No\*\* | -| `--risk LIST` | List of risk levels (1-5) | No\*\* | -| `--description TEXT` | Description of the filter | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--category TEXT` | Application category to filter by (repeat for multiple) | Yes | +| `--subcategory TEXT` | Application subcategory to filter by (repeat for multiple) | Yes | +| `--technology TEXT` | Technology to filter by (repeat for multiple) | Yes | +| `--risk INTEGER` | Risk level (1-5) to filter by (repeat for multiple) | Yes | | `--evasive` | Filter for evasive applications | No | | `--pervasive` | Filter for pervasive applications | No | | `--excessive-bandwidth-use` | Filter for bandwidth-heavy applications | No | @@ -45,21 +49,19 @@ scm set object application-filter [OPTIONS] | `--prone-to-misuse` | Filter for applications prone to misuse | No | | `--no-certifications` | Filter for uncertified applications | No | -\* One of --folder, --snippet, or --device is required. - -\*\* At least one filtering criterion must be specified. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create Basic Filter by Category and Risk ```bash -$ scm set object application-filter \ +$ scm set object application-filter high-risk-apps \ --folder Texas \ - --name high-risk-apps \ - --category "file-sharing,peer-to-peer" \ - --risk 4 --risk 5 \ - --description "High-risk file sharing applications" + --category file-sharing --category peer-to-peer \ + --subcategory file-transfer \ + --technology peer-to-peer \ + --risk 4 --risk 5 ---> 100% Created application filter: high-risk-apps in folder Texas ``` @@ -67,14 +69,15 @@ Created application filter: high-risk-apps in folder Texas #### Create Filter with Security Characteristics ```bash -$ scm set object application-filter \ +$ scm set object application-filter malware-apps \ --folder Texas \ - --name malware-apps \ - --category "file-sharing" \ + --category file-sharing \ + --subcategory file-transfer \ + --technology peer-to-peer \ + --risk 5 \ --used-by-malware \ --has-known-vulnerabilities \ - --transfers-files \ - --description "Applications with security concerns" + --transfers-files ---> 100% Created application filter: malware-apps in folder Texas ``` @@ -86,25 +89,30 @@ Delete an application filter object from SCM. ### Syntax ```bash -scm delete object application-filter [OPTIONS] +scm delete object application-filter NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application filter object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the application filter object | No\* | -| `--snippet TEXT` | Snippet containing the application filter object | No\* | -| `--device TEXT` | Device containing the application filter object | No\* | -| `--name TEXT` | Name of the application filter object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object application-filter --folder Texas --name high-risk-apps --force +$ scm delete object application-filter high-risk-apps --folder Texas --force ---> 100% Deleted application filter: high-risk-apps from folder Texas ``` @@ -205,30 +213,37 @@ Display application filter objects. ### Syntax ```bash -scm show object application-filter [OPTIONS] +scm show object application-filter [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application filter object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the application filter object | No\* | -| `--snippet TEXT` | Snippet containing the application filter object | No\* | -| `--device TEXT` | Device containing the application filter object | No\* | -| `--name TEXT` | Name of the application filter object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Application Filter ```bash -$ scm show object application-filter --folder Texas --name high-risk-apps +$ scm show object application-filter high-risk-apps --folder Texas ---> 100% Application Filter: high-risk-apps Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/application-group.md b/docs-site/docs/cli/objects/application-group.md index 2c266e1..3eede39 100644 --- a/docs-site/docs/cli/objects/application-group.md +++ b/docs-site/docs/cli/objects/application-group.md @@ -19,46 +19,44 @@ Create or update an application group object. ### Syntax ```bash -scm set object application-group [OPTIONS] +scm set object application-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application group | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the application group object | No\* | -| `--snippet TEXT` | Snippet for the application group object | No\* | -| `--device TEXT` | Device for the application group object | No\* | -| `--name TEXT` | Name of the application group | Yes | -| `--members LIST` | Comma-separated list of application names | Yes | -| `--description TEXT` | Description of the group | No | -| `--tag LIST` | Tags for categorization | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--members TEXT` | Application name in the group (repeat for multiple) | Yes | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Basic Application Group ```bash -$ scm set object application-group \ +$ scm set object application-group business-apps \ --folder Texas \ - --name business-apps \ - --members "salesforce,office365,zoom,custom-crm" \ - --description "Business critical applications" + --members salesforce --members office365 --members zoom --members custom-crm ---> 100% Created application group: business-apps in folder Texas ``` -#### Create an Application Group with Tags +#### Create a Collaboration Application Group ```bash -$ scm set object application-group \ +$ scm set object application-group collaboration-tools \ --folder Texas \ - --name collaboration-tools \ - --members "slack,ms-teams,zoom,webex" \ - --tag "collaboration,approved" \ - --description "Approved collaboration applications" + --members slack --members ms-teams --members zoom --members webex ---> 100% Created application group: collaboration-tools in folder Texas ``` @@ -70,25 +68,30 @@ Delete an application group object from SCM. ### Syntax ```bash -scm delete object application-group [OPTIONS] +scm delete object application-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application group object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the application group object | No\* | -| `--snippet TEXT` | Snippet containing the application group object | No\* | -| `--device TEXT` | Device containing the application group object | No\* | -| `--name TEXT` | Name of the application group object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object application-group --folder Texas --name business-apps --force +$ scm delete object application-group business-apps --folder Texas --force ---> 100% Deleted application group: business-apps from folder Texas ``` @@ -177,30 +180,37 @@ Display application group objects. ### Syntax ```bash -scm show object application-group [OPTIONS] +scm show object application-group [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application group object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the application group object | No\* | -| `--snippet TEXT` | Snippet containing the application group object | No\* | -| `--device TEXT` | Device containing the application group object | No\* | -| `--name TEXT` | Name of the application group object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Application Group ```bash -$ scm show object application-group --folder Texas --name business-apps +$ scm show object application-group business-apps --folder Texas ---> 100% Application Group: business-apps Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/application.md b/docs-site/docs/cli/objects/application.md index 7cce5df..a3a5b82 100644 --- a/docs-site/docs/cli/objects/application.md +++ b/docs-site/docs/cli/objects/application.md @@ -19,54 +19,53 @@ Create or update an application object. ### Syntax ```bash -scm set object application [OPTIONS] +scm set object application NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the application object | No\* | -| `--snippet TEXT` | Snippet for the application object | No\* | -| `--device TEXT` | Device for the application object | No\* | -| `--name TEXT` | Name of the application | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--category TEXT` | Primary category | Yes | | `--subcategory TEXT` | Subcategory within the main category | Yes | | `--technology TEXT` | Technology type | Yes | -| `--risk INT` | Risk level (1-5) | Yes | -| `--ports LIST` | Protocol and port combinations | Yes | +| `--risk INTEGER` | Risk level (1-5) | Yes | +| `--ports TEXT` | Protocol/port combination, e.g. `tcp/8080` (repeat for multiple) | No | | `--description TEXT` | Description of the application | No | -| `--able-to-transfer-files` | Can transfer files | No | -| `--has-known-vulnerabilities` | Has known security vulnerabilities | No | -| `--tunnels-other-applications` | Can tunnel other applications | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | | `--evasive` | Uses evasive techniques | No | -| `--pervasive` | Pervasive use | No | +| `--pervasive` | Widely used | No | | `--excessive-bandwidth-use` | Consumes excessive bandwidth | No | | `--used-by-malware` | Known to be used by malware | No | -| `--no-app-id-caching` | Disable app-id caching | No | -| `--parent-app TEXT` | Parent application name | No | -| `--timeout INT` | Session timeout in seconds | No | -| `--tcp-timeout INT` | TCP session timeout | No | -| `--udp-timeout INT` | UDP session timeout | No | -| `--tcp-half-closed-timeout INT` | TCP half-closed timeout | No | -| `--tcp-time-wait-timeout INT` | TCP time-wait timeout | No | -| `--tag LIST` | Tags for categorization | No | +| `--transfers-files` | Can transfer files | No | +| `--has-known-vulnerabilities` | Has known security vulnerabilities | No | +| `--tunnels-other-apps` | Can tunnel other applications | No | +| `--prone-to-misuse` | Prone to misuse | No | +| `--no-certifications` | Lacks certifications | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Basic Application ```bash -$ scm set object application \ +$ scm set object application custom-crm \ --folder Texas \ - --name custom-crm \ --category business-systems \ --subcategory database \ --technology client-server \ --risk 3 \ - --ports "tcp/8080,tcp/8443" \ + --ports tcp/8080 --ports tcp/8443 \ --description "Custom CRM application" ---> 100% Created application: custom-crm in folder Texas @@ -75,35 +74,32 @@ Created application: custom-crm in folder Texas #### Create an Application with Security Attributes ```bash -$ scm set object application \ +$ scm set object application file-transfer-app \ --folder Texas \ - --name file-transfer-app \ --category collaboration \ --subcategory file-sharing \ --technology peer-to-peer \ --risk 4 \ - --ports "tcp/2121,udp/2121" \ - --able-to-transfer-files \ + --ports tcp/2121 --ports udp/2121 \ + --transfers-files \ --has-known-vulnerabilities \ --description "P2P file transfer application" ---> 100% Created application: file-transfer-app in folder Texas ``` -#### Create an Application with Timeouts +#### Create an Application with Tags ```bash -$ scm set object application \ +$ scm set object application database-app \ --folder Shared \ - --name database-app \ --category business-systems \ --subcategory database \ --technology client-server \ --risk 1 \ - --ports "tcp/1433" \ - --timeout 7200 \ - --tcp-timeout 1800 \ - --description "SQL Server application with extended timeouts" + --ports tcp/1433 \ + --tags database --tags internal \ + --description "SQL Server application" ---> 100% Created application: database-app in folder Shared ``` @@ -115,25 +111,30 @@ Delete an application object from SCM. ### Syntax ```bash -scm delete object application [OPTIONS] +scm delete object application NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the application object | No\* | -| `--snippet TEXT` | Snippet containing the application object | No\* | -| `--device TEXT` | Device containing the application object | No\* | -| `--name TEXT` | Name of the application object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object application --folder Texas --name custom-crm --force +$ scm delete object application custom-crm --folder Texas --force ---> 100% Deleted application: custom-crm from folder Texas ``` @@ -225,30 +226,37 @@ Display application objects. ### Syntax ```bash -scm show object application [OPTIONS] +scm show object application [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the application object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the application object | No\* | -| `--snippet TEXT` | Snippet containing the application object | No\* | -| `--device TEXT` | Device containing the application object | No\* | -| `--name TEXT` | Name of the application object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Application ```bash -$ scm show object application --folder Texas --name custom-crm +$ scm show object application custom-crm --folder Texas ---> 100% Application: custom-crm Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/auto-tag-action.md b/docs-site/docs/cli/objects/auto-tag-action.md index 7f5c394..4022db7 100644 --- a/docs-site/docs/cli/objects/auto-tag-action.md +++ b/docs-site/docs/cli/objects/auto-tag-action.md @@ -22,22 +22,27 @@ Create or update an auto tag action. scm set object auto-tag-action NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the auto tag action | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the auto tag action (positional argument) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Description of the auto tag action | No | | `--log-type TEXT` | Log type (traffic, threat, etc.) | No | | `--filter TEXT` | Filter expression | No | -| `--tags LIST` | Tags to apply | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | | `--send-to-panorama` | Send to Panorama | No | | `--quarantine` | Enable quarantine | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -77,17 +82,22 @@ Delete an auto tag action from SCM. scm delete object auto-tag-action NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the auto tag action to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the auto tag action to delete (positional argument) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -168,22 +178,29 @@ Display auto tag action objects. ### Syntax ```bash -scm show object auto-tag-action [OPTIONS] +scm show object auto-tag-action [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the auto tag action to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of a specific auto tag action | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: ### Examples @@ -191,7 +208,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Auto Tag Action ```bash -$ scm show object auto-tag-action --folder Texas --name threat-block +$ scm show object auto-tag-action threat-block --folder Texas ---> 100% Auto Tag Action: threat-block ============================================================ diff --git a/docs-site/docs/cli/objects/dynamic-user-group.md b/docs-site/docs/cli/objects/dynamic-user-group.md index 3a5b523..1e0002b 100644 --- a/docs-site/docs/cli/objects/dynamic-user-group.md +++ b/docs-site/docs/cli/objects/dynamic-user-group.md @@ -30,31 +30,35 @@ Create or update a dynamic user group object. ### Syntax ```bash -scm set object dynamic-user-group [OPTIONS] +scm set object dynamic-user-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the dynamic user group | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the dynamic user group object | No\* | -| `--snippet TEXT` | Snippet for the dynamic user group object | No\* | -| `--device TEXT` | Device for the dynamic user group object | No\* | -| `--name TEXT` | Name of the dynamic user group | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--filter TEXT` | Tag-based filter expression (max 2047 characters) | Yes | | `--description TEXT` | Description (max 1023 characters) | No | -| `--tag LIST` | Tags for categorization | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create Basic Dynamic User Group ```bash -$ scm set object dynamic-user-group \ +$ scm set object dynamic-user-group it-admins \ --folder Texas \ - --name it-admins \ --filter "'IT' and 'Admin'" \ --description "IT department administrators" ---> 100% @@ -64,9 +68,8 @@ Created dynamic user group: it-admins in folder Texas #### Create with Complex Filter Expression ```bash -$ scm set object dynamic-user-group \ +$ scm set object dynamic-user-group remote-employees \ --folder Texas \ - --name remote-employees \ --filter "'Remote' and ('Engineering' or 'Sales' or 'Support')" \ --description "Remote workers in technical departments" ---> 100% @@ -80,25 +83,30 @@ Delete a dynamic user group object from SCM. ### Syntax ```bash -scm delete object dynamic-user-group [OPTIONS] +scm delete object dynamic-user-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the dynamic user group object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the dynamic user group object | No\* | -| `--snippet TEXT` | Snippet containing the dynamic user group object | No\* | -| `--device TEXT` | Device containing the dynamic user group object | No\* | -| `--name TEXT` | Name of the dynamic user group object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object dynamic-user-group --folder Texas --name it-admins --force +$ scm delete object dynamic-user-group it-admins --folder Texas --force ---> 100% Deleted dynamic user group: it-admins from folder Texas ``` @@ -196,30 +204,37 @@ Display dynamic user group objects. ### Syntax ```bash -scm show object dynamic-user-group [OPTIONS] +scm show object dynamic-user-group [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the dynamic user group object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the dynamic user group object | No\* | -| `--snippet TEXT` | Snippet containing the dynamic user group object | No\* | -| `--device TEXT` | Device containing the dynamic user group object | No\* | -| `--name TEXT` | Name of the dynamic user group object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Dynamic User Group ```bash -$ scm show object dynamic-user-group --folder Texas --name it-admins +$ scm show object dynamic-user-group it-admins --folder Texas ---> 100% Dynamic User Group: it-admins Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/external-dynamic-list.md b/docs-site/docs/cli/objects/external-dynamic-list.md index 5533f9c..afa3959 100644 --- a/docs-site/docs/cli/objects/external-dynamic-list.md +++ b/docs-site/docs/cli/objects/external-dynamic-list.md @@ -46,30 +46,35 @@ Create or update an external dynamic list object. ### Syntax ```bash -scm set object external-dynamic-list [OPTIONS] +scm set object external-dynamic-list NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the external dynamic list | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the external dynamic list object | No\* | -| `--snippet TEXT` | Snippet for the external dynamic list object | No\* | -| `--device TEXT` | Device for the external dynamic list object | No\* | -| `--name TEXT` | Name of the external dynamic list | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--type TEXT` | EDL type (predefined_ip, predefined_url, ip, domain, url, imsi, imei) | Yes | | `--url TEXT` | Source URL for the list | Yes | | `--description TEXT` | Description of the EDL | No | -| `--exception-list LIST` | Items to exclude from the list | No | +| `--exception-list TEXT` | Item to exclude from the list (repeat for multiple) | No | | `--username TEXT` | Username for basic authentication | No | | `--password TEXT` | Password for basic authentication | No | -| `--certificate-profile TEXT` | Certificate profile for mutual TLS | No | +| `--certificate-profile TEXT` | Certificate profile for authentication | No | | `--recurring TEXT` | Update frequency (five_minute, hourly, daily, weekly, monthly) | No\*\* | | `--hour TEXT` | Hour for updates (00-23) | No\*\*\* | | `--day TEXT` | Day for updates | No\*\*\* | -| `--expand-domain` | Expand to include subdomains (domain type only) | No | +| `--expand-domain / --no-expand-domain` | Expand to include subdomains (domain type only) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. \*\* Required for custom EDL types (ip, domain, url, imsi, imei). @@ -80,9 +85,8 @@ scm set object external-dynamic-list [OPTIONS] #### Create Predefined IP Blocklist ```bash -$ scm set object external-dynamic-list \ +$ scm set object external-dynamic-list paloalto-bulletproof \ --folder Texas \ - --name paloalto-bulletproof \ --type predefined_ip \ --url "panw-bulletproof-ip-list" \ --description "Palo Alto Networks Bulletproof IP list" @@ -93,9 +97,8 @@ Created external dynamic list: paloalto-bulletproof in folder Texas #### Create Custom IP List with Hourly Updates ```bash -$ scm set object external-dynamic-list \ +$ scm set object external-dynamic-list custom-threats \ --folder Texas \ - --name custom-threats \ --type ip \ --url "https://threats.example.com/ips.txt" \ --recurring hourly \ @@ -107,9 +110,8 @@ Created external dynamic list: custom-threats in folder Texas #### Create Domain List with Authentication ```bash -$ scm set object external-dynamic-list \ +$ scm set object external-dynamic-list malware-domains \ --folder Texas \ - --name malware-domains \ --type domain \ --url "https://secure.example.com/domains.txt" \ --username "api_user" \ @@ -129,25 +131,30 @@ Delete an external dynamic list object from SCM. ### Syntax ```bash -scm delete object external-dynamic-list [OPTIONS] +scm delete object external-dynamic-list NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the external dynamic list object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the external dynamic list object | No\* | -| `--snippet TEXT` | Snippet containing the external dynamic list object | No\* | -| `--device TEXT` | Device containing the external dynamic list object | No\* | -| `--name TEXT` | Name of the external dynamic list object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object external-dynamic-list --folder Texas --name custom-threats --force +$ scm delete object external-dynamic-list custom-threats --folder Texas --force ---> 100% Deleted external dynamic list: custom-threats from folder Texas ``` @@ -244,30 +251,37 @@ Display external dynamic list objects. ### Syntax ```bash -scm show object external-dynamic-list [OPTIONS] +scm show object external-dynamic-list [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the external dynamic list object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the external dynamic list object | No\* | -| `--snippet TEXT` | Snippet containing the external dynamic list object | No\* | -| `--device TEXT` | Device containing the external dynamic list object | No\* | -| `--name TEXT` | Name of the external dynamic list object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific External Dynamic List ```bash -$ scm show object external-dynamic-list --folder Texas --name custom-threats +$ scm show object external-dynamic-list custom-threats --folder Texas ---> 100% External Dynamic List: custom-threats Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/hip-object.md b/docs-site/docs/cli/objects/hip-object.md index 41b41ef..9a99fe3 100644 --- a/docs-site/docs/cli/objects/hip-object.md +++ b/docs-site/docs/cli/objects/hip-object.md @@ -20,77 +20,76 @@ Create or update a HIP object. ### Syntax ```bash -scm set object hip-object [OPTIONS] +scm set object hip-object NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HIP object (max 31 characters) | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the HIP object | No\* | -| `--snippet TEXT` | Snippet for the HIP object | No\* | -| `--device TEXT` | Device for the HIP object | No\* | -| `--name TEXT` | Name of the HIP object (max 31 characters) | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--description TEXT` | Description (max 255 characters) | No | | `--host-info-os TEXT` | OS vendor (Microsoft, Apple, Google, Linux, Other) | No | | `--host-info-os-value TEXT` | OS version or "All" | No | | `--host-info-domain TEXT` | Domain criteria (is, is_not, contains) | No | | `--host-info-domain-value TEXT` | Domain value to match | No | -| `--host-info-managed` | Managed state (true/false) | No | -| `--patch-management-enabled` | Enable patch management checks | No | -| `--patch-management-vendor-name TEXT` | Vendor name | No | -| `--patch-management-product-name TEXT` | Product name | No | -| `--patch-management-criteria-is-installed TEXT` | Installation criteria | No | -| `--patch-management-missing-patches TEXT` | Missing patches check | No | -| `--disk-encryption-enabled` | Enable disk encryption checks | No | -| `--disk-encryption-vendor-name TEXT` | Encryption vendor | No | -| `--disk-encryption-product-name TEXT` | Encryption product | No | -| `--disk-encryption-criteria-is-installed TEXT` | Installation criteria (is, is_not) | No | -| `--disk-encryption-state TEXT` | Encryption state (is, is_not) | No | -| `--mobile-device-jailbroken TEXT` | Jailbreak status | No | -| `--mobile-device-disk-encrypted TEXT` | Disk encryption status | No | -| `--mobile-device-passcode-set TEXT` | Passcode requirement | No | - -\* One of --folder, --snippet, or --device is required. +| `--host-info-managed / --no-host-info-managed` | Managed state criteria | No | +| `--network-info-type TEXT` | Network type (is, is_not) | No | +| `--network-info-value TEXT` | Network value (wifi, mobile, ethernet, unknown) | No | +| `--patch-management-enabled / --no-patch-management-enabled` | Whether patch management is enabled | No | +| `--patch-management-missing-patches TEXT` | Missing patches check (has-any, has-none, has-all) | No | +| `--patch-management-severity INTEGER` | Patch severity level | No | +| `--disk-encryption-enabled / --no-disk-encryption-enabled` | Whether disk encryption is enabled | No | +| `--mobile-device-jailbroken / --no-mobile-device-jailbroken` | Jailbroken status | No | +| `--mobile-device-disk-encrypted / --no-mobile-device-disk-encrypted` | Disk encryption status | No | +| `--mobile-device-passcode-set / --no-mobile-device-passcode-set` | Passcode status | No | +| `--certificate-profile TEXT` | Certificate profile name | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples -#### Create Basic Windows Patch Compliance +#### Create a Windows Workstation Compliance Policy ```bash -$ scm set object hip-object \ +$ scm set object hip-object windows-compliance \ --folder Texas \ - --name windows-patches \ - --description "Windows security patch compliance" \ - --patch-management-vendor-name "Microsoft Corporation" \ - --patch-management-product-name "Windows" \ - --patch-management-criteria-is-installed yes \ - --patch-management-missing-patches check-not-exist + --description "Windows workstation compliance" \ + --host-info-os Microsoft \ + --host-info-os-value All \ + --host-info-managed \ + --disk-encryption-enabled \ + --patch-management-enabled ---> 100% -Created HIP object: windows-patches in folder Texas +Created HIP object: windows-compliance in folder Texas ``` -#### Create Disk Encryption Check +#### Create a Mobile Device Policy ```bash -$ scm set object hip-object \ +$ scm set object hip-object mobile-policy \ --folder Texas \ - --name disk-encryption \ - --description "Disk encryption requirement" \ - --disk-encryption-vendor-name "BitLocker" \ - --disk-encryption-product-name "BitLocker Drive Encryption" \ - --disk-encryption-criteria-is-installed is \ - --disk-encryption-state is + --description "Mobile device compliance" \ + --no-mobile-device-jailbroken \ + --mobile-device-disk-encrypted \ + --mobile-device-passcode-set ---> 100% -Created HIP object: disk-encryption in folder Texas +Created HIP object: mobile-policy in folder Texas ``` #### Create Domain Membership Check ```bash -$ scm set object hip-object \ +$ scm set object hip-object corp-domain \ --folder Texas \ - --name corp-domain \ --description "Corporate domain membership" \ --host-info-domain contains \ --host-info-domain-value "corp.company.com" \ @@ -107,25 +106,30 @@ Delete a HIP object from SCM. ### Syntax ```bash -scm delete object hip-object [OPTIONS] +scm delete object hip-object NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HIP object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the HIP object | No\* | -| `--snippet TEXT` | Snippet containing the HIP object | No\* | -| `--device TEXT` | Device containing the HIP object | No\* | -| `--name TEXT` | Name of the HIP object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object hip-object --folder Texas --name windows-patches --force +$ scm delete object hip-object windows-patches --folder Texas --force ---> 100% Deleted HIP object: windows-patches from folder Texas ``` @@ -223,30 +227,37 @@ Display HIP objects. ### Syntax ```bash -scm show object hip-object [OPTIONS] +scm show object hip-object [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HIP object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the HIP object | No\* | -| `--snippet TEXT` | Snippet containing the HIP object | No\* | -| `--device TEXT` | Device containing the HIP object | No\* | -| `--name TEXT` | Name of the HIP object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific HIP Object ```bash -$ scm show object hip-object --folder Texas --name windows-patches +$ scm show object hip-object windows-patches --folder Texas ---> 100% HIP Object: windows-patches Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/hip-profile.md b/docs-site/docs/cli/objects/hip-profile.md index a451239..ead456f 100644 --- a/docs-site/docs/cli/objects/hip-profile.md +++ b/docs-site/docs/cli/objects/hip-profile.md @@ -33,30 +33,34 @@ Create or update a HIP profile object. ### Syntax ```bash -scm set object hip-profile [OPTIONS] +scm set object hip-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HIP profile (max 31 characters) | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the HIP profile object | No\* | -| `--snippet TEXT` | Snippet for the HIP profile object | No\* | -| `--device TEXT` | Device for the HIP profile object | No\* | -| `--name TEXT` | Name of the HIP profile (max 31 characters) | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--match TEXT` | Match criteria in JSON format (max 2048 characters) | Yes | | `--description TEXT` | Description (max 255 characters) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create Basic HIP Profile ```bash -$ scm set object hip-profile \ +$ scm set object hip-profile basic-compliance \ --folder Texas \ - --name basic-compliance \ --match '{"windows-patches": {"is": true}}' \ --description "Basic Windows patch compliance" ---> 100% @@ -66,9 +70,8 @@ Created HIP profile: basic-compliance in folder Texas #### Create Multi-Object Compliance Profile ```bash -$ scm set object hip-profile \ +$ scm set object hip-profile secure-endpoints \ --folder Texas \ - --name secure-endpoints \ --match '{"windows-patches": {"is": true}, "disk-encryption": {"is": true}, "antivirus": {"is": true}}' \ --description "Comprehensive endpoint security" ---> 100% @@ -82,25 +85,30 @@ Delete a HIP profile object from SCM. ### Syntax ```bash -scm delete object hip-profile [OPTIONS] +scm delete object hip-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HIP profile object to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the HIP profile object | No\* | -| `--snippet TEXT` | Snippet containing the HIP profile object | No\* | -| `--device TEXT` | Device containing the HIP profile object | No\* | -| `--name TEXT` | Name of the HIP profile object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object hip-profile --folder Texas --name secure-endpoints --force +$ scm delete object hip-profile secure-endpoints --folder Texas --force ---> 100% Deleted HIP profile: secure-endpoints from folder Texas ``` @@ -192,30 +200,37 @@ Display HIP profile objects. ### Syntax ```bash -scm show object hip-profile [OPTIONS] +scm show object hip-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HIP profile object to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the HIP profile object | No\* | -| `--snippet TEXT` | Snippet containing the HIP profile object | No\* | -| `--device TEXT` | Device containing the HIP profile object | No\* | -| `--name TEXT` | Name of the HIP profile object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific HIP Profile ```bash -$ scm show object hip-profile --folder Texas --name secure-endpoints +$ scm show object hip-profile secure-endpoints --folder Texas ---> 100% HIP Profile: secure-endpoints Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/http-server-profile.md b/docs-site/docs/cli/objects/http-server-profile.md index 4758308..a156b49 100644 --- a/docs-site/docs/cli/objects/http-server-profile.md +++ b/docs-site/docs/cli/objects/http-server-profile.md @@ -36,31 +36,35 @@ Create or update an HTTP server profile object. ### Syntax ```bash -scm set object http-server-profile [OPTIONS] +scm set object http-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HTTP server profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the HTTP server profile object | No\* | -| `--snippet TEXT` | Snippet for the HTTP server profile object | No\* | -| `--device TEXT` | Device for the HTTP server profile object | No\* | -| `--name TEXT` | Name of the HTTP server profile | Yes | -| `--servers JSON` | JSON array of server configurations | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--servers TEXT` | JSON array of server configurations | Yes | | `--description TEXT` | Description of the profile | No | | `--tag-registration` | Enable tag registration on match | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create Basic HTTP Server Profile ```bash -$ scm set object http-server-profile \ +$ scm set object http-server-profile syslog-http \ --folder Texas \ - --name syslog-http \ --servers '[{"name": "primary", "address": "10.0.1.50", "protocol": "HTTP", "port": 8080, "http_method": "POST"}]' \ --description "HTTP syslog forwarder" ---> 100% @@ -70,9 +74,8 @@ Created HTTP server profile: syslog-http in folder Texas #### Create HTTPS Profile with Authentication ```bash -$ scm set object http-server-profile \ +$ scm set object http-server-profile splunk-hec \ --folder Texas \ - --name splunk-hec \ --servers '[{"name": "splunk", "address": "splunk.company.com", "protocol": "HTTPS", "port": 8088, "http_method": "POST", "username": "hec_user", "password": "hec_token", "tls_version": "1.2"}]' \ --description "Splunk HTTP Event Collector" ---> 100% @@ -82,9 +85,8 @@ Created HTTP server profile: splunk-hec in folder Texas #### Create Multi-Server Profile for Redundancy ```bash -$ scm set object http-server-profile \ +$ scm set object http-server-profile siem-collectors \ --folder Texas \ - --name siem-collectors \ --servers '[{"name": "primary", "address": "siem1.company.com", "protocol": "HTTPS", "port": 443, "http_method": "POST"}, {"name": "secondary", "address": "siem2.company.com", "protocol": "HTTPS", "port": 443, "http_method": "POST"}]' \ --tag-registration \ --description "SIEM collector endpoints" @@ -99,25 +101,30 @@ Delete an HTTP server profile object from SCM. ### Syntax ```bash -scm delete object http-server-profile [OPTIONS] +scm delete object http-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HTTP server profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the HTTP server profile object | No\* | -| `--snippet TEXT` | Snippet containing the HTTP server profile object | No\* | -| `--device TEXT` | Device containing the HTTP server profile object | No\* | -| `--name TEXT` | Name of the HTTP server profile object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object http-server-profile --folder Texas --name syslog-http --force +$ scm delete object http-server-profile syslog-http --folder Texas --force ---> 100% Deleted HTTP server profile: syslog-http from folder Texas ``` @@ -231,30 +238,37 @@ Display HTTP server profile objects. ### Syntax ```bash -scm show object http-server-profile [OPTIONS] +scm show object http-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the HTTP server profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the HTTP server profile object | No\* | -| `--snippet TEXT` | Snippet containing the HTTP server profile object | No\* | -| `--device TEXT` | Device containing the HTTP server profile object | No\* | -| `--name TEXT` | Name of the HTTP server profile object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific HTTP Server Profile ```bash -$ scm show object http-server-profile --folder Texas --name splunk-hec +$ scm show object http-server-profile splunk-hec --folder Texas ---> 100% HTTP Server Profile: splunk-hec Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/log-forwarding-profile.md b/docs-site/docs/cli/objects/log-forwarding-profile.md index 5b43ba7..79c1cdf 100644 --- a/docs-site/docs/cli/objects/log-forwarding-profile.md +++ b/docs-site/docs/cli/objects/log-forwarding-profile.md @@ -34,31 +34,35 @@ Create or update a log forwarding profile object. ### Syntax ```bash -scm set object log-forwarding-profile [OPTIONS] +scm set object log-forwarding-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the log forwarding profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the log forwarding profile object | No\* | -| `--snippet TEXT` | Snippet for the log forwarding profile object | No\* | -| `--device TEXT` | Device for the log forwarding profile object | No\* | -| `--name TEXT` | Name of the log forwarding profile | Yes | -| `--match-list JSON` | JSON array of match list configurations | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--match-list TEXT` | JSON array of match list configurations | No | | `--description TEXT` | Description of the profile | No | | `--enhanced-application-logging` | Enable enhanced application logging | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create Basic Traffic Log Forwarding ```bash -$ scm set object log-forwarding-profile \ +$ scm set object log-forwarding-profile traffic-logs \ --folder Texas \ - --name traffic-logs \ --match-list '[{"name": "all-traffic", "log_type": "traffic", "filter": "All Logs", "syslog_profiles": ["central-syslog"]}]' \ --description "Forward all traffic logs" ---> 100% @@ -68,9 +72,8 @@ Created log forwarding profile: traffic-logs in folder Texas #### Create Threat Log Forwarding with HTTP ```bash -$ scm set object log-forwarding-profile \ +$ scm set object log-forwarding-profile threat-logs \ --folder Texas \ - --name threat-logs \ --match-list '[{"name": "threats", "log_type": "threat", "filter": "All Logs", "http_profiles": ["splunk-hec"], "syslog_profiles": ["security-syslog"]}]' \ --enhanced-application-logging \ --description "Forward threat logs to SIEM" @@ -81,9 +84,8 @@ Created log forwarding profile: threat-logs in folder Texas #### Create Multi-Destination Forwarding ```bash -$ scm set object log-forwarding-profile \ +$ scm set object log-forwarding-profile comprehensive-logging \ --folder Texas \ - --name comprehensive-logging \ --match-list '[{"name": "traffic", "log_type": "traffic", "filter": "All Logs", "syslog_profiles": ["central-syslog"]}, {"name": "threats", "log_type": "threat", "filter": "All Logs", "http_profiles": ["splunk-hec"]}, {"name": "urls", "log_type": "url", "filter": "All Logs", "http_profiles": ["splunk-hec"]}]' \ --description "Comprehensive log forwarding" ---> 100% @@ -97,25 +99,30 @@ Delete a log forwarding profile object from SCM. ### Syntax ```bash -scm delete object log-forwarding-profile [OPTIONS] +scm delete object log-forwarding-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the log forwarding profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the log forwarding profile object | No\* | -| `--snippet TEXT` | Snippet containing the log forwarding profile object | No\* | -| `--device TEXT` | Device containing the log forwarding profile object | No\* | -| `--name TEXT` | Name of the log forwarding profile object to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object log-forwarding-profile --folder Texas --name traffic-logs --force +$ scm delete object log-forwarding-profile traffic-logs --folder Texas --force ---> 100% Deleted log forwarding profile: traffic-logs from folder Texas ``` @@ -228,30 +235,37 @@ Display log forwarding profile objects. ### Syntax ```bash -scm show object log-forwarding-profile [OPTIONS] +scm show object log-forwarding-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the log forwarding profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the log forwarding profile object | No\* | -| `--snippet TEXT` | Snippet containing the log forwarding profile object | No\* | -| `--device TEXT` | Device containing the log forwarding profile object | No\* | -| `--name TEXT` | Name of the log forwarding profile object to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Log Forwarding Profile ```bash -$ scm show object log-forwarding-profile --folder Texas --name threat-logs +$ scm show object log-forwarding-profile threat-logs --folder Texas ---> 100% Log Forwarding Profile: threat-logs Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/quarantined-device.md b/docs-site/docs/cli/objects/quarantined-device.md index b81458b..16f37dd 100644 --- a/docs-site/docs/cli/objects/quarantined-device.md +++ b/docs-site/docs/cli/objects/quarantined-device.md @@ -18,20 +18,26 @@ Create a quarantined device entry. ### Syntax ```bash -scm set object quarantined-device HOST_ID [OPTIONS] +scm set object quarantined-device [OPTIONS] ``` ### Options | Option | Description | Required | | --- | --- | --- | -| `HOST_ID` | Host ID of the device (positional) | Yes | +| `--host-id TEXT` | Host ID of the device to quarantine | Yes | | `--serial-number TEXT` | Serial number of the device | No | +:::note +Quarantined devices are identified by `--host-id` rather than a positional name, and +do not use a container location (`--folder`, `--snippet`, or `--device`). +::: + ### Example ```bash -$ scm set object quarantined-device abc123 \ +$ scm set object quarantined-device \ + --host-id abc123 \ --serial-number SN12345 ---> 100% Created quarantined device: abc123 @@ -44,20 +50,20 @@ Delete a quarantined device entry. ### Syntax ```bash -scm delete object quarantined-device HOST_ID +scm delete object quarantined-device [OPTIONS] ``` ### Options | Option | Description | Required | | --- | --- | --- | -| `HOST_ID` | Host ID of the device (positional) | Yes | +| `--host-id TEXT` | Host ID of the quarantined device to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete object quarantined-device abc123 --force +$ scm delete object quarantined-device --host-id abc123 --force ---> 100% Deleted quarantined device: abc123 ``` @@ -77,6 +83,7 @@ scm load object quarantined-device [OPTIONS] | Option | Description | Required | | --- | --- | --- | | `--file TEXT` | Path to YAML file containing quarantined device definitions | Yes | +| `--dry-run` | Preview changes without applying them | No | ### YAML File Format @@ -117,6 +124,8 @@ scm show object quarantined-device [OPTIONS] | --- | --- | --- | | `--host-id TEXT` | Filter by host ID | No | | `--serial-number TEXT` | Filter by serial number | No | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | :::note When no filters are specified, all quarantined devices are listed by default. diff --git a/docs-site/docs/cli/objects/region.md b/docs-site/docs/cli/objects/region.md index 2cb5814..0332758 100644 --- a/docs-site/docs/cli/objects/region.md +++ b/docs-site/docs/cli/objects/region.md @@ -22,19 +22,24 @@ Create or update a region. scm set object region NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the region | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the region (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--latitude FLOAT` | Latitude (-90 to 90) | No | | `--longitude FLOAT` | Longitude (-180 to 180) | No | -| `--addresses TEXT` | Associated addresses | No | +| `--address TEXT` | Address CIDR to associate (repeat for multiple) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -54,8 +59,8 @@ Created region: us-west in folder Texas ```bash $ scm set object region branch-offices \ --folder Texas \ - --addresses 10.0.0.0/8 \ - --addresses 172.16.0.0/12 + --address 10.0.0.0/8 \ + --address 172.16.0.0/12 ---> 100% Created region: branch-offices in folder Texas ``` @@ -70,17 +75,22 @@ Delete a region from SCM. scm delete object region NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the region to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the region (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -164,30 +174,37 @@ Display region objects. ### Syntax ```bash -scm show object region [OPTIONS] +scm show object region [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the region to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of specific region to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Region ```bash -$ scm show object region --folder Texas --name us-west +$ scm show object region us-west --folder Texas ---> 100% Region: us-west Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/schedule.md b/docs-site/docs/cli/objects/schedule.md index e6ae06c..8672ca8 100644 --- a/docs-site/docs/cli/objects/schedule.md +++ b/docs-site/docs/cli/objects/schedule.md @@ -23,15 +23,20 @@ Create or update a schedule. scm set object schedule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the schedule | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the schedule (positional) | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--schedule-type TEXT` | Schedule type: recurring-daily, recurring-weekly, or non-recurring | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | | `--time-range TEXT` | Time ranges (for daily/non-recurring) | No | | `--monday TEXT` | Monday time ranges (for weekly) | No | | `--tuesday TEXT` | Tuesday time ranges (for weekly) | No | @@ -41,7 +46,7 @@ scm set object schedule NAME [OPTIONS] | `--saturday TEXT` | Saturday time ranges (for weekly) | No | | `--sunday TEXT` | Sunday time ranges (for weekly) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples @@ -81,17 +86,22 @@ Delete a schedule from SCM. scm delete object schedule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the schedule to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `NAME` | Name of the schedule (positional) | Yes | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example @@ -178,30 +188,37 @@ Display schedule objects. ### Syntax ```bash -scm show object schedule [OPTIONS] +scm show object schedule [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the schedule to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | No\* | -| `--snippet TEXT` | Snippet location | No\* | -| `--device TEXT` | Device location | No\* | -| `--name TEXT` | Name of specific schedule to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Schedule ```bash -$ scm show object schedule --folder Texas --name business-hours +$ scm show object schedule business-hours --folder Texas ---> 100% Schedule: business-hours Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/service-group.md b/docs-site/docs/cli/objects/service-group.md index 46b65ad..872e85d 100644 --- a/docs-site/docs/cli/objects/service-group.md +++ b/docs-site/docs/cli/objects/service-group.md @@ -20,33 +20,35 @@ Create or update a service group object. ### Syntax ```bash -scm set object service-group [OPTIONS] +scm set object service-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service group | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the service group object | No\* | -| `--snippet TEXT` | Snippet for the service group object | No\* | -| `--device TEXT` | Device for the service group object | No\* | -| `--name TEXT` | Name of the service group | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--members TEXT` | Comma-separated list of service or service group names | Yes | -| `--description TEXT` | Description of the group | No | -| `--tag TEXT` | Tags for categorization (comma-separated) | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Basic Service Group ```bash -$ scm set object service-group \ +$ scm set object service-group web-services \ --folder Texas \ - --name web-services \ - --members "http,https,ssl,web-browsing" \ - --description "Standard web services" + --members "http,https,ssl,web-browsing" ---> 100% Created service group: web-services in folder Texas ``` @@ -54,12 +56,10 @@ Created service group: web-services in folder Texas #### Create a Service Group with Tags ```bash -$ scm set object service-group \ +$ scm set object service-group database-services \ --folder Texas \ - --name database-services \ --members "mysql,ms-sql,oracle,postgresql,custom-db" \ - --tag "database,backend" \ - --description "Database service ports" + --tags database --tags backend ---> 100% Created service group: database-services in folder Texas ``` @@ -67,11 +67,9 @@ Created service group: database-services in folder Texas #### Create a Nested Service Group ```bash -$ scm set object service-group \ +$ scm set object service-group all-services \ --folder Texas \ - --name all-services \ - --members "web-services,database-services,mail-services" \ - --description "All allowed services (nested groups)" + --members "web-services,database-services,mail-services" ---> 100% Created service group: all-services in folder Texas ``` @@ -83,25 +81,30 @@ Delete a service group object from SCM. ### Syntax ```bash -scm delete object service-group [OPTIONS] +scm delete object service-group NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service group to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the service group object | No\* | -| `--snippet TEXT` | Snippet containing the service group object | No\* | -| `--device TEXT` | Device containing the service group object | No\* | -| `--name TEXT` | Name of the service group to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object service-group --folder Texas --name web-services --force +$ scm delete object service-group web-services --folder Texas --force ---> 100% Deleted service group: web-services from folder Texas ``` @@ -216,30 +219,37 @@ Display service group objects. ### Syntax ```bash -scm show object service-group [OPTIONS] +scm show object service-group [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service group to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the service group object | No\* | -| `--snippet TEXT` | Snippet containing the service group object | No\* | -| `--device TEXT` | Device containing the service group object | No\* | -| `--name TEXT` | Name of the service group to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Service Group ```bash -$ scm show object service-group --folder Texas --name web-services +$ scm show object service-group web-services --folder Texas ---> 100% Service Group: web-services Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/service.md b/docs-site/docs/cli/objects/service.md index 497321b..f094e01 100644 --- a/docs-site/docs/cli/objects/service.md +++ b/docs-site/docs/cli/objects/service.md @@ -29,35 +29,39 @@ Create or update a service object. ### Syntax ```bash -scm set object service [OPTIONS] +scm set object service NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the service object | No\* | -| `--snippet TEXT` | Snippet for the service object | No\* | -| `--device TEXT` | Device for the service object | No\* | -| `--name TEXT` | Name of the service | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--protocol TEXT` | Protocol type (tcp or udp) | Yes | | `--port TEXT` | Port specification | Yes | | `--description TEXT` | Description of the service | No | -| `--tag TEXT` | Tags for categorization (comma-separated) | No | -| `--timeout INT` | Session timeout in seconds (TCP only) | No | -| `--halfclose-timeout INT` | TCP half-close timeout | No | -| `--timewait-timeout INT` | TCP time-wait timeout | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | +| `--timeout INTEGER` | Session timeout in seconds (TCP only) | No | +| `--halfclose-timeout INTEGER` | TCP half-close timeout | No | +| `--timewait-timeout INTEGER` | TCP time-wait timeout | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Basic TCP Service ```bash -$ scm set object service \ +$ scm set object service custom-web \ --folder Texas \ - --name custom-web \ --protocol tcp \ --port "8080,8443" \ --description "Custom web service ports" @@ -68,9 +72,8 @@ Created service: custom-web in folder Texas #### Create a UDP Service with Port Range ```bash -$ scm set object service \ +$ scm set object service custom-voip \ --folder Texas \ - --name custom-voip \ --protocol udp \ --port "5060-5070" \ --description "VoIP signaling ports" @@ -81,9 +84,8 @@ Created service: custom-voip in folder Texas #### Create a TCP Service with Timeout Overrides ```bash -$ scm set object service \ +$ scm set object service database-service \ --folder Texas \ - --name database-service \ --protocol tcp \ --port "3306" \ --timeout 7200 \ @@ -101,25 +103,30 @@ Delete a service object from SCM. ### Syntax ```bash -scm delete object service [OPTIONS] +scm delete object service NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the service object | No\* | -| `--snippet TEXT` | Snippet containing the service object | No\* | -| `--device TEXT` | Device containing the service object | No\* | -| `--name TEXT` | Name of the service to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object service --folder Texas --name custom-web --force +$ scm delete object service custom-web --folder Texas --force ---> 100% Deleted service: custom-web from folder Texas ``` @@ -225,30 +232,37 @@ Display service objects. ### Syntax ```bash -scm show object service [OPTIONS] +scm show object service [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the service to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the service object | No\* | -| `--snippet TEXT` | Snippet containing the service object | No\* | -| `--device TEXT` | Device containing the service object | No\* | -| `--name TEXT` | Name of the service to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Service ```bash -$ scm show object service --folder Texas --name custom-web +$ scm show object service custom-web --folder Texas ---> 100% Service: custom-web Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/syslog-server-profile.md b/docs-site/docs/cli/objects/syslog-server-profile.md index 814d7b7..4406876 100644 --- a/docs-site/docs/cli/objects/syslog-server-profile.md +++ b/docs-site/docs/cli/objects/syslog-server-profile.md @@ -31,55 +31,67 @@ Create or update a syslog server profile object. ### Syntax ```bash -scm set object syslog-server-profile [OPTIONS] +scm set object syslog-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the syslog server profile | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the syslog server profile object | No\* | -| `--snippet TEXT` | Snippet for the syslog server profile object | No\* | -| `--device TEXT` | Device for the syslog server profile object | No\* | -| `--name TEXT` | Name of the syslog server profile | Yes | -| `--servers JSON` | JSON array of server configurations | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--server-name TEXT` | Name of the syslog server | Yes | +| `--server-address TEXT` | IP address or hostname of the syslog server | Yes | +| `--transport TEXT` | Transport protocol (UDP, TCP, SSL) | Yes | +| `--port INTEGER` | Port number (1-65535) | Yes | +| `--format TEXT` | Log format (BSD, IETF) | Yes | +| `--facility TEXT` | Syslog facility (LOG_USER, LOG_LOCAL0-7) | Yes | | `--description TEXT` | Description of the profile | No | +| `--tags TEXT` | Tag to apply (repeat for multiple) | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. + +:::note +The `set` command configures a single syslog server per profile. To define profiles +with multiple servers, use the `load` command with a YAML file. +::: ### Examples #### Create Basic Syslog Profile with TCP ```bash -$ scm set object syslog-server-profile \ +$ scm set object syslog-server-profile central-syslog \ --folder Texas \ - --name central-syslog \ - --servers '[{"name": "primary", "server": "10.0.1.50", "port": 514, "transport": "TCP", "format": "BSD", "facility": "LOG_USER"}]' \ + --server-name primary \ + --server-address 10.0.1.50 \ + --transport TCP \ + --port 514 \ + --format BSD \ + --facility LOG_USER \ --description "Central syslog collection" ---> 100% Created syslog server profile: central-syslog in folder Texas ``` -#### Create Profile with Multiple Servers - -```bash -$ scm set object syslog-server-profile \ - --folder Texas \ - --name redundant-syslog \ - --servers '[{"name": "primary", "server": "syslog1.company.com", "port": 514, "transport": "UDP", "format": "BSD", "facility": "LOG_USER"}, {"name": "secondary", "server": "syslog2.company.com", "port": 514, "transport": "UDP", "format": "BSD", "facility": "LOG_USER"}]' \ - --description "Redundant syslog servers" ----> 100% -Created syslog server profile: redundant-syslog in folder Texas -``` - #### Create Compliance Syslog Profile with IETF Format ```bash -$ scm set object syslog-server-profile \ +$ scm set object syslog-server-profile compliance \ --folder Shared \ - --name compliance \ - --servers '[{"name": "compliance-srv", "server": "10.10.10.50", "port": 6514, "transport": "TCP", "format": "IETF", "facility": "LOG_LOCAL7"}]' \ + --server-name compliance-srv \ + --server-address 10.10.10.50 \ + --transport TCP \ + --port 6514 \ + --format IETF \ + --facility LOG_LOCAL7 \ --description "Compliance logging with IETF format" ---> 100% Created syslog server profile: compliance in folder Shared @@ -92,25 +104,30 @@ Delete a syslog server profile object from SCM. ### Syntax ```bash -scm delete object syslog-server-profile [OPTIONS] +scm delete object syslog-server-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the syslog server profile to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the syslog server profile object | No\* | -| `--snippet TEXT` | Snippet containing the syslog server profile object | No\* | -| `--device TEXT` | Device containing the syslog server profile object | No\* | -| `--name TEXT` | Name of the syslog server profile to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object syslog-server-profile --folder Texas --name central-syslog --force +$ scm delete object syslog-server-profile central-syslog --folder Texas --force ---> 100% Deleted syslog server profile: central-syslog from folder Texas ``` @@ -232,30 +249,37 @@ Display syslog server profile objects. ### Syntax ```bash -scm show object syslog-server-profile [OPTIONS] +scm show object syslog-server-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the syslog server profile to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the syslog server profile object | No\* | -| `--snippet TEXT` | Snippet containing the syslog server profile object | No\* | -| `--device TEXT` | Device containing the syslog server profile object | No\* | -| `--name TEXT` | Name of the syslog server profile to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Syslog Server Profile ```bash -$ scm show object syslog-server-profile --folder Texas --name central-syslog +$ scm show object syslog-server-profile central-syslog --folder Texas ---> 100% Syslog Server Profile: central-syslog Location: Folder 'Texas' diff --git a/docs-site/docs/cli/objects/tag.md b/docs-site/docs/cli/objects/tag.md index 0897fde..6b993a1 100644 --- a/docs-site/docs/cli/objects/tag.md +++ b/docs-site/docs/cli/objects/tag.md @@ -40,30 +40,34 @@ Create or update a tag object. ### Syntax ```bash -scm set object tag [OPTIONS] +scm set object tag NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tag | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the tag object | No\* | -| `--snippet TEXT` | Snippet for the tag object | No\* | -| `--device TEXT` | Device for the tag object | No\* | -| `--name TEXT` | Name of the tag | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--color TEXT` | Color for visual identification | No | | `--comments TEXT` | Descriptive comments about the tag | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Examples #### Create a Tag with Color ```bash -$ scm set object tag \ +$ scm set object tag production \ --folder Texas \ - --name production \ --color "Red" \ --comments "Production environment resources" ---> 100% @@ -73,18 +77,16 @@ Created tag: production in folder Texas #### Create Multiple Environment Tags ```bash -$ scm set object tag \ +$ scm set object tag critical \ --folder Texas \ - --name critical \ --color "Orange" ---> 100% Created tag: critical in folder Texas ``` ```bash -$ scm set object tag \ +$ scm set object tag database \ --folder Texas \ - --name database \ --color "Blue" ---> 100% Created tag: database in folder Texas @@ -97,25 +99,30 @@ Delete a tag object from SCM. ### Syntax ```bash -scm delete object tag [OPTIONS] +scm delete object tag NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tag to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the tag object | No\* | -| `--snippet TEXT` | Snippet containing the tag object | No\* | -| `--device TEXT` | Device containing the tag object | No\* | -| `--name TEXT` | Name of the tag to delete | Yes | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | | `--force` | Skip confirmation prompt | No | -\* One of --folder, --snippet, or --device is required. +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. ### Example ```bash -$ scm delete object tag --folder Texas --name production --force +$ scm delete object tag production --folder Texas --force ---> 100% Deleted tag: production from folder Texas ``` @@ -221,30 +228,37 @@ Display tag objects. ### Syntax ```bash -scm show object tag [OPTIONS] +scm show object tag [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the tag to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the tag object | No\* | -| `--snippet TEXT` | Snippet containing the tag object | No\* | -| `--device TEXT` | Device containing the tag object | No\* | -| `--name TEXT` | Name of the tag to show | No | +| `--folder TEXT` | Folder location | Yes\* | +| `--snippet TEXT` | Snippet location | Yes\* | +| `--device TEXT` | Device location | Yes\* | +| `--max-results INTEGER` | Maximum number of results to display | No | +| `--output [table\|json\|yaml]` | Output format (default: `table`) | No | + +\* Exactly one of `--folder`, `--snippet`, or `--device` is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` argument is provided, all items are listed by default. ::: -\* One of --folder, --snippet, or --device is required. - ### Examples #### Show Specific Tag ```bash -$ scm show object tag --folder Texas --name production +$ scm show object tag production --folder Texas ---> 100% Tag: production Location: Folder 'Texas' diff --git a/docs-site/docs/cli/operations/index.md b/docs-site/docs/cli/operations/index.md index b15aead..a40dda9 100644 --- a/docs-site/docs/cli/operations/index.md +++ b/docs-site/docs/cli/operations/index.md @@ -60,7 +60,7 @@ scm operations logging-status --device 007951000123456 Check on an async job: ```bash -scm operations status --job-id abc-123 +scm operations status --id abc-123 ``` ## Sync vs Async diff --git a/docs-site/docs/cli/security/anti-spyware-profile.md b/docs-site/docs/cli/security/anti-spyware-profile.md index fbbae21..5cbcab5 100644 --- a/docs-site/docs/cli/security/anti-spyware-profile.md +++ b/docs-site/docs/cli/security/anti-spyware-profile.md @@ -19,9 +19,15 @@ Create or update an anti-spyware profile. ### Syntax ```bash -scm set security anti-spyware-profile [OPTIONS] +scm set security anti-spyware-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | @@ -29,7 +35,6 @@ scm set security anti-spyware-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name | Yes | | `--description TEXT` | Profile description | No | | `--cloud-inline-analysis / --no-cloud-inline-analysis` | Enable cloud inline analysis | No | | `--block-critical-high` | Add default rule to block critical and high severity threats | No | @@ -41,9 +46,8 @@ scm set security anti-spyware-profile [OPTIONS] #### Create Basic Profile ```bash -$ scm set security anti-spyware-profile \ +$ scm set security anti-spyware-profile strict-security \ --folder Texas \ - --name strict-security \ --description "Block critical threats" ---> 100% Created anti-spyware profile: strict-security in folder Texas @@ -52,9 +56,8 @@ Created anti-spyware profile: strict-security in folder Texas #### Create Profile Blocking Critical and High Severity ```bash -$ scm set security anti-spyware-profile \ +$ scm set security anti-spyware-profile block-threats \ --folder Texas \ - --name block-threats \ --block-critical-high \ --cloud-inline-analysis ---> 100% @@ -64,9 +67,8 @@ Created anti-spyware profile: block-threats in folder Texas #### Create Profile in Snippet ```bash -$ scm set security anti-spyware-profile \ - --snippet Security-Best-Practice \ - --name standard-protection +$ scm set security anti-spyware-profile standard-protection \ + --snippet Security-Best-Practice ---> 100% Created anti-spyware profile: standard-protection in snippet Security-Best-Practice ``` @@ -78,9 +80,15 @@ Delete an anti-spyware profile from SCM. ### Syntax ```bash -scm delete security anti-spyware-profile [OPTIONS] +scm delete security anti-spyware-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to delete | Yes | + ### Options | Option | Description | Required | @@ -88,7 +96,6 @@ scm delete security anti-spyware-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -96,9 +103,8 @@ scm delete security anti-spyware-profile [OPTIONS] ### Example ```bash -$ scm delete security anti-spyware-profile \ +$ scm delete security anti-spyware-profile strict-security \ --folder Texas \ - --name strict-security \ --force ---> 100% Deleted anti-spyware profile: strict-security from folder Texas @@ -179,9 +185,15 @@ Display anti-spyware profile objects. ### Syntax ```bash -scm show security anti-spyware-profile [OPTIONS] +scm show security anti-spyware-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -189,12 +201,13 @@ scm show security anti-spyware-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -202,9 +215,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Profile ```bash -$ scm show security anti-spyware-profile \ - --folder Texas \ - --name strict-security +$ scm show security anti-spyware-profile strict-security \ + --folder Texas ---> 100% Anti-Spyware Profile: strict-security Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/app-override-rule.md b/docs-site/docs/cli/security/app-override-rule.md index 63f8b09..138df18 100644 --- a/docs-site/docs/cli/security/app-override-rule.md +++ b/docs-site/docs/cli/security/app-override-rule.md @@ -20,9 +20,15 @@ Create or update an app override rule. ### Syntax ```bash -scm set security app-override-rule [OPTIONS] +scm set security app-override-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name | Yes | + ### Options | Option | Description | Required | @@ -30,7 +36,6 @@ scm set security app-override-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name | Yes | | `--application TEXT` | Application to override | Yes | | `--port TEXT` | Port(s) for the rule | Yes | | `--protocol TEXT` | Protocol (tcp or udp) | Yes | @@ -48,9 +53,8 @@ scm set security app-override-rule [OPTIONS] #### Create Basic Override Rule ```bash -$ scm set security app-override-rule \ +$ scm set security app-override-rule override-https \ --folder Texas \ - --name override-https \ --application ssl \ --port 8443 \ --protocol tcp @@ -61,9 +65,8 @@ Created app override rule: override-https in folder Texas #### Create Override with Zones ```bash -$ scm set security app-override-rule \ +$ scm set security app-override-rule custom-app-override \ --folder Texas \ - --name custom-app-override \ --application web-browsing \ --port 9090 \ --protocol tcp \ @@ -81,33 +84,38 @@ Change the position of an app override rule. Rules are processed in order from t ### Syntax ```bash -scm set security app-override-rule --move [OPTIONS] +scm move security app-override-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the rule to move | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the rules | No\* | -| `--snippet TEXT` | Snippet containing the rules | No\* | -| `--device TEXT` | Device containing the rules | No\* | -| `--name TEXT` | Name of the rule to move | Yes | -| `--location TEXT` | Where to move the rule (top, bottom, before, after) | Yes | -| `--reference TEXT` | Reference rule name (required with before/after) | No\*\* | +| `--folder TEXT` | Folder containing the rule | No\* | +| `--snippet TEXT` | Snippet containing the rule | No\* | +| `--device TEXT` | Device containing the rule | No\* | +| `--destination TEXT` | Where to move (top, bottom, before, after) | Yes | +| `--destination-rule TEXT` | UUID of the reference rule (required with before/after) | No\*\* | +| `--rulebase TEXT` | Rulebase (pre or post; default: pre) | No | \* One of --folder, --snippet, or --device is required. -\*\* Required when `--location` is `before` or `after`. +\*\* Required when `--destination` is `before` or `after`. ### Examples #### Move Rule to Top ```bash -$ scm set security app-override-rule --move \ +$ scm move security app-override-rule override-https \ --folder Texas \ - --name override-https \ - --location top + --destination top ---> 100% Moved app override rule: override-https to top in folder Texas ``` @@ -115,13 +123,12 @@ Moved app override rule: override-https to top in folder Texas #### Move Rule After Another Rule ```bash -$ scm set security app-override-rule --move \ +$ scm move security app-override-rule custom-app-override \ --folder Texas \ - --name custom-app-override \ - --location after \ - --reference override-https + --destination after \ + --destination-rule 123e4567-e89b-12d3-a456-426614174000 ---> 100% -Moved app override rule: custom-app-override after override-https in folder Texas +Moved app override rule: custom-app-override after rule 123e4567-e89b-12d3-a456-426614174000 in folder Texas ``` ## Delete App Override Rule @@ -131,9 +138,15 @@ Delete an app override rule from SCM. ### Syntax ```bash -scm delete security app-override-rule [OPTIONS] +scm delete security app-override-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name to delete | Yes | + ### Options | Option | Description | Required | @@ -141,7 +154,7 @@ scm delete security app-override-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name to delete | Yes | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -149,9 +162,8 @@ scm delete security app-override-rule [OPTIONS] ### Example ```bash -$ scm delete security app-override-rule \ +$ scm delete security app-override-rule override-https \ --folder Texas \ - --name override-https \ --force ---> 100% Deleted app override rule: override-https from folder Texas @@ -240,9 +252,15 @@ Display app override rule objects. ### Syntax ```bash -scm show security app-override-rule [OPTIONS] +scm show security app-override-rule [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -250,12 +268,14 @@ scm show security app-override-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name to display | No | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -263,9 +283,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Rule ```bash -$ scm show security app-override-rule \ - --folder Texas \ - --name override-https +$ scm show security app-override-rule override-https \ + --folder Texas ---> 100% App Override Rule: override-https Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/authentication-rule.md b/docs-site/docs/cli/security/authentication-rule.md index cfec473..bf378bf 100644 --- a/docs-site/docs/cli/security/authentication-rule.md +++ b/docs-site/docs/cli/security/authentication-rule.md @@ -20,9 +20,15 @@ Create or update an authentication rule. ### Syntax ```bash -scm set security authentication-rule [OPTIONS] +scm set security authentication-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name | Yes | + ### Options | Option | Description | Required | @@ -30,7 +36,6 @@ scm set security authentication-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name | Yes | | `--rulebase TEXT` | Rulebase (pre, post, default) | No | | `--description TEXT` | Description | No | | `--source-zones TEXT` | Source zones | No | @@ -48,9 +53,8 @@ scm set security authentication-rule [OPTIONS] #### Create Basic Authentication Rule ```bash -$ scm set security authentication-rule \ +$ scm set security authentication-rule auth-web \ --folder Texas \ - --name auth-web \ --source-zones trust \ --destination-zones untrust \ --authentication-enforcement my-auth-profile @@ -61,9 +65,8 @@ Created authentication rule: auth-web in folder Texas #### Create Rule with Service and Category ```bash -$ scm set security authentication-rule \ +$ scm set security authentication-rule auth-sensitive \ --folder Texas \ - --name auth-sensitive \ --source-zones trust \ --destination-zones dmz \ --service "service-https" \ @@ -81,33 +84,38 @@ Change the position of an authentication rule. Rules are processed in order from ### Syntax ```bash -scm set security authentication-rule --move [OPTIONS] +scm move security authentication-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the rule to move | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the rules | No\* | -| `--snippet TEXT` | Snippet containing the rules | No\* | -| `--device TEXT` | Device containing the rules | No\* | -| `--name TEXT` | Name of the rule to move | Yes | -| `--location TEXT` | Where to move the rule (top, bottom, before, after) | Yes | -| `--reference TEXT` | Reference rule name (required with before/after) | No\*\* | +| `--folder TEXT` | Folder containing the rule | No\* | +| `--snippet TEXT` | Snippet containing the rule | No\* | +| `--device TEXT` | Device containing the rule | No\* | +| `--destination TEXT` | Where to move (top, bottom, before, after) | Yes | +| `--destination-rule TEXT` | UUID of the reference rule (required with before/after) | No\*\* | +| `--rulebase TEXT` | Rulebase (pre or post; default: pre) | No | \* One of --folder, --snippet, or --device is required. -\*\* Required when `--location` is `before` or `after`. +\*\* Required when `--destination` is `before` or `after`. ### Examples #### Move Rule to Top ```bash -$ scm set security authentication-rule --move \ +$ scm move security authentication-rule auth-sensitive \ --folder Texas \ - --name auth-sensitive \ - --location top + --destination top ---> 100% Moved authentication rule: auth-sensitive to top in folder Texas ``` @@ -115,13 +123,12 @@ Moved authentication rule: auth-sensitive to top in folder Texas #### Move Rule Before Another Rule ```bash -$ scm set security authentication-rule --move \ +$ scm move security authentication-rule auth-web \ --folder Texas \ - --name auth-web \ - --location before \ - --reference auth-sensitive + --destination before \ + --destination-rule 123e4567-e89b-12d3-a456-426614174000 ---> 100% -Moved authentication rule: auth-web before auth-sensitive in folder Texas +Moved authentication rule: auth-web before rule 123e4567-e89b-12d3-a456-426614174000 in folder Texas ``` ## Delete Authentication Rule @@ -131,9 +138,15 @@ Delete an authentication rule from SCM. ### Syntax ```bash -scm delete security authentication-rule [OPTIONS] +scm delete security authentication-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name to delete | Yes | + ### Options | Option | Description | Required | @@ -141,7 +154,7 @@ scm delete security authentication-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name to delete | Yes | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -149,9 +162,8 @@ scm delete security authentication-rule [OPTIONS] ### Example ```bash -$ scm delete security authentication-rule \ +$ scm delete security authentication-rule auth-web \ --folder Texas \ - --name auth-web \ --force ---> 100% Deleted authentication rule: auth-web from folder Texas @@ -245,9 +257,15 @@ Display authentication rule objects. ### Syntax ```bash -scm show security authentication-rule [OPTIONS] +scm show security authentication-rule [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -255,12 +273,14 @@ scm show security authentication-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name to display | No | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -268,9 +288,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Rule ```bash -$ scm show security authentication-rule \ - --folder Texas \ - --name auth-web +$ scm show security authentication-rule auth-web \ + --folder Texas ---> 100% Authentication Rule: auth-web Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/decryption-profile.md b/docs-site/docs/cli/security/decryption-profile.md index 1b342b2..1c3c82c 100644 --- a/docs-site/docs/cli/security/decryption-profile.md +++ b/docs-site/docs/cli/security/decryption-profile.md @@ -29,9 +29,15 @@ Create or update a decryption profile. ### Syntax ```bash -scm set security decryption-profile [OPTIONS] +scm set security decryption-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | @@ -39,7 +45,6 @@ scm set security decryption-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name | Yes | | `--description TEXT` | Profile description | No | | `--ssl-forward-proxy TEXT` | SSL forward proxy settings as JSON | No | | `--ssl-inbound-proxy TEXT` | SSL inbound proxy settings as JSON | No | @@ -53,9 +58,8 @@ scm set security decryption-profile [OPTIONS] #### Create SSL Forward Proxy Profile ```bash -$ scm set security decryption-profile \ +$ scm set security decryption-profile ssl-forward \ --folder Texas \ - --name ssl-forward \ --ssl-forward-proxy '{"block_expired_certificate": true, "block_untrusted_issuer": true}' ---> 100% Created decryption profile: ssl-forward in folder Texas @@ -64,9 +68,8 @@ Created decryption profile: ssl-forward in folder Texas #### Create Profile with Protocol Settings ```bash -$ scm set security decryption-profile \ +$ scm set security decryption-profile custom-decrypt \ --folder Texas \ - --name custom-decrypt \ --ssl-forward-proxy '{"block_expired_certificate": true}' \ --ssl-protocol-settings '{"min_version": "tls1-2", "max_version": "tls1-3"}' ---> 100% @@ -76,9 +79,8 @@ Created decryption profile: custom-decrypt in folder Texas #### Create No-Decrypt Profile ```bash -$ scm set security decryption-profile \ +$ scm set security decryption-profile no-decrypt \ --folder Texas \ - --name no-decrypt \ --ssl-no-proxy '{"block_expired_certificate": false}' ---> 100% Created decryption profile: no-decrypt in folder Texas @@ -91,9 +93,15 @@ Delete a decryption profile from SCM. ### Syntax ```bash -scm delete security decryption-profile [OPTIONS] +scm delete security decryption-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to delete | Yes | + ### Options | Option | Description | Required | @@ -101,7 +109,6 @@ scm delete security decryption-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -109,9 +116,8 @@ scm delete security decryption-profile [OPTIONS] ### Example ```bash -$ scm delete security decryption-profile \ +$ scm delete security decryption-profile ssl-forward \ --folder Texas \ - --name ssl-forward \ --force ---> 100% Deleted decryption profile: ssl-forward from folder Texas @@ -197,9 +203,15 @@ Display decryption profile objects. ### Syntax ```bash -scm show security decryption-profile [OPTIONS] +scm show security decryption-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -207,12 +219,13 @@ scm show security decryption-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -220,9 +233,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Profile ```bash -$ scm show security decryption-profile \ - --folder Texas \ - --name ssl-forward +$ scm show security decryption-profile ssl-forward \ + --folder Texas ---> 100% Decryption Profile: ssl-forward Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/decryption-rule.md b/docs-site/docs/cli/security/decryption-rule.md index 53f52ac..09bb179 100644 --- a/docs-site/docs/cli/security/decryption-rule.md +++ b/docs-site/docs/cli/security/decryption-rule.md @@ -20,9 +20,15 @@ Create or update a decryption rule. ### Syntax ```bash -scm set security decryption-rule [OPTIONS] +scm set security decryption-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name | Yes | + ### Options | Option | Description | Required | @@ -30,7 +36,6 @@ scm set security decryption-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name | Yes | | `--action TEXT` | Action (decrypt or no-decrypt) | Yes | | `--rulebase TEXT` | Rulebase (pre, post, default) | No | | `--description TEXT` | Description | No | @@ -48,9 +53,8 @@ scm set security decryption-rule [OPTIONS] #### Create No-Decrypt Rule ```bash -$ scm set security decryption-rule \ +$ scm set security decryption-rule no-decrypt-internal \ --folder Texas \ - --name no-decrypt-internal \ --action no-decrypt \ --source-zones trust \ --destination-zones trust @@ -61,9 +65,8 @@ Created decryption rule: no-decrypt-internal in folder Texas #### Create Decrypt Rule with SSL Forward Proxy ```bash -$ scm set security decryption-rule \ +$ scm set security decryption-rule decrypt-outbound \ --folder Texas \ - --name decrypt-outbound \ --action decrypt \ --type '{"ssl_forward_proxy": {}}' \ --profile ssl-forward-profile \ @@ -80,33 +83,38 @@ Change the position of a decryption rule. Rules are processed in order from top ### Syntax ```bash -scm set security decryption-rule --move [OPTIONS] +scm move security decryption-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the rule to move | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the rules | No\* | -| `--snippet TEXT` | Snippet containing the rules | No\* | -| `--device TEXT` | Device containing the rules | No\* | -| `--name TEXT` | Name of the rule to move | Yes | -| `--location TEXT` | Where to move the rule (top, bottom, before, after) | Yes | -| `--reference TEXT` | Reference rule name (required with before/after) | No\*\* | +| `--folder TEXT` | Folder containing the rule | No\* | +| `--snippet TEXT` | Snippet containing the rule | No\* | +| `--device TEXT` | Device containing the rule | No\* | +| `--destination TEXT` | Where to move (top, bottom, before, after) | Yes | +| `--destination-rule TEXT` | UUID of the reference rule (required with before/after) | No\*\* | +| `--rulebase TEXT` | Rulebase (pre or post; default: pre) | No | \* One of --folder, --snippet, or --device is required. -\*\* Required when `--location` is `before` or `after`. +\*\* Required when `--destination` is `before` or `after`. ### Examples #### Move Rule to Top ```bash -$ scm set security decryption-rule --move \ +$ scm move security decryption-rule no-decrypt-internal \ --folder Texas \ - --name no-decrypt-internal \ - --location top + --destination top ---> 100% Moved decryption rule: no-decrypt-internal to top in folder Texas ``` @@ -114,13 +122,12 @@ Moved decryption rule: no-decrypt-internal to top in folder Texas #### Move Rule After Another Rule ```bash -$ scm set security decryption-rule --move \ +$ scm move security decryption-rule decrypt-outbound \ --folder Texas \ - --name decrypt-outbound \ - --location after \ - --reference no-decrypt-internal + --destination after \ + --destination-rule 123e4567-e89b-12d3-a456-426614174000 ---> 100% -Moved decryption rule: decrypt-outbound after no-decrypt-internal in folder Texas +Moved decryption rule: decrypt-outbound after rule 123e4567-e89b-12d3-a456-426614174000 in folder Texas ``` ## Delete Decryption Rule @@ -130,9 +137,15 @@ Delete a decryption rule from SCM. ### Syntax ```bash -scm delete security decryption-rule [OPTIONS] +scm delete security decryption-rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name to delete | Yes | + ### Options | Option | Description | Required | @@ -140,7 +153,7 @@ scm delete security decryption-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name to delete | Yes | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -148,9 +161,8 @@ scm delete security decryption-rule [OPTIONS] ### Example ```bash -$ scm delete security decryption-rule \ +$ scm delete security decryption-rule no-decrypt-internal \ --folder Texas \ - --name no-decrypt-internal \ --force ---> 100% Deleted decryption rule: no-decrypt-internal from folder Texas @@ -243,9 +255,15 @@ Display decryption rule objects. ### Syntax ```bash -scm show security decryption-rule [OPTIONS] +scm show security decryption-rule [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Rule name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -253,12 +271,14 @@ scm show security decryption-rule [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Rule name to display | No | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -266,9 +286,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Rule ```bash -$ scm show security decryption-rule \ - --folder Texas \ - --name decrypt-outbound +$ scm show security decryption-rule decrypt-outbound \ + --folder Texas ---> 100% Decryption Rule: decrypt-outbound Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/dns-security-profile.md b/docs-site/docs/cli/security/dns-security-profile.md index a0f81f1..40c32ab 100644 --- a/docs-site/docs/cli/security/dns-security-profile.md +++ b/docs-site/docs/cli/security/dns-security-profile.md @@ -19,9 +19,15 @@ Create or update a DNS security profile. ### Syntax ```bash -scm set security dns-security-profile [OPTIONS] +scm set security dns-security-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | @@ -29,7 +35,6 @@ scm set security dns-security-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name | Yes | | `--description TEXT` | Profile description | No | | `--botnet-domains TEXT` | Botnet domains settings as JSON | No | @@ -40,9 +45,8 @@ scm set security dns-security-profile [OPTIONS] #### Create Profile with Sinkhole ```bash -$ scm set security dns-security-profile \ +$ scm set security dns-security-profile dns-sec-default \ --folder Texas \ - --name dns-sec-default \ --botnet-domains '{"dns_security_categories": [{"name": "pan-dns-sec-malware", "action": "sinkhole"}]}' ---> 100% Created DNS security profile: dns-sec-default in folder Texas @@ -51,9 +55,8 @@ Created DNS security profile: dns-sec-default in folder Texas #### Create Profile with Whitelist ```bash -$ scm set security dns-security-profile \ +$ scm set security dns-security-profile dns-sec-custom \ --folder Texas \ - --name dns-sec-custom \ --botnet-domains '{"whitelist": [{"name": "example.com"}]}' ---> 100% Created DNS security profile: dns-sec-custom in folder Texas @@ -66,9 +69,15 @@ Delete a DNS security profile from SCM. ### Syntax ```bash -scm delete security dns-security-profile [OPTIONS] +scm delete security dns-security-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to delete | Yes | + ### Options | Option | Description | Required | @@ -76,7 +85,6 @@ scm delete security dns-security-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -84,9 +92,8 @@ scm delete security dns-security-profile [OPTIONS] ### Example ```bash -$ scm delete security dns-security-profile \ +$ scm delete security dns-security-profile dns-sec-default \ --folder Texas \ - --name dns-sec-default \ --force ---> 100% Deleted DNS security profile: dns-sec-default from folder Texas @@ -173,9 +180,15 @@ Display DNS security profile objects. ### Syntax ```bash -scm show security dns-security-profile [OPTIONS] +scm show security dns-security-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -183,12 +196,13 @@ scm show security dns-security-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -196,9 +210,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Profile ```bash -$ scm show security dns-security-profile \ - --folder Texas \ - --name dns-sec-default +$ scm show security dns-security-profile dns-sec-default \ + --folder Texas ---> 100% DNS Security Profile: dns-sec-default Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/rules.md b/docs-site/docs/cli/security/rules.md index 6aa4fe8..35005f4 100644 --- a/docs-site/docs/cli/security/rules.md +++ b/docs-site/docs/cli/security/rules.md @@ -7,7 +7,7 @@ Security rules define policies that control traffic flow between zones. The `scm The `rule` commands allow you to: - Create security rules with source/destination zones, addresses, and applications -- Update existing rule configurations and security profiles +- Update existing rule configurations - Delete rules that are no longer needed - Move rules to control processing order - Bulk import rules from YAML files @@ -23,7 +23,7 @@ Security rules consist of several components: | Applications | Applications to match (e.g., web-browsing, ssl) | | Services | Services to match (e.g., application-default) | | Action | What happens to matching traffic (allow, deny, drop) | -| Profiles | Security profiles applied to allowed traffic | +| Rulebase | Where the rule lives (pre, post, or default) | ## Set Security Rule @@ -32,74 +32,71 @@ Create or update a security rule. ### Syntax ```bash -scm set security rule [OPTIONS] +scm set security rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security rule | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder for the security rule | Yes | -| `--name TEXT` | Name of the security rule | Yes | -| `--description TEXT` | Description for the rule | No | -| `--source-zones LIST` | Source security zones | Yes | -| `--destination-zones LIST` | Destination security zones | Yes | -| `--source-addresses LIST` | Source address or address groups | Yes | -| `--destination-addresses LIST` | Destination address or address groups | Yes | -| `--applications LIST` | Applications to match | Yes | -| `--services LIST` | Services to match | Yes | -| `--action TEXT` | Action to take (allow, deny, drop) | Yes | -| `--log-start BOOLEAN` | Log at session start | No | -| `--log-end BOOLEAN` | Log at session end | No | -| `--disabled BOOLEAN` | Whether rule is disabled | No | -| `--tags LIST` | List of tags to apply to the rule | No | -| `--profile-group TEXT` | Security profile group to apply | No | -| `--anti-virus TEXT` | Anti-virus profile to apply | No | -| `--anti-spyware TEXT` | Anti-spyware profile to apply | No | -| `--vulnerability TEXT` | Vulnerability protection profile to apply | No | -| `--url-filtering TEXT` | URL filtering profile to apply | No | -| `--file-blocking TEXT` | File blocking profile to apply | No | -| `--data-filtering TEXT` | Data filtering profile to apply | No | -| `--wildfire-analysis TEXT` | WildFire analysis profile to apply | No | +| `--folder TEXT` | Folder path for the security rule | One of folder/snippet/device | +| `--snippet TEXT` | Snippet path for the security rule | One of folder/snippet/device | +| `--device TEXT` | Device path for the security rule | One of folder/snippet/device | +| `--source-zones TEXT` | Source security zones (repeat for multiple) | Yes | +| `--destination-zones TEXT` | Destination security zones (repeat for multiple) | Yes | +| `--source-addresses TEXT` | Source addresses or address groups (repeat for multiple) | No | +| `--destination-addresses TEXT` | Destination addresses or address groups (repeat for multiple) | No | +| `--applications TEXT` | Applications to match (repeat for multiple) | No | +| `--services TEXT` | Services to match (repeat for multiple) | No | +| `--action TEXT` | Action to take (allow, deny, drop; default: allow) | No | +| `--description TEXT` | Description of the security rule | No | +| `--tags TEXT` | Tags (repeat for multiple) | No | +| `--enabled / --disabled` | Enable or disable the rule (default: enabled) | No | +| `--log-start` | Log at session start | No | +| `--log-end` | Log at session end | No | +| `--log-setting TEXT` | Log forwarding profile | No | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | ### Examples #### Create an Allow Rule ```bash -$ scm set security rule \ +$ scm set security rule Allow-Internal-Web \ --folder Shared \ - --name "Allow-Internal-Web" \ --source-zones Trust \ --destination-zones DMZ \ - --source-addresses "any" \ - --destination-addresses "web-servers" \ + --source-addresses any \ + --destination-addresses web-servers \ --applications web-browsing \ --services application-default \ --action allow \ - --log-end true + --log-end ---> 100% Created security rule: Allow-Internal-Web in folder Shared ``` -#### Create a Block Rule with Security Profiles +#### Create a Block Rule in the Post Rulebase ```bash -$ scm set security rule \ +$ scm set security rule Block-Malicious-Web \ --folder Shared \ - --name "Block-Malicious-Web" \ --source-zones Untrust \ --destination-zones DMZ \ - --source-addresses "any" \ - --destination-addresses "any" \ + --source-addresses any \ + --destination-addresses any \ --applications any \ --services application-default \ --action deny \ - --log-start true \ - --log-end true \ - --anti-virus "default-av" \ - --anti-spyware "default-as" \ - --url-filtering "strict-url-filtering" + --log-start \ + --log-end \ + --rulebase post ---> 100% Created security rule: Block-Malicious-Web in folder Shared ``` @@ -111,29 +108,36 @@ Change the position of a security rule. Security rules are processed in order fr ### Syntax ```bash -scm set security rule --move [OPTIONS] +scm move security rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security rule to move | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the security rules | Yes | -| `--name TEXT` | Name of the security rule to move | Yes | -| `--location TEXT` | Where to move the rule (top, bottom, before, after) | Yes | -| `--reference TEXT` | Reference rule name (required with before/after) | No\*\* | +| `--folder TEXT` | Folder containing the rule | One of folder/snippet/device | +| `--snippet TEXT` | Snippet containing the rule | One of folder/snippet/device | +| `--device TEXT` | Device containing the rule | One of folder/snippet/device | +| `--destination TEXT` | Where to move (top, bottom, before, after) | Yes | +| `--destination-rule TEXT` | UUID of the reference rule (required with before/after) | No\*\* | +| `--rulebase TEXT` | Rulebase (pre or post; default: pre) | No | -\*\* Required when `--location` is `before` or `after`. +\*\* Required when `--destination` is `before` or `after`. ### Examples #### Move Rule to Top ```bash -$ scm set security rule --move \ +$ scm move security rule Block-Malicious-Web \ --folder Shared \ - --name "Block-Malicious-Web" \ - --location top + --destination top ---> 100% Moved security rule: Block-Malicious-Web to top in folder Shared ``` @@ -141,13 +145,12 @@ Moved security rule: Block-Malicious-Web to top in folder Shared #### Move Rule After Another Rule ```bash -$ scm set security rule --move \ +$ scm move security rule Allow-Internal-Web \ --folder Shared \ - --name "Allow-Internal-Web" \ - --location after \ - --reference "Allow-Internal-DNS" + --destination after \ + --destination-rule 123e4567-e89b-12d3-a456-426614174000 ---> 100% -Moved security rule: Allow-Internal-Web after Allow-Internal-DNS in folder Shared +Moved security rule: Allow-Internal-Web after rule 123e4567-e89b-12d3-a456-426614174000 in folder Shared ``` ## Delete Security Rule @@ -157,23 +160,30 @@ Delete a security rule from SCM. ### Syntax ```bash -scm delete security rule [OPTIONS] +scm delete security rule NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security rule to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder containing the security rule | Yes | -| `--name TEXT` | Name of the security rule to delete | Yes | +| `--folder TEXT` | Folder containing the security rule | One of folder/snippet/device | +| `--snippet TEXT` | Snippet containing the security rule | One of folder/snippet/device | +| `--device TEXT` | Device containing the security rule | One of folder/snippet/device | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete security rule \ +$ scm delete security rule Allow-Internal-Web \ --folder Shared \ - --name "Allow-Internal-Web" \ --force ---> 100% Deleted security rule: Allow-Internal-Web from folder Shared @@ -193,7 +203,7 @@ scm load security rule [OPTIONS] | Option | Description | Required | | --- | --- | --- | -| `--file TEXT` | Path to YAML file containing security rule definitions | Yes | +| `--file PATH` | Path to YAML file containing security rule definitions | Yes | | `--folder TEXT` | Override folder location for all rules | No | | `--snippet TEXT` | Override snippet location for all rules | No | | `--device TEXT` | Override device location for all rules | No | @@ -243,9 +253,6 @@ security_rules: action: deny log_start: true log_end: true - anti_virus: default-av - anti_spyware: default-as - url_filtering: strict-url-filtering tags: - security - blocking @@ -290,18 +297,28 @@ Display security rule objects. ### Syntax ```bash -scm show security rule [OPTIONS] +scm show security rule [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the security rule to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder location | Yes | -| `--name TEXT` | Rule name to display | No | +| `--folder TEXT` | Folder containing the security rule | One of folder/snippet/device | +| `--snippet TEXT` | Snippet containing the security rule | One of folder/snippet/device | +| `--device TEXT` | Device containing the security rule | One of folder/snippet/device | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -309,9 +326,7 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Rule ```bash -$ scm show security rule \ - --folder Shared \ - --name "Allow-Internal-Web" +$ scm show security rule Allow-Internal-Web --folder Shared ---> 100% Security Rule: Allow-Internal-Web Location: Folder 'Shared' @@ -358,7 +373,10 @@ scm backup security rule [OPTIONS] | Option | Description | Required | | --- | --- | --- | -| `--folder TEXT` | Folder to backup rules from | Yes | +| `--folder TEXT` | Folder to backup rules from | One of folder/snippet/device | +| `--snippet TEXT` | Snippet to backup rules from | One of folder/snippet/device | +| `--device TEXT` | Device to backup rules from | One of folder/snippet/device | +| `--rulebase TEXT` | Rulebase to use (pre, post, or default; default: pre) | No | | `--file TEXT` | Output filename (defaults to auto-generated) | No | ### Examples @@ -384,8 +402,8 @@ Successfully backed up 15 security rules to shared-security-rules.yaml ## Best Practices 1. **Order Rules Carefully**: Place more specific rules above general rules since rules are processed top to bottom; use the Move command to control order. -2. **Apply Security Profiles**: Attach anti-virus, anti-spyware, vulnerability protection, and URL filtering profiles to allow rules for defense in depth. -3. **Enable Logging**: Use `--log-end true` on all rules for visibility; add `--log-start true` for deny rules to capture blocked traffic. +2. **Mind the Rulebase**: Use the `pre` rulebase for rules evaluated before local rules and `post` for cleanup rules evaluated after. +3. **Enable Logging**: Use `--log-end` on all rules for visibility; add `--log-start` for deny rules to capture blocked traffic. 4. **Use Descriptive Names**: Name rules to clearly indicate their purpose (e.g., `Allow-Internal-Web`, `Block-Malicious-Traffic`). 5. **Tag Rules for Organization**: Apply tags to group related rules by function, department, or compliance requirement. 6. **Backup Before Changes**: Always backup existing rules before making bulk modifications via load commands. diff --git a/docs-site/docs/cli/security/url-access-profile.md b/docs-site/docs/cli/security/url-access-profile.md index 7315237..776502f 100644 --- a/docs-site/docs/cli/security/url-access-profile.md +++ b/docs-site/docs/cli/security/url-access-profile.md @@ -19,9 +19,15 @@ Create or update a URL access profile. ### Syntax ```bash -scm set security url-access-profile [OPTIONS] +scm set security url-access-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | @@ -29,7 +35,6 @@ scm set security url-access-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name | Yes | | `--description TEXT` | Description | No | | `--block TEXT` | URL categories to block (can specify multiple) | No | | `--alert TEXT` | URL categories to alert (can specify multiple) | No | @@ -45,9 +50,8 @@ scm set security url-access-profile [OPTIONS] #### Create Profile with Blocked Categories ```bash -$ scm set security url-access-profile \ +$ scm set security url-access-profile strict-url \ --folder Texas \ - --name strict-url \ --block adult \ --block malware \ --alert hacking @@ -58,9 +62,8 @@ Created URL access profile: strict-url in folder Texas #### Create Profile with Safe Search ```bash -$ scm set security url-access-profile \ +$ scm set security url-access-profile safe-browsing \ --folder Texas \ - --name safe-browsing \ --block adult \ --block gambling \ --safe-search \ @@ -76,9 +79,15 @@ Delete a URL access profile from SCM. ### Syntax ```bash -scm delete security url-access-profile [OPTIONS] +scm delete security url-access-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to delete | Yes | + ### Options | Option | Description | Required | @@ -86,7 +95,6 @@ scm delete security url-access-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -94,9 +102,8 @@ scm delete security url-access-profile [OPTIONS] ### Example ```bash -$ scm delete security url-access-profile \ +$ scm delete security url-access-profile strict-url \ --folder Texas \ - --name strict-url \ --force ---> 100% Deleted URL access profile: strict-url from folder Texas @@ -186,9 +193,15 @@ Display URL access profile objects. ### Syntax ```bash -scm show security url-access-profile [OPTIONS] +scm show security url-access-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -196,12 +209,13 @@ scm show security url-access-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -209,9 +223,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Profile ```bash -$ scm show security url-access-profile \ - --folder Texas \ - --name strict-url +$ scm show security url-access-profile strict-url \ + --folder Texas ---> 100% URL Access Profile: strict-url Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/url-category.md b/docs-site/docs/cli/security/url-category.md index 71ed3b2..84495f7 100644 --- a/docs-site/docs/cli/security/url-category.md +++ b/docs-site/docs/cli/security/url-category.md @@ -26,9 +26,15 @@ Create or update a custom URL category. ### Syntax ```bash -scm set security url-category [OPTIONS] +scm set security url-category NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Category name | Yes | + ### Options | Option | Description | Required | @@ -36,7 +42,6 @@ scm set security url-category [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Category name | Yes | | `--description TEXT` | Description | No | | `--type TEXT` | Type: "URL List" or "Category Match" (default: URL List) | No | | `--url TEXT` | URL entries (can specify multiple) | No | @@ -48,9 +53,8 @@ scm set security url-category [OPTIONS] #### Create URL List Category ```bash -$ scm set security url-category \ +$ scm set security url-category custom-block \ --folder Texas \ - --name custom-block \ --url malware.example.com \ --url phishing.test.org ---> 100% @@ -60,9 +64,8 @@ Created URL category: custom-block in folder Texas #### Create Category Match Type ```bash -$ scm set security url-category \ +$ scm set security url-category match-category \ --folder Texas \ - --name match-category \ --type "Category Match" \ --url gambling \ --url adult @@ -77,9 +80,15 @@ Delete a custom URL category from SCM. ### Syntax ```bash -scm delete security url-category [OPTIONS] +scm delete security url-category NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Category name to delete | Yes | + ### Options | Option | Description | Required | @@ -87,7 +96,6 @@ scm delete security url-category [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Category name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -95,9 +103,8 @@ scm delete security url-category [OPTIONS] ### Example ```bash -$ scm delete security url-category \ +$ scm delete security url-category custom-block \ --folder Texas \ - --name custom-block \ --force ---> 100% Deleted URL category: custom-block from folder Texas @@ -185,9 +192,15 @@ Display custom URL category objects. ### Syntax ```bash -scm show security url-category [OPTIONS] +scm show security url-category [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Category name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -195,12 +208,13 @@ scm show security url-category [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Category name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -208,9 +222,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Category ```bash -$ scm show security url-category \ - --folder Texas \ - --name custom-block +$ scm show security url-category custom-block \ + --folder Texas ---> 100% URL Category: custom-block Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/vulnerability-protection-profile.md b/docs-site/docs/cli/security/vulnerability-protection-profile.md index a36e26f..1ab621f 100644 --- a/docs-site/docs/cli/security/vulnerability-protection-profile.md +++ b/docs-site/docs/cli/security/vulnerability-protection-profile.md @@ -19,9 +19,15 @@ Create or update a vulnerability protection profile. ### Syntax ```bash -scm set security vulnerability-protection-profile [OPTIONS] +scm set security vulnerability-protection-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | @@ -29,7 +35,6 @@ scm set security vulnerability-protection-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name | Yes | | `--description TEXT` | Profile description | No | | `--block-critical-high` | Add default rule to block critical and high severity vulnerabilities | No | @@ -40,9 +45,8 @@ scm set security vulnerability-protection-profile [OPTIONS] #### Create Basic Profile ```bash -$ scm set security vulnerability-protection-profile \ +$ scm set security vulnerability-protection-profile strict-vuln \ --folder Texas \ - --name strict-vuln \ --description "Block critical vulnerabilities" ---> 100% Created vulnerability protection profile: strict-vuln in folder Texas @@ -51,9 +55,8 @@ Created vulnerability protection profile: strict-vuln in folder Texas #### Create Profile with Block Critical/High Rule ```bash -$ scm set security vulnerability-protection-profile \ +$ scm set security vulnerability-protection-profile vuln-protection \ --folder Texas \ - --name vuln-protection \ --block-critical-high ---> 100% Created vulnerability protection profile: vuln-protection in folder Texas @@ -66,9 +69,15 @@ Delete a vulnerability protection profile from SCM. ### Syntax ```bash -scm delete security vulnerability-protection-profile [OPTIONS] +scm delete security vulnerability-protection-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to delete | Yes | + ### Options | Option | Description | Required | @@ -76,7 +85,6 @@ scm delete security vulnerability-protection-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -84,9 +92,8 @@ scm delete security vulnerability-protection-profile [OPTIONS] ### Example ```bash -$ scm delete security vulnerability-protection-profile \ +$ scm delete security vulnerability-protection-profile strict-vuln \ --folder Texas \ - --name strict-vuln \ --force ---> 100% Deleted vulnerability protection profile: strict-vuln from folder Texas @@ -166,9 +173,15 @@ Display vulnerability protection profile objects. ### Syntax ```bash -scm show security vulnerability-protection-profile [OPTIONS] +scm show security vulnerability-protection-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -176,12 +189,13 @@ scm show security vulnerability-protection-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -189,9 +203,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Profile ```bash -$ scm show security vulnerability-protection-profile \ - --folder Texas \ - --name strict-vuln +$ scm show security vulnerability-protection-profile strict-vuln \ + --folder Texas ---> 100% Vulnerability Protection Profile: strict-vuln Location: Folder 'Texas' diff --git a/docs-site/docs/cli/security/wildfire-antivirus-profile.md b/docs-site/docs/cli/security/wildfire-antivirus-profile.md index 91e3568..afe2353 100644 --- a/docs-site/docs/cli/security/wildfire-antivirus-profile.md +++ b/docs-site/docs/cli/security/wildfire-antivirus-profile.md @@ -19,9 +19,15 @@ Create or update a WildFire antivirus profile. ### Syntax ```bash -scm set security wildfire-antivirus-profile [OPTIONS] +scm set security wildfire-antivirus-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name | Yes | + ### Options | Option | Description | Required | @@ -29,7 +35,6 @@ scm set security wildfire-antivirus-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name | Yes | | `--description TEXT` | Profile description | No | | `--rules TEXT` | Rules configuration as JSON | No | | `--packet-capture / --no-packet-capture` | Enable packet capture | No | @@ -41,9 +46,8 @@ scm set security wildfire-antivirus-profile [OPTIONS] #### Create Basic Profile ```bash -$ scm set security wildfire-antivirus-profile \ +$ scm set security wildfire-antivirus-profile wf-basic \ --folder Texas \ - --name wf-basic \ --description "Basic WildFire profile" ---> 100% Created WildFire antivirus profile: wf-basic in folder Texas @@ -52,9 +56,8 @@ Created WildFire antivirus profile: wf-basic in folder Texas #### Create Profile with Custom Rules ```bash -$ scm set security wildfire-antivirus-profile \ +$ scm set security wildfire-antivirus-profile wf-custom \ --folder Texas \ - --name wf-custom \ --rules '[{"name":"Forward All","direction":"both","analysis":"public-cloud","application":["any"],"file_type":["any"]}]' ---> 100% Created WildFire antivirus profile: wf-custom in folder Texas @@ -63,9 +66,8 @@ Created WildFire antivirus profile: wf-custom in folder Texas #### Create Profile with Packet Capture ```bash -$ scm set security wildfire-antivirus-profile \ +$ scm set security wildfire-antivirus-profile wf-capture \ --folder Texas \ - --name wf-capture \ --packet-capture ---> 100% Created WildFire antivirus profile: wf-capture in folder Texas @@ -78,9 +80,15 @@ Delete a WildFire antivirus profile from SCM. ### Syntax ```bash -scm delete security wildfire-antivirus-profile [OPTIONS] +scm delete security wildfire-antivirus-profile NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to delete | Yes | + ### Options | Option | Description | Required | @@ -88,7 +96,6 @@ scm delete security wildfire-antivirus-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to delete | Yes | | `--force` | Skip confirmation prompt | No | \* One of --folder, --snippet, or --device is required. @@ -96,9 +103,8 @@ scm delete security wildfire-antivirus-profile [OPTIONS] ### Example ```bash -$ scm delete security wildfire-antivirus-profile \ +$ scm delete security wildfire-antivirus-profile wf-basic \ --folder Texas \ - --name wf-basic \ --force ---> 100% Deleted WildFire antivirus profile: wf-basic from folder Texas @@ -186,9 +192,15 @@ Display WildFire antivirus profile objects. ### Syntax ```bash -scm show security wildfire-antivirus-profile [OPTIONS] +scm show security wildfire-antivirus-profile [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Profile name to display; omit to list all | No | + ### Options | Option | Description | Required | @@ -196,12 +208,13 @@ scm show security wildfire-antivirus-profile [OPTIONS] | `--folder TEXT` | Folder location | No\* | | `--snippet TEXT` | Snippet location | No\* | | `--device TEXT` | Device location | No\* | -| `--name TEXT` | Profile name to display | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required. :::note -When no `--name` is specified, all items are listed by default. +When no `NAME` is specified, all items are listed by default. ::: ### Examples @@ -209,9 +222,8 @@ When no `--name` is specified, all items are listed by default. #### Show Specific Profile ```bash -$ scm show security wildfire-antivirus-profile \ - --folder Texas \ - --name wf-basic +$ scm show security wildfire-antivirus-profile wf-basic \ + --folder Texas ---> 100% WildFire Antivirus Profile: wf-basic Location: Folder 'Texas' diff --git a/docs-site/docs/cli/setup/device.md b/docs-site/docs/cli/setup/device.md index 5daaa6e..c9602af 100644 --- a/docs-site/docs/cli/setup/device.md +++ b/docs-site/docs/cli/setup/device.md @@ -20,7 +20,7 @@ The `device` commands allow you to: | Field | Flag | Description | | --- | --- | --- | -| `name` | `--name` | Lookup key (name or serial number). Required. | +| `name` | `NAME` argument | Lookup key (name or serial number). Required. | | `display_name` | `--display-name` | Human-friendly display name. | | `folder` | `--folder` | Folder to move the device into. | | `description` | `--description` | Free-text description. | @@ -36,14 +36,19 @@ Update an existing device. ### Syntax ```bash -scm set setup device [OPTIONS] +scm set setup device NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Device name or serial number | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Device name or serial number | Yes | | `--display-name TEXT` | Human-friendly display name | No | | `--folder TEXT` | Folder to move device into | No | | `--description TEXT` | Free-text description | No | @@ -55,7 +60,7 @@ scm set setup device [OPTIONS] #### Attach Labels to a Device ```bash -$ scm set setup device --name PA-VM-01 --labels production --labels west +$ scm set setup device PA-VM-01 --labels production --labels west ---> 100% Updated device: PA-VM-01 ``` @@ -63,7 +68,7 @@ Updated device: PA-VM-01 #### Move Device into a Folder ```bash -$ scm set setup device --name 0123456789 --folder Austin +$ scm set setup device 0123456789 --folder Austin ---> 100% Updated device: 0123456789 ``` @@ -71,8 +76,7 @@ Updated device: 0123456789 #### Set Multiple Fields at Once ```bash -$ scm set setup device \ - --name PA-VM-01 \ +$ scm set setup device PA-VM-01 \ --display-name "Edge-FW" \ --description "Edge firewall" \ --labels production @@ -89,15 +93,22 @@ Display device objects. ### Syntax ```bash -scm show setup device [OPTIONS] +scm show setup device [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name or serial number of the device to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name or serial number of the device to show | No | | `--folder TEXT` | Filter devices by folder | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note When no options are specified, all devices are listed by default. @@ -131,7 +142,7 @@ Name: PA-VM-02 #### Show a Single Device ```bash -$ scm show setup device --name PA-VM-01 +$ scm show setup device PA-VM-01 ---> 100% Device: PA-VM-01 ================================================================================ @@ -153,7 +164,7 @@ ID: device-PA-VM-01 #### Show Device by Serial Number ```bash -$ scm show setup device --name 0123456789 +$ scm show setup device 0123456789 ---> 100% Device: 0123456789 ================================================================================ diff --git a/docs-site/docs/cli/setup/folder.md b/docs-site/docs/cli/setup/folder.md index 143e1de..2ee5505 100644 --- a/docs-site/docs/cli/setup/folder.md +++ b/docs-site/docs/cli/setup/folder.md @@ -19,14 +19,19 @@ Create or update a folder. ### Syntax ```bash -scm set setup folder [OPTIONS] +scm set setup folder NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Folder name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Folder name | Yes | | `--parent TEXT` | Parent folder name | Yes | | `--description TEXT` | Description | No | | `--labels TEXT` | Labels to apply | No | @@ -37,8 +42,7 @@ scm set setup folder [OPTIONS] #### Create a Top-Level Folder ```bash -$ scm set setup folder \ - --name Texas \ +$ scm set setup folder Texas \ --parent "All" ---> 100% Created folder: Texas (parent: All) @@ -47,8 +51,7 @@ Created folder: Texas (parent: All) #### Create a Child Folder with Description ```bash -$ scm set setup folder \ - --name Branch \ +$ scm set setup folder Branch \ --parent Texas \ --description "Branch offices" ---> 100% @@ -58,8 +61,7 @@ Created folder: Branch (parent: Texas) #### Create a Folder with Labels ```bash -$ scm set setup folder \ - --name Austin \ +$ scm set setup folder Austin \ --parent Texas \ --labels production \ --labels west-region @@ -74,20 +76,25 @@ Delete a folder from SCM. ### Syntax ```bash -scm delete setup folder [OPTIONS] +scm delete setup folder NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the folder to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the folder to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete setup folder --name Branch --force +$ scm delete setup folder Branch --force ---> 100% Deleted folder: Branch ``` @@ -159,17 +166,24 @@ Display folder objects. ### Syntax ```bash -scm show setup folder [OPTIONS] +scm show setup folder [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the folder to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the folder to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all folders are listed by default. +When no `NAME` is specified, all folders are listed by default. ::: ### Examples @@ -177,7 +191,7 @@ When no `--name` is specified, all folders are listed by default. #### Show Specific Folder ```bash -$ scm show setup folder --name Texas +$ scm show setup folder Texas ---> 100% Folder: Texas ================================================================================ diff --git a/docs-site/docs/cli/setup/label.md b/docs-site/docs/cli/setup/label.md index c06af96..efc9029 100644 --- a/docs-site/docs/cli/setup/label.md +++ b/docs-site/docs/cli/setup/label.md @@ -19,14 +19,19 @@ Create or update a label. ### Syntax ```bash -scm set setup label [OPTIONS] +scm set setup label NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Label name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Label name | Yes | | `--description TEXT` | Description | No | ### Examples @@ -34,7 +39,7 @@ scm set setup label [OPTIONS] #### Create a Simple Label ```bash -$ scm set setup label --name production +$ scm set setup label production ---> 100% Created label: production ``` @@ -42,8 +47,7 @@ Created label: production #### Create a Label with Description ```bash -$ scm set setup label \ - --name staging \ +$ scm set setup label staging \ --description "Staging environment" ---> 100% Created label: staging @@ -56,20 +60,25 @@ Delete a label from SCM. ### Syntax ```bash -scm delete setup label [OPTIONS] +scm delete setup label NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the label to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the label to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete setup label --name staging --force +$ scm delete setup label staging --force ---> 100% Deleted label: staging ``` @@ -135,17 +144,24 @@ Display label objects. ### Syntax ```bash -scm show setup label [OPTIONS] +scm show setup label [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the label to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the label to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all labels are listed by default. +When no `NAME` is specified, all labels are listed by default. ::: ### Examples @@ -153,7 +169,7 @@ When no `--name` is specified, all labels are listed by default. #### Show Specific Label ```bash -$ scm show setup label --name production +$ scm show setup label production ---> 100% Label: production ================================================================================ @@ -225,13 +241,13 @@ Labels can be applied to folders, snippets, and devices. Create the labels first ```bash # Folder -scm set setup folder --name Austin --parent Texas --labels production +scm set setup folder Austin --parent Texas --labels production # Snippet -scm set setup snippet --name Web-Security --labels production +scm set setup snippet Web-Security --labels production # Device (must already exist) -scm set setup device --name PA-VM-01 --labels production --labels west +scm set setup device PA-VM-01 --labels production --labels west ``` See [folders](folder.md), [snippets](snippet.md), and [devices](device.md) for full details on each command. diff --git a/docs-site/docs/cli/setup/snippet.md b/docs-site/docs/cli/setup/snippet.md index f58c82a..311bb26 100644 --- a/docs-site/docs/cli/setup/snippet.md +++ b/docs-site/docs/cli/setup/snippet.md @@ -19,14 +19,19 @@ Create or update a snippet. ### Syntax ```bash -scm set setup snippet [OPTIONS] +scm set setup snippet NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Snippet name | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Snippet name | Yes | | `--description TEXT` | Description | No | | `--labels TEXT` | Labels to apply | No | | `--enable-prefix` | Enable prefix for the snippet | No | @@ -36,7 +41,7 @@ scm set setup snippet [OPTIONS] #### Create a Simple Snippet ```bash -$ scm set setup snippet --name "DNS-Best-Practice" +$ scm set setup snippet "DNS-Best-Practice" ---> 100% Created snippet: DNS-Best-Practice ``` @@ -44,8 +49,7 @@ Created snippet: DNS-Best-Practice #### Create a Snippet with Description and Labels ```bash -$ scm set setup snippet \ - --name "Web-Security" \ +$ scm set setup snippet "Web-Security" \ --description "Web security config" \ --labels prod ---> 100% @@ -55,8 +59,7 @@ Created snippet: Web-Security #### Create a Snippet with Prefix Enabled ```bash -$ scm set setup snippet \ - --name "Branch-Template" \ +$ scm set setup snippet "Branch-Template" \ --description "Standard branch config" \ --enable-prefix ---> 100% @@ -70,20 +73,25 @@ Delete a snippet from SCM. ### Syntax ```bash -scm delete setup snippet [OPTIONS] +scm delete setup snippet NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the snippet to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the snippet to delete | Yes | | `--force` | Skip confirmation prompt | No | ### Example ```bash -$ scm delete setup snippet --name "DNS-Best-Practice" --force +$ scm delete setup snippet "DNS-Best-Practice" --force ---> 100% Deleted snippet: DNS-Best-Practice ``` @@ -155,17 +163,24 @@ Display snippet objects. ### Syntax ```bash -scm show setup snippet [OPTIONS] +scm show setup snippet [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the snippet to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the snippet to show | No | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | :::note -When no `--name` is specified, all snippets are listed by default. +When no `NAME` is specified, all snippets are listed by default. ::: ### Examples @@ -173,7 +188,7 @@ When no `--name` is specified, all snippets are listed by default. #### Show Specific Snippet ```bash -$ scm show setup snippet --name "DNS-Best-Practice" +$ scm show setup snippet "DNS-Best-Practice" ---> 100% Snippet: DNS-Best-Practice ================================================================================ diff --git a/docs-site/docs/cli/setup/variable.md b/docs-site/docs/cli/setup/variable.md index 4a54a7e..1c27009 100644 --- a/docs-site/docs/cli/setup/variable.md +++ b/docs-site/docs/cli/setup/variable.md @@ -33,14 +33,19 @@ Create or update a variable. ### Syntax ```bash -scm set setup variable [OPTIONS] +scm set setup variable NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Variable name (must start with $) | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Variable name (must start with $) | Yes | | `--type TEXT` | Variable type | Yes | | `--value TEXT` | Variable value | Yes | | `--folder TEXT` | Folder scope | No\* | @@ -55,8 +60,7 @@ scm set setup variable [OPTIONS] #### Create an Egress Max Variable ```bash -$ scm set setup variable \ - --name "\$egress-max" \ +$ scm set setup variable "\$egress-max" \ --type egress-max \ --value 1000 \ --folder Texas @@ -67,8 +71,7 @@ Created variable: $egress-max in folder Texas #### Create an FQDN Variable in a Snippet ```bash -$ scm set setup variable \ - --name "\$dns-server" \ +$ scm set setup variable "\$dns-server" \ --type fqdn \ --value dns.example.com \ --snippet "DNS-Config" @@ -79,8 +82,7 @@ Created variable: $dns-server in snippet DNS-Config #### Create a Variable with Description ```bash -$ scm set setup variable \ - --name "\$web-port" \ +$ scm set setup variable "\$web-port" \ --type port \ --value 8080 \ --folder Texas \ @@ -96,14 +98,19 @@ Delete a variable from SCM. ### Syntax ```bash -scm delete setup variable [OPTIONS] +scm delete setup variable NAME [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the variable to delete | Yes | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the variable to delete | Yes | | `--folder TEXT` | Folder scope | No\* | | `--snippet TEXT` | Snippet scope | No\* | | `--device TEXT` | Device scope | No\* | @@ -114,7 +121,7 @@ scm delete setup variable [OPTIONS] ### Example ```bash -$ scm delete setup variable --name "\$egress-max" --folder Texas --force +$ scm delete setup variable "\$egress-max" --folder Texas --force ---> 100% Deleted variable: $egress-max from folder Texas ``` @@ -190,22 +197,29 @@ Display variable objects. ### Syntax ```bash -scm show setup variable [OPTIONS] +scm show setup variable [NAME] [OPTIONS] ``` +### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| `NAME` | Name of the variable to show; omit to list all | No | + ### Options | Option | Description | Required | | --- | --- | --- | -| `--name TEXT` | Name of the variable to show | No | | `--folder TEXT` | Folder scope | No\* | | `--snippet TEXT` | Snippet scope | No\* | | `--device TEXT` | Device scope | No\* | +| `--output, -o [table\|json\|yaml]` | Output format (default: table) | No | +| `--max-results INTEGER` | Maximum number of results to display | No | \* One of --folder, --snippet, or --device is required when listing variables. :::note -When no `--name` is specified, all variables in the specified container are listed +When no `NAME` is specified, all variables in the specified container are listed by default. ::: @@ -214,7 +228,7 @@ by default. #### Show Specific Variable ```bash -$ scm show setup variable --folder Texas --name "\$egress-max" +$ scm show setup variable "\$egress-max" --folder Texas ---> 100% Variable: $egress-max ================================================================================ diff --git a/docs-site/docs/guide/advanced-topics.md b/docs-site/docs/guide/advanced-topics.md index 3ae517d..d6eedaa 100644 --- a/docs-site/docs/guide/advanced-topics.md +++ b/docs-site/docs/guide/advanced-topics.md @@ -1,6 +1,6 @@ # Advanced Topics -The `scm` CLI supports advanced workflows including multi-folder operations, bulk loading, mock mode testing, verbose output, environment variable configuration, and script integration. This guide covers techniques for power users and automation scenarios. +The `scm` CLI supports advanced workflows including multi-folder operations, bulk loading, mock mode testing, debug output, environment variable configuration, and script integration. This guide covers techniques for power users and automation scenarios. ## Overview @@ -9,7 +9,7 @@ This guide covers advanced CLI capabilities: - Work with objects across multiple configuration folders - Perform bulk operations using YAML files - Test commands safely with mock mode -- Enable verbose output for troubleshooting +- Enable debug output for troubleshooting - Configure the CLI with environment variables - Integrate the CLI into scripts and automation pipelines @@ -25,15 +25,15 @@ Before using these advanced features, ensure you have: ### Folder-Based Organization -All object-related commands require the `--folder` parameter to specify where objects should be created, updated, or retrieved from. SCM uses folders to organize configurations hierarchically. +Object-related commands require exactly one container option — `--folder`, `--snippet`, or `--device` — to specify where objects should be created, updated, or retrieved from. SCM uses folders to organize configurations hierarchically; snippets and devices hold shared and device-level configuration. ### Mock Mode -The `--mock` flag allows you to run any command without making actual API calls. This is useful for validating syntax, testing YAML files, and developing scripts. +Setting the `SCM_MOCK=1` environment variable allows you to run any command without making actual API calls. This is useful for validating syntax, testing YAML files, and developing scripts. -### Verbose Output +### Debug Output -The `--verbose` flag provides detailed information about API requests, responses, timing, and errors. Use it when troubleshooting unexpected behavior. +The global `--debug` flag (or `SCM_LOG_LEVEL=DEBUG`) provides detailed information about API requests, responses, timing, and errors. Use it when troubleshooting unexpected behavior. ## Examples @@ -42,9 +42,8 @@ The `--verbose` flag provides detailed information about API requests, responses #### Specifying Folders ```bash -$ scm set object address \ +$ scm set object address web-server \ --folder Shared \ - --name web-server \ --ip-netmask 10.1.1.10/32 ---> 100% Created address: web-server in folder Shared @@ -79,7 +78,7 @@ Name: branch-server Load multiple objects of the same type from a YAML file: ```bash -$ scm load object address --folder Shared --file addresses.yaml +$ scm load object address --file addresses.yaml --folder Shared ---> 100% ✓ Loaded address: web-server-1 ✓ Loaded address: web-server-2 @@ -118,13 +117,11 @@ version control to track changes over time. #### Testing Commands Safely -Add the `--mock` flag to any command to run it without API calls: +Set `SCM_MOCK=1` to run any command without API calls: ```bash -$ scm set object address \ - --mock \ +$ SCM_MOCK=1 scm set object address test-server \ --folder Shared \ - --name test-server \ --ip-netmask 10.1.1.10/32 ---> 100% Created address: test-server in folder Shared (mock mode) @@ -136,21 +133,19 @@ Mock mode is useful for: - Validating YAML files before bulk loading - Script development and testing -### Verbose Output +### Debug Output -#### Troubleshooting with Verbose Mode +#### Troubleshooting with Debug Mode -Add the `--verbose` flag to see detailed operation information: +Add the global `--debug` flag to see detailed operation information: ```bash -$ scm set object address \ - --verbose \ +$ scm --debug set object address test-server \ --folder Shared \ - --name test-server \ --ip-netmask 10.1.1.10/32 ``` -Verbose output includes: +Debug output includes: - API request details - Response data @@ -167,6 +162,7 @@ Verbose output includes: | `SCM_CLIENT_SECRET` | Client secret for authentication | `export SCM_CLIENT_SECRET=client-secret-value` | | `SCM_TSG_ID` | Tenant Service Group ID | `export SCM_TSG_ID=tsg-id-value` | | `SCM_LOG_LEVEL` | Logging level (DEBUG, INFO, etc.) | `export SCM_LOG_LEVEL=DEBUG` | +| `SCM_MOCK` | Run in mock mode without API calls | `export SCM_MOCK=1` | :::info For full details on configuration sources and precedence, see @@ -195,9 +191,8 @@ This separation allows clean output parsing in scripts. ```bash #!/bin/bash # Create an address and check if successful -scm set object address \ +scm set object address test-server \ --folder Shared \ - --name test-server \ --ip-netmask 10.1.1.10/32 2>error.log if [ $? -eq 0 ]; then @@ -220,10 +215,10 @@ ADDRESSES=$(scm show object address \ ## Best Practices -1. **Use mock mode for development**: Always test new scripts and YAML files with `--mock` before running against production. +1. **Use mock mode for development**: Always test new scripts and YAML files with `SCM_MOCK=1` before running against production. 2. **Organize objects by folder**: Use SCM folders to logically separate configurations by environment or location. 3. **Store YAML files in version control**: Track configuration changes alongside your infrastructure code. -4. **Use verbose mode for debugging**: Enable `--verbose` when troubleshooting unexpected behavior. +4. **Use debug mode for troubleshooting**: Enable `--debug` (or `SCM_LOG_LEVEL=DEBUG`) when troubleshooting unexpected behavior. 5. **Handle exit codes in scripts**: Always check return codes when integrating the CLI into automation pipelines. 6. **Use environment variables for CI/CD**: Set credentials via environment variables in automated workflows rather than hardcoding them. diff --git a/docs-site/docs/guide/ai-agents.md b/docs-site/docs/guide/ai-agents.md index 5a80925..3c7ec3a 100644 --- a/docs-site/docs/guide/ai-agents.md +++ b/docs-site/docs/guide/ai-agents.md @@ -153,7 +153,7 @@ file committed to version control. Use environment variables or pre-configured c ## Best Practices -1. **Start with mock mode**: When testing agent workflows, append `--mock` to commands to validate the command structure without making API calls. +1. **Start with mock mode**: When testing agent workflows, set `SCM_MOCK=1` to validate the command structure without making API calls. 2. **Use dry runs for bulk operations**: Always run `scm load ... --dry-run` before applying YAML imports to catch errors early. 3. **Commit explicitly**: Instruct your agent to commit only when you confirm. Staged changes have no effect until committed. 4. **Scope agent permissions**: Limit the agent to specific folders to prevent unintended changes across your SCM tenant. diff --git a/docs-site/docs/guide/configuration-objects.md b/docs-site/docs/guide/configuration-objects.md index f2177b9..e4c943a 100644 --- a/docs-site/docs/guide/configuration-objects.md +++ b/docs-site/docs/guide/configuration-objects.md @@ -6,7 +6,7 @@ The `scm` CLI provides a consistent interface for managing configuration objects Configuration objects are the building blocks of your SCM environment. The CLI allows you to: -- Create and update objects across multiple categories (objects, network, security, deployment) +- Create and update objects across multiple categories (object, network, security, sase) - Delete objects that are no longer needed - List and inspect existing objects - Bulk import objects from YAML files @@ -31,16 +31,20 @@ The CLI organizes configuration management commands into logical categories: | `object` | Network objects | Address, address group, application, service, tag | | `network` | Network configurations | Security zone | | `security` | Security policies | Security rule, anti-spyware profile, decryption profile | -| `deployment` | Deployment settings | Bandwidth allocation | +| `sase` | SASE deployment settings | Bandwidth allocation, remote network | ### Command Pattern All object commands follow a consistent pattern: ```bash -scm [OPTIONS] +scm [NAME] [OPTIONS] ``` +The object name is a positional argument: required for `set` and `delete`, optional +for `show` (omit it to list all objects). Most `set`, `delete`, and `show` commands +accept exactly one container option: `--folder`, `--snippet`, or `--device`. + ### Object Relationships Configuration objects often have dependencies. For example: @@ -61,22 +65,21 @@ Creating an address group that references a nonexistent address will fail. #### Address Objects ```bash -$ scm set object address \ +$ scm set object address web-server \ --folder Shared \ - --name web-server \ --ip-netmask 10.1.1.10/32 ---> 100% Created address: web-server in folder Shared ``` ```bash -$ scm delete object address --folder Shared --name web-server +$ scm delete object address web-server --folder Shared ---> 100% Deleted address: web-server from folder Shared ``` ```bash -$ scm load object address --folder Shared --file addresses.yaml +$ scm load object address --file addresses.yaml --folder Shared ---> 100% ✓ Loaded address: web-server-1 ✓ Loaded address: web-server-2 @@ -87,28 +90,27 @@ Successfully loaded 2 out of 2 addresses from 'addresses.yaml' #### Address Groups ```bash -$ scm set object address-group \ +$ scm set object address-group web-servers \ --folder Shared \ - --name web-servers \ - --static \ - --members "web-server-1,web-server-2" + --type static \ + --members web-server-1 --members web-server-2 ---> 100% -Created address-group: web-servers in folder Shared +Created address group: web-servers in folder Shared ``` ```bash -$ scm delete object address-group --folder Shared --name web-servers +$ scm delete object address-group web-servers --folder Shared ---> 100% -Deleted address-group: web-servers from folder Shared +Deleted address group: web-servers from folder Shared ``` ```bash -$ scm load object address-group --folder Shared --file address-groups.yaml +$ scm load object address-group --file address-groups.yaml --folder Shared ---> 100% -✓ Loaded address-group: web-servers -✓ Loaded address-group: db-servers +✓ Loaded address group: web-servers +✓ Loaded address group: db-servers -Successfully loaded 2 out of 2 address-groups from 'address-groups.yaml' +Successfully loaded 2 out of 2 address groups from 'address-groups.yaml' ``` ### Network Category @@ -116,25 +118,24 @@ Successfully loaded 2 out of 2 address-groups from 'address-groups.yaml' #### Security Zones ```bash -$ scm set network security-zone \ +$ scm set network zone Trust \ --folder Shared \ - --name Trust \ --mode layer3 ---> 100% -Created security-zone: Trust in folder Shared +Created zone: Trust in folder Shared ``` ```bash -$ scm delete network security-zone --folder Shared --name Trust +$ scm delete network zone Trust --folder Shared ---> 100% -Deleted security-zone: Trust from folder Shared +Deleted zone: Trust from folder Shared ``` ```bash -$ scm load network security-zone --folder Shared --file security-zones.yaml +$ scm load network zone --file security-zones.yaml --folder Shared ---> 100% -✓ Loaded security-zone: Trust -✓ Loaded security-zone: Untrust +✓ Loaded zone: Trust +✓ Loaded zone: Untrust Successfully loaded 2 out of 2 security-zones from 'security-zones.yaml' ``` @@ -144,9 +145,8 @@ Successfully loaded 2 out of 2 security-zones from 'security-zones.yaml' #### Security Rules ```bash -$ scm set security rule \ +$ scm set security rule Allow-Web \ --folder Shared \ - --name "Allow-Web" \ --source-zones Trust \ --destination-zones Untrust ---> 100% @@ -154,13 +154,13 @@ Created security rule: Allow-Web in folder Shared ``` ```bash -$ scm delete security rule --folder Shared --name "Allow-Web" +$ scm delete security rule Allow-Web --folder Shared ---> 100% Deleted security rule: Allow-Web from folder Shared ``` ```bash -$ scm load security rule --folder Shared --file security-rules.yaml +$ scm load security rule --file security-rules.yaml --folder Shared ---> 100% ✓ Loaded security rule: Allow-Web ✓ Loaded security rule: Block-Malware @@ -173,23 +173,21 @@ Successfully loaded 2 out of 2 security rules from 'security-rules.yaml' #### Bandwidth Allocation ```bash -$ scm set deployment bandwidth \ - --folder Shared \ - --name "Standard-Branch" \ - --egress-guaranteed 50 \ - --egress-max 100 +$ scm set sase bandwidth-allocation Standard-Branch \ + --bandwidth 100 \ + --spn-name-list spn1,spn2 ---> 100% -Created bandwidth: Standard-Branch in folder Shared +Created bandwidth allocation: Standard-Branch (100 Mbps) ``` ```bash -$ scm delete deployment bandwidth --folder Shared --name "Standard-Branch" +$ scm delete sase bandwidth-allocation Standard-Branch --spn-name-list spn1,spn2 ---> 100% -Deleted bandwidth: Standard-Branch from folder Shared +Deleted bandwidth allocation: Standard-Branch ``` ```bash -$ scm load deployment bandwidth --folder Shared --file bandwidth-allocations.yaml +$ scm load sase bandwidth-allocation --file bandwidth-allocations.yaml ---> 100% ✓ Loaded bandwidth: Standard-Branch ✓ Loaded bandwidth: Premium-Branch @@ -204,12 +202,11 @@ Successfully loaded 2 out of 2 bandwidth allocations from 'bandwidth-allocations Every object type has a `set` command with required and optional parameters: ```bash -$ scm set object address \ +$ scm set object address web-server \ --folder Shared \ - --name web-server \ --ip-netmask 10.1.1.10/32 \ --description "Web server" \ - --tags "web,production" + --tags web --tags production ---> 100% Created address: web-server in folder Shared ``` @@ -219,9 +216,8 @@ Created address: web-server in folder Shared Updating uses the same `set` command. The CLI updates the object if it already exists: ```bash -$ scm set object address \ +$ scm set object address web-server \ --folder Shared \ - --name web-server \ --ip-netmask 10.1.1.20/32 \ --description "Updated web server" ---> 100% @@ -250,7 +246,7 @@ Name: web-server-2 Load multiple objects from YAML files: ```bash -$ scm load object address --folder Shared --file addresses.yaml +$ scm load object address --file addresses.yaml --folder Shared ---> 100% ✓ Loaded address: web-server-1 ✓ Loaded address: web-server-2 @@ -264,35 +260,32 @@ Create objects in the correct order to satisfy dependencies: ```bash # First create the address objects -$ scm set object address \ +$ scm set object address web-server-1 \ --folder Shared \ - --name web-server-1 \ --ip-netmask 10.1.1.10/32 ---> 100% Created address: web-server-1 in folder Shared -$ scm set object address \ +$ scm set object address web-server-2 \ --folder Shared \ - --name web-server-2 \ --ip-netmask 10.1.1.11/32 ---> 100% Created address: web-server-2 in folder Shared # Then create an address group that references them -$ scm set object address-group \ +$ scm set object address-group web-servers \ --folder Shared \ - --name web-servers \ - --static \ - --members "web-server-1,web-server-2" + --type static \ + --members web-server-1 --members web-server-2 ---> 100% -Created address-group: web-servers in folder Shared +Created address group: web-servers in folder Shared ``` ## Best Practices 1. **Create dependencies first**: Always create referenced objects before the objects that reference them (e.g., addresses before address groups). 2. **Use YAML for complex setups**: Bulk loading from YAML files ensures consistency and is easier to maintain in version control. -3. **Validate with mock mode**: Use `--mock` to test commands before making changes to production. +3. **Validate with mock mode**: Set `SCM_MOCK=1` to test commands before making changes to production. 4. **Use descriptive names**: Choose clear, meaningful names for objects to make policies easier to understand. 5. **Organize by folder**: Use SCM folders to logically separate objects by environment or location. diff --git a/docs-site/docs/guide/configuration.md b/docs-site/docs/guide/configuration.md index c86c68c..db95f05 100644 --- a/docs-site/docs/guide/configuration.md +++ b/docs-site/docs/guide/configuration.md @@ -87,7 +87,7 @@ To fix this issue: ### Mock Mode To test commands without credentials or API calls, opt in explicitly with the -`SCM_MOCK` environment variable (or the `--mock` flag where available): +`SCM_MOCK` environment variable (or the `--mock` flag on `scm context test`): ```bash $ SCM_MOCK=1 scm show object address --folder Shared @@ -480,7 +480,7 @@ def get_auth_config(): 2. **Reserve environment variables for CI/CD**: Use environment variables only for automation pipelines. 3. **Never commit secrets to version control**: Keep credentials in contexts or environment variables, never in code. 4. **Set appropriate log levels**: Use `INFO` for general use, `WARNING` for production, `DEBUG` for troubleshooting. -5. **Test with mock mode first**: Use `--mock` to validate commands before using real credentials. +5. **Test with mock mode first**: Use `SCM_MOCK=1` (or `scm context test --mock`) to validate commands before using real credentials. 6. **Use `context test` to verify authentication**: Test connectivity without switching contexts by specifying the context name. 7. **Name contexts meaningfully**: Use descriptive names like `production`, `staging`, or `dev-tenant1`. diff --git a/docs-site/docs/guide/data-models.md b/docs-site/docs/guide/data-models.md index 67e2d99..3c9e8f9 100644 --- a/docs-site/docs/guide/data-models.md +++ b/docs-site/docs/guide/data-models.md @@ -30,36 +30,37 @@ CLI commands accept parameters in several formats depending on the field type. Most simple parameters are provided as strings: ```bash -$ scm set object address \ - --name "web-server" \ +$ scm set object address web-server \ --description "Web server in DMZ" \ - --folder "Shared" + --folder "Shared" \ + --ip-netmask 10.1.1.10/32 ---> 100% Created address: web-server in folder Shared ``` #### Boolean Parameters -Boolean parameters accept `true` or `false`: +Boolean parameters are flags, often paired with a negated form (e.g. `--enabled / --disabled`): ```bash -$ scm set security rule \ - --name "Allow-Web" \ +$ scm set security rule Allow-Web \ --folder "Shared" \ - --disabled false + --source-zones Trust \ + --destination-zones Untrust \ + --disabled ---> 100% Created security rule: Allow-Web in folder Shared ``` #### List Parameters -Lists are provided as comma-separated values: +Lists are provided by repeating the option once per value: ```bash -$ scm set object address \ - --name "web-server" \ +$ scm set object address web-server \ --folder "Shared" \ - --tags "web,dmz,production" + --ip-netmask 10.1.1.10/32 \ + --tags web --tags dmz --tags production ---> 100% Created address: web-server in folder Shared ``` @@ -207,15 +208,15 @@ bandwidth_allocations: | Exclusive fields | Mutually exclusive fields cannot both be specified | :::tip -Use the `--mock` flag to validate YAML files and command parameters -without making changes to your SCM environment. +Set the `SCM_MOCK=1` environment variable to validate YAML files and command +parameters without making changes to your SCM environment. ::: ## Best Practices 1. **Use YAML files for complex or bulk operations**: Structured files are easier to maintain and review than long command lines. 2. **Keep YAML files in version control**: Track configuration changes alongside your infrastructure code. -3. **Validate before applying**: Use `--mock` to validate YAML files and commands before making changes. +3. **Validate before applying**: Use `SCM_MOCK=1` to validate YAML files and commands before making changes. 4. **Check command help for formats**: Use `--help` with any command to see required parameters and accepted formats. 5. **Use `snake_case` in YAML**: Match the SDK model field names exactly when writing YAML files. diff --git a/docs-site/docs/guide/getting-started.md b/docs-site/docs/guide/getting-started.md index 525aa0e..e555a4b 100644 --- a/docs-site/docs/guide/getting-started.md +++ b/docs-site/docs/guide/getting-started.md @@ -143,9 +143,8 @@ Avoid committing credentials to version control. ### Creating an Address Object ```bash -$ scm set object address \ +$ scm set object address web-server \ --folder Shared \ - --name web-server \ --ip-netmask 192.168.1.100/32 \ --description "Web server" ---> 100% @@ -168,9 +167,8 @@ Name: web-server ### Updating an Address Object ```bash -$ scm set object address \ +$ scm set object address web-server \ --folder Shared \ - --name web-server \ --ip-netmask 192.168.1.200/32 \ --description "Updated web server" ---> 100% @@ -180,7 +178,7 @@ Updated address: web-server in folder Shared ### Deleting an Address Object ```bash -$ scm delete object address --folder Shared --name web-server +$ scm delete object address web-server --folder Shared ---> 100% Deleted address: web-server from folder Shared ``` @@ -210,7 +208,7 @@ addresses: Then load these address objects: ```bash -$ scm load object address --folder Shared --file addresses.yaml +$ scm load object address --file addresses.yaml --folder Shared ---> 100% ✓ Loaded address: web-server-1 ✓ Loaded address: web-server-2 @@ -233,7 +231,7 @@ scm set object address --help ## Best Practices 1. **Use contexts for authentication**: Contexts provide safe, isolated credential storage for multiple tenants. -2. **Start with mock mode**: Use the `--mock` flag to test commands without making API changes. +2. **Start with mock mode**: Set the `SCM_MOCK=1` environment variable to test commands without making API changes. 3. **Use YAML for bulk operations**: Loading objects from YAML files is more maintainable than individual commands. 4. **Test authentication first**: Run `scm context test` to verify credentials before running commands. 5. **Check help for options**: Every command supports `--help` to show available parameters. diff --git a/docs-site/docs/guide/operations.md b/docs-site/docs/guide/operations.md index 5c5ddcc..60f6b18 100644 --- a/docs-site/docs/guide/operations.md +++ b/docs-site/docs/guide/operations.md @@ -1,17 +1,16 @@ # Operations Management -The `scm` CLI provides capabilities beyond managing individual configuration objects, including deployment, job monitoring, health checks, and audit logging. This guide covers operational workflows for managing your SCM environment. +The `scm` CLI provides capabilities beyond managing individual configuration objects, including committing changes, job monitoring, device operations, and local config retrieval. This guide covers operational workflows for managing your SCM environment. ## Overview This guide covers operational tasks you can perform with the CLI: -- Deploy configuration changes by committing and pushing +- Deploy configuration changes by committing them - Monitor asynchronous jobs and check their status -- Inspect license status and system health -- Review audit logs to track changes -- Manage scheduled tasks for recurring operations -- Troubleshoot issues with diagnostic tools +- Run diagnostic operations against managed devices +- Retrieve on-device configuration versions +- Troubleshoot issues with debug output ## Prerequisites @@ -19,21 +18,21 @@ Before performing operational tasks, ensure you have: - The `scm` CLI installed and authenticated (see [Getting Started](getting-started.md)) - Appropriate permissions for deployment and operational commands -- Configuration changes staged and ready to deploy (for commit/push operations) +- Configuration changes staged and ready to deploy (for commit operations) ## Core Concepts ### Deployment Workflow -After making changes to your configurations, you must commit and push those changes to make them active. The workflow is: +After making changes to your configurations, you must commit those changes to make them active. The workflow is: 1. Make configuration changes using `set`, `delete`, or `load` commands -2. Commit the candidate configuration with a description -3. Push the committed configuration to devices +2. Commit the staged configuration with `scm commit`, naming the affected folder(s) +3. Monitor the commit job until it completes ### Asynchronous Jobs -Many SCM operations run asynchronously and generate jobs. Use the job monitoring commands to track their progress and verify completion. +Many SCM operations run asynchronously and generate jobs. Use the `scm jobs` commands to track their progress and verify completion. ## Examples @@ -42,30 +41,39 @@ Many SCM operations run asynchronously and generate jobs. Use the job monitoring #### Committing Changes ```bash -$ scm set deployment commit \ +$ scm commit \ + --folder Texas \ --description "Updated address objects and security rules" ---> 100% -Commit initiated successfully +Commit job 12345 started ``` -#### Pushing Configurations +#### Committing Multiple Folders ```bash -$ scm set deployment push +$ scm commit \ + --folder Texas \ + --folder California \ + --description "Multi-site update" ---> 100% -Push initiated successfully +Commit job 12346 started ``` +#### Waiting for a Commit to Finish + ```bash -$ scm set deployment push \ - --device-groups "Branch-Firewalls,DataCenter" +$ scm commit \ + --folder Texas \ + --description "Update" \ + --sync \ + --timeout 600 ---> 100% -Push initiated to device groups: Branch-Firewalls, DataCenter +Commit job 12347 completed successfully ``` :::tip -After committing or pushing, check job status to confirm the operation -completed successfully. +After committing, check job status to confirm the operation completed +successfully. ::: ### Job Monitoring @@ -73,171 +81,90 @@ completed successfully. #### Checking Job Status ```bash -$ scm get operations job --job-id "12345" +$ scm jobs status --id 12345 ---> 100% Job: 12345 - Status: completed - Type: commit - Result: success + Status: FIN + Type: CommitAll + Result: OK ``` #### Listing Recent Jobs ```bash -$ scm get operations jobs --limit 10 +$ scm jobs list --max-results 10 ---> 100% -Recent Jobs: +SCM Jobs ------------------------------------------------------------ -Job ID: 12345 - Type: commit - Status: completed +Id: 12345 + Type: CommitAll + Status: FIN ------------------------------------------------------------ -Job ID: 12344 - Type: push - Status: in_progress +Id: 12344 + Type: CommitAll + Status: PEND ------------------------------------------------------------ ``` -### License Management - -#### Checking License Status +#### Waiting for a Job ```bash -$ scm get operations licenses +$ scm jobs wait --id 12344 --timeout 600 ---> 100% -License Status: - Type: Enterprise - Status: Active - Expiration: 2026-12-31 +Job 12344 completed with status: FIN ``` -### Health Monitoring - -#### System Status - -```bash -$ scm get operations status ----> 100% -System Status: Healthy - API: Connected - Services: All Running -``` +### Device Operations -#### Connectivity Tests +Run diagnostics against a managed firewall by serial number. Each command +polls to completion by default; add `--async` to get a job ID immediately. ```bash -$ scm get operations connectivity-test --target firewalls ----> 100% -Connectivity Test Results: - Target: firewalls - Status: All reachable +$ scm operations route-table --device 007951000123456 +$ scm operations interfaces --device 007951000123456 +$ scm operations logging-status --device 007951000123456 ``` -### User Management - -#### Listing Users +#### Dispatching Asynchronously ```bash -$ scm get operations users ----> 100% -Users: - admin (Administrator) - readonly (Read-Only) -``` - -#### User Roles +$ scm operations route-table --device 007951000123456 --async +Dispatched job: abc-123 -```bash -$ scm get operations roles ----> 100% -Available Roles: - Administrator - Read-Only - Security Admin +$ scm operations status --id abc-123 +Job abc-123: completed ``` -### Audit Logs - -#### Retrieving Audit Logs +See [Device Operations](../cli/operations/index.md) for the full list of +operations. -```bash -$ scm get operations audit-logs --limit 20 ----> 100% -Audit Logs (last 20 entries): - 2026-03-08 10:30:00 - admin - Created address: web-server - 2026-03-08 10:25:00 - admin - Updated security rule: Allow-Web -``` +### Local Device Configurations -#### Filtering Audit Logs +List and download configuration versions stored on a device: ```bash -$ scm get operations audit-logs --filter-user "admin" ----> 100% -Audit Logs (filtered by user: admin): - 2026-03-08 10:30:00 - Created address: web-server - 2026-03-08 10:25:00 - Updated security rule: Allow-Web -``` - -```bash -$ scm get operations audit-logs \ - --start-date "2026-03-01" \ - --end-date "2026-03-08" ----> 100% -Audit Logs (2026-03-01 to 2026-03-08): - 2026-03-08 10:30:00 - admin - Created address: web-server - 2026-03-05 14:00:00 - admin - Committed configuration -``` - -### Scheduled Tasks - -#### Listing Scheduled Tasks - -```bash -$ scm get operations scheduled-tasks ----> 100% -Scheduled Tasks: - Daily-Backup (0 0 * * *) - Active -``` - -#### Creating Backup Tasks - -```bash -$ scm set operations scheduled-task \ - --type backup \ - --name "Daily-Backup" \ - --schedule "0 0 * * *" ----> 100% -Created scheduled task: Daily-Backup +$ scm local list --device 007951000123456 +$ scm local download --device 007951000123456 --version 42 --output config.xml ``` ### Troubleshooting -#### Diagnostic Tools +#### Debug Output ```bash -$ scm get operations diagnostics ----> 100% -Diagnostics: - API Connectivity: OK - Authentication: Valid - Configuration Sync: Up to date +$ scm --debug show object address --folder Texas ``` -#### Log Collection - -```bash -$ scm get operations collect-logs --output-dir "./support-logs" ----> 100% -Logs collected to ./support-logs/ -``` +The global `--debug` flag enables debug logging (including SDK auth/HTTP +traffic) and full tracebacks. Alternatively, set `SCM_LOG_LEVEL=DEBUG`. ## Best Practices 1. **Use descriptive commit messages**: Document your changes clearly for audit trail purposes. -2. **Check job status after operations**: Verify that commits and pushes complete successfully before proceeding. -3. **Review audit logs periodically**: Track changes made through the CLI and other interfaces to maintain accountability. -4. **Set up scheduled tasks for backups**: Automate recurring operations to ensure consistent configuration backups. -5. **Use verbose mode for troubleshooting**: Add `--verbose` to commands when diagnosing operational issues. +2. **Check job status after operations**: Verify that commits complete successfully before proceeding. +3. **Use `--sync` for scripted commits**: Blocking until the commit finishes simplifies automation pipelines. +4. **Prefer `--async` for slow device operations**: Dispatch the job, capture the ID, and poll with `scm operations status --id`. +5. **Use debug mode for troubleshooting**: Add the global `--debug` flag when diagnosing operational issues. ## Next Steps diff --git a/pyproject.toml b/pyproject.toml index 7c9071a..bfa7c4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pan-scm-cli" -version = "1.5.0" +version = "2.0.0" description = "CICD and Network Engineer-friendly CLI tool for Palo Alto Networks Strata Cloud Manager" authors = ["Calvin Remsburg "] readme = "README.md" diff --git a/src/scm_cli/commands/deployment.py b/src/scm_cli/commands/deployment.py index 9dc0015..5097e64 100644 --- a/src/scm_cli/commands/deployment.py +++ b/src/scm_cli/commands/deployment.py @@ -33,11 +33,13 @@ # ============================================================================================================================================================================================= # Define typer option constants -NAME_OPTION = typer.Option(..., "--name", help="Name of the bandwidth allocation") BANDWIDTH_OPTION = typer.Option(..., "--bandwidth", help="Bandwidth value in Mbps") DESCRIPTION_OPTION = typer.Option(None, "--description", help="Description of the bandwidth allocation") FILE_OPTION = typer.Option(..., "--file", help="YAML file to load configurations from") DRY_RUN_OPTION = typer.Option(False, "--dry-run", help="Simulate execution without applying changes") +BACKUP_FILE_OPTION = typer.Option(None, "--file", help="Output file path for the backup (defaults to .yaml)") +MAX_RESULTS_OPTION = typer.Option(None, "--max-results", help="Maximum number of results to display") +TAGS_OPTION = typer.Option(None, "--tags", help="Tags (repeat for multiple)") # List options for multiline definitions SUBNETS_SC_OPTION = typer.Option( @@ -58,14 +60,16 @@ @backup_app.command("bandwidth-allocation") @handle_command_errors("backing up bandwidth allocations") -def backup_bandwidth_allocation(): +def backup_bandwidth_allocation( + file: Path | None = BACKUP_FILE_OPTION, +): """Back up all bandwidth allocations to a YAML file. - The backup file will be named 'bandwidth-allocations.yaml' in the current directory. + Defaults to 'bandwidth-allocations.yaml' in the current directory. Example: ------- - scm backup sase bandwidth + scm backup sase bandwidth-allocation --file my-allocations.yaml Note: Bandwidth allocations are global and do not have a folder parameter. @@ -95,7 +99,7 @@ def backup_bandwidth_allocation(): yaml_data = {"bandwidth_allocations": backup_data} # Generate filename (no folder parameter for bandwidth allocations) - filename = "bandwidth-allocations.yaml" + filename = str(file) if file else "bandwidth-allocations.yaml" # Write to YAML file with open(filename, "w") as f: @@ -108,7 +112,7 @@ def backup_bandwidth_allocation(): @delete_app.command("bandwidth-allocation") @handle_command_errors("deleting bandwidth allocation") def delete_bandwidth_allocation( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the bandwidth allocation"), spn_name_list: str = typer.Option(..., "--spn-name-list", help="SPN names (comma-separated if multiple)"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): @@ -116,9 +120,8 @@ def delete_bandwidth_allocation( Example: ------- - scm delete sase bandwidth-allocation \ - --name primary \ - --spn-name-list ["spn1", "spn2"] + scm delete sase bandwidth-allocation primary \ + --spn-name-list spn1,spn2 Note: Bandwidth allocations are global resources and do not require a folder parameter. @@ -199,37 +202,34 @@ def _apply(allocation_data: dict): @set_app.command("bandwidth-allocation") @handle_command_errors("creating bandwidth allocation") def set_bandwidth_allocation( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the bandwidth allocation"), bandwidth: int = BANDWIDTH_OPTION, spn_name_list: str = typer.Option(..., "--spn-name-list", help="SPN names (comma-separated if multiple)"), description: str | None = DESCRIPTION_OPTION, - tags: str | None = typer.Option(None, "--tags", help="Tags (comma-separated if multiple)"), + tags: list[str] | None = TAGS_OPTION, ): """Create or update a bandwidth allocation. Example: ------- - scm set sase bandwidth-allocation \ - --name primary \ + scm set sase bandwidth-allocation primary \ --bandwidth 1000 \ - --spn-name-list ["spn1", "spn2"] \ + --spn-name-list spn1,spn2 \ --description "Primary allocation" \ - --tags ["production"] + --tags production Note: Bandwidth allocations are global resources and do not require a folder parameter. """ - # Convert comma-separated strings to lists + # Convert comma-separated SPN string to a list spn_list = ([spn.strip() for spn in spn_name_list.split(",")] if "," in spn_name_list else [spn_name_list.strip()]) if isinstance(spn_name_list, str) else spn_name_list - tag_list = ([tag.strip() for tag in tags.split(",")] if tags and "," in tags else [tags.strip()] if tags else []) if isinstance(tags, str) else tags or [] - # Validate input using Pydantic model allocation = BandwidthAllocation( name=name, bandwidth=bandwidth, spn_name_list=spn_list, - tags=tag_list, + tags=list(tags) if tags else [], ) # Call the SDK client to create the bandwidth allocation @@ -256,8 +256,9 @@ def set_bandwidth_allocation( @show_app.command("bandwidth-allocation") @handle_command_errors("showing bandwidth allocation") def show_bandwidth_allocation( - name: str | None = typer.Option(None, "--name", help="Name of the bandwidth allocation to show"), + name: str | None = typer.Argument(None, help="Name of the bandwidth allocation to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display bandwidth allocations. @@ -267,7 +268,7 @@ def show_bandwidth_allocation( scm show sase bandwidth-allocation # Show a specific bandwidth allocation by name - scm show sase bandwidth-allocation --name primary + scm show sase bandwidth-allocation primary Note: Bandwidth allocations do not have a folder parameter. @@ -280,6 +281,8 @@ def show_bandwidth_allocation( # List all bandwidth allocations (default behavior) allocations = scm_client.list_bandwidth_allocations() + if max_results is not None: + allocations = allocations[:max_results] if not allocations: emit([], output) @@ -301,14 +304,16 @@ def show_bandwidth_allocation( @backup_app.command("service-connection") @handle_command_errors("backing up service connections") -def backup_service_connection(): +def backup_service_connection( + file: Path | None = BACKUP_FILE_OPTION, +): """Back up all service connections to a YAML file. - The backup file will be named 'service-connections.yaml' in the current directory. + Defaults to 'service-connections.yaml' in the current directory. Example: ------- - scm backup sase service-connection + scm backup sase service-connection --file my-connections.yaml """ # List all service connections @@ -355,7 +360,7 @@ def backup_service_connection(): yaml_data = {"service_connections": backup_data} # Generate filename - filename = "service-connections.yaml" + filename = str(file) if file else "service-connections.yaml" # Write to YAML file with open(filename, "w") as f: @@ -368,14 +373,14 @@ def backup_service_connection(): @delete_app.command("service-connection") @handle_command_errors("deleting service connection") def delete_service_connection( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the service connection"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a service connection. Example: ------- - scm delete sase service-connection --name primary-connection + scm delete sase service-connection primary-connection """ if not force: @@ -438,7 +443,7 @@ def _apply(connection_data: dict): @set_app.command("service-connection") @handle_command_errors("creating service connection") def set_service_connection( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the service connection"), ipsec_tunnel: str = typer.Option(..., "--ipsec-tunnel", help="IPsec tunnel for the service connection"), region: str = typer.Option(..., "--region", help="Region for the service connection"), onboarding_type: str = typer.Option("classic", "--onboarding-type", help="Onboarding type"), @@ -458,8 +463,7 @@ def set_service_connection( Example: ------- - scm set sase service-connection \ - --name primary-connection \ + scm set sase service-connection primary-connection \ --ipsec-tunnel ipsec-tunnel-1 \ --region us-east-1 \ --subnets ["10.0.0.0/24", "10.0.1.0/24"] \ @@ -529,8 +533,9 @@ def set_service_connection( @show_app.command("service-connection") @handle_command_errors("showing service connection") def show_service_connection( - name: str | None = typer.Option(None, "--name", help="Name of the service connection to show"), + name: str | None = typer.Argument(None, help="Name of the service connection to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display service connections. @@ -540,7 +545,7 @@ def show_service_connection( scm show sase service-connection # Show a specific service connection by name - scm show sase service-connection --name primary-connection + scm show sase service-connection primary-connection """ if name: @@ -549,8 +554,10 @@ def show_service_connection( emit(connection, output, title=f"Service Connection: {name}") return connection - # List all service connections in the folder + # List all service connections connections = scm_client.list_service_connections() + if max_results is not None: + connections = connections[:max_results] if not connections: emit([], output) @@ -572,14 +579,16 @@ def show_service_connection( @backup_app.command("remote-network") @handle_command_errors("backing up remote networks") -def backup_remote_network(): +def backup_remote_network( + file: Path | None = BACKUP_FILE_OPTION, +): """Back up all remote networks to a YAML file. - The backup file will be named 'remote-networks.yaml' in the current directory. + Defaults to 'remote-networks.yaml' in the current directory. Example: ------- - scm backup sase remote-network + scm backup sase remote-network --file my-networks.yaml """ # List all remote networks @@ -611,7 +620,7 @@ def backup_remote_network(): yaml_data = {"remote_networks": backup_data} # Generate filename - filename = "remote-networks.yaml" + filename = str(file) if file else "remote-networks.yaml" # Write to YAML file with open(filename, "w") as f: @@ -624,14 +633,14 @@ def backup_remote_network(): @delete_app.command("remote-network") @handle_command_errors("deleting remote network") def delete_remote_network( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the remote network"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a remote network. Example: ------- - scm delete sase remote-network --name branch-network + scm delete sase remote-network branch-network """ if not force: @@ -696,7 +705,7 @@ def _apply(network_data: dict): @set_app.command("remote-network") @handle_command_errors("creating remote network") def set_remote_network( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the remote network"), region: str = typer.Option(..., "--region", help="Region for the remote network"), license_type: str = typer.Option("FWAAS-AGGREGATE", "--license-type", help="License type"), description: str | None = DESCRIPTION_OPTION, @@ -715,8 +724,7 @@ def set_remote_network( Example: ------- - scm set sase remote-network \ - --name branch-network \ + scm set sase remote-network branch-network \ --region us-west-1 \ --license-type FWAAS-AGGREGATE \ --spn-name spn-west \ @@ -784,8 +792,9 @@ def set_remote_network( @show_app.command("remote-network") @handle_command_errors("showing remote network") def show_remote_network( - name: str | None = typer.Option(None, "--name", help="Name of the remote network to show"), + name: str | None = typer.Argument(None, help="Name of the remote network to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display remote networks. @@ -795,7 +804,7 @@ def show_remote_network( scm show sase remote-network # Show a specific remote network by name - scm show sase remote-network --name branch-network + scm show sase remote-network branch-network """ if name: @@ -808,6 +817,8 @@ def show_remote_network( # List all remote networks networks = scm_client.list_remote_networks() + if max_results is not None: + networks = networks[:max_results] if not networks: emit([], output) @@ -919,6 +930,7 @@ def set_bgp_routing( @handle_command_errors("showing BGP routing") def show_bgp_routing( output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, # noqa: ARG001 - singleton config, no list mode ): """Display BGP routing configuration. @@ -939,7 +951,6 @@ def show_bgp_routing( # ============================================================================================================================================================================================= # Internal DNS Server option constants -DNS_NAME_OPTION = typer.Option(..., "--name", help="Name of the internal DNS server") DNS_DOMAIN_OPTION = typer.Option(..., "--domain-name", help="DNS domain name(s) (comma-separated if multiple)") DNS_PRIMARY_OPTION = typer.Option(..., "--primary", help="Primary DNS server IP address") DNS_SECONDARY_OPTION = typer.Option(None, "--secondary", help="Secondary DNS server IP address") @@ -949,14 +960,16 @@ def show_bgp_routing( @backup_app.command("internal-dns-server") @handle_command_errors("backing up internal DNS servers") -def backup_internal_dns_server(): +def backup_internal_dns_server( + file: Path | None = BACKUP_FILE_OPTION, +): """Back up all internal DNS servers to a YAML file. - The backup file will be named 'internal-dns-servers.yaml' in the current directory. + Defaults to 'internal-dns-servers.yaml' in the current directory. Example: ------- - scm backup sase internal-dns-server + scm backup sase internal-dns-server --file my-dns-servers.yaml """ servers = scm_client.list_internal_dns_servers() @@ -972,7 +985,7 @@ def backup_internal_dns_server(): backup_data.append(server_dict) yaml_data = {"internal_dns_servers": backup_data} - filename = "internal-dns-servers.yaml" + filename = str(file) if file else "internal-dns-servers.yaml" with open(filename, "w") as f: yaml.dump(yaml_data, f, default_flow_style=False, sort_keys=False) @@ -984,14 +997,14 @@ def backup_internal_dns_server(): @delete_app.command("internal-dns-server") @handle_command_errors("deleting internal DNS server") def delete_internal_dns_server( - name: str = DNS_NAME_OPTION, + name: str = typer.Argument(..., help="Name of the internal DNS server"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an internal DNS server. Example: ------- - scm delete sase internal-dns-server --name my-dns-server + scm delete sase internal-dns-server my-dns-server """ if not force: @@ -1049,7 +1062,7 @@ def _apply(server_data: dict): @set_app.command("internal-dns-server") @handle_command_errors("creating internal DNS server") def set_internal_dns_server( - name: str = DNS_NAME_OPTION, + name: str = typer.Argument(..., help="Name of the internal DNS server"), domain_name: str = DNS_DOMAIN_OPTION, primary: str = DNS_PRIMARY_OPTION, secondary: str | None = DNS_SECONDARY_OPTION, @@ -1058,8 +1071,7 @@ def set_internal_dns_server( Example: ------- - scm set sase internal-dns-server \ - --name corp-dns \ + scm set sase internal-dns-server corp-dns \ --domain-name corp.example.com \ --primary 10.0.0.1 \ --secondary 10.0.0.2 @@ -1096,8 +1108,9 @@ def set_internal_dns_server( @show_app.command("internal-dns-server") @handle_command_errors("showing internal DNS server") def show_internal_dns_server( - name: str | None = typer.Option(None, "--name", help="Name of the internal DNS server to show"), + name: str | None = typer.Argument(None, help="Name of the internal DNS server to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display internal DNS servers. @@ -1107,7 +1120,7 @@ def show_internal_dns_server( scm show sase internal-dns-server # Show a specific internal DNS server by name - scm show sase internal-dns-server --name corp-dns + scm show sase internal-dns-server corp-dns """ if name: @@ -1116,6 +1129,8 @@ def show_internal_dns_server( return server servers = scm_client.list_internal_dns_servers() + if max_results is not None: + servers = servers[:max_results] if not servers: emit([], output) @@ -1140,6 +1155,7 @@ def show_internal_dns_server( def show_network_location( value: str | None = typer.Option(None, "--value", help="System value of the network location (e.g., us-west-1)"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display network locations (read-only). @@ -1158,6 +1174,8 @@ def show_network_location( return location locations = scm_client.list_network_locations() + if max_results is not None: + locations = locations[:max_results] if not locations: emit([], output) diff --git a/src/scm_cli/commands/identity.py b/src/scm_cli/commands/identity.py index 325889f..6c2b0dd 100644 --- a/src/scm_cli/commands/identity.py +++ b/src/scm_cli/commands/identity.py @@ -41,11 +41,11 @@ # COMMAND OPTIONS # ============================================================================================================================================================================================= -NAME_OPTION = typer.Option(..., "--name", help="Name of the resource") DESCRIPTION_OPTION = typer.Option(None, "--description", help="Description of the resource") FOLDER_OPTION = typer.Option(None, "--folder", help="Folder to scope the resource to") SNIPPET_OPTION = typer.Option(None, "--snippet", help="Snippet to scope the resource to") DEVICE_OPTION = typer.Option(None, "--device", help="Device to scope the resource to") +MAX_RESULTS_OPTION = typer.Option(None, "--max-results", help="Maximum number of results to display") FILE_OPTION = typer.Option(..., "--file", help="YAML file to load configurations from") DRY_RUN_OPTION = typer.Option(False, "--dry-run", help="Simulate execution without applying changes") @@ -104,10 +104,10 @@ def _bulk_load_profiles(items: list[dict], apply_item, label: str, plural_label: @set_app.command("authentication-profile") @handle_command_errors("creating authentication profile") def set_authentication_profile( + name: str = typer.Argument(..., help="Name of the authentication profile"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, method: str | None = typer.Option(None, "--method", help="Authentication method as JSON string"), user_domain: str | None = typer.Option(None, "--user-domain", help="User domain"), username_modifier: str | None = typer.Option(None, "--username-modifier", help="Username modifier pattern"), @@ -120,7 +120,7 @@ def set_authentication_profile( Examples -------- - scm set identity authentication-profile --folder Texas --name my-auth \\ + scm set identity authentication-profile my-auth --folder Texas \\ --method '{"ldap": {"server_profile": "corp-ldap", "login_attribute": "sAMAccountName"}}' """ @@ -166,45 +166,46 @@ def set_authentication_profile( @show_app.command("authentication-profile") @handle_command_errors("showing authentication profiles") def show_authentication_profile( + name: str | None = typer.Argument(None, help="Name of the authentication profile to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the authentication profile to show"), - list_items: bool = typer.Option(False, "--list", "-l", help="List all authentication profiles"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display authentication profiles. Examples -------- - scm show identity authentication-profile --folder Texas --list - scm show identity authentication-profile --folder Texas --name my-auth + scm show identity authentication-profile --folder Texas + scm show identity authentication-profile my-auth --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: - location_type, location_value = validate_location_params(folder, snippet, device) profile = scm_client.get_authentication_profile(name=name, **{location_type: location_value}) emit(profile, output, title=f"Authentication Profile: {name}") return profiles = scm_client.list_authentication_profiles(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"Authentication Profiles in {location}" if location else "Authentication Profiles" - emit(profiles, output, columns=["name", "user_domain", "method", "allow_list"], title=title) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, columns=["name", "user_domain", "method", "allow_list"], title=f"Authentication Profiles in {location_value}") @delete_app.command("authentication-profile") @handle_command_errors("deleting authentication profile") def delete_authentication_profile( + name: str = typer.Argument(..., help="Name of the authentication profile to delete"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an authentication profile. - Example: scm delete identity authentication-profile --folder Texas --name my-auth + Example: scm delete identity authentication-profile my-auth --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -318,17 +319,17 @@ def backup_authentication_profile( @set_app.command("kerberos-server-profile") @handle_command_errors("creating Kerberos server profile") def set_kerberos_server_profile( + name: str = typer.Argument(..., help="Name of the Kerberos server profile"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, servers: str | None = typer.Option(None, "--servers", help="Server list as JSON string"), ): r"""Create or update a Kerberos server profile. Examples -------- - scm set identity kerberos-server-profile --folder Texas --name corp-kerberos \\ + scm set identity kerberos-server-profile corp-kerberos --folder Texas \\ --servers '[{"name": "kdc1", "host": "kdc1.example.com", "port": 88}]' """ @@ -364,45 +365,46 @@ def set_kerberos_server_profile( @show_app.command("kerberos-server-profile") @handle_command_errors("showing Kerberos server profiles") def show_kerberos_server_profile( + name: str | None = typer.Argument(None, help="Name of the Kerberos server profile to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the Kerberos server profile to show"), - list_items: bool = typer.Option(False, "--list", "-l", help="List all Kerberos server profiles"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display Kerberos server profiles. Examples -------- - scm show identity kerberos-server-profile --folder Texas --list - scm show identity kerberos-server-profile --folder Texas --name corp-kerberos + scm show identity kerberos-server-profile --folder Texas + scm show identity kerberos-server-profile corp-kerberos --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: - location_type, location_value = validate_location_params(folder, snippet, device) profile = scm_client.get_kerberos_server_profile(name=name, **{location_type: location_value}) emit(profile, output, title=f"Kerberos Server Profile: {name}") return profiles = scm_client.list_kerberos_server_profiles(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"Kerberos Server Profiles in {location}" if location else "Kerberos Server Profiles" - emit(profiles, output, columns=["name", "server"], title=title) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, columns=["name", "server"], title=f"Kerberos Server Profiles in {location_value}") @delete_app.command("kerberos-server-profile") @handle_command_errors("deleting Kerberos server profile") def delete_kerberos_server_profile( + name: str = typer.Argument(..., help="Name of the Kerberos server profile to delete"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a Kerberos server profile. - Example: scm delete identity kerberos-server-profile --folder Texas --name corp-kerberos + Example: scm delete identity kerberos-server-profile corp-kerberos --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -516,10 +518,10 @@ def backup_kerberos_server_profile( @set_app.command("ldap-server-profile") @handle_command_errors("creating LDAP server profile") def set_ldap_server_profile( + name: str = typer.Argument(..., help="Name of the LDAP server profile"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, servers: str | None = typer.Option(None, "--servers", help="Server list as JSON string"), base: str | None = typer.Option(None, "--base", help="Base distinguished name"), bind_dn: str | None = typer.Option(None, "--bind-dn", help="Bind distinguished name"), @@ -531,7 +533,7 @@ def set_ldap_server_profile( Examples -------- - scm set identity ldap-server-profile --folder Texas --name corp-ldap \\ + scm set identity ldap-server-profile corp-ldap --folder Texas \\ --servers '[{"name": "ldap1", "address": "ldap.example.com", "port": 389}]' \\ --base "dc=example,dc=com" --ldap-type active-directory @@ -573,45 +575,46 @@ def set_ldap_server_profile( @show_app.command("ldap-server-profile") @handle_command_errors("showing LDAP server profiles") def show_ldap_server_profile( + name: str | None = typer.Argument(None, help="Name of the LDAP server profile to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the LDAP server profile to show"), - list_items: bool = typer.Option(False, "--list", "-l", help="List all LDAP server profiles"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display LDAP server profiles. Examples -------- - scm show identity ldap-server-profile --folder Texas --list - scm show identity ldap-server-profile --folder Texas --name corp-ldap + scm show identity ldap-server-profile --folder Texas + scm show identity ldap-server-profile corp-ldap --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: - location_type, location_value = validate_location_params(folder, snippet, device) profile = scm_client.get_ldap_server_profile(name=name, **{location_type: location_value}) emit(profile, output, title=f"LDAP Server Profile: {name}") return profiles = scm_client.list_ldap_server_profiles(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"LDAP Server Profiles in {location}" if location else "LDAP Server Profiles" - emit(profiles, output, columns=["name", "ldap_type", "base", "server"], title=title) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, columns=["name", "ldap_type", "base", "server"], title=f"LDAP Server Profiles in {location_value}") @delete_app.command("ldap-server-profile") @handle_command_errors("deleting LDAP server profile") def delete_ldap_server_profile( + name: str = typer.Argument(..., help="Name of the LDAP server profile to delete"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an LDAP server profile. - Example: scm delete identity ldap-server-profile --folder Texas --name corp-ldap + Example: scm delete identity ldap-server-profile corp-ldap --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -725,10 +728,10 @@ def backup_ldap_server_profile( @set_app.command("radius-server-profile") @handle_command_errors("creating RADIUS server profile") def set_radius_server_profile( + name: str = typer.Argument(..., help="Name of the RADIUS server profile"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, servers: str | None = typer.Option(None, "--servers", help="Server list as JSON string"), protocol: str | None = typer.Option(None, "--protocol", help="Protocol config as JSON string"), timeout: int | None = typer.Option(None, "--timeout", help="Timeout in seconds (1-120)"), @@ -738,7 +741,7 @@ def set_radius_server_profile( Examples -------- - scm set identity radius-server-profile --folder Texas --name corp-radius \\ + scm set identity radius-server-profile corp-radius --folder Texas \\ --servers '[{"name": "rad1", "ip_address": "10.0.0.1", "port": 1812, "secret": "s3cret"}]' \\ --protocol '{"CHAP": {}}' --timeout 5 --retries 3 @@ -779,45 +782,46 @@ def set_radius_server_profile( @show_app.command("radius-server-profile") @handle_command_errors("showing RADIUS server profiles") def show_radius_server_profile( + name: str | None = typer.Argument(None, help="Name of the RADIUS server profile to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the RADIUS server profile to show"), - list_items: bool = typer.Option(False, "--list", "-l", help="List all RADIUS server profiles"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display RADIUS server profiles. Examples -------- - scm show identity radius-server-profile --folder Texas --list - scm show identity radius-server-profile --folder Texas --name corp-radius + scm show identity radius-server-profile --folder Texas + scm show identity radius-server-profile corp-radius --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: - location_type, location_value = validate_location_params(folder, snippet, device) profile = scm_client.get_radius_server_profile(name=name, **{location_type: location_value}) emit(profile, output, title=f"RADIUS Server Profile: {name}") return profiles = scm_client.list_radius_server_profiles(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"RADIUS Server Profiles in {location}" if location else "RADIUS Server Profiles" - emit(profiles, output, columns=["name", "server", "timeout", "retries"], title=title) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, columns=["name", "server", "timeout", "retries"], title=f"RADIUS Server Profiles in {location_value}") @delete_app.command("radius-server-profile") @handle_command_errors("deleting RADIUS server profile") def delete_radius_server_profile( + name: str = typer.Argument(..., help="Name of the RADIUS server profile to delete"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a RADIUS server profile. - Example: scm delete identity radius-server-profile --folder Texas --name corp-radius + Example: scm delete identity radius-server-profile corp-radius --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -931,10 +935,10 @@ def backup_radius_server_profile( @set_app.command("saml-server-profile") @handle_command_errors("creating SAML server profile") def set_saml_server_profile( + name: str = typer.Argument(..., help="Name of the SAML server profile"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, entity_id: str = typer.Option(..., "--entity-id", help="Entity ID"), certificate: str = typer.Option(..., "--certificate", help="Certificate name"), sso_url: str = typer.Option(..., "--sso-url", help="Single Sign-On URL"), @@ -948,7 +952,7 @@ def set_saml_server_profile( Examples -------- - scm set identity saml-server-profile --folder Texas --name corp-saml \\ + scm set identity saml-server-profile corp-saml --folder Texas \\ --entity-id "https://idp.example.com" --certificate idp-cert \\ --sso-url "https://idp.example.com/sso" --sso-bindings post @@ -990,45 +994,46 @@ def set_saml_server_profile( @show_app.command("saml-server-profile") @handle_command_errors("showing SAML server profiles") def show_saml_server_profile( + name: str | None = typer.Argument(None, help="Name of the SAML server profile to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the SAML server profile to show"), - list_items: bool = typer.Option(False, "--list", "-l", help="List all SAML server profiles"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display SAML server profiles. Examples -------- - scm show identity saml-server-profile --folder Texas --list - scm show identity saml-server-profile --folder Texas --name corp-saml + scm show identity saml-server-profile --folder Texas + scm show identity saml-server-profile corp-saml --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: - location_type, location_value = validate_location_params(folder, snippet, device) profile = scm_client.get_saml_server_profile(name=name, **{location_type: location_value}) emit(profile, output, title=f"SAML Server Profile: {name}") return profiles = scm_client.list_saml_server_profiles(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"SAML Server Profiles in {location}" if location else "SAML Server Profiles" - emit(profiles, output, columns=["name", "entity_id", "sso_url", "sso_bindings"], title=title) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, columns=["name", "entity_id", "sso_url", "sso_bindings"], title=f"SAML Server Profiles in {location_value}") @delete_app.command("saml-server-profile") @handle_command_errors("deleting SAML server profile") def delete_saml_server_profile( + name: str = typer.Argument(..., help="Name of the SAML server profile to delete"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a SAML server profile. - Example: scm delete identity saml-server-profile --folder Texas --name corp-saml + Example: scm delete identity saml-server-profile corp-saml --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -1142,10 +1147,10 @@ def backup_saml_server_profile( @set_app.command("tacacs-server-profile") @handle_command_errors("creating TACACS+ server profile") def set_tacacs_server_profile( + name: str = typer.Argument(..., help="Name of the TACACS+ server profile"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, servers: str | None = typer.Option(None, "--servers", help="Server list as JSON string"), protocol: str | None = typer.Option(None, "--protocol", help="Protocol type (CHAP, PAP)"), timeout: int | None = typer.Option(None, "--timeout", help="Timeout in seconds (1-30)"), @@ -1155,7 +1160,7 @@ def set_tacacs_server_profile( Examples -------- - scm set identity tacacs-server-profile --folder Texas --name corp-tacacs \\ + scm set identity tacacs-server-profile corp-tacacs --folder Texas \\ --servers '[{"name": "tac1", "address": "10.0.0.1", "port": 49, "secret": "s3cret"}]' \\ --protocol CHAP --timeout 5 @@ -1195,45 +1200,46 @@ def set_tacacs_server_profile( @show_app.command("tacacs-server-profile") @handle_command_errors("showing TACACS+ server profiles") def show_tacacs_server_profile( + name: str | None = typer.Argument(None, help="Name of the TACACS+ server profile to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the TACACS+ server profile to show"), - list_items: bool = typer.Option(False, "--list", "-l", help="List all TACACS+ server profiles"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display TACACS+ server profiles. Examples -------- - scm show identity tacacs-server-profile --folder Texas --list - scm show identity tacacs-server-profile --folder Texas --name corp-tacacs + scm show identity tacacs-server-profile --folder Texas + scm show identity tacacs-server-profile corp-tacacs --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: - location_type, location_value = validate_location_params(folder, snippet, device) profile = scm_client.get_tacacs_server_profile(name=name, **{location_type: location_value}) emit(profile, output, title=f"TACACS+ Server Profile: {name}") return profiles = scm_client.list_tacacs_server_profiles(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"TACACS+ Server Profiles in {location}" if location else "TACACS+ Server Profiles" - emit(profiles, output, columns=["name", "protocol", "server", "timeout"], title=title) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, columns=["name", "protocol", "server", "timeout"], title=f"TACACS+ Server Profiles in {location_value}") @delete_app.command("tacacs-server-profile") @handle_command_errors("deleting TACACS+ server profile") def delete_tacacs_server_profile( + name: str = typer.Argument(..., help="Name of the TACACS+ server profile to delete"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a TACACS+ server profile. - Example: scm delete identity tacacs-server-profile --folder Texas --name corp-tacacs + Example: scm delete identity tacacs-server-profile corp-tacacs --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) diff --git a/src/scm_cli/commands/insights.py b/src/scm_cli/commands/insights.py index e102d98..5c701b4 100644 --- a/src/scm_cli/commands/insights.py +++ b/src/scm_cli/commands/insights.py @@ -112,7 +112,6 @@ def show_alerts( "--folder", help="Filter alerts by folder", ), - mock: bool = typer.Option(False, "--mock", help="Run in mock mode"), ): """Show alerts from Strata Cloud Manager. @@ -139,8 +138,6 @@ def show_alerts( scm insights alerts --real-time """ - # Note: scm_client automatically uses mock mode when no credentials are available - if alert_id: # Get specific alert alert = scm_client.get_alert(alert_id=alert_id, folder=folder) @@ -221,7 +218,6 @@ def show_mobile_users( "--folder", help="Filter by folder", ), - mock: bool = typer.Option(False, "--mock", help="Run in mock mode"), ): """Show mobile users insights from Strata Cloud Manager. @@ -239,8 +235,6 @@ def show_mobile_users( scm insights mobile-users --list --export json --output users.json """ - # Note: scm_client automatically uses mock mode when no credentials are available - if user_id: # Get specific user user = scm_client.get_mobile_user(user_id=user_id, folder=folder) @@ -299,7 +293,6 @@ def show_locations( "--folder", help="Filter by folder", ), - mock: bool = typer.Option(False, "--mock", help="Run in mock mode"), ): """Show locations insights from Strata Cloud Manager. @@ -317,8 +310,6 @@ def show_locations( scm insights locations --list --export csv --output locations.csv """ - # Note: scm_client automatically uses mock mode when no credentials are available - if location_id: # Get specific location location = scm_client.get_location(location_id=location_id, folder=folder) @@ -380,7 +371,6 @@ def show_remote_networks( "--folder", help="Filter by folder", ), - mock: bool = typer.Option(False, "--mock", help="Run in mock mode"), ): """Show remote networks insights from Strata Cloud Manager. @@ -398,8 +388,6 @@ def show_remote_networks( scm insights remote-networks --list --export json --output networks.json """ - # Note: scm_client automatically uses mock mode when no credentials are available - if network_id: # Get specific network network = scm_client.get_remote_network_insights(network_id=network_id, folder=folder, include_metrics=show_metrics) @@ -459,7 +447,6 @@ def show_service_connections( "--folder", help="Filter by folder", ), - mock: bool = typer.Option(False, "--mock", help="Run in mock mode"), ): """Show service connections insights from Strata Cloud Manager. @@ -477,8 +464,6 @@ def show_service_connections( scm insights service-connections --list --metrics --export csv --output connections.csv """ - # Note: scm_client automatically uses mock mode when no credentials are available - if connection_id: # Get specific connection connection = scm_client.get_service_connection_insights(connection_id=connection_id, folder=folder, include_metrics=show_metrics) @@ -552,7 +537,6 @@ def show_tunnels( "--folder", help="Filter by folder", ), - mock: bool = typer.Option(False, "--mock", help="Run in mock mode"), ): """Show tunnels insights from Strata Cloud Manager. @@ -573,8 +557,6 @@ def show_tunnels( scm insights tunnels --list --stats --export json --output tunnels.json """ - # Note: scm_client automatically uses mock mode when no credentials are available - if tunnel_id: # Get specific tunnel tunnel = scm_client.get_tunnel( diff --git a/src/scm_cli/commands/mobile_agent.py b/src/scm_cli/commands/mobile_agent.py index 2011eba..24186e4 100644 --- a/src/scm_cli/commands/mobile_agent.py +++ b/src/scm_cli/commands/mobile_agent.py @@ -98,10 +98,10 @@ def _bulk_load_items(items: list[dict], apply_item, label: str) -> list[dict]: "--folder", help="Folder path for the resource", ) -NAME_OPTION = typer.Option( +MAX_RESULTS_OPTION = typer.Option( None, - "--name", - help="Name of the resource", + "--max-results", + help="Maximum number of results to display", ) DESCRIPTION_OPTION = typer.Option( None, @@ -184,9 +184,10 @@ def _bulk_load_items(items: list[dict], apply_item, label: str) -> list[dict]: @show_app.command("agent-version") @handle_command_errors("showing agent version") def show_agent_version( + name: str | None = typer.Argument(None, help="Name of the agent version to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the agent version to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display agent versions. @@ -196,7 +197,7 @@ def show_agent_version( scm show mobile-agent agent-version --folder "Mobile Users" # Show a specific agent version by name - scm show mobile-agent agent-version --folder "Mobile Users" --name "5.2.0" + scm show mobile-agent agent-version "5.2.0" --folder "Mobile Users" """ if name: @@ -207,6 +208,8 @@ def show_agent_version( # Default: list all agent versions versions = scm_client.list_agent_versions(folder=folder) + if max_results is not None: + versions = versions[:max_results] emit( versions, output, @@ -277,15 +280,15 @@ def backup_auth_setting( @delete_app.command("auth-setting") @handle_command_errors("deleting auth setting") def delete_auth_setting( + name: str = typer.Argument(..., help="Name of the auth setting to delete"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an auth setting. Examples -------- - scm delete mobile-agent auth-setting --folder "Mobile Users" --name "saml-auth" + scm delete mobile-agent auth-setting "saml-auth" --folder "Mobile Users" """ if not force: @@ -356,8 +359,8 @@ def _apply(setting_data: dict): @set_app.command("auth-setting") @handle_command_errors("creating/updating auth setting") def set_auth_setting( + name: str = typer.Argument(..., help="Name of the auth setting"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, authentication_profile: str | None = AUTHENTICATION_PROFILE_OPTION, os: str | None = OS_OPTION, @@ -367,9 +370,8 @@ def set_auth_setting( Examples -------- - scm set mobile-agent auth-setting \ + scm set mobile-agent auth-setting "saml-auth" \ --folder "Mobile Users" \ - --name "saml-auth" \ --authentication-profile "best-practice" \ --os Any @@ -417,9 +419,10 @@ def set_auth_setting( @show_app.command("auth-setting") @handle_command_errors("showing auth setting") def show_auth_setting( + name: str | None = typer.Argument(None, help="Name of the auth setting to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the auth setting to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display auth settings. @@ -429,7 +432,7 @@ def show_auth_setting( scm show mobile-agent auth-setting --folder "Mobile Users" # Show a specific auth setting by name - scm show mobile-agent auth-setting --folder "Mobile Users" --name "saml-auth" + scm show mobile-agent auth-setting "saml-auth" --folder "Mobile Users" """ if name: @@ -440,6 +443,8 @@ def show_auth_setting( # Default: list all auth settings settings_list = scm_client.list_auth_settings(folder=folder) + if max_results is not None: + settings_list = settings_list[:max_results] emit( settings_list, output, @@ -472,7 +477,7 @@ def show_auth_setting( PROFILE_ID_OPTION = typer.Option( None, "--id", - help="UUID of the resource (alternative to --name)", + help="UUID of the resource (alternative to the NAME argument)", ) _PROFILE_TYPE_KEY_MAP = { @@ -525,8 +530,8 @@ def backup_forwarding_profile( @delete_app.command("forwarding-profile") @handle_command_errors("deleting forwarding profile") def delete_forwarding_profile( + name: str | None = typer.Argument(None, help="Name of the forwarding profile to delete (omit when using --id)"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, profile_id: str | None = PROFILE_ID_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): @@ -534,11 +539,14 @@ def delete_forwarding_profile( Examples -------- - scm delete mobile-agent forwarding-profile --folder "Mobile Users" --name "ztna-profile" + scm delete mobile-agent forwarding-profile "ztna-profile" --folder "Mobile Users" scm delete mobile-agent forwarding-profile --id "123e4567-e89b-12d3-a456-426655440000" """ + if not profile_id and not name: + error("Error: provide NAME or --id") + raise typer.Exit(code=1) identifier = profile_id or name if not force: typer.confirm(f"Delete forwarding profile '{identifier}'?", abort=True) @@ -594,8 +602,8 @@ def _apply(profile_data: dict): @set_app.command("forwarding-profile") @handle_command_errors("creating/updating forwarding profile") def set_forwarding_profile( + name: str = typer.Argument(..., help="Name of the forwarding profile"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, definition_method: str | None = DEFINITION_METHOD_OPTION, profile_type: str | None = PROFILE_TYPE_OPTION, @@ -609,9 +617,8 @@ def set_forwarding_profile( Examples -------- - scm set mobile-agent forwarding-profile \ + scm set mobile-agent forwarding-profile "ztna-profile" \ --folder "Mobile Users" \ - --name "ztna-profile" \ --profile-type ztna-agent """ @@ -659,10 +666,11 @@ def set_forwarding_profile( @show_app.command("forwarding-profile") @handle_command_errors("showing forwarding profile") def show_forwarding_profile( + name: str | None = typer.Argument(None, help="Name of the forwarding profile to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the forwarding profile to show"), profile_id: str | None = PROFILE_ID_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display forwarding profiles. @@ -672,7 +680,7 @@ def show_forwarding_profile( scm show mobile-agent forwarding-profile --folder "Mobile Users" # Show a specific forwarding profile by name - scm show mobile-agent forwarding-profile --folder "Mobile Users" --name "ztna-profile" + scm show mobile-agent forwarding-profile "ztna-profile" --folder "Mobile Users" # Show a specific forwarding profile by UUID scm show mobile-agent forwarding-profile --id "123e4567-e89b-12d3-a456-426655440000" @@ -684,6 +692,8 @@ def show_forwarding_profile( return profile profiles = scm_client.list_forwarding_profiles(folder=folder) + if max_results is not None: + profiles = profiles[:max_results] emit( profiles, output, @@ -753,8 +763,8 @@ def backup_forwarding_profile_destination( @delete_app.command("forwarding-profile-destination") @handle_command_errors("deleting forwarding profile destination") def delete_forwarding_profile_destination( + name: str | None = typer.Argument(None, help="Name of the forwarding profile destination to delete (omit when using --id)"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, destination_id: str | None = PROFILE_ID_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): @@ -762,9 +772,12 @@ def delete_forwarding_profile_destination( Examples -------- - scm delete mobile-agent forwarding-profile-destination --folder "Mobile Users" --name "internal-apps" + scm delete mobile-agent forwarding-profile-destination "internal-apps" --folder "Mobile Users" """ + if not destination_id and not name: + error("Error: provide NAME or --id") + raise typer.Exit(code=1) identifier = destination_id or name if not force: typer.confirm(f"Delete forwarding profile destination '{identifier}'?", abort=True) @@ -817,8 +830,8 @@ def _apply(destination_data: dict): @set_app.command("forwarding-profile-destination") @handle_command_errors("creating/updating forwarding profile destination") def set_forwarding_profile_destination( + name: str = typer.Argument(..., help="Name of the forwarding profile destination"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, fqdn: list[str] | None = FQDN_OPTION, ip_address: list[str] | None = IP_ADDRESS_OPTION, @@ -830,9 +843,8 @@ def set_forwarding_profile_destination( Examples -------- - scm set mobile-agent forwarding-profile-destination \ + scm set mobile-agent forwarding-profile-destination "internal-apps" \ --folder "Mobile Users" \ - --name "internal-apps" \ --fqdn "*.example.com:8080" \ --ip-address "10.0.0.0/8" @@ -874,10 +886,11 @@ def set_forwarding_profile_destination( @show_app.command("forwarding-profile-destination") @handle_command_errors("showing forwarding profile destination") def show_forwarding_profile_destination( + name: str | None = typer.Argument(None, help="Name of the forwarding profile destination to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the forwarding profile destination to show"), destination_id: str | None = PROFILE_ID_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display forwarding profile destinations. @@ -887,7 +900,7 @@ def show_forwarding_profile_destination( scm show mobile-agent forwarding-profile-destination --folder "Mobile Users" # Show a specific destination by name - scm show mobile-agent forwarding-profile-destination --folder "Mobile Users" --name "internal-apps" + scm show mobile-agent forwarding-profile-destination "internal-apps" --folder "Mobile Users" # Show a specific destination by UUID scm show mobile-agent forwarding-profile-destination --id "123e4567-e89b-12d3-a456-426655440000" @@ -899,6 +912,8 @@ def show_forwarding_profile_destination( return destination destinations = scm_client.list_forwarding_profile_destinations(folder=folder) + if max_results is not None: + destinations = destinations[:max_results] emit( destinations, output, @@ -1001,15 +1016,15 @@ def backup_agent_profile( @delete_app.command("agent-profile") @handle_command_errors("deleting agent profile") def delete_agent_profile( + name: str = typer.Argument(..., help="Name of the agent profile to delete"), folder: str = GP_FOLDER_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an agent profile. Examples -------- - scm delete mobile-agent agent-profile --folder "Mobile Users" --name "corp-app-settings" + scm delete mobile-agent agent-profile "corp-app-settings" --folder "Mobile Users" """ if not force: @@ -1067,8 +1082,8 @@ def _apply(profile_data: dict): @set_app.command("agent-profile") @handle_command_errors("creating/updating agent profile") def set_agent_profile( + name: str = typer.Argument(..., help="Name of the agent profile"), folder: str = GP_FOLDER_OPTION, - name: str = NAME_OPTION, os: list[str] | None = GP_OS_OPTION, connect_method: str | None = typer.Option(None, "--connect-method", help="Connect method (user-logon, pre-logon, on-demand, pre-logon-then-on-demand)"), tunnel_mtu: int | None = typer.Option(None, "--tunnel-mtu", help="GlobalProtect connection MTU in bytes (1000-1420)"), @@ -1083,9 +1098,8 @@ def set_agent_profile( Examples -------- - scm set mobile-agent agent-profile \ + scm set mobile-agent agent-profile "corp-app-settings" \ --folder "Mobile Users" \ - --name "corp-app-settings" \ --connect-method user-logon \ --tunnel-mtu 1400 \ --os Windows --os Mac @@ -1133,9 +1147,10 @@ def set_agent_profile( @show_app.command("agent-profile") @handle_command_errors("showing agent profile") def show_agent_profile( + name: str | None = typer.Argument(None, help="Name of the agent profile to show; omit to list all"), folder: str = GP_FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the agent profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display agent profiles (GlobalProtect app settings). @@ -1145,7 +1160,7 @@ def show_agent_profile( scm show mobile-agent agent-profile --folder "Mobile Users" # Show a specific agent profile by name - scm show mobile-agent agent-profile --folder "Mobile Users" --name "corp-app-settings" + scm show mobile-agent agent-profile "corp-app-settings" --folder "Mobile Users" """ if name: @@ -1154,6 +1169,8 @@ def show_agent_profile( return profile profiles = scm_client.list_agent_profiles(folder=folder) + if max_results is not None: + profiles = profiles[:max_results] emit( profiles, output, @@ -1212,15 +1229,15 @@ def backup_tunnel_profile( @delete_app.command("tunnel-profile") @handle_command_errors("deleting tunnel profile") def delete_tunnel_profile( + name: str = typer.Argument(..., help="Name of the tunnel profile to delete"), folder: str = GP_FOLDER_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a tunnel profile. Examples -------- - scm delete mobile-agent tunnel-profile --folder "Mobile Users" --name "corp-tunnel" + scm delete mobile-agent tunnel-profile "corp-tunnel" --folder "Mobile Users" """ if not force: @@ -1278,8 +1295,8 @@ def _apply(profile_data: dict): @set_app.command("tunnel-profile") @handle_command_errors("creating/updating tunnel profile") def set_tunnel_profile( + name: str = typer.Argument(..., help="Name of the tunnel profile"), folder: str = GP_FOLDER_OPTION, - name: str = NAME_OPTION, no_direct_access_to_local_network: bool | None = typer.Option( None, "--no-direct-access-to-local-network/--allow-direct-access-to-local-network", @@ -1304,9 +1321,8 @@ def set_tunnel_profile( Examples -------- - scm set mobile-agent tunnel-profile \ + scm set mobile-agent tunnel-profile "corp-tunnel" \ --folder "Mobile Users" \ - --name "corp-tunnel" \ --access-route 10.0.0.0/8 \ --no-direct-access-to-local-network @@ -1357,9 +1373,10 @@ def set_tunnel_profile( @show_app.command("tunnel-profile") @handle_command_errors("showing tunnel profile") def show_tunnel_profile( + name: str | None = typer.Argument(None, help="Name of the tunnel profile to show; omit to list all"), folder: str = GP_FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the tunnel profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display tunnel profiles (GlobalProtect tunnel settings). @@ -1369,7 +1386,7 @@ def show_tunnel_profile( scm show mobile-agent tunnel-profile --folder "Mobile Users" # Show a specific tunnel profile by name - scm show mobile-agent tunnel-profile --folder "Mobile Users" --name "corp-tunnel" + scm show mobile-agent tunnel-profile "corp-tunnel" --folder "Mobile Users" """ if name: @@ -1378,6 +1395,8 @@ def show_tunnel_profile( return profile profiles = scm_client.list_tunnel_profiles(folder=folder) + if max_results is not None: + profiles = profiles[:max_results] emit( profiles, output, @@ -1392,7 +1411,7 @@ def show_tunnel_profile( # ============================================================================================================================================================================================= # Infrastructure settings live only in the 'Mobile Users' folder, and the SCM -# API addresses them by name everywhere (including list), so show/backup need --name. +# API addresses them by name everywhere (including list), so backup needs --name. INFRA_FOLDER_OPTION = typer.Option( "Mobile Users", "--folder", @@ -1408,8 +1427,8 @@ def show_tunnel_profile( @set_app.command("infrastructure-setting") @handle_command_errors("creating/updating infrastructure setting") def set_infrastructure_setting( + name: str = typer.Argument(..., help="Name of the infrastructure setting"), folder: str = INFRA_FOLDER_OPTION, - name: str = INFRA_NAME_OPTION, dns_servers: str = typer.Option(..., "--dns-servers", help="DNS server entries as JSON list"), ip_pools: str = typer.Option(..., "--ip-pools", help="IP pools as JSON list"), portal_hostname: str = typer.Option(..., "--portal-hostname", help="Portal hostname configuration as JSON"), @@ -1422,8 +1441,7 @@ def set_infrastructure_setting( Examples -------- - scm set mobile-agent infrastructure-setting \ - --name "gp-infra" \ + scm set mobile-agent infrastructure-setting "gp-infra" \ --dns-servers '[{"name": "dns-1", "dns_suffix": ["example.com"]}]' \ --ip-pools '[{"name": "pool-1", "ip_pool": ["10.0.0.0/16"]}]' \ --portal-hostname '{"default_domain": {"hostname": "acme"}}' @@ -1473,17 +1491,19 @@ def set_infrastructure_setting( @show_app.command("infrastructure-setting") @handle_command_errors("showing infrastructure setting") def show_infrastructure_setting( + name: str = typer.Argument(..., help="Name of the infrastructure setting"), folder: str = INFRA_FOLDER_OPTION, - name: str = INFRA_NAME_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display an infrastructure setting. - The SCM API requires a name for this endpoint; there is no list-all mode. + The SCM API requires a name for this endpoint; there is no list-all mode + (--max-results is accepted for grammar consistency but has no effect). Examples -------- - scm show mobile-agent infrastructure-setting --name "gp-infra" + scm show mobile-agent infrastructure-setting "gp-infra" """ setting = scm_client.get_infrastructure_setting(folder=folder, name=name) @@ -1494,15 +1514,15 @@ def show_infrastructure_setting( @delete_app.command("infrastructure-setting") @handle_command_errors("deleting infrastructure setting") def delete_infrastructure_setting( + name: str = typer.Argument(..., help="Name of the infrastructure setting to delete"), folder: str = INFRA_FOLDER_OPTION, - name: str = INFRA_NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an infrastructure setting. Examples -------- - scm delete mobile-agent infrastructure-setting --name "gp-infra" + scm delete mobile-agent infrastructure-setting "gp-infra" """ if not force: @@ -1605,10 +1625,12 @@ def _apply(setting_data: dict): @handle_command_errors("showing global settings") def show_global_setting( output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display the GlobalProtect global settings. - Global settings are a tenant-wide singleton; there is nothing to filter by. + Global settings are a tenant-wide singleton; there is nothing to filter by + (--max-results is accepted for grammar consistency but has no effect). Examples -------- @@ -1723,15 +1745,15 @@ def backup_forwarding_profile_source_application( @delete_app.command("forwarding-profile-source-application") @handle_command_errors("deleting forwarding profile source application") def delete_forwarding_profile_source_application( + name: str = typer.Argument(..., help="Name of the forwarding profile source application to delete"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a forwarding profile source application. Examples -------- - scm delete mobile-agent forwarding-profile-source-application --folder "Mobile Users" --name "office-apps" + scm delete mobile-agent forwarding-profile-source-application "office-apps" --folder "Mobile Users" """ if not force: @@ -1790,8 +1812,8 @@ def _apply(app_data: dict): @set_app.command("forwarding-profile-source-application") @handle_command_errors("creating/updating forwarding profile source application") def set_forwarding_profile_source_application( + name: str = typer.Argument(..., help="Name of the forwarding profile source application"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, application: list[str] | None = APPLICATION_OPTION, ): @@ -1799,9 +1821,8 @@ def set_forwarding_profile_source_application( Examples -------- - scm set mobile-agent forwarding-profile-source-application \ + scm set mobile-agent forwarding-profile-source-application "office-apps" \ --folder "Mobile Users" \ - --name "office-apps" \ --application slack \ --application zoom @@ -1845,9 +1866,10 @@ def set_forwarding_profile_source_application( @show_app.command("forwarding-profile-source-application") @handle_command_errors("showing forwarding profile source application") def show_forwarding_profile_source_application( + name: str | None = typer.Argument(None, help="Name of the source application to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the source application to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display forwarding profile source applications. @@ -1857,7 +1879,7 @@ def show_forwarding_profile_source_application( scm show mobile-agent forwarding-profile-source-application --folder "Mobile Users" # Show a specific source application by name - scm show mobile-agent forwarding-profile-source-application --folder "Mobile Users" --name "office-apps" + scm show mobile-agent forwarding-profile-source-application "office-apps" --folder "Mobile Users" """ if name: @@ -1868,6 +1890,8 @@ def show_forwarding_profile_source_application( # Default: list all source applications apps_list = scm_client.list_forwarding_profile_source_applications(folder=folder) + if max_results is not None: + apps_list = apps_list[:max_results] emit( apps_list, output, @@ -1955,15 +1979,15 @@ def backup_forwarding_profile_user_location( @delete_app.command("forwarding-profile-user-location") @handle_command_errors("deleting forwarding profile user location") def delete_forwarding_profile_user_location( + name: str = typer.Argument(..., help="Name of the forwarding profile user location to delete"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a forwarding profile user location. Examples -------- - scm delete mobile-agent forwarding-profile-user-location --folder "Mobile Users" --name "branch-network" + scm delete mobile-agent forwarding-profile-user-location "branch-network" --folder "Mobile Users" """ if not force: @@ -2022,8 +2046,8 @@ def _apply(location_data: dict): @set_app.command("forwarding-profile-user-location") @handle_command_errors("creating/updating forwarding profile user location") def set_forwarding_profile_user_location( + name: str = typer.Argument(..., help="Name of the forwarding profile user location"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, internal_host_ip: str | None = INTERNAL_HOST_IP_OPTION, internal_host_fqdn: str | None = INTERNAL_HOST_FQDN_OPTION, @@ -2037,15 +2061,13 @@ def set_forwarding_profile_user_location( Examples -------- # IP address based location - scm set mobile-agent forwarding-profile-user-location \ + scm set mobile-agent forwarding-profile-user-location "branch-network" \ --folder "Mobile Users" \ - --name "branch-network" \ --ip-address "10.1.0.0/16" # Internal host detection based location - scm set mobile-agent forwarding-profile-user-location \ + scm set mobile-agent forwarding-profile-user-location "corp-office" \ --folder "Mobile Users" \ - --name "corp-office" \ --internal-host-fqdn "intranet.example.com" """ @@ -2092,9 +2114,10 @@ def set_forwarding_profile_user_location( @show_app.command("forwarding-profile-user-location") @handle_command_errors("showing forwarding profile user location") def show_forwarding_profile_user_location( + name: str | None = typer.Argument(None, help="Name of the user location to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the user location to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display forwarding profile user locations. @@ -2104,7 +2127,7 @@ def show_forwarding_profile_user_location( scm show mobile-agent forwarding-profile-user-location --folder "Mobile Users" # Show a specific user location by name - scm show mobile-agent forwarding-profile-user-location --folder "Mobile Users" --name "branch-network" + scm show mobile-agent forwarding-profile-user-location "branch-network" --folder "Mobile Users" """ if name: @@ -2115,6 +2138,8 @@ def show_forwarding_profile_user_location( # Default: list all user locations locations_list = scm_client.list_forwarding_profile_user_locations(folder=folder) + if max_results is not None: + locations_list = locations_list[:max_results] emit( locations_list, output, @@ -2223,15 +2248,15 @@ def backup_forwarding_profile_regional_and_custom_proxy( @delete_app.command("forwarding-profile-regional-and-custom-proxy") @handle_command_errors("deleting forwarding profile regional and custom proxy") def delete_forwarding_profile_regional_and_custom_proxy( + name: str = typer.Argument(..., help="Name of the forwarding profile regional and custom proxy to delete"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a forwarding profile regional and custom proxy. Examples -------- - scm delete mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" --name "emea-proxy" + scm delete mobile-agent forwarding-profile-regional-and-custom-proxy "emea-proxy" --folder "Mobile Users" """ if not force: @@ -2293,8 +2318,8 @@ def _apply(proxy_data: dict): @set_app.command("forwarding-profile-regional-and-custom-proxy") @handle_command_errors("creating/updating forwarding profile regional and custom proxy") def set_forwarding_profile_regional_and_custom_proxy( + name: str = typer.Argument(..., help="Name of the forwarding profile regional and custom proxy"), folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, type: str | None = PROXY_TYPE_OPTION, proxy_1_fqdn: str | None = PROXY_1_FQDN_OPTION, @@ -2313,9 +2338,8 @@ def set_forwarding_profile_regional_and_custom_proxy( Examples -------- - scm set mobile-agent forwarding-profile-regional-and-custom-proxy \ + scm set mobile-agent forwarding-profile-regional-and-custom-proxy "emea-proxy" \ --folder "Mobile Users" \ - --name "emea-proxy" \ --type gp-and-pac \ --proxy-1-fqdn "proxy1.example.com" \ --proxy-1-port 8080 \ @@ -2386,9 +2410,10 @@ def set_forwarding_profile_regional_and_custom_proxy( @show_app.command("forwarding-profile-regional-and-custom-proxy") @handle_command_errors("showing forwarding profile regional and custom proxy") def show_forwarding_profile_regional_and_custom_proxy( + name: str | None = typer.Argument(None, help="Name of the regional and custom proxy to show; omit to list all"), folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the regional and custom proxy to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display forwarding profile regional and custom proxies. @@ -2398,7 +2423,7 @@ def show_forwarding_profile_regional_and_custom_proxy( scm show mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" # Show a specific regional and custom proxy by name - scm show mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" --name "emea-proxy" + scm show mobile-agent forwarding-profile-regional-and-custom-proxy "emea-proxy" --folder "Mobile Users" """ if name: @@ -2409,6 +2434,8 @@ def show_forwarding_profile_regional_and_custom_proxy( # Default: list all regional and custom proxies proxies_list = scm_client.list_forwarding_profile_regional_and_custom_proxies(folder=folder) + if max_results is not None: + proxies_list = proxies_list[:max_results] emit( proxies_list, output, diff --git a/src/scm_cli/commands/network.py b/src/scm_cli/commands/network.py index 2574d60..e9b2960 100644 --- a/src/scm_cli/commands/network.py +++ b/src/scm_cli/commands/network.py @@ -63,16 +63,6 @@ # ============================================================================================================================================================================================= # Define typer option constants -FOLDER_OPTION = typer.Option( - ..., - "--folder", - help="Folder path for the zone", -) -NAME_OPTION = typer.Option( - ..., - "--name", - help="Name of the zone", -) MODE_OPTION = typer.Option( ..., "--mode", @@ -122,25 +112,15 @@ ) # NAT rule option constants -NAT_FOLDER_OPTION = typer.Option( - ..., - "--folder", - help="Folder path for the NAT rule", -) -NAT_NAME_OPTION = typer.Option( - ..., - "--name", - help="Name of the NAT rule", -) NAT_DESCRIPTION_OPTION = typer.Option( None, "--description", help="Description of the NAT rule", ) -NAT_TAG_OPTION = typer.Option( +NAT_TAGS_OPTION = typer.Option( None, - "--tag", - help="Tags for the NAT rule", + "--tags", + help="Tags for the NAT rule (repeat for multiple)", ) NAT_DISABLED_OPTION = typer.Option( False, @@ -211,8 +191,6 @@ ) # IPsec crypto profile option constants (module-level to avoid B008) -IPSEC_FOLDER_OPTION = typer.Option("Texas", "--folder", help="Folder path for the IPsec crypto profile") -IPSEC_NAME_OPTION = typer.Option(..., "--name", help="Name of the IPsec crypto profile") IPSEC_ESP_ENCRYPTION_OPTION: list[str] = typer.Option( ["aes-256-cbc"], "--esp-encryption", @@ -323,6 +301,7 @@ def load_ike_crypto_profile( folder: str = typer.Option(None, "--folder", help="Override folder location"), snippet: str = typer.Option(None, "--snippet", help="Override snippet location"), device: str = typer.Option(None, "--device", help="Override device location"), + dry_run: bool = DRY_RUN_OPTION, ) -> None: """Load IKE crypto profiles from a YAML file.""" if not Path(file).exists(): @@ -336,6 +315,16 @@ def load_ike_crypto_profile( profiles = data["ike_crypto_profiles"] if not isinstance(profiles, list): profiles = [profiles] + + if dry_run: + info("Dry run mode: would apply the following configurations:") + if folder or snippet or device: + override_type = "folder" if folder else ("snippet" if snippet else "device") + override_value = folder or snippet or device + info(f"Container override: {override_type} = '{override_value}'") + typer.echo(yaml.dump(profiles)) + return None + created_count = 0 def _apply(profile_data: dict): @@ -411,11 +400,12 @@ def set_ike_crypto_profile( @show_app.command("ike-crypto-profile", help="Show IKE crypto profile details.") @handle_command_errors("showing IKE crypto profile") def show_ike_crypto_profile( - name: str = typer.Option(None, "--name", help="Name of specific IKE crypto profile to show"), + name: str | None = typer.Argument(None, help="Name of the IKE crypto profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show IKE crypto profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -428,6 +418,8 @@ def show_ike_crypto_profile( return profile else: profiles = scm_client.list_ike_crypto_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"IKE Crypto Profiles in {location_type} '{location_value}'") return profiles @@ -602,11 +594,12 @@ def set_aggregate_interface( @show_app.command("aggregate-interface", help="Show aggregate interface details.") @handle_command_errors("showing aggregate interface") def show_aggregate_interface( - name: str = typer.Option(None, "--name", help="Name of specific aggregate interface to show"), + name: str | None = typer.Argument(None, help="Name of the aggregate interface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show aggregate interface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -619,6 +612,8 @@ def show_aggregate_interface( return iface else: interfaces = scm_client.list_aggregate_interfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"Aggregate Interfaces in {location_type} '{location_value}'") return interfaces @@ -870,11 +865,12 @@ def set_ike_gateway( @show_app.command("ike-gateway", help="Show IKE gateway details.") @handle_command_errors("showing IKE gateway") def show_ike_gateway( - name: str = typer.Option(None, "--name", help="Name of specific IKE gateway to show"), + name: str | None = typer.Argument(None, help="Name of the IKE gateway to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show IKE gateway details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -887,6 +883,8 @@ def show_ike_gateway( return gateway else: gateways = scm_client.list_ike_gateways(folder=folder, snippet=snippet, device=device) + if max_results is not None: + gateways = gateways[:max_results] emit(gateways, output, title=f"IKE Gateways in {location_type} '{location_value}'") return gateways @@ -959,23 +957,26 @@ def backup_security_zone( @delete_app.command("zone") @handle_command_errors("deleting security zone") def delete_zone( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the security zone to delete"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a security zone. - Example: scm delete network zone --folder Texas --name trust + Example: scm delete network zone trust --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - typer.confirm(f"Delete zone '{name}' from folder '{folder}'?", abort=True) + typer.confirm(f"Delete zone '{name}' from {location_type} '{location_value}'?", abort=True) # Call the SDK client to delete the zone - result = scm_client.delete_zone(folder=folder, name=name) + result = scm_client.delete_zone(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted zone: {name} from folder {folder}") + success(f"Deleted zone: {name} from {location_type} {location_value}") else: - error(f"Zone not found: {name} in folder {folder}") + error(f"Zone not found: {name} in {location_type} {location_value}") raise typer.Exit(code=1) @@ -1003,21 +1004,26 @@ def _apply(zone_data: dict): # Convert to the SDK model and create the zone sdk_data = zone.to_sdk_model() - return scm_client.create_zone( + result = scm_client.create_zone( folder=zone.folder, + snippet=zone.snippet, + device=zone.device, name=sdk_data["name"], mode=sdk_data["mode"], interfaces=sdk_data["interfaces"], ) + return zone, result # Apply each zone results = [] - for _item, result, exc in run_bulk(config["security_zones"], _apply): + for _item, outcome, exc in run_bulk(config["security_zones"], _apply): if exc is not None: raise exc # abort on first error, matching the previous sequential behavior + zone, result = outcome results.append(result) - success(f"Applied zone: {result['name']} in folder {result['folder']}") + container = zone.folder or zone.snippet or zone.device + success(f"Applied zone: {result['name']} in {container}") return results @@ -1025,8 +1031,10 @@ def _apply(zone_data: dict): @set_app.command("zone") @handle_command_errors("creating security zone") def set_zone( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the security zone"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), mode: str = MODE_OPTION, interfaces: list[str] | None = INTERFACES_OPTION, enable_user_id: bool | None = ENABLE_USER_ID_OPTION, @@ -1035,10 +1043,11 @@ def set_zone( Example: ------- - scm set network zone --folder Texas --name trust --mode layer3 \ + scm set network zone trust --folder Texas --mode layer3 \ --interfaces ["ethernet1/1"] --enable-user-id """ + location_type, location_value = validate_location_params(folder, snippet, device) # Validate mode parameter valid_modes = ["layer3", "layer2", "virtual-wire", "tap", "external", "tunnel"] if mode not in valid_modes: @@ -1066,9 +1075,8 @@ def set_zone( network=network_config, description=None, tags=None, - # Add None defaults for optional fields - snippet=None, - device=None, + snippet=snippet, + device=device, enable_user_identification=enable_user_id, enable_device_identification=None, ) @@ -1079,6 +1087,8 @@ def set_zone( result = scm_client.create_zone( folder=zone.folder, + snippet=zone.snippet, + device=zone.device, name=zone.name, mode=sdk_model["mode"], interfaces=sdk_model["interfaces"], @@ -1088,20 +1098,23 @@ def set_zone( action = result.get("__action__", "created") if action == "created": - success(f"Created zone: {result['name']} in folder {result['folder']}") + success(f"Created zone: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated zone: {result['name']} in folder {result['folder']}") + success(f"Updated zone: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for zone: {result['name']} in folder {result['folder']}") + info(f"No changes needed for zone: {result['name']} in {location_type} {location_value}") return result @show_app.command("zone") @handle_command_errors("showing security zone") def show_zone( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the security zone to show"), + name: str | None = typer.Argument(None, help="Name of the security zone to show; omit to list all"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ): """Display security zones. @@ -1111,16 +1124,19 @@ def show_zone( scm show network zone --folder Texas # Show a specific security zone by name - scm show network zone --folder Texas --name trust + scm show network zone trust --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if name: - zone = scm_client.get_security_zone(folder=folder, name=name) + zone = scm_client.get_security_zone(folder=folder, snippet=snippet, device=device, name=name) emit(zone, output, title=f"Security Zone: {name}") return zone else: - zones = scm_client.list_security_zones(folder=folder) - emit(zones, output, title=f"Security Zones in folder '{folder}'") + zones = scm_client.list_security_zones(folder=folder, snippet=snippet, device=device) + if max_results is not None: + zones = zones[:max_results] + emit(zones, output, title=f"Security Zones in {location_type} '{location_value}'") return zones @@ -1177,22 +1193,25 @@ def backup_ipsec_crypto_profile( @delete_app.command("ipsec-crypto-profile") @handle_command_errors("deleting IPsec crypto profile") def delete_ipsec_crypto_profile( - folder: str = IPSEC_FOLDER_OPTION, - name: str = IPSEC_NAME_OPTION, + name: str = typer.Argument(..., help="Name of the IPsec crypto profile to delete"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an IPsec crypto profile. - Example: scm delete network ipsec-crypto-profile --folder Texas --name my-profile + Example: scm delete network ipsec-crypto-profile my-profile --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - typer.confirm(f"Delete IPsec crypto profile '{name}' from folder '{folder}'?", abort=True) - result = scm_client.delete_ipsec_crypto_profile(folder=folder, name=name) + typer.confirm(f"Delete IPsec crypto profile '{name}' from {location_type} '{location_value}'?", abort=True) + result = scm_client.delete_ipsec_crypto_profile(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted IPsec crypto profile: {name} from folder {folder}") + success(f"Deleted IPsec crypto profile: {name} from {location_type} {location_value}") else: - error(f"IPsec crypto profile not found: {name} in folder {folder}") + error(f"IPsec crypto profile not found: {name} in {location_type} {location_value}") raise typer.Exit(code=1) @@ -1218,7 +1237,9 @@ def _apply(profile_data: dict): sdk_data = profile.to_sdk_model() return scm_client.create_ipsec_crypto_profile( - folder=profile.folder or "Texas", + folder=profile.folder, + snippet=profile.snippet, + device=profile.device, name=sdk_data["name"], esp_encryption=sdk_data["esp"]["encryption"], esp_authentication=sdk_data["esp"]["authentication"], @@ -1242,8 +1263,10 @@ def _apply(profile_data: dict): @set_app.command("ipsec-crypto-profile") @handle_command_errors("creating IPsec crypto profile") def set_ipsec_crypto_profile( - folder: str = IPSEC_FOLDER_OPTION, - name: str = IPSEC_NAME_OPTION, + name: str = typer.Argument(..., help="Name of the IPsec crypto profile"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), esp_encryption: list[str] = IPSEC_ESP_ENCRYPTION_OPTION, esp_authentication: list[str] = IPSEC_ESP_AUTHENTICATION_OPTION, dh_group: str = IPSEC_DH_GROUP_OPTION, @@ -1254,14 +1277,15 @@ def set_ipsec_crypto_profile( Example: ------- - scm set network ipsec-crypto-profile --folder Texas --name my-profile \ + scm set network ipsec-crypto-profile my-profile --folder Texas \ --esp-encryption aes-256-cbc --esp-authentication sha256 --dh-group group14 """ + location_type, location_value = validate_location_params(folder, snippet, device) profile = IPSecCryptoProfile( folder=folder, - snippet=None, - device=None, + snippet=snippet, + device=device, name=name, esp_encryption=esp_encryption, esp_authentication=esp_authentication, @@ -1280,6 +1304,8 @@ def set_ipsec_crypto_profile( result = scm_client.create_ipsec_crypto_profile( folder=folder, + snippet=snippet, + device=device, name=name, esp_encryption=sdk_data["esp"]["encryption"], esp_authentication=sdk_data["esp"]["authentication"], @@ -1290,20 +1316,23 @@ def set_ipsec_crypto_profile( action = result.get("__action__", "created") if action == "created": - success(f"Created IPsec crypto profile: {result['name']} in folder {result.get('folder', folder)}") + success(f"Created IPsec crypto profile: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated IPsec crypto profile: {result['name']} in folder {result.get('folder', folder)}") + success(f"Updated IPsec crypto profile: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for IPsec crypto profile: {result['name']} in folder {result.get('folder', folder)}") + info(f"No changes needed for IPsec crypto profile: {result['name']} in {location_type} {location_value}") return result @show_app.command("ipsec-crypto-profile") @handle_command_errors("showing IPsec crypto profile") def show_ipsec_crypto_profile( - folder: str = IPSEC_FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the IPsec crypto profile to show"), + name: str | None = typer.Argument(None, help="Name of the IPsec crypto profile to show; omit to list all"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ): """Display IPsec crypto profiles. @@ -1313,16 +1342,19 @@ def show_ipsec_crypto_profile( scm show network ipsec-crypto-profile --folder Texas # Show a specific IPsec crypto profile - scm show network ipsec-crypto-profile --folder Texas --name my-profile + scm show network ipsec-crypto-profile my-profile --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if name: - profile = scm_client.get_ipsec_crypto_profile(folder=folder, name=name) + profile = scm_client.get_ipsec_crypto_profile(folder=folder, snippet=snippet, device=device, name=name) emit(profile, output, title=f"IPsec Crypto Profile: {name}") return profile else: - profiles = scm_client.list_ipsec_crypto_profiles(folder=folder) - emit(profiles, output, title=f"IPsec Crypto Profiles in folder '{folder}'") + profiles = scm_client.list_ipsec_crypto_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] + emit(profiles, output, title=f"IPsec Crypto Profiles in {location_type} '{location_value}'") return profiles @@ -1379,22 +1411,25 @@ def backup_nat_rule( @delete_app.command("nat-rule") @handle_command_errors("deleting NAT rule") def delete_nat_rule( - folder: str = NAT_FOLDER_OPTION, - name: str = NAT_NAME_OPTION, + name: str = typer.Argument(..., help="Name of the NAT rule to delete"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a NAT rule. - Example: scm delete network nat-rule --folder Texas --name outbound-nat + Example: scm delete network nat-rule outbound-nat --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - typer.confirm(f"Delete NAT rule '{name}' from folder '{folder}'?", abort=True) - result = scm_client.delete_nat_rule(folder=folder, name=name) + typer.confirm(f"Delete NAT rule '{name}' from {location_type} '{location_value}'?", abort=True) + result = scm_client.delete_nat_rule(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted NAT rule: {name} from folder {folder}") + success(f"Deleted NAT rule: {name} from {location_type} {location_value}") else: - error(f"NAT rule not found: {name} in folder {folder}") + error(f"NAT rule not found: {name} in {location_type} {location_value}") raise typer.Exit(code=1) @@ -1513,10 +1548,12 @@ def load_nat_rule( @set_app.command("nat-rule") @handle_command_errors("creating NAT rule") def set_nat_rule( - folder: str = NAT_FOLDER_OPTION, - name: str = NAT_NAME_OPTION, + name: str = typer.Argument(..., help="Name of the NAT rule"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), description: str | None = NAT_DESCRIPTION_OPTION, - tag: list[str] | None = NAT_TAG_OPTION, + tags: list[str] | None = NAT_TAGS_OPTION, disabled: bool = NAT_DISABLED_OPTION, nat_type: str = NAT_NAT_TYPE_OPTION, from_zone: list[str] | None = NAT_FROM_ZONES_OPTION, @@ -1532,20 +1569,23 @@ def set_nat_rule( Example: ------- - scm set network nat-rule --folder Texas --name outbound-nat \ + scm set network nat-rule outbound-nat --folder Texas \ --from-zone trust --to-zone untrust --source any --destination any \ --source-translation '{"dynamic_ip_and_port": {"type": "dynamic_ip_and_port", "translated_address": ["10.0.0.1"]}}' """ + location_type, location_value = validate_location_params(folder, snippet, device) # Parse JSON strings for translation configs src_translation = json.loads(source_translation) if source_translation else None dst_translation = json.loads(destination_translation) if destination_translation else None result = scm_client.create_nat_rule( folder=folder, + snippet=snippet, + device=device, name=name, description=description, - tag=tag, + tag=tags, disabled=disabled, nat_type=nat_type, from_zones=from_zone or ["any"], @@ -1561,11 +1601,11 @@ def set_nat_rule( action = result.pop("__action__", "created") if action == "created": - success(f"Created NAT rule: {result['name']} in folder {folder}") + success(f"Created NAT rule: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated NAT rule: {result['name']} in folder {folder}") + success(f"Updated NAT rule: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for NAT rule: {result['name']} in folder {folder}") + info(f"No changes needed for NAT rule: {result['name']} in {location_type} {location_value}") return result @@ -1573,9 +1613,12 @@ def set_nat_rule( @show_app.command("nat-rule") @handle_command_errors("showing NAT rule") def show_nat_rule( - folder: str = NAT_FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the NAT rule to show"), + name: str | None = typer.Argument(None, help="Name of the NAT rule to show; omit to list all"), + folder: str = typer.Option(None, "--folder", help="Folder location"), + snippet: str = typer.Option(None, "--snippet", help="Snippet location"), + device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ): """Display NAT rules. @@ -1585,16 +1628,19 @@ def show_nat_rule( scm show network nat-rule --folder Texas # Show a specific NAT rule by name - scm show network nat-rule --folder Texas --name outbound-nat + scm show network nat-rule outbound-nat --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if name: - rule = scm_client.get_nat_rule(folder=folder, name=name) + rule = scm_client.get_nat_rule(folder=folder, snippet=snippet, device=device, name=name) emit(rule, output, title=f"NAT Rule: {name}") return rule else: - rules = scm_client.list_nat_rules(folder=folder) - emit(rules, output, title=f"NAT Rules in folder '{folder}'") + rules = scm_client.list_nat_rules(folder=folder, snippet=snippet, device=device) + if max_results is not None: + rules = rules[:max_results] + emit(rules, output, title=f"NAT Rules in {location_type} '{location_value}'") return rules @@ -1762,11 +1808,12 @@ def set_dhcp_interface( @show_app.command("dhcp-interface", help="Show DHCP interface details.") @handle_command_errors("showing DHCP interface") def show_dhcp_interface( - name: str = typer.Option(None, "--name", help="Name of specific DHCP interface to show"), + name: str | None = typer.Argument(None, help="Name of the DHCP interface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show DHCP interface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -1779,6 +1826,8 @@ def show_dhcp_interface( return iface else: interfaces = scm_client.list_dhcp_interfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"DHCP Interfaces in {location_type} '{location_value}'") return interfaces @@ -1959,11 +2008,12 @@ def set_ethernet_interface( @show_app.command("ethernet-interface", help="Show ethernet interface details.") @handle_command_errors("showing ethernet interface") def show_ethernet_interface( - name: str = typer.Option(None, "--name", help="Name of specific ethernet interface to show"), + name: str | None = typer.Argument(None, help="Name of the ethernet interface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show ethernet interface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -1976,6 +2026,8 @@ def show_ethernet_interface( return iface else: interfaces = scm_client.list_ethernet_interfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"Ethernet Interfaces in {location_type} '{location_value}'") return interfaces @@ -2145,11 +2197,12 @@ def set_layer2_subinterface( @show_app.command("layer2-subinterface", help="Show layer2 subinterface details.") @handle_command_errors("showing layer2 subinterface") def show_layer2_subinterface( - name: str = typer.Option(None, "--name", help="Name of specific layer2 subinterface to show"), + name: str | None = typer.Argument(None, help="Name of the layer2 subinterface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show layer2 subinterface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -2162,6 +2215,8 @@ def show_layer2_subinterface( return iface else: interfaces = scm_client.list_layer2_subinterfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"Layer2 Subinterfaces in {location_type} '{location_value}'") return interfaces @@ -2305,7 +2360,7 @@ def set_layer3_subinterface( folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), - tag: int = typer.Option(None, "--tag", help="VLAN tag (1-4096)"), + vlan_tag: int = typer.Option(None, "--vlan-tag", help="VLAN tag (1-4096)"), parent_interface: str = typer.Option(None, "--parent-interface", help="Parent interface name"), comment: str = typer.Option(None, "--comment", help="Interface description/comment"), mtu: int = typer.Option(None, "--mtu", help="MTU (576-9216)"), @@ -2315,8 +2370,8 @@ def set_layer3_subinterface( """Create or update a layer3 subinterface.""" location_type, location_value = validate_location_params(folder, snippet, device) iface_data: dict[str, Any] = {"name": name, location_type: location_value} - if tag is not None: - iface_data["tag"] = tag + if vlan_tag is not None: + iface_data["tag"] = vlan_tag if parent_interface: iface_data["parent_interface"] = parent_interface if comment: @@ -2342,11 +2397,12 @@ def set_layer3_subinterface( @show_app.command("layer3-subinterface", help="Show layer3 subinterface details.") @handle_command_errors("showing layer3 subinterface") def show_layer3_subinterface( - name: str = typer.Option(None, "--name", help="Name of specific layer3 subinterface to show"), + name: str | None = typer.Argument(None, help="Name of the layer3 subinterface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show layer3 subinterface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -2359,6 +2415,8 @@ def show_layer3_subinterface( return iface else: interfaces = scm_client.list_layer3_subinterfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"Layer3 Subinterfaces in {location_type} '{location_value}'") return interfaces @@ -2536,11 +2594,12 @@ def set_loopback_interface( @show_app.command("loopback-interface", help="Show loopback interface details.") @handle_command_errors("showing loopback interface") def show_loopback_interface( - name: str = typer.Option(None, "--name", help="Name of specific loopback interface to show"), + name: str | None = typer.Argument(None, help="Name of the loopback interface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show loopback interface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -2553,6 +2612,8 @@ def show_loopback_interface( return iface else: interfaces = scm_client.list_loopback_interfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"Loopback Interfaces in {location_type} '{location_value}'") return interfaces @@ -2727,11 +2788,12 @@ def set_tunnel_interface( @show_app.command("tunnel-interface", help="Show tunnel interface details.") @handle_command_errors("showing tunnel interface") def show_tunnel_interface( - name: str = typer.Option(None, "--name", help="Name of specific tunnel interface to show"), + name: str | None = typer.Argument(None, help="Name of the tunnel interface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show tunnel interface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -2744,6 +2806,8 @@ def show_tunnel_interface( return iface else: interfaces = scm_client.list_tunnel_interfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"Tunnel Interfaces in {location_type} '{location_value}'") return interfaces @@ -2924,11 +2988,12 @@ def set_vlan_interface( @show_app.command("vlan-interface", help="Show VLAN interface details.") @handle_command_errors("showing VLAN interface") def show_vlan_interface( - name: str = typer.Option(None, "--name", help="Name of specific VLAN interface to show"), + name: str | None = typer.Argument(None, help="Name of the VLAN interface to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show VLAN interface details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -2941,6 +3006,8 @@ def show_vlan_interface( return iface else: interfaces = scm_client.list_vlan_interfaces(folder=folder, snippet=snippet, device=device) + if max_results is not None: + interfaces = interfaces[:max_results] emit(interfaces, output, title=f"VLAN Interfaces in {location_type} '{location_value}'") return interfaces @@ -3103,11 +3170,12 @@ def set_bgp_address_family_profile( @show_app.command("bgp-address-family-profile", help="Show BGP address family profile details.") @handle_command_errors("showing BGP address family profile") def show_bgp_address_family_profile( - name: str = typer.Option(None, "--name", help="Name of specific profile to show"), + name: str | None = typer.Argument(None, help="Name of the profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show BGP address family profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -3120,6 +3188,8 @@ def show_bgp_address_family_profile( return profile else: profiles = scm_client.list_bgp_address_family_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"BGP Address Family Profiles in {location_type} '{location_value}'") return profiles @@ -3276,11 +3346,12 @@ def set_bgp_auth_profile( @show_app.command("bgp-auth-profile", help="Show BGP auth profile details.") @handle_command_errors("showing BGP auth profile") def show_bgp_auth_profile( - name: str = typer.Option(None, "--name", help="Name of specific profile to show"), + name: str | None = typer.Argument(None, help="Name of the profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show BGP auth profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -3293,6 +3364,8 @@ def show_bgp_auth_profile( return profile else: profiles = scm_client.list_bgp_auth_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"BGP Auth Profiles in {location_type} '{location_value}'") return profiles @@ -3446,11 +3519,12 @@ def set_ospf_auth_profile( @show_app.command("ospf-auth-profile", help="Show OSPF auth profile details.") @handle_command_errors("showing OSPF auth profile") def show_ospf_auth_profile( - name: str = typer.Option(None, "--name", help="Name of specific profile to show"), + name: str | None = typer.Argument(None, help="Name of the profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show OSPF auth profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -3463,6 +3537,8 @@ def show_ospf_auth_profile( return profile else: profiles = scm_client.list_ospf_auth_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"OSPF Auth Profiles in {location_type} '{location_value}'") return profiles @@ -3622,11 +3698,12 @@ def set_route_access_list( @show_app.command("route-access-list", help="Show route access list details.") @handle_command_errors("showing route access list") def show_route_access_list( - name: str = typer.Option(None, "--name", help="Name of specific route access list to show"), + name: str | None = typer.Argument(None, help="Name of the route access list to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show route access list details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -3639,6 +3716,8 @@ def show_route_access_list( return item else: items = scm_client.list_route_access_lists(folder=folder, snippet=snippet, device=device) + if max_results is not None: + items = items[:max_results] emit(items, output, title=f"Route Access Lists in {location_type} '{location_value}'") return items @@ -3798,11 +3877,12 @@ def set_route_prefix_list( @show_app.command("route-prefix-list", help="Show route prefix list details.") @handle_command_errors("showing route prefix list") def show_route_prefix_list( - name: str = typer.Option(None, "--name", help="Name of specific route prefix list to show"), + name: str | None = typer.Argument(None, help="Name of the route prefix list to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show route prefix list details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -3815,6 +3895,8 @@ def show_route_prefix_list( return item else: items = scm_client.list_route_prefix_lists(folder=folder, snippet=snippet, device=device) + if max_results is not None: + items = items[:max_results] emit(items, output, title=f"Route Prefix Lists in {location_type} '{location_value}'") return items @@ -3962,11 +4044,12 @@ def set_bgp_filtering_profile( @show_app.command("bgp-filtering-profile", help="Show BGP filtering profile details.") @handle_command_errors("showing BGP filtering profile") def show_bgp_filtering_profile( - name: str = typer.Option(None, "--name", help="Name of specific profile to show"), + name: str | None = typer.Argument(None, help="Name of the profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show BGP filtering profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -3979,6 +4062,8 @@ def show_bgp_filtering_profile( return profile else: profiles = scm_client.list_bgp_filtering_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"BGP Filtering Profiles in {location_type} '{location_value}'") return profiles @@ -4126,11 +4211,12 @@ def set_bgp_redistribution_profile( @show_app.command("bgp-redistribution-profile", help="Show BGP redistribution profile details.") @handle_command_errors("showing BGP redistribution profile") def show_bgp_redistribution_profile( - name: str = typer.Option(None, "--name", help="Name of specific profile to show"), + name: str | None = typer.Argument(None, help="Name of the profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show BGP redistribution profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -4143,6 +4229,8 @@ def show_bgp_redistribution_profile( return profile else: profiles = scm_client.list_bgp_redistribution_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"BGP Redistribution Profiles in {location_type} '{location_value}'") return profiles @@ -4290,11 +4378,12 @@ def set_bgp_route_map( @show_app.command("bgp-route-map", help="Show BGP route map details.") @handle_command_errors("showing BGP route map") def show_bgp_route_map( - name: str = typer.Option(None, "--name", help="Name of specific BGP route map to show"), + name: str | None = typer.Argument(None, help="Name of the BGP route map to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show BGP route map details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -4307,6 +4396,8 @@ def show_bgp_route_map( return item else: items = scm_client.list_bgp_route_maps(folder=folder, snippet=snippet, device=device) + if max_results is not None: + items = items[:max_results] emit(items, output, title=f"BGP Route Maps in {location_type} '{location_value}'") return items @@ -4460,11 +4551,12 @@ def set_bgp_route_map_redistribution( @show_app.command("bgp-route-map-redistribution", help="Show BGP route map redistribution details.") @handle_command_errors("showing BGP route map redistribution") def show_bgp_route_map_redistribution( - name: str = typer.Option(None, "--name", help="Name of specific BGP route map redistribution to show"), + name: str | None = typer.Argument(None, help="Name of the BGP route map redistribution to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show BGP route map redistribution details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -4477,6 +4569,8 @@ def show_bgp_route_map_redistribution( return item else: items = scm_client.list_bgp_route_map_redistributions(folder=folder, snippet=snippet, device=device) + if max_results is not None: + items = items[:max_results] emit(items, output, title=f"BGP Route Map Redistributions in {location_type} '{location_value}'") return items @@ -4645,11 +4739,12 @@ def set_dns_proxy( @show_app.command("dns-proxy", help="Show DNS proxy details.") @handle_command_errors("showing DNS proxy") def show_dns_proxy( - name: str = typer.Option(None, "--name", help="Name of specific DNS proxy to show"), + name: str | None = typer.Argument(None, help="Name of the DNS proxy to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show DNS proxy details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -4662,6 +4757,8 @@ def show_dns_proxy( return proxy else: proxies = scm_client.list_dns_proxies(folder=folder, snippet=snippet, device=device) + if max_results is not None: + proxies = proxies[:max_results] emit(proxies, output, title=f"DNS Proxies in {location_type} '{location_value}'") return proxies @@ -4827,11 +4924,12 @@ def set_pbf_rule( @show_app.command("pbf-rule", help="Show PBF rule details.") @handle_command_errors("showing PBF rule") def show_pbf_rule( - name: str = typer.Option(None, "--name", help="Name of specific PBF rule to show"), + name: str | None = typer.Argument(None, help="Name of the PBF rule to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show PBF rule details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -4844,6 +4942,8 @@ def show_pbf_rule( return rule else: rules = scm_client.list_pbf_rules(folder=folder, snippet=snippet, device=device) + if max_results is not None: + rules = rules[:max_results] emit(rules, output, title=f"PBF Rules in {location_type} '{location_value}'") return rules @@ -5012,11 +5112,12 @@ def set_qos_profile( @show_app.command("qos-profile", help="Show QoS profile details.") @handle_command_errors("showing QoS profile") def show_qos_profile( - name: str = typer.Option(None, "--name", help="Name of specific QoS profile to show"), + name: str | None = typer.Argument(None, help="Name of the QoS profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show QoS profile details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -5030,6 +5131,8 @@ def show_qos_profile( return profile else: profiles = scm_client.list_qos_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + profiles = profiles[:max_results] emit(profiles, output, title=f"QoS Profiles in {location_type} '{location_value}'") return profiles @@ -5198,11 +5301,12 @@ def set_qos_rule( @show_app.command("qos-rule", help="Show QoS rule details.") @handle_command_errors("showing QoS rule") def show_qos_rule( - name: str = typer.Option(None, "--name", help="Name of specific QoS rule to show"), + name: str | None = typer.Argument(None, help="Name of the QoS rule to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder location"), snippet: str = typer.Option(None, "--snippet", help="Snippet location"), device: str = typer.Option(None, "--device", help="Device location"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = typer.Option(None, "--max-results", help="Maximum number of results to display"), ) -> None: """Show QoS rule details.""" location_type, location_value = validate_location_params(folder, snippet, device) @@ -5215,5 +5319,7 @@ def show_qos_rule( return rule else: rules = scm_client.list_qos_rules(folder=folder, snippet=snippet, device=device) + if max_results is not None: + rules = rules[:max_results] emit(rules, output, title=f"QoS Rules in {location_type} '{location_value}'") return rules diff --git a/src/scm_cli/commands/objects.py b/src/scm_cli/commands/objects.py index 1f013ac..11b505e 100644 --- a/src/scm_cli/commands/objects.py +++ b/src/scm_cli/commands/objects.py @@ -14,7 +14,7 @@ from ..utils import parse_comma_separated_list, validate_location_params from ..utils.bulk import run_bulk from ..utils.decorators import handle_command_errors -from ..utils.output import OUTPUT_OPTION, OutputFormat, emit, error, info, redact, success, warning +from ..utils.output import OUTPUT_OPTION, OutputFormat, emit, error, info, redact, success from ..utils.sdk_client import scm_client from ..utils.validators import ( Address, @@ -38,14 +38,6 @@ Tag, ) -# ============================================================================================================================================================================================= -# HELPER FUNCTIONS -# ============================================================================================================================================================================================= - -# Sentinel returned by bulk-load workers for items skipped due to unsupported container overrides -_SKIPPED = object() - - # ============================================================================================================================================================================================= # TYPER APP CONFIGURATION # ============================================================================================================================================================================================= @@ -65,12 +57,12 @@ FOLDER_OPTION = typer.Option( None, "--folder", - help="Folder path for the address group", + help="Folder location", ) -NAME_OPTION = typer.Option( +MAX_RESULTS_OPTION = typer.Option( None, - "--name", - help="Name of the address group", + "--max-results", + help="Maximum number of results to display", ) TYPE_OPTION = typer.Option( None, @@ -95,7 +87,7 @@ TAGS_OPTION = typer.Option( None, "--tags", - help="List of tags", + help="Tags (repeat for multiple)", ) FILE_OPTION = typer.Option( None, @@ -151,11 +143,6 @@ "--file", help="YAML file containing HIP profiles", ) -HIP_PROFILE_FOLDER_OPTION = typer.Option( - None, - "--folder", - help="Override folder path for all HIP profiles", -) HIP_PROFILE_DRY_RUN_OPTION = typer.Option( False, "--dry-run", @@ -168,18 +155,13 @@ "--file", help="YAML file containing HTTP server profiles", ) -HTTP_SERVER_PROFILE_FOLDER_OPTION = typer.Option( - None, - "--folder", - help="Override folder path for all HTTP server profiles", -) HTTP_SERVER_PROFILE_DRY_RUN_OPTION = typer.Option( False, "--dry-run", help="Preview changes without applying them", ) -# Misc profile options for syslog, etc. +# Container location options SNIPPET_OPTION = typer.Option( None, "--snippet", @@ -190,11 +172,6 @@ "--device", help="Device location", ) -TAG_OPTION = typer.Option( - None, - "--tag", - help="Tags to apply", -) # External Dynamic List options EXCEPTION_LIST_OPTION = typer.Option( @@ -490,24 +467,27 @@ def backup_address_group( @delete_app.command("address-group") @handle_command_errors("deleting address group") def delete_address_group( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the address group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an address group. Examples -------- - scm delete object address-group --folder Texas --name test123 + scm delete object address-group test123 --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete address group '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete address group '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_address_group(folder=folder, name=name) + result = scm_client.delete_address_group(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted address group: {name} from folder {folder}") + success(f"Deleted address group: {name} from {location_type} {location_value}") return result @@ -569,8 +549,14 @@ def _apply(ag_data: dict): ag_data["folder"] = folder ag_data.pop("snippet", None) ag_data.pop("device", None) - elif snippet or device: - return _SKIPPED + elif snippet: + ag_data["snippet"] = snippet + ag_data.pop("folder", None) + ag_data.pop("device", None) + elif device: + ag_data["device"] = device + ag_data.pop("folder", None) + ag_data.pop("snippet", None) # Validate using the Pydantic model address_group = AddressGroup(**ag_data) @@ -578,6 +564,8 @@ def _apply(ag_data: dict): # Call the SDK client to create the address group return scm_client.create_address_group( folder=address_group.folder, + snippet=address_group.snippet, + device=address_group.device, name=address_group.name, type=address_group.type, members=address_group.members, @@ -597,13 +585,6 @@ def _apply(ag_data: dict): # Continue processing other objects continue - if result is _SKIPPED: - if snippet: - warning(f"Warning: Address groups do not support snippets. Skipping group '{ag_data.get('name', 'unknown')}'") - else: - warning(f"Warning: Address groups do not support devices. Skipping group '{ag_data.get('name', 'unknown')}'") - continue - results.append(result) # Track if created or updated based on response @@ -623,8 +604,10 @@ def _apply(ag_data: dict): @set_app.command("address-group") @handle_command_errors("creating address group") def set_address_group( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the address group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, type: str = TYPE_OPTION, members: list[str] | None = MEMBERS_OPTION, filter: str | None = FILTER_OPTION, @@ -636,22 +619,22 @@ def set_address_group( Example: ------- # Static address group - scm set object address-group \ + scm set object address-group test-static \ --folder Texas \ - --name test-static \ --type static \ - --members ["addr1", "addr2"] \ + --members addr1 --members addr2 \ --description "test static group" # Dynamic address group - scm set object address-group \ + scm set object address-group test-dynamic \ --folder Texas \ - --name test-dynamic \ --type dynamic \ --filter "'web' and 'production'" \ --description "test dynamic group" """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Parse comma-separated list options parsed_members = parse_comma_separated_list(members) if members else [] parsed_tags = parse_comma_separated_list(tags) if tags else [] @@ -659,6 +642,8 @@ def set_address_group( # Validate inputs using the Pydantic model address_group = AddressGroup( folder=folder, + snippet=snippet, + device=device, name=name, type=type, members=parsed_members, @@ -670,6 +655,8 @@ def set_address_group( # Call the SDK client to create the address group result = scm_client.create_address_group( folder=address_group.folder, + snippet=address_group.snippet, + device=address_group.device, name=address_group.name, type=address_group.type, members=address_group.members, @@ -680,17 +667,20 @@ def set_address_group( action = result.pop("__action__", "created") if action == "updated": - success(f"Updated address group: {result['name']} in folder {result['folder']}") + success(f"Updated address group: {result['name']} in {location_type} {location_value}") else: - success(f"Created address group: {result['name']} in folder {result['folder']}") + success(f"Created address group: {result['name']} in {location_type} {location_value}") return result @show_app.command("address-group") @handle_command_errors("showing address group") def show_address_group( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the address group to show"), + name: str | None = typer.Argument(None, help="Name of the address group to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display address group objects. @@ -701,22 +691,26 @@ def show_address_group( scm show object address-group --folder Texas # Show a specific address group by name - scm show object address-group --folder Texas --name web-servers + scm show object address-group web-servers --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific address group by name - group = scm_client.get_address_group(folder=folder, name=name) + group = scm_client.get_address_group(folder=folder, snippet=snippet, device=device, name=name) emit(group, output, title=f"Address Group: {group.get('name', name)}") return group - # Default behavior: list all address groups in the folder - groups = scm_client.list_address_groups(folder=folder) + # Default behavior: list all address groups in the container + groups = scm_client.list_address_groups(folder=folder, snippet=snippet, device=device) + if max_results is not None: + groups = groups[:max_results] emit( groups, output, columns=["name", "folder", "static", "dynamic", "description", "tag"], - title=f"Address Groups in folder '{folder}'", + title=f"Address Groups in {location_type} '{location_value}'", ) return groups or None @@ -786,24 +780,27 @@ def backup_address( @delete_app.command("address") @handle_command_errors("deleting address") def delete_address( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the address"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an address object. Examples -------- - scm delete object address --folder Texas --name webserver + scm delete object address webserver --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete address '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete address '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_address(folder=folder, name=name) + result = scm_client.delete_address(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted address: {name} from folder {folder}") + success(f"Deleted address: {name} from {location_type} {location_value}") return result @@ -886,6 +883,8 @@ def _apply(addr_data: dict): # Call the SDK client to create the address return scm_client.create_address( folder=address.folder, + snippet=address.snippet, + device=address.device, name=address.name, description=address.description, tags=address.tags, @@ -926,8 +925,10 @@ def _apply(addr_data: dict): @set_app.command("address") @handle_command_errors("creating address") def set_address( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the address"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, description: str | None = DESCRIPTION_OPTION, tags: list[str] | None = TAGS_OPTION, ip_netmask: str | None = IP_NETMASK_OPTION, @@ -939,19 +940,22 @@ def set_address( Example: ------- - scm set object address \ + scm set object address webserver \ --folder Texas \ - --name webserver \ --ip-netmask 192.168.1.100/32 \ --description "Web server" \ - --tags ["server", "web"] + --tags server --tags web Note: Exactly one of ip-netmask, ip-range, ip-wildcard, or fqdn must be provided. """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Validate inputs using the Pydantic model address_data: dict[str, Any] = { "folder": folder, + "snippet": snippet, + "device": device, "name": name, "tags": tags or [], "ip_netmask": ip_netmask, @@ -969,6 +973,8 @@ def set_address( # Call the SDK client to create the address result = scm_client.create_address( folder=address.folder, + snippet=address.snippet, + device=address.device, name=address.name, description=description, # Pass None if not provided, not empty string tags=address.tags, @@ -982,11 +988,11 @@ def set_address( action = result.pop("__action__", "created") if action == "created": - success(f"Created address: {result['name']} in folder {result['folder']}") + success(f"Created address: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated address: {result['name']} in folder {result['folder']}") + success(f"Updated address: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for address: {result['name']} in folder {result['folder']}") + info(f"No changes needed for address: {result['name']} in {location_type} {location_value}") return result @@ -994,11 +1000,14 @@ def set_address( @show_app.command("address") @handle_command_errors("showing address") def show_address( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the address to show"), + name: str | None = typer.Argument(None, help="Name of the address to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, exclude_folder: list[str] | None = EXCLUDE_FOLDER_OPTION, exclude_snippet: list[str] | None = EXCLUDE_SNIPPET_OPTION, exclude_device: list[str] | None = EXCLUDE_DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display address objects. @@ -1009,31 +1018,36 @@ def show_address( scm show object address --folder Texas # Show a specific address by name - scm show object address --folder Texas --name webserver + scm show object address webserver --folder Texas # List addresses excluding specific folders scm show object address --folder Texas --exclude-folder "All" """ - # Show context info if log level is INFO + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific address by name - address = scm_client.get_address(folder=folder, name=name) + address = scm_client.get_address(folder=folder, snippet=snippet, device=device, name=name) emit(address, output, title=f"Address: {address.get('name', name)}") return address - # Default behavior: list all addresses in the folder + # Default behavior: list all addresses in the container addresses = scm_client.list_addresses( folder=folder, + snippet=snippet, + device=device, exclude_folders=exclude_folder or None, exclude_snippets=exclude_snippet or None, exclude_devices=exclude_device or None, ) + if max_results is not None: + addresses = addresses[:max_results] emit( addresses, output, columns=["name", "folder", "ip_netmask", "ip_range", "ip_wildcard", "fqdn", "description", "tag"], - title=f"Addresses in folder '{folder}'", + title=f"Addresses in {location_type} '{location_value}'", ) return addresses or None @@ -1100,24 +1114,27 @@ def backup_application( @delete_app.command("application") @handle_command_errors("deleting application") def delete_application( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the application"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an application. Example: ------- - scm delete object application --folder Texas --name custom-app + scm delete object application custom-app --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete application '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete application '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_application(folder=folder, name=name) + result = scm_client.delete_application(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted application: {name} from folder {folder}") + success(f"Deleted application: {name} from {location_type} {location_value}") return result @@ -1179,8 +1196,14 @@ def _apply(app_data: dict): app_data["folder"] = folder app_data.pop("snippet", None) app_data.pop("device", None) - elif snippet or device: - return _SKIPPED + elif snippet: + app_data["snippet"] = snippet + app_data.pop("folder", None) + app_data.pop("device", None) + elif device: + app_data["device"] = device + app_data.pop("folder", None) + app_data.pop("snippet", None) # Validate using the Pydantic model application = Application(**app_data) @@ -1188,6 +1211,8 @@ def _apply(app_data: dict): # Call the SDK client to create the application return scm_client.create_application( folder=application.folder, + snippet=application.snippet, + device=application.device, name=application.name, category=application.category, subcategory=application.subcategory, @@ -1218,13 +1243,6 @@ def _apply(app_data: dict): # Continue processing other objects continue - if result is _SKIPPED: - if snippet: - warning(f"Warning: Applications do not support snippets. Skipping application '{app_data.get('name', 'unknown')}'") - else: - warning(f"Warning: Applications do not support devices. Skipping application '{app_data.get('name', 'unknown')}'") - continue - results.append(result) # Track if created or updated based on response @@ -1244,8 +1262,10 @@ def _apply(app_data: dict): @set_app.command("application") @handle_command_errors("creating application") def set_application( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the application"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, category: str = CATEGORY_OPTION, subcategory: str = SUBCATEGORY_OPTION, technology: str = TECHNOLOGY_OPTION, @@ -1267,21 +1287,24 @@ def set_application( Example: ------- - scm set object application \ + scm set object application custom-database \ --folder Texas \ - --name custom-database \ --category business-systems \ --subcategory database \ --technology client-server \ --risk 3 \ --description "Custom database application" \ - --ports ["tcp/1521", "tcp/1522"] \ + --ports tcp/1521 --ports tcp/1522 \ --transfers-files """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Validate inputs using the Pydantic model application = Application( folder=folder, + snippet=snippet, + device=device, name=name, category=category, subcategory=subcategory, @@ -1303,6 +1326,8 @@ def set_application( # Call the SDK client to create the application result = scm_client.create_application( folder=application.folder, + snippet=application.snippet, + device=application.device, name=application.name, category=application.category, subcategory=application.subcategory, @@ -1323,17 +1348,20 @@ def set_application( action = result.pop("__action__", "created") if action == "updated": - success(f"Updated application: {result['name']} in folder {result['folder']}") + success(f"Updated application: {result['name']} in {location_type} {location_value}") else: - success(f"Created application: {result['name']} in folder {result['folder']}") + success(f"Created application: {result['name']} in {location_type} {location_value}") return result @show_app.command("application") @handle_command_errors("showing application") def show_application( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the application to show"), + name: str | None = typer.Argument(None, help="Name of the application to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display application objects. @@ -1344,22 +1372,26 @@ def show_application( scm show object application --folder Texas # Show a specific application by name - scm show object application --folder Texas --name custom-database + scm show object application custom-database --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific application by name - application = scm_client.get_application(folder=folder, name=name) + application = scm_client.get_application(folder=folder, snippet=snippet, device=device, name=name) emit(application, output, title=f"Application: {application.get('name', name)}") return application - # List all applications in the folder (default behavior) - applications = scm_client.list_applications(folder=folder) + # List all applications in the container (default behavior) + applications = scm_client.list_applications(folder=folder, snippet=snippet, device=device) + if max_results is not None: + applications = applications[:max_results] emit( applications, output, columns=["name", "folder", "category", "subcategory", "technology", "risk", "description"], - title=f"Applications in folder '{folder}'", + title=f"Applications in {location_type} '{location_value}'", ) return applications or None @@ -1426,24 +1458,27 @@ def backup_application_group( @delete_app.command("application-group") @handle_command_errors("deleting application group") def delete_application_group( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the application group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an application group. Example: ------- - scm delete object application-group --folder Texas --name web-apps + scm delete object application-group web-apps --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete application group '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete application group '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_application_group(folder=folder, name=name) + result = scm_client.delete_application_group(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted application group: {name} from folder {folder}") + success(f"Deleted application group: {name} from {location_type} {location_value}") return result @@ -1512,6 +1547,8 @@ def _apply(group_data: dict): # Call the SDK client to create the application group return scm_client.create_application_group( folder=app_group.folder, + snippet=app_group.snippet, + device=app_group.device, name=app_group.name, members=app_group.members, ) @@ -1541,26 +1578,31 @@ def _apply(group_data: dict): @set_app.command("application-group") @handle_command_errors("creating application group") def set_application_group( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the application group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, members: list[str] = APP_GROUP_MEMBERS_OPTION, ): r"""Create or update an application group. Example: ------- - scm set object application-group \ + scm set object application-group web-apps \ --folder Texas \ - --name web-apps \ - --members ["ssl", "web-browsing", "http", "https"] + --members ssl --members web-browsing --members http --members https """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Parse comma-separated members parsed_members = parse_comma_separated_list(members) # Validate inputs using the Pydantic model app_group = ApplicationGroup( folder=folder, + snippet=snippet, + device=device, name=name, members=parsed_members, ) @@ -1568,23 +1610,28 @@ def set_application_group( # Call the SDK client to create the application group result = scm_client.create_application_group( folder=app_group.folder, + snippet=app_group.snippet, + device=app_group.device, name=app_group.name, members=app_group.members, ) action = result.pop("__action__", "created") if action == "updated": - success(f"Updated application group: {result['name']} in folder {result['folder']}") + success(f"Updated application group: {result['name']} in {location_type} {location_value}") else: - success(f"Created application group: {result['name']} in folder {result['folder']}") + success(f"Created application group: {result['name']} in {location_type} {location_value}") return result @show_app.command("application-group") @handle_command_errors("showing application group") def show_application_group( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the application group to show"), + name: str | None = typer.Argument(None, help="Name of the application group to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display application group objects. @@ -1595,22 +1642,26 @@ def show_application_group( scm show object application-group --folder Texas # Show a specific application group by name - scm show object application-group --folder Texas --name web-apps + scm show object application-group web-apps --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific application group by name - group = scm_client.get_application_group(folder=folder, name=name) + group = scm_client.get_application_group(folder=folder, snippet=snippet, device=device, name=name) emit(group, output, title=f"Application Group: {group.get('name', name)}") return group - # List all application groups in the folder (default behavior) - groups = scm_client.list_application_groups(folder=folder) + # List all application groups in the container (default behavior) + groups = scm_client.list_application_groups(folder=folder, snippet=snippet, device=device) + if max_results is not None: + groups = groups[:max_results] emit( groups, output, columns=["name", "folder", "members"], - title=f"Application Groups in folder '{folder}'", + title=f"Application Groups in {location_type} '{location_value}'", ) return groups or None @@ -1677,24 +1728,27 @@ def backup_application_filter( @delete_app.command("application-filter") @handle_command_errors("deleting application filter") def delete_application_filter( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the application filter"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an application filter. Example: ------- - scm delete object application-filter --folder Texas --name high-risk-apps + scm delete object application-filter high-risk-apps --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete application filter '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete application filter '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_application_filter(folder=folder, name=name) + result = scm_client.delete_application_filter(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted application filter: {name} from folder {folder}") + success(f"Deleted application filter: {name} from {location_type} {location_value}") return result @@ -1763,6 +1817,8 @@ def _apply(filter_data: dict): # Call the SDK client to create the application filter return scm_client.create_application_filter( folder=app_filter.folder, + snippet=app_filter.snippet, + device=app_filter.device, name=app_filter.name, category=app_filter.category, subcategory=app_filter.subcategory, @@ -1804,8 +1860,10 @@ def _apply(filter_data: dict): @set_app.command("application-filter") @handle_command_errors("creating application filter") def set_application_filter( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the application filter"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, category: list[str] = FILTER_CATEGORY_OPTION, subcategory: list[str] = FILTER_SUBCATEGORY_OPTION, technology: list[str] = FILTER_TECHNOLOGY_OPTION, @@ -1824,20 +1882,23 @@ def set_application_filter( Example: ------- - scm set object application-filter \ + scm set object application-filter high-risk-apps \ --folder Texas \ - --name high-risk-apps \ - --category ["business-systems"] \ - --subcategory ["database"] \ - --technology ["client-server"] \ - --risk [4, 5] \ + --category business-systems \ + --subcategory database \ + --technology client-server \ + --risk 4 --risk 5 \ --has-known-vulnerabilities \ --used-by-malware """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Validate inputs using the Pydantic model app_filter = ApplicationFilter( folder=folder, + snippet=snippet, + device=device, name=name, category=category, subcategory=subcategory, @@ -1857,6 +1918,8 @@ def set_application_filter( # Call the SDK client to create the application filter result = scm_client.create_application_filter( folder=app_filter.folder, + snippet=app_filter.snippet, + device=app_filter.device, name=app_filter.name, category=app_filter.category, subcategory=app_filter.subcategory, @@ -1877,11 +1940,11 @@ def set_application_filter( action = result.pop("__action__", "created") if action == "created": - success(f"Created application filter: {result['name']} in folder {result['folder']}") + success(f"Created application filter: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated application filter: {result['name']} in folder {result['folder']}") + success(f"Updated application filter: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for application filter: {result['name']} in folder {result['folder']}") + info(f"No changes needed for application filter: {result['name']} in {location_type} {location_value}") return result @@ -1889,8 +1952,11 @@ def set_application_filter( @show_app.command("application-filter") @handle_command_errors("showing application filter") def show_application_filter( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the application filter to show"), + name: str | None = typer.Argument(None, help="Name of the application filter to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display application filter objects. @@ -1901,22 +1967,26 @@ def show_application_filter( scm show object application-filter --folder Texas # Show a specific application filter by name - scm show object application-filter --folder Texas --name high-risk-apps + scm show object application-filter high-risk-apps --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific application filter by name - filter_obj = scm_client.get_application_filter(folder=folder, name=name) + filter_obj = scm_client.get_application_filter(folder=folder, snippet=snippet, device=device, name=name) emit(filter_obj, output, title=f"Application Filter: {filter_obj.get('name', name)}") return filter_obj - # List all application filters in the folder (default behavior) - filters = scm_client.list_application_filters(folder=folder) + # List all application filters in the container (default behavior) + filters = scm_client.list_application_filters(folder=folder, snippet=snippet, device=device) + if max_results is not None: + filters = filters[:max_results] emit( filters, output, columns=["name", "folder", "category", "sub_category", "technology", "risk"], - title=f"Application Filters in folder '{folder}'", + title=f"Application Filters in {location_type} '{location_value}'", ) return filters or None @@ -1988,24 +2058,27 @@ def backup_dynamic_user_group( @delete_app.command("dynamic-user-group") @handle_command_errors("deleting dynamic user group") def delete_dynamic_user_group( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the dynamic user group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a dynamic user group. Example: ------- - scm delete object dynamic-user-group --folder Texas --name it-admins + scm delete object dynamic-user-group it-admins --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete dynamic user group '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete dynamic user group '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_dynamic_user_group(folder=folder, name=name) + result = scm_client.delete_dynamic_user_group(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted dynamic user group: {name} from folder {folder}") + success(f"Deleted dynamic user group: {name} from {location_type} {location_value}") return result @@ -2074,6 +2147,8 @@ def _apply(group_data: dict): # Call the SDK client to create the dynamic user group return scm_client.create_dynamic_user_group( folder=dug.folder, + snippet=dug.snippet, + device=dug.device, name=dug.name, filter=dug.filter, description=dug.description, @@ -2105,8 +2180,10 @@ def _apply(group_data: dict): @set_app.command("dynamic-user-group") @handle_command_errors("creating dynamic user group") def set_dynamic_user_group( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the dynamic user group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, filter: str = FILTER_EXPRESSION_OPTION, description: str | None = DESCRIPTION_OPTION, tags: list[str] | None = TAGS_OPTION, @@ -2115,17 +2192,20 @@ def set_dynamic_user_group( Example: ------- - scm set object dynamic-user-group \\ + scm set object dynamic-user-group it-admins \\ --folder Texas \\ - --name it-admins \\ --filter "tag.Department='IT' and tag.Role='Admin'" \\ --description "IT administrators" \\ - --tags ["automation", "admin"] + --tags automation --tags admin """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Validate inputs using the Pydantic model dug = DynamicUserGroup( folder=folder, + snippet=snippet, + device=device, name=name, filter=filter, description=description or "", @@ -2135,6 +2215,8 @@ def set_dynamic_user_group( # Call the SDK client to create the dynamic user group result = scm_client.create_dynamic_user_group( folder=dug.folder, + snippet=dug.snippet, + device=dug.device, name=dug.name, filter=dug.filter, description=dug.description, @@ -2143,17 +2225,20 @@ def set_dynamic_user_group( action = result.pop("__action__", "created") if action == "updated": - success(f"Updated dynamic user group: {result['name']} in folder {result['folder']}") + success(f"Updated dynamic user group: {result['name']} in {location_type} {location_value}") else: - success(f"Created dynamic user group: {result['name']} in folder {result['folder']}") + success(f"Created dynamic user group: {result['name']} in {location_type} {location_value}") return result @show_app.command("dynamic-user-group") @handle_command_errors("showing dynamic user group") def show_dynamic_user_group( - folder: str = FOLDER_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the dynamic user group to show"), + name: str | None = typer.Argument(None, help="Name of the dynamic user group to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display dynamic user group objects. @@ -2164,22 +2249,26 @@ def show_dynamic_user_group( scm show object dynamic-user-group --folder Texas # Show a specific dynamic user group by name - scm show object dynamic-user-group --folder Texas --name it-admins + scm show object dynamic-user-group it-admins --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific dynamic user group by name - group = scm_client.get_dynamic_user_group(folder=folder, name=name) + group = scm_client.get_dynamic_user_group(folder=folder, snippet=snippet, device=device, name=name) emit(group, output, title=f"Dynamic User Group: {group.get('name', name)}") return group - # List all dynamic user groups in the folder (default behavior) - groups = scm_client.list_dynamic_user_groups(folder=folder) + # List all dynamic user groups in the container (default behavior) + groups = scm_client.list_dynamic_user_groups(folder=folder, snippet=snippet, device=device) + if max_results is not None: + groups = groups[:max_results] emit( groups, output, columns=["name", "folder", "filter", "description", "tag"], - title=f"Dynamic User Groups in folder '{folder}'", + title=f"Dynamic User Groups in {location_type} '{location_value}'", ) return groups or None @@ -2276,24 +2365,27 @@ def backup_external_dynamic_list( @delete_app.command("external-dynamic-list") @handle_command_errors("deleting external dynamic list") def delete_external_dynamic_list( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the external dynamic list"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an external dynamic list. Example: ------- - scm delete object external-dynamic-list --folder Texas --name malicious-ips + scm delete object external-dynamic-list malicious-ips --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete external dynamic list '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete external dynamic list '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_external_dynamic_list(folder=folder, name=name) + result = scm_client.delete_external_dynamic_list(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted external dynamic list: {name} from folder {folder}") + success(f"Deleted external dynamic list: {name} from {location_type} {location_value}") return result @@ -2439,8 +2531,10 @@ def _apply(edl_config: dict): @set_app.command("external-dynamic-list") @handle_command_errors("creating/updating external dynamic list") def set_external_dynamic_list( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the external dynamic list"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, type: str = typer.Option( ..., help="Type of EDL (predefined_ip, predefined_url, ip, domain, url, imsi, imei)", @@ -2461,23 +2555,25 @@ def set_external_dynamic_list( Example: ------- # Create a predefined IP list - scm set object external-dynamic-list --folder Texas --name paloalto-bulletproof \\ + scm set object external-dynamic-list paloalto-bulletproof --folder Texas \\ --type predefined_ip --url "https://saasedl.paloaltonetworks.com/feeds/BulletproofIPList" # Create a custom IP blocklist with hourly updates - scm set object external-dynamic-list --folder Texas --name custom-blocklist \\ + scm set object external-dynamic-list custom-blocklist --folder Texas \\ --type ip --url "https://example.com/blocklist.txt" --recurring hourly # Create a domain list with daily updates at 3 AM - scm set object external-dynamic-list --folder Texas --name malicious-domains \\ + scm set object external-dynamic-list malicious-domains --folder Texas \\ --type domain --url "https://example.com/domains.txt" --recurring daily --hour 03 \\ --expand-domain """ - # Validate the configuration + location_type, location_value = validate_location_params(folder, snippet, device) edl_config: dict[str, Any] = { "folder": folder, + "snippet": snippet, + "device": device, "name": name, "type": type, "url": url, @@ -2504,6 +2600,8 @@ def set_external_dynamic_list( # Create/update the external dynamic list result = scm_client.create_external_dynamic_list( folder=folder, + snippet=snippet, + device=device, name=name, type_config=edl_data["type"], ) @@ -2512,11 +2610,11 @@ def set_external_dynamic_list( action = result.pop("__action__", "created") if action == "created": - success(f"Created external dynamic list: {result.get('name', name)} in folder {result.get('folder', folder)}") + success(f"Created external dynamic list: {result.get('name', name)} in {location_type} {location_value}") elif action == "updated": - success(f"Updated external dynamic list: {result.get('name', name)} in folder {result.get('folder', folder)}") + success(f"Updated external dynamic list: {result.get('name', name)} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for external dynamic list: {result.get('name', name)} in folder {result.get('folder', folder)}") + info(f"No changes needed for external dynamic list: {result.get('name', name)} in {location_type} {location_value}") return result @@ -2524,11 +2622,14 @@ def set_external_dynamic_list( @show_app.command("external-dynamic-list") @handle_command_errors("showing external dynamic list") def show_external_dynamic_list( - folder: str = FOLDER_OPTION, - name: str = typer.Option(None, help="Name of the external dynamic list to show"), + name: str | None = typer.Argument(None, help="Name of the external dynamic list to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): - """Show external dynamic list details or list all external dynamic lists in a folder. + """Show external dynamic list details or list all external dynamic lists in a container. Examples -------- @@ -2536,22 +2637,26 @@ def show_external_dynamic_list( scm show object external-dynamic-list --folder Texas # Show a specific external dynamic list by name - scm show object external-dynamic-list --folder Texas --name malicious-ips + scm show object external-dynamic-list malicious-ips --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific external dynamic list by name - edl = scm_client.get_external_dynamic_list(folder=folder, name=name) + edl = scm_client.get_external_dynamic_list(folder=folder, snippet=snippet, device=device, name=name) emit(edl, output, title=f"External Dynamic List: {edl.get('name', name)}") return edl - # List all external dynamic lists in the folder (default behavior) - edls = scm_client.list_external_dynamic_lists(folder=folder) + # List all external dynamic lists in the container (default behavior) + edls = scm_client.list_external_dynamic_lists(folder=folder, snippet=snippet, device=device) + if max_results is not None: + edls = edls[:max_results] emit( edls, output, columns=["name", "folder", "type"], - title=f"External Dynamic Lists in folder '{folder}'", + title=f"External Dynamic Lists in {location_type} '{location_value}'", ) return edls or None @@ -2748,24 +2853,27 @@ def backup_hip_object( @delete_app.command("hip-object") @handle_command_errors("deleting HIP object") def delete_hip_object( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the HIP object"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a HIP object. Example: ------- - scm delete object hip-object --folder Texas --name windows-compliance + scm delete object hip-object windows-compliance --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete HIP object '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete HIP object '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() - result = scm_client.delete_hip_object(folder=folder, name=name) + result = scm_client.delete_hip_object(folder=folder, snippet=snippet, device=device, name=name) if result: - success(f"Deleted HIP object: {name} from folder {folder}") + success(f"Deleted HIP object: {name} from {location_type} {location_value}") return result @@ -2900,8 +3008,10 @@ def _apply(hip_data: dict): @set_app.command("hip-object") @handle_command_errors("creating HIP object") def set_hip_object( - folder: str = FOLDER_OPTION, - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the HIP object"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, description: str = typer.Option("", help="Description of the HIP object"), # Host info options host_info_domain: str = typer.Option(None, help="Domain criteria (is, is_not, contains)"), @@ -2930,9 +3040,8 @@ def set_hip_object( Example: ------- # Create a Windows workstation compliance policy - scm set object hip-object \\ + scm set object hip-object windows-compliance \\ --folder Texas \\ - --name windows-compliance \\ --description "Windows workstation compliance" \\ --host-info-os Microsoft \\ --host-info-os-value All \\ @@ -2941,26 +3050,28 @@ def set_hip_object( --patch-management-enabled # Create a mobile device policy - scm set object hip-object \\ + scm set object hip-object mobile-policy \\ --folder Texas \\ - --name mobile-policy \\ --description "Mobile device compliance" \\ --mobile-device-jailbroken false \\ --mobile-device-disk-encrypted \\ --mobile-device-passcode-set # Create a network-based policy - scm set object hip-object \\ + scm set object hip-object wifi-only \\ --folder Texas \\ - --name wifi-only \\ --description "WiFi network only" \\ --network-info-type is \\ --network-info-value wifi """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Build the HIP object data from options hip_data: dict[str, Any] = { "folder": folder, + "snippet": snippet, + "device": device, "name": name, "description": description, } @@ -3015,6 +3126,8 @@ def set_hip_object( # Call the SDK client to create the HIP object result = scm_client.create_hip_object( folder=hip_obj.folder, + snippet=hip_obj.snippet, + device=hip_obj.device, name=hip_obj.name, description=sdk_data.get("description"), host_info=sdk_data.get("host_info"), @@ -3029,11 +3142,11 @@ def set_hip_object( action = result.pop("__action__", "created") if action == "created": - success(f"Created HIP object: {result['name']} in folder {result['folder']}") + success(f"Created HIP object: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated HIP object: {result['name']} in folder {result['folder']}") + success(f"Updated HIP object: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for HIP object: {result['name']} in folder {result['folder']}") + info(f"No changes needed for HIP object: {result['name']} in {location_type} {location_value}") return result @@ -3041,8 +3154,11 @@ def set_hip_object( @show_app.command("hip-object") @handle_command_errors("showing HIP object") def show_hip_object( - folder: str = FOLDER_OPTION, - name: str = typer.Option(None, help="Name of the HIP object to show"), + name: str | None = typer.Argument(None, help="Name of the HIP object to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ): """Display HIP object configurations. @@ -3053,22 +3169,26 @@ def show_hip_object( scm show object hip-object --folder Texas # Show a specific HIP object by name - scm show object hip-object --folder Texas --name windows-compliance + scm show object hip-object windows-compliance --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Get a specific HIP object by name - hip_obj = scm_client.get_hip_object(folder=folder, name=name) + hip_obj = scm_client.get_hip_object(folder=folder, snippet=snippet, device=device, name=name) emit(hip_obj, output, title=f"HIP Object: {hip_obj.get('name', name)}") return hip_obj - # List all HIP objects in the folder (default behavior) - hip_objects = scm_client.list_hip_objects(folder=folder) + # List all HIP objects in the container (default behavior) + hip_objects = scm_client.list_hip_objects(folder=folder, snippet=snippet, device=device) + if max_results is not None: + hip_objects = hip_objects[:max_results] emit( hip_objects, output, columns=["name", "folder", "description"], - title=f"HIP Objects in folder '{folder}'", + title=f"HIP Objects in {location_type} '{location_value}'", ) return hip_objects or None @@ -3142,25 +3262,28 @@ def backup_hip_profile( @delete_app.command("hip-profile") @handle_command_errors("deleting HIP profile") def delete_hip_profile( - folder: str = typer.Option(..., "--folder", help="Folder containing the HIP profile"), - name: str = typer.Option(..., "--name", help="Name of the HIP profile to delete"), + name: str = typer.Argument(..., help="Name of the HIP profile"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a HIP profile. Examples -------- - scm delete object hip-profile --folder Texas --name my-hip-profile + scm delete object hip-profile my-hip-profile --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete HIP profile '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete HIP profile '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() # Delete the HIP profile - info(f"Deleting HIP profile '{name}' from folder '{folder}'...") - scm_client.delete_hip_profile(folder=folder, name=name) - success(f"Deleted HIP profile: {name} from folder {folder}") + info(f"Deleting HIP profile '{name}' from {location_type} '{location_value}'...") + scm_client.delete_hip_profile(folder=folder, snippet=snippet, device=device, name=name) + success(f"Deleted HIP profile: {name} from {location_type} {location_value}") @load_app.command("hip-profile", help="Load HIP profiles from a YAML file.") @@ -3291,15 +3414,21 @@ def _apply(profile_data: dict): @set_app.command("hip-profile") @handle_command_errors("creating/updating HIP profile") def set_hip_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the HIP profile"), - name: str = typer.Option(..., "--name", help="Name of the HIP profile"), + name: str = typer.Argument(..., help="Name of the HIP profile"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, match: str = typer.Option(..., "--match", help="Match criteria for the HIP profile"), description: str = typer.Option(None, "--description", help="Description of the HIP profile"), ): """Create or update a HIP profile.""" + location_type, location_value = validate_location_params(folder, snippet, device) + # Create the HIP profile object hip_profile = HIPProfile( folder=folder, + snippet=snippet, + device=device, name=name, match=match, description=description, @@ -3310,7 +3439,9 @@ def set_hip_profile( # Create or update the HIP profile result = scm_client.create_hip_profile( - folder=profile_data["folder"], + folder=hip_profile.folder, + snippet=hip_profile.snippet, + device=hip_profile.device, name=profile_data["name"], match=profile_data["match"], description=profile_data.get("description"), @@ -3320,11 +3451,11 @@ def set_hip_profile( action = result.pop("__action__", "created") if action == "created": - success(f"Created HIP profile: {result['name']} in folder {result['folder']}") + success(f"Created HIP profile: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated HIP profile: {result['name']} in folder {result['folder']}") + success(f"Updated HIP profile: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for HIP profile: {result['name']} in folder {result['folder']}") + info(f"No changes needed for HIP profile: {result['name']} in {location_type} {location_value}") return result @@ -3332,11 +3463,14 @@ def set_hip_profile( @show_app.command("hip-profile") @handle_command_errors("showing HIP profile") def show_hip_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the HIP profile"), - name: str = typer.Option(None, "--name", help="Name of specific HIP profile to show"), + name: str | None = typer.Argument(None, help="Name of the HIP profile to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, -) -> dict[str, Any] | None: - """Show HIP profile details or list all HIP profiles in a folder. +) -> dict[str, Any] | list[dict[str, Any]] | None: + """Show HIP profile details or list all HIP profiles in a container. Examples -------- @@ -3344,24 +3478,28 @@ def show_hip_profile( scm show object hip-profile --folder Texas # Show a specific HIP profile by name - scm show object hip-profile --folder Texas --name windows-compliance + scm show object hip-profile windows-compliance --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Show specific HIP profile - hip_profile = scm_client.get_hip_profile(folder=folder, name=name) + hip_profile = scm_client.get_hip_profile(folder=folder, snippet=snippet, device=device, name=name) emit(hip_profile, output, title=f"HIP Profile: {hip_profile.get('name', name)}") return hip_profile - # Default behavior: list all HIP profiles in the folder - hip_profiles = scm_client.list_hip_profiles(folder=folder) + # Default behavior: list all HIP profiles in the container + hip_profiles = scm_client.list_hip_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + hip_profiles = hip_profiles[:max_results] emit( hip_profiles, output, columns=["name", "folder", "match", "description"], - title=f"HIP profiles in folder '{folder}'", + title=f"HIP profiles in {location_type} '{location_value}'", ) - return None + return hip_profiles or None # ============================================================================================================================================================================================= @@ -3439,19 +3577,22 @@ def backup_http_server_profile( @delete_app.command("http-server-profile") @handle_command_errors("deleting HTTP server profile") def delete_http_server_profile( - folder: str = typer.Option(..., "--folder", help="Folder containing the HTTP server profile"), - name: str = typer.Option(..., "--name", help="Name of the HTTP server profile to delete"), + name: str = typer.Argument(..., help="Name of the HTTP server profile"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: - """Delete an HTTP server profile from a specific folder.""" + """Delete an HTTP server profile from a specific container.""" + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete HTTP server profile '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete HTTP server profile '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() # Delete the HTTP server profile - info(f"Deleting HTTP server profile '{name}' from folder '{folder}'...") - scm_client.delete_http_server_profile(folder=folder, name=name) - success(f"Deleted HTTP server profile: {name} from folder {folder}") + info(f"Deleting HTTP server profile '{name}' from {location_type} '{location_value}'...") + scm_client.delete_http_server_profile(folder=folder, snippet=snippet, device=device, name=name) + success(f"Deleted HTTP server profile: {name} from {location_type} {location_value}") @load_app.command("http-server-profile", help="Load HTTP server profiles from a YAML file.") @@ -3584,8 +3725,10 @@ def _apply(profile_data: dict): @set_app.command("http-server-profile") @handle_command_errors("creating/updating HTTP server profile") def set_http_server_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the HTTP server profile"), - name: str = typer.Option(..., "--name", help="Name of the HTTP server profile"), + name: str = typer.Argument(..., help="Name of the HTTP server profile"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, servers: str = typer.Option(..., "--servers", help="JSON string of server configurations"), description: str = typer.Option(None, "--description", help="Description of the HTTP server profile"), tag_registration: bool = typer.Option(False, "--tag-registration", help="Register tags on match"), @@ -3595,6 +3738,8 @@ def set_http_server_profile( Server configuration must be provided as a JSON string, e.g.: --servers '[{"name": "server1", "address": "192.168.1.100", "protocol": "HTTPS", "port": 443}]' """ + location_type, location_value = validate_location_params(folder, snippet, device) + # Parse servers JSON import json as json_lib @@ -3608,6 +3753,8 @@ def set_http_server_profile( # Create the HTTP server profile object http_server_profile = HTTPServerProfile( folder=folder, + snippet=snippet, + device=device, name=name, servers=servers_list, description=description, @@ -3620,7 +3767,9 @@ def set_http_server_profile( # Create or update the HTTP server profile result = scm_client.create_http_server_profile( - folder=profile_data["folder"], + folder=http_server_profile.folder, + snippet=http_server_profile.snippet, + device=http_server_profile.device, name=profile_data["name"], servers=profile_data["server"], description=profile_data.get("description"), @@ -3632,11 +3781,11 @@ def set_http_server_profile( action = result.pop("__action__", "created") if action == "created": - success(f"Created HTTP server profile: {result['name']} in folder {result['folder']}") + success(f"Created HTTP server profile: {result['name']} in {location_type} {location_value}") elif action == "updated": - success(f"Updated HTTP server profile: {result['name']} in folder {result['folder']}") + success(f"Updated HTTP server profile: {result['name']} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for HTTP server profile: {result['name']} in folder {result['folder']}") + info(f"No changes needed for HTTP server profile: {result['name']} in {location_type} {location_value}") return result @@ -3644,12 +3793,14 @@ def set_http_server_profile( @show_app.command("http-server-profile") @handle_command_errors("showing HTTP server profile") def show_http_server_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the HTTP server profile"), - name: str = typer.Option(None, "--name", help="Name of specific HTTP server profile to show"), - list: bool = typer.Option(False, "--list", help="List all HTTP server profiles in the folder"), + name: str | None = typer.Argument(None, help="Name of the HTTP server profile to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, -) -> dict[str, Any] | None: - """Show HTTP server profile details or list all HTTP server profiles in a folder. +) -> dict[str, Any] | list[dict[str, Any]] | None: + """Show HTTP server profile details or list all HTTP server profiles in a container. Examples -------- @@ -3657,24 +3808,28 @@ def show_http_server_profile( scm show object http-server-profile --folder Texas # Show a specific HTTP server profile by name - scm show object http-server-profile --folder Texas --name syslog-collector + scm show object http-server-profile syslog-collector --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Show specific HTTP server profile (server configs may carry passwords) - http_server_profile = scm_client.get_http_server_profile(folder=folder, name=name) + http_server_profile = scm_client.get_http_server_profile(folder=folder, snippet=snippet, device=device, name=name) emit(redact(http_server_profile), output, title=f"HTTP Server Profile: {http_server_profile.get('name', name)}") return http_server_profile - # List all HTTP server profiles in the folder (default behavior) - http_server_profiles = scm_client.list_http_server_profiles(folder=folder) + # List all HTTP server profiles in the container (default behavior) + http_server_profiles = scm_client.list_http_server_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + http_server_profiles = http_server_profiles[:max_results] emit( redact(http_server_profiles), output, columns=["name", "folder", "tag_registration", "server", "description"], - title=f"HTTP server profiles in folder '{folder}'", + title=f"HTTP server profiles in {location_type} '{location_value}'", ) - return None + return http_server_profiles or None @backup_app.command("log-forwarding-profile") @@ -3747,28 +3902,31 @@ def backup_log_forwarding_profile( @delete_app.command("log-forwarding-profile") @handle_command_errors("deleting log forwarding profile") def delete_log_forwarding_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the log forwarding profile"), - name: str = typer.Option(..., "--name", help="Name of the log forwarding profile to delete"), + name: str = typer.Argument(..., help="Name of the log forwarding profile"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a log forwarding profile. Examples -------- - scm delete object log-forwarding-profile --folder Texas --name my-lfp + scm delete object log-forwarding-profile my-lfp --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete log forwarding profile '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete log forwarding profile '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() # Delete the log forwarding profile - deleted = scm_client.delete_log_forwarding_profile(folder=folder, name=name) + deleted = scm_client.delete_log_forwarding_profile(folder=folder, snippet=snippet, device=device, name=name) if deleted: - success(f"Deleted log forwarding profile: {name} from folder {folder}") + success(f"Deleted log forwarding profile: {name} from {location_type} {location_value}") else: - error(f"Failed to delete log forwarding profile '{name}' from folder '{folder}'") + error(f"Failed to delete log forwarding profile '{name}' from {location_type} '{location_value}'") raise typer.Exit(code=1) @@ -3903,8 +4061,10 @@ def _apply(profile_data: dict): @set_app.command("log-forwarding-profile") @handle_command_errors("creating/updating log forwarding profile") def set_log_forwarding_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the log forwarding profile"), - name: str = typer.Option(..., "--name", help="Name of the log forwarding profile"), + name: str = typer.Argument(..., help="Name of the log forwarding profile"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, match_list: str = typer.Option(None, "--match-list", help="Match list configuration as JSON string"), description: str = typer.Option(None, "--description", help="Description of the log forwarding profile"), enhanced_application_logging: bool = typer.Option( @@ -3916,6 +4076,8 @@ def set_log_forwarding_profile( """Create or update a log forwarding profile.""" import json + location_type, location_value = validate_location_params(folder, snippet, device) + # Parse match list if provided match_list_data = None if match_list: @@ -3931,6 +4093,8 @@ def set_log_forwarding_profile( # Validate using Pydantic model profile_data: dict[str, Any] = { "folder": folder, + "snippet": snippet, + "device": device, "name": name, } @@ -3946,6 +4110,8 @@ def set_log_forwarding_profile( # Create the log forwarding profile using SDK result = scm_client.create_log_forwarding_profile( folder=profile.folder, + snippet=profile.snippet, + device=profile.device, name=profile.name, description=profile.description, enhanced_application_logging=profile.enhanced_application_logging, @@ -3957,11 +4123,11 @@ def set_log_forwarding_profile( action = result.pop("__action__", "created") if action == "created": - success(f"Created log forwarding profile: {name} in folder {folder}") + success(f"Created log forwarding profile: {name} in {location_type} {location_value}") elif action == "updated": - success(f"Updated log forwarding profile: {name} in folder {folder}") + success(f"Updated log forwarding profile: {name} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for log forwarding profile: {name} in folder {folder}") + info(f"No changes needed for log forwarding profile: {name} in {location_type} {location_value}") else: error(f"Failed to create/update log forwarding profile '{name}'") raise typer.Exit(code=1) @@ -3970,12 +4136,14 @@ def set_log_forwarding_profile( @show_app.command("log-forwarding-profile") @handle_command_errors("showing log forwarding profile") def show_log_forwarding_profile( - folder: str = typer.Option(..., "--folder", help="Folder path for the log forwarding profile"), - name: str = typer.Option(None, "--name", help="Name of specific log forwarding profile to show"), - list: bool = typer.Option(False, "--list", help="List all log forwarding profiles in the folder"), + name: str | None = typer.Argument(None, help="Name of the log forwarding profile to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, -) -> dict[str, Any] | None: - """Show log forwarding profile details or list all log forwarding profiles in a folder. +) -> dict[str, Any] | list[dict[str, Any]] | None: + """Show log forwarding profile details or list all log forwarding profiles in a container. Examples -------- @@ -3983,24 +4151,28 @@ def show_log_forwarding_profile( scm show object log-forwarding-profile --folder Texas # Show a specific log forwarding profile by name - scm show object log-forwarding-profile --folder Texas --name security-logs + scm show object log-forwarding-profile security-logs --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Show specific log forwarding profile - log_forwarding_profile = scm_client.get_log_forwarding_profile(folder=folder, name=name) + log_forwarding_profile = scm_client.get_log_forwarding_profile(folder=folder, snippet=snippet, device=device, name=name) emit(log_forwarding_profile, output, title=f"Log Forwarding Profile: {log_forwarding_profile.get('name', name)}") return log_forwarding_profile - # List all log forwarding profiles in the folder (default behavior) - log_forwarding_profiles = scm_client.list_log_forwarding_profiles(folder=folder) + # List all log forwarding profiles in the container (default behavior) + log_forwarding_profiles = scm_client.list_log_forwarding_profiles(folder=folder, snippet=snippet, device=device) + if max_results is not None: + log_forwarding_profiles = log_forwarding_profiles[:max_results] emit( log_forwarding_profiles, output, columns=["name", "folder", "enhanced_application_logging", "match_list", "description"], - title=f"Log forwarding profiles in folder '{folder}'", + title=f"Log forwarding profiles in {location_type} '{location_value}'", ) - return None + return log_forwarding_profiles or None @backup_app.command("region", help="Export regions to a YAML file.") @@ -4054,9 +4226,9 @@ def backup_region( @handle_command_errors("deleting region") def delete_region( name: str = typer.Argument(..., help="Name of the region to delete"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a region. @@ -4066,9 +4238,7 @@ def delete_region( scm delete object region us-east --folder Texas """ - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Retrieve the region first to confirm it exists region = scm_client.get_region( @@ -4108,6 +4278,7 @@ def load_region( folder: str = typer.Option(None, "--folder", help="Override folder location"), snippet: str = typer.Option(None, "--snippet", help="Override snippet location"), device: str = typer.Option(None, "--device", help="Override device location"), + dry_run: bool = DRY_RUN_OPTION, ) -> None: """Load regions from a YAML file.""" # Validate file exists @@ -4127,6 +4298,13 @@ def load_region( if not isinstance(regions, list): regions = [regions] + if dry_run: + info("Dry run mode: would apply the following configurations:") + if folder or snippet or device: + info(f"Container override: {folder or snippet or device}") + typer.echo(yaml.dump(regions)) + return + # Process each region def _apply(region_data: dict): # Validate with Pydantic model @@ -4170,14 +4348,14 @@ def _apply(region_data: dict): @delete_app.command("quarantined-device", help="Delete a quarantined device.") @handle_command_errors("deleting quarantined device") def delete_quarantined_device( - host_id: str = typer.Argument(..., help="Host ID of the quarantined device to delete"), + host_id: str = typer.Option(..., "--host-id", help="Host ID of the quarantined device to delete"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a quarantined device by host ID. Examples -------- - scm delete object quarantined-device 01abcdef-2345-6789-abcd-ef0123456789 + scm delete object quarantined-device --host-id 01abcdef-2345-6789-abcd-ef0123456789 """ if not force: @@ -4193,6 +4371,7 @@ def delete_quarantined_device( @handle_command_errors("loading quarantined devices") def load_quarantined_device( file: str = typer.Option(..., "--file", "-f", help="Input YAML file path"), + dry_run: bool = DRY_RUN_OPTION, ) -> None: """Load quarantined devices from a YAML file.""" # Validate file exists @@ -4212,6 +4391,11 @@ def load_quarantined_device( if not isinstance(devices, list): devices = [devices] + if dry_run: + info("Dry run mode: would apply the following configurations:") + typer.echo(yaml.dump(devices)) + return + # Process each device def _apply(device_data: dict): # Validate with Pydantic model @@ -4243,14 +4427,12 @@ def set_region( latitude: float = typer.Option(None, "--latitude", help="Latitude of the region (-90 to 90)"), longitude: float = typer.Option(None, "--longitude", help="Longitude of the region (-180 to 180)"), addresses: list[str] | None = REGION_ADDRESSES_OPTION, - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, ) -> None: """Create or update a region.""" - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Build region data region_data: dict[str, Any] = { @@ -4297,26 +4479,25 @@ def set_region( @show_app.command("region", help="Show region details.") @handle_command_errors("showing region") def show_region( - name: str = typer.Option(None, "--name", help="Name of specific region to show"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + name: str | None = typer.Argument(None, help="Name of the region to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ) -> list[dict[str, Any]] | dict[str, Any] | None: """Show region details. Examples -------- - # List all regions (default behavior) - scm show object region + # List all regions in a folder (default behavior) + scm show object region --folder Texas # Show a specific region by name - scm show object region --name US-South + scm show object region US-South --folder Texas """ - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) if name: # Show specific region @@ -4340,6 +4521,8 @@ def show_region( snippet=snippet, device=device, ) + if max_results is not None: + regions = regions[:max_results] emit( regions, output, @@ -4352,7 +4535,7 @@ def show_region( @set_app.command("quarantined-device", help="Create a quarantined device entry.") @handle_command_errors("creating quarantined device") def set_quarantined_device( - host_id: str = typer.Argument(..., help="Host ID of the device to quarantine"), + host_id: str = typer.Option(..., "--host-id", help="Host ID of the device to quarantine"), serial_number: str = typer.Option(None, "--serial-number", help="Serial number of the device"), ) -> None: """Create a quarantined device entry.""" @@ -4381,6 +4564,7 @@ def set_quarantined_device( def show_quarantined_device( host_id: str = typer.Option(None, "--host-id", help="Filter by host ID"), serial_number: str = typer.Option(None, "--serial-number", help="Filter by serial number"), + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ) -> list[dict[str, Any]] | dict[str, Any] | None: """Show quarantined devices. @@ -4398,6 +4582,8 @@ def show_quarantined_device( host_id=host_id, serial_number=serial_number, ) + if max_results is not None: + devices = devices[:max_results] emit( devices, output, @@ -4472,28 +4658,31 @@ def backup_service( @delete_app.command("service") @handle_command_errors("deleting service") def delete_service( - folder: str = typer.Option(..., "--folder", help="Folder path for the service"), - name: str = typer.Option(..., "--name", help="Name of the service to delete"), + name: str = typer.Argument(..., help="Name of the service"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a service. Examples -------- - scm delete object service --folder Texas --name web-service + scm delete object service web-service --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete service '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete service '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() # Delete the service - deleted = scm_client.delete_service(folder=folder, name=name) + deleted = scm_client.delete_service(folder=folder, snippet=snippet, device=device, name=name) if deleted: - success(f"Deleted service: {name} from folder {folder}") + success(f"Deleted service: {name} from {location_type} {location_value}") else: - error(f"Failed to delete service '{name}' from folder '{folder}'") + error(f"Failed to delete service '{name}' from {location_type} '{location_value}'") raise typer.Exit(code=1) @@ -4638,8 +4827,10 @@ def _apply(service_data: dict): @set_app.command("service") @handle_command_errors("creating/updating service") def set_service( - folder: str = typer.Option(..., "--folder", help="Folder path for the service"), - name: str = typer.Option(..., "--name", help="Name of the service"), + name: str = typer.Argument(..., help="Name of the service"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, protocol: str = typer.Option(..., "--protocol", help="Protocol type (tcp or udp)"), port: str = typer.Option( ..., @@ -4647,7 +4838,7 @@ def set_service( help="Port number, range (e.g., 80-443), or comma-separated list (e.g., 80,443,8080)", ), description: str = typer.Option(None, "--description", help="Description of the service"), - tag: str = typer.Option(None, "--tag", help="Comma-separated list of tags"), + tags: list[str] | None = TAGS_OPTION, timeout: int = typer.Option(None, "--timeout", help="Timeout override in seconds (TCP only)"), halfclose_timeout: int = typer.Option( None, @@ -4661,6 +4852,8 @@ def set_service( ), ) -> None: """Create or update a service.""" + location_type, location_value = validate_location_params(folder, snippet, device) + # Build protocol configuration protocol_config = {protocol.lower(): {"port": port}} @@ -4676,13 +4869,13 @@ def set_service( protocol_config["tcp"]["override"] = override # Parse tags if provided - tag_list = None - if tag: - tag_list = parse_comma_separated_list([tag]) + tag_list = parse_comma_separated_list(tags) if tags else None # Validate using Pydantic model service_data: dict[str, Any] = { "folder": folder, + "snippet": snippet, + "device": device, "name": name, "protocol": protocol_config, } @@ -4697,6 +4890,8 @@ def set_service( # Create the service using SDK result = scm_client.create_service( folder=service.folder, + snippet=service.snippet, + device=service.device, name=service.name, protocol=service.protocol, description=service.description, @@ -4708,11 +4903,11 @@ def set_service( action = result.pop("__action__", "created") if action == "created": - success(f"Created service: {name} in folder {folder}") + success(f"Created service: {name} in {location_type} {location_value}") elif action == "updated": - success(f"Updated service: {name} in folder {folder}") + success(f"Updated service: {name} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for service: {name} in folder {folder}") + info(f"No changes needed for service: {name} in {location_type} {location_value}") else: error(f"Failed to create/update service '{name}'") raise typer.Exit(code=1) @@ -4721,12 +4916,14 @@ def set_service( @show_app.command("service") @handle_command_errors("showing service") def show_service( - folder: str = typer.Option(..., "--folder", help="Folder path for the service"), - name: str = typer.Option(None, "--name", help="Name of specific service to show"), - list: bool = typer.Option(False, "--list", help="List all services in the folder"), + name: str | None = typer.Argument(None, help="Name of the service to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, -) -> dict[str, Any] | None: - """Show service details or list all services in a folder. +) -> dict[str, Any] | list[dict[str, Any]] | None: + """Show service details or list all services in a container. Examples -------- @@ -4734,24 +4931,28 @@ def show_service( scm show object service --folder Texas # Show a specific service by name - scm show object service --folder Texas --name web-server + scm show object service web-server --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Show specific service - service = scm_client.get_service(folder=folder, name=name) + service = scm_client.get_service(folder=folder, snippet=snippet, device=device, name=name) emit(service, output, title=f"Service: {service.get('name', name)}") return service - # List all services in the folder (default behavior) - services = scm_client.list_services(folder=folder) + # List all services in the container (default behavior) + services = scm_client.list_services(folder=folder, snippet=snippet, device=device) + if max_results is not None: + services = services[:max_results] emit( services, output, columns=["name", "folder", "protocol", "description", "tag"], - title=f"Services in folder '{folder}'", + title=f"Services in {location_type} '{location_value}'", ) - return None + return services or None @backup_app.command("service-group") @@ -4816,28 +5017,31 @@ def backup_service_group( @delete_app.command("service-group") @handle_command_errors("deleting service group") def delete_service_group( - folder: str = typer.Option(..., "--folder", help="Folder path for the service group"), - name: str = typer.Option(..., "--name", help="Name of the service group to delete"), + name: str = typer.Argument(..., help="Name of the service group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a service group. Examples -------- - scm delete object service-group --folder Texas --name web-services + scm delete object service-group web-services --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - confirm = typer.confirm(f"Delete service group '{name}' from folder '{folder}'?") + confirm = typer.confirm(f"Delete service group '{name}' from {location_type} '{location_value}'?") if not confirm: raise typer.Abort() # Delete the service group - deleted = scm_client.delete_service_group(folder=folder, name=name) + deleted = scm_client.delete_service_group(folder=folder, snippet=snippet, device=device, name=name) if deleted: - success(f"Deleted service group: {name} from folder {folder}") + success(f"Deleted service group: {name} from {location_type} {location_value}") else: - error(f"Failed to delete service group '{name}' from folder '{folder}'") + error(f"Failed to delete service group '{name}' from {location_type} '{location_value}'") raise typer.Exit(code=1) @@ -4966,12 +5170,16 @@ def _apply(group_data: dict): @set_app.command("service-group") @handle_command_errors("creating/updating service group") def set_service_group( - folder: str = typer.Option(..., "--folder", help="Folder path for the service group"), - name: str = typer.Option(..., "--name", help="Name of the service group"), + name: str = typer.Argument(..., help="Name of the service group"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, members: str = typer.Option(..., "--members", help="Comma-separated list of service or service group names"), - tag: str = typer.Option(None, "--tag", help="Comma-separated list of tags"), + tags: list[str] | None = TAGS_OPTION, ) -> None: """Create or update a service group.""" + location_type, location_value = validate_location_params(folder, snippet, device) + # Parse members member_list = parse_comma_separated_list([members]) if not member_list: @@ -4979,13 +5187,13 @@ def set_service_group( raise typer.Exit(code=1) # Parse tags if provided - tag_list = None - if tag: - tag_list = parse_comma_separated_list([tag]) + tag_list = parse_comma_separated_list(tags) if tags else None # Validate using Pydantic model service_group_data: dict[str, Any] = { "folder": folder, + "snippet": snippet, + "device": device, "name": name, "members": member_list, } @@ -4998,6 +5206,8 @@ def set_service_group( # Create the service group using SDK result = scm_client.create_service_group( folder=service_group.folder, + snippet=service_group.snippet, + device=service_group.device, name=service_group.name, members=service_group.members, tag=service_group.tag, @@ -5008,11 +5218,11 @@ def set_service_group( action = result.pop("__action__", "created") if action == "created": - success(f"Created service group: {name} in folder {folder}") + success(f"Created service group: {name} in {location_type} {location_value}") elif action == "updated": - success(f"Updated service group: {name} in folder {folder}") + success(f"Updated service group: {name} in {location_type} {location_value}") elif action == "no_change": - info(f"No changes needed for service group: {name} in folder {folder}") + info(f"No changes needed for service group: {name} in {location_type} {location_value}") else: error(f"Failed to create/update service group '{name}'") raise typer.Exit(code=1) @@ -5021,12 +5231,14 @@ def set_service_group( @show_app.command("service-group") @handle_command_errors("showing service group") def show_service_group( - folder: str = typer.Option(..., "--folder", help="Folder path for the service group"), - name: str = typer.Option(None, "--name", help="Name of specific service group to show"), - list: bool = typer.Option(False, "--list", help="List all service groups in the folder"), + name: str | None = typer.Argument(None, help="Name of the service group to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, -) -> dict[str, Any] | None: - """Show service group details or list all service groups in a folder. +) -> dict[str, Any] | list[dict[str, Any]] | None: + """Show service group details or list all service groups in a container. Examples -------- @@ -5034,24 +5246,28 @@ def show_service_group( scm show object service-group --folder Texas # Show a specific service group by name - scm show object service-group --folder Texas --name web-services + scm show object service-group web-services --folder Texas """ + location_type, location_value = validate_location_params(folder, snippet, device) + if name: # Show specific service group - service_group = scm_client.get_service_group(folder=folder, name=name) + service_group = scm_client.get_service_group(folder=folder, snippet=snippet, device=device, name=name) emit(service_group, output, title=f"Service Group: {service_group.get('name', name)}") return service_group - # List all service groups in the folder (default behavior) - service_groups = scm_client.list_service_groups(folder=folder) + # List all service groups in the container (default behavior) + service_groups = scm_client.list_service_groups(folder=folder, snippet=snippet, device=device) + if max_results is not None: + service_groups = service_groups[:max_results] emit( service_groups, output, columns=["name", "folder", "members", "tag"], - title=f"Service groups in folder '{folder}'", + title=f"Service groups in {location_type} '{location_value}'", ) - return None + return service_groups or None # ============================================================================================================================================================================================= @@ -5110,9 +5326,9 @@ def backup_syslog_server_profile( @handle_command_errors("deleting syslog server profile") def delete_syslog_server_profile( name: str = typer.Argument(..., help="Name of the syslog server profile to delete"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a syslog server profile. @@ -5122,11 +5338,7 @@ def delete_syslog_server_profile( scm delete object syslog-server-profile my-syslog --folder Texas """ - # Use the imported scm_client - - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Retrieve the profile first to confirm it exists profile = scm_client.get_syslog_server_profile( @@ -5296,17 +5508,13 @@ def set_syslog_server_profile( format: str = typer.Option(..., "--format", help="Log format (BSD, IETF)"), facility: str = typer.Option(..., "--facility", help="Syslog facility (LOG_USER, LOG_LOCAL0-7)"), description: str = DESCRIPTION_OPTION, - folder: str = FOLDER_OPTION, - snippet: str = SNIPPET_OPTION, - device: str = DEVICE_OPTION, - tag: list[str] = TAG_OPTION, + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + tags: list[str] | None = TAGS_OPTION, ) -> None: """Create or update a syslog server profile.""" - # Use the imported scm_client - - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Build syslog server profile data profile_data: dict[str, Any] = { @@ -5334,8 +5542,8 @@ def set_syslog_server_profile( # Add optional fields if description: profile_data["description"] = description - if tag: - profile_data["tag"] = tag + if tags: + profile_data["tag"] = parse_comma_separated_list(tags) # Validate with Pydantic model validated_profile = SyslogServerProfile(**profile_data) @@ -5362,28 +5570,25 @@ def set_syslog_server_profile( @show_app.command("syslog-server-profile", help="Show syslog server profile details.") @handle_command_errors("showing syslog server profile") def show_syslog_server_profile( - name: str = typer.Option(None, "--name", help="Name of specific syslog server profile to show"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + name: str | None = typer.Argument(None, help="Name of the syslog server profile to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ) -> list[dict[str, Any]] | dict[str, Any] | None: """Show syslog server profile details. Examples -------- - # List all syslog server profiles (default behavior) - scm show object syslog-server-profile + # List all syslog server profiles in a folder (default behavior) + scm show object syslog-server-profile --folder Texas # Show a specific syslog server profile by name - scm show object syslog-server-profile --name primary-syslog + scm show object syslog-server-profile primary-syslog --folder Texas """ - # Use the imported scm_client - - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) if name: # Show specific syslog server profile @@ -5407,6 +5612,8 @@ def show_syslog_server_profile( snippet=snippet, device=device, ) + if max_results is not None: + profiles = profiles[:max_results] emit( profiles, output, @@ -5472,9 +5679,9 @@ def backup_schedule( @handle_command_errors("deleting schedule") def delete_schedule( name: str = typer.Argument(..., help="Name of the schedule to delete"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a schedule. @@ -5484,9 +5691,7 @@ def delete_schedule( scm delete object schedule business-hours --folder Texas """ - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Retrieve the schedule first to confirm it exists schedule = scm_client.get_schedule( @@ -5526,6 +5731,7 @@ def load_schedule( folder: str = typer.Option(None, "--folder", help="Override folder location"), snippet: str = typer.Option(None, "--snippet", help="Override snippet location"), device: str = typer.Option(None, "--device", help="Override device location"), + dry_run: bool = DRY_RUN_OPTION, ) -> None: """Load schedules from a YAML file.""" # Validate file exists @@ -5545,6 +5751,13 @@ def load_schedule( if not isinstance(schedules, list): schedules = [schedules] + if dry_run: + info("Dry run mode: would apply the following configurations:") + if folder or snippet or device: + info(f"Container override: {folder or snippet or device}") + typer.echo(yaml.dump(schedules)) + return + # Process each schedule def _apply(schedule_data: dict): # Create/update the schedule directly (YAML already has SDK format) @@ -5580,14 +5793,12 @@ def set_schedule( days_friday: list[str] | None = SCHEDULE_FRIDAY_OPTION, days_saturday: list[str] | None = SCHEDULE_SATURDAY_OPTION, days_sunday: list[str] | None = SCHEDULE_SUNDAY_OPTION, - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, ) -> None: """Create or update a schedule.""" - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Build days mapping for weekly schedules days = None @@ -5652,26 +5863,25 @@ def set_schedule( @show_app.command("schedule", help="Show schedule details.") @handle_command_errors("showing schedule") def show_schedule( - name: str = typer.Option(None, "--name", help="Name of specific schedule to show"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + name: str | None = typer.Argument(None, help="Name of the schedule to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ) -> list[dict[str, Any]] | dict[str, Any] | None: """Show schedule details. Examples -------- - # List all schedules (default behavior) - scm show object schedule + # List all schedules in a folder (default behavior) + scm show object schedule --folder Texas # Show a specific schedule by name - scm show object schedule --name BusinessHours + scm show object schedule BusinessHours --folder Texas """ - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) if name: # Show specific schedule @@ -5695,6 +5905,8 @@ def show_schedule( snippet=snippet, device=device, ) + if max_results is not None: + schedules = schedules[:max_results] emit( schedules, output, @@ -5759,22 +5971,20 @@ def backup_tag( @delete_app.command("tag", help="Delete a tag.") @handle_command_errors("deleting tag") def delete_tag( - name: str = typer.Option(..., "--name", help="Name of the tag to delete"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + name: str = typer.Argument(..., help="Name of the tag to delete"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete a tag. Examples -------- - scm delete object tag --name production --folder Texas + scm delete object tag production --folder Texas """ - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Retrieve the tag first to confirm it exists tag = scm_client.get_tag( @@ -5814,6 +6024,7 @@ def load_tag( folder: str = typer.Option(None, "--folder", help="Override folder location"), snippet: str = typer.Option(None, "--snippet", help="Override snippet location"), device: str = typer.Option(None, "--device", help="Override device location"), + dry_run: bool = DRY_RUN_OPTION, ) -> None: """Load tags from a YAML file.""" # Validate file exists @@ -5833,6 +6044,13 @@ def load_tag( if not isinstance(tags, list): tags = [tags] + if dry_run: + info("Dry run mode: would apply the following configurations:") + if folder or snippet or device: + info(f"Container override: {folder or snippet or device}") + typer.echo(yaml.dump(tags)) + return + # Process each tag def _apply(tag_data: dict): # Validate with Pydantic model @@ -5876,17 +6094,15 @@ def _apply(tag_data: dict): @set_app.command("tag", help="Create or update a tag.") @handle_command_errors("creating/updating tag") def set_tag( - name: str = typer.Option(..., "--name", help="Name of the tag"), + name: str = typer.Argument(..., help="Name of the tag"), color: str = typer.Option(None, "--color", help="Color for the tag (e.g., Red, Blue, Green)"), comments: str = typer.Option(None, "--comments", help="Comments for the tag"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, ) -> None: """Create or update a tag.""" - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) # Build tag data tag_data = { @@ -5931,26 +6147,25 @@ def set_tag( @show_app.command("tag", help="Show tag details.") @handle_command_errors("showing tag") def show_tag( - name: str = typer.Option(None, "--name", help="Name of specific tag to show"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + name: str | None = typer.Argument(None, help="Name of the tag to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ) -> list[dict[str, Any]] | dict[str, Any] | None: """Show tag details. Examples -------- - # List all tags (default behavior) - scm show object tag + # List all tags in a folder (default behavior) + scm show object tag --folder Texas # Show a specific tag by name - scm show object tag --name Production + scm show object tag Production --folder Texas """ - # Determine container location - if not any([folder, snippet, device]): - folder = "Texas" # Default to Texas folder + validate_location_params(folder, snippet, device) if name: # Show specific tag @@ -5974,6 +6189,8 @@ def show_tag( snippet=snippet, device=device, ) + if max_results is not None: + tags = tags[:max_results] emit( tags, output, @@ -5992,9 +6209,9 @@ def show_tag( @handle_command_errors("creating/updating auto tag action") def set_auto_tag_action( name: str = typer.Argument(..., help="Name of the auto tag action"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, description: str = typer.Option(None, "--description", help="Description"), log_type: str = typer.Option(None, "--log-type", help="Log type (traffic, threat, etc.)"), filter_expr: str = typer.Option(None, "--filter", help="Filter expression"), @@ -6003,8 +6220,7 @@ def set_auto_tag_action( quarantine: bool = typer.Option(None, "--quarantine", help="Enable quarantine"), ) -> None: """Create or update an auto tag action.""" - if not any([folder, snippet, device]): - folder = "Texas" + validate_location_params(folder, snippet, device) tag_data: dict[str, Any] = {"name": name} if folder: @@ -6045,17 +6261,13 @@ def set_auto_tag_action( @handle_command_errors("deleting auto tag action") def delete_auto_tag_action( name: str = typer.Argument(..., help="Name of the auto tag action to delete"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ) -> None: """Delete an auto tag action.""" - if not any([folder, snippet, device]): - folder = "Texas" - - location_type = "folder" if folder else ("snippet" if snippet else "device") - location_value = folder or snippet or device + location_type, location_value = validate_location_params(folder, snippet, device) if not force: confirm = typer.confirm(f"Delete auto tag action '{name}' from {location_type} '{location_value}'?") @@ -6132,10 +6344,11 @@ def _apply(entry: dict): @show_app.command("auto-tag-action", help="Show auto tag action details.") @handle_command_errors("showing auto tag action") def show_auto_tag_action( - name: str = typer.Option(None, "--name", help="Name of specific auto tag action to show"), - folder: str = typer.Option(None, "--folder", help="Folder location"), - snippet: str = typer.Option(None, "--snippet", help="Snippet location"), - device: str = typer.Option(None, "--device", help="Device location"), + name: str | None = typer.Argument(None, help="Name of the auto tag action to show; omit to list all"), + folder: str | None = FOLDER_OPTION, + snippet: str | None = SNIPPET_OPTION, + device: str | None = DEVICE_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, output: OutputFormat = OUTPUT_OPTION, ) -> list[dict[str, Any]] | dict[str, Any] | None: """Show auto tag action details. @@ -6143,11 +6356,10 @@ def show_auto_tag_action( Examples -------- scm show object auto-tag-action --folder Texas - scm show object auto-tag-action --folder Texas --name my-action + scm show object auto-tag-action my-action --folder Texas """ - if not any([folder, snippet, device]): - folder = "Texas" + validate_location_params(folder, snippet, device) if name: action = scm_client.get_auto_tag_action( @@ -6169,6 +6381,8 @@ def show_auto_tag_action( snippet=snippet, device=device, ) + if max_results is not None: + actions = actions[:max_results] emit( actions, output, @@ -6187,8 +6401,7 @@ def backup_auto_tag_action( file: str = typer.Option(None, "--file", help="Output file path"), ) -> None: """Backup auto tag actions to a YAML file.""" - if not any([folder, snippet, device]): - folder = "Texas" + validate_location_params(folder, snippet, device) actions = scm_client.list_auto_tag_actions( folder=folder, diff --git a/src/scm_cli/commands/operations.py b/src/scm_cli/commands/operations.py index ea060aa..a85b53d 100644 --- a/src/scm_cli/commands/operations.py +++ b/src/scm_cli/commands/operations.py @@ -63,7 +63,7 @@ def _run_operation(device: str, operation: str, async_mode: bool, timeout: int, if async_mode: job_id = result.get("job_id", "unknown") success(f"Job dispatched: {job_id}") - info(f"Check status with: scm operations status --job-id {job_id}") + info(f"Check status with: scm operations status --id {job_id}") return results = result.get("results", []) @@ -170,14 +170,14 @@ def logging_status(device: str = DEVICE_OPTION, async_mode: bool = ASYNC_OPTION, @app.command("status") @handle_command_errors("checking job status") def operation_status( - job_id: str = typer.Option(..., "--job-id", "-j", help="Job ID to check"), + job_id: str = typer.Option(..., "--id", help="Job ID to check"), output: OutputFormat = OUTPUT_OPTION, ): """Check status of a dispatched device operation job. Examples -------- - scm operations status --job-id abc-123 + scm operations status --id abc-123 """ result = scm_client.get_device_operation_status(job_id=job_id) diff --git a/src/scm_cli/commands/security.py b/src/scm_cli/commands/security.py index 120aac0..037cb26 100644 --- a/src/scm_cli/commands/security.py +++ b/src/scm_cli/commands/security.py @@ -15,7 +15,7 @@ from ..utils import parse_comma_separated_list, validate_location_params from ..utils.bulk import run_bulk from ..utils.decorators import handle_command_errors -from ..utils.output import OUTPUT_OPTION, OutputFormat, emit, error, info, success, warning +from ..utils.output import OUTPUT_OPTION, OutputFormat, emit, error, info, success from ..utils.sdk_client import scm_client from ..utils.validators import ( AntiSpywareProfile, @@ -63,10 +63,10 @@ "--device", help="Device path for the security rule", ) -NAME_OPTION = typer.Option( - ..., - "--name", - help="Name of the security rule", +MAX_RESULTS_OPTION = typer.Option( + None, + "--max-results", + help="Maximum number of results to display", ) FILE_OPTION = typer.Option( ..., @@ -337,10 +337,10 @@ def backup_security_rule( @delete_app.command("rule") @handle_command_errors("deleting security rule") def delete_security_rule( + name: str = typer.Argument(..., help="Name of the security rule to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the security rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the security rule"), device: str = typer.Option(None, "--device", help="Device containing the security rule"), - name: str = NAME_OPTION, rulebase: str = RULEBASE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): @@ -348,13 +348,13 @@ def delete_security_rule( Examples: # Delete from folder - scm delete security rule --folder Texas --name test + scm delete security rule test --folder Texas # Delete from snippet - scm delete security rule --snippet DNS-Best-Practice --name block-dns + scm delete security rule block-dns --snippet DNS-Best-Practice # Delete from device - scm delete security rule --device austin-01 --name local-rule + scm delete security rule local-rule --device austin-01 """ # Validate location parameters @@ -365,12 +365,7 @@ def delete_security_rule( if not confirm: raise typer.Abort() - # For now, SDK only supports folder - if location_type != "folder": - error(f"Error: Deleting security rules from {location_type} is not yet supported by the SDK") - raise typer.Exit(code=1) - - result = scm_client.delete_security_rule(folder=location_value, name=name, rulebase=rulebase) + result = scm_client.delete_security_rule(**{location_type: location_value}, name=name, rulebase=rulebase) if result: success(f"Deleted security rule: {name} from {location_type} {location_value} rulebase {rulebase}") else: @@ -460,18 +455,11 @@ def load_security_rule( # Validate using the Pydantic model rule = SecurityRule(**rule_data) - # For now, SDK only supports folder - if hasattr(rule, "snippet") and rule.snippet: - warning(f"Warning: Creating security rules in snippets is not yet supported by the SDK. Skipping rule '{rule.name}'") - continue - elif hasattr(rule, "device") and rule.device: - warning(f"Warning: Creating security rules on devices is not yet supported by the SDK. Skipping rule '{rule.name}'") - continue - # Call the SDK client to create the security rule sdk_data = rule.to_sdk_model() + container_kwargs = {key: sdk_data[key] for key in ("folder", "snippet", "device") if sdk_data.get(key)} result = scm_client.create_security_rule( - folder=sdk_data["folder"], + **container_kwargs, name=sdk_data["name"], source_zones=sdk_data["source_zones"], destination_zones=sdk_data["destination_zones"], @@ -515,10 +503,10 @@ def load_security_rule( @set_app.command("rule") @handle_command_errors("creating security rule") def set_security_rule( + name: str = typer.Argument(..., help="Name of the security rule"), folder: str = typer.Option(None, "--folder", help="Folder path for the security rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the security rule"), device: str = typer.Option(None, "--device", help="Device path for the security rule"), - name: str = NAME_OPTION, source_zones: list[str] = SOURCE_ZONES_OPTION, destination_zones: list[str] = DESTINATION_ZONES_OPTION, source_addresses: list[str] | None = SOURCE_ADDRESSES_OPTION, @@ -538,11 +526,11 @@ def set_security_rule( Examples: # Create basic rule - scm set security rule --folder Texas --name test \\ + scm set security rule test --folder Texas \\ --source-zones trust --destination-zones untrust # Create rule with full options - scm set security rule --folder Texas --name web-allow \\ + scm set security rule web-allow --folder Texas \\ --source-zones trust --destination-zones untrust \\ --source-addresses internal-net --destination-addresses any \\ --applications web-browsing --applications ssl \\ @@ -552,7 +540,7 @@ def set_security_rule( --tags web --tags production # Create rule in post rulebase - scm set security rule --folder Texas --name cleanup \\ + scm set security rule cleanup --folder Texas \\ --source-zones any --destination-zones any \\ --action deny --log-start --log-end \\ --rulebase post @@ -561,11 +549,6 @@ def set_security_rule( # Validate location parameters location_type, location_value = validate_location_params(folder, snippet, device) - # For now, SDK only supports folder - if location_type != "folder": - error(f"Error: Creating security rules in {location_type} is not yet supported by the SDK") - raise typer.Exit(code=1) - # Parse comma-separated list options parsed_src_zones = parse_comma_separated_list(source_zones) parsed_dst_zones = parse_comma_separated_list(destination_zones) @@ -577,7 +560,7 @@ def set_security_rule( # Validate and create security rule rule = SecurityRule( - folder=location_value, + **{location_type: location_value}, name=name, source_zones=parsed_src_zones, destination_zones=parsed_dst_zones, @@ -605,7 +588,7 @@ def set_security_rule( # Call SDK client to create the rule result = scm_client.create_security_rule( - folder=rule.folder, + **{location_type: location_value}, name=rule.name, source_zones=rule.source_zones, destination_zones=rule.destination_zones, @@ -636,15 +619,16 @@ def set_security_rule( @show_app.command("rule") @handle_command_errors("showing security rule") def show_security_rule( + name: str | None = typer.Argument(None, help="Name of the security rule to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the security rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the security rule"), device: str = typer.Option(None, "--device", help="Device containing the security rule"), rulebase: str = RULEBASE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the security rule to show"), exclude_folder: list[str] | None = EXCLUDE_FOLDER_OPTION, exclude_snippet: list[str] | None = EXCLUDE_SNIPPET_OPTION, exclude_device: list[str] | None = EXCLUDE_DEVICE_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display security rules. @@ -657,7 +641,7 @@ def show_security_rule( scm show security rule --folder Texas --rulebase post # Show a specific security rule by name - scm show security rule --folder Texas --name "Allow Web Traffic" + scm show security rule "Allow Web Traffic" --folder Texas # List rules excluding specific folders scm show security rule --folder Texas --exclude-folder "All" @@ -671,13 +655,8 @@ def show_security_rule( location_type, location_value = validate_location_params(folder, snippet, device) if name: - # For now, SDK only supports folder for get operations - if location_type != "folder": - error(f"Error: Getting security rules from {location_type} is not yet supported by the SDK") - raise typer.Exit(code=1) - # Get a specific security rule by name - rule = scm_client.get_security_rule(folder=location_value, name=name, rulebase=rulebase) + rule = scm_client.get_security_rule(**{location_type: location_value}, name=name, rulebase=rulebase) emit(rule, output, title=f"Security Rule: {rule.get('name', 'N/A')}") return rule @@ -693,6 +672,9 @@ def show_security_rule( exclude_devices=exclude_device or None, ) + if max_results is not None: + rules = rules[:max_results] + if not rules: emit([], output) return @@ -774,23 +756,23 @@ def backup_anti_spyware_profile( @delete_app.command("anti-spyware-profile") @handle_command_errors("deleting anti-spyware profile") def delete_anti_spyware_profile( + name: str = typer.Argument(..., help="Name of the anti-spyware profile to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the anti-spyware profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the anti-spyware profile"), device: str = typer.Option(None, "--device", help="Device containing the anti-spyware profile"), - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an anti-spyware profile. Examples: # Delete from folder - scm delete security anti-spyware-profile --folder Texas --name strict-security + scm delete security anti-spyware-profile strict-security --folder Texas # Delete from snippet - scm delete security anti-spyware-profile --snippet DNS-Best-Practice --name dns-protection + scm delete security anti-spyware-profile dns-protection --snippet DNS-Best-Practice # Delete from device - scm delete security anti-spyware-profile --device austin-01 --name local-profile + scm delete security anti-spyware-profile local-profile --device austin-01 """ # Validate location parameters @@ -933,10 +915,10 @@ def _apply(profile_data: dict): @set_app.command("anti-spyware-profile") @handle_command_errors("creating anti-spyware profile") def set_anti_spyware_profile( + name: str = typer.Argument(..., help="Name of the anti-spyware profile"), folder: str = typer.Option(None, "--folder", help="Folder path for the anti-spyware profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the anti-spyware profile"), device: str = typer.Option(None, "--device", help="Device path for the anti-spyware profile"), - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, cloud_inline_analysis: bool = typer.Option( False, @@ -953,16 +935,16 @@ def set_anti_spyware_profile( Examples: # Create basic profile in folder - scm set security anti-spyware-profile --folder Texas --name strict-security \ + scm set security anti-spyware-profile strict-security --folder Texas \ --description "Block critical threats" # Create profile with cloud inline analysis - scm set security anti-spyware-profile --folder Texas --name cloud-protection \ + scm set security anti-spyware-profile cloud-protection --folder Texas \ --cloud-inline-analysis # Create profile in snippet - scm set security anti-spyware-profile --snippet Security-Best-Practice \ - --name standard-protection + scm set security anti-spyware-profile standard-protection \ + --snippet Security-Best-Practice """ # Validate location parameters @@ -1033,11 +1015,12 @@ def set_anti_spyware_profile( @show_app.command("anti-spyware-profile") @handle_command_errors("showing anti-spyware profile") def show_anti_spyware_profile( + name: str | None = typer.Argument(None, help="Name of the anti-spyware profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the anti-spyware profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the anti-spyware profile"), device: str = typer.Option(None, "--device", help="Device containing the anti-spyware profile"), - name: str | None = typer.Option(None, "--name", help="Name of the anti-spyware profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display anti-spyware profiles. @@ -1046,7 +1029,7 @@ def show_anti_spyware_profile( scm show security anti-spyware-profile --folder Texas # Show a specific anti-spyware profile by name - scm show security anti-spyware-profile --folder Texas --name strict-security + scm show security anti-spyware-profile strict-security --folder Texas # List profiles in snippet scm show security anti-spyware-profile --snippet Security-Best-Practice @@ -1068,6 +1051,9 @@ def show_anti_spyware_profile( kwargs = {location_type: location_value} profiles = scm_client.list_anti_spyware_profiles(**kwargs, exact_match=False) + if max_results is not None: + profiles = profiles[:max_results] + if not profiles: emit([], output) return @@ -1149,23 +1135,23 @@ def backup_decryption_profile( @delete_app.command("decryption-profile") @handle_command_errors("deleting decryption profile") def delete_decryption_profile( + name: str = typer.Argument(..., help="Name of the decryption profile to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the decryption profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the decryption profile"), device: str = typer.Option(None, "--device", help="Device containing the decryption profile"), - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a decryption profile. Examples: # Delete from folder - scm delete security decryption-profile --folder Texas --name ssl-forward-proxy + scm delete security decryption-profile ssl-forward-proxy --folder Texas # Delete from snippet - scm delete security decryption-profile --snippet DNS-Best-Practice --name ssl-inbound + scm delete security decryption-profile ssl-inbound --snippet DNS-Best-Practice # Delete from device - scm delete security decryption-profile --device austin-01 --name no-decrypt + scm delete security decryption-profile no-decrypt --device austin-01 """ # Validate location parameters @@ -1308,10 +1294,10 @@ def _apply(profile_data: dict): @set_app.command("decryption-profile") @handle_command_errors("creating decryption profile") def set_decryption_profile( + name: str = typer.Argument(..., help="Name of the decryption profile"), folder: str = typer.Option(None, "--folder", help="Folder path for the decryption profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the decryption profile"), device: str = typer.Option(None, "--device", help="Device path for the decryption profile"), - name: str = NAME_OPTION, description: str | None = typer.Option( None, "--description", @@ -1342,19 +1328,19 @@ def set_decryption_profile( Examples: # Create basic SSL forward proxy profile - scm set security decryption-profile --folder Texas --name ssl-forward \ + scm set security decryption-profile ssl-forward --folder Texas \ --ssl-forward-proxy '{"block_expired_certificate": true, "block_untrusted_issuer": true}' # Create SSL inbound inspection profile - scm set security decryption-profile --folder Texas --name ssl-inbound \ + scm set security decryption-profile ssl-inbound --folder Texas \ --ssl-inbound-proxy '{"block_if_no_resource": true, "block_unsupported_cipher": true}' # Create no-decrypt profile - scm set security decryption-profile --folder Texas --name no-decrypt \ + scm set security decryption-profile no-decrypt --folder Texas \ --ssl-no-proxy '{"block_expired_certificate": false, "block_untrusted_issuer": false}' # Create profile with protocol settings - scm set security decryption-profile --folder Texas --name custom-decrypt \ + scm set security decryption-profile custom-decrypt --folder Texas \ --ssl-forward-proxy '{"block_expired_certificate": true}' \ --ssl-protocol-settings '{"min_version": "tls1-2", "max_version": "tls1-3"}' @@ -1412,11 +1398,12 @@ def set_decryption_profile( @show_app.command("decryption-profile") @handle_command_errors("showing decryption profile") def show_decryption_profile( + name: str | None = typer.Argument(None, help="Name of the decryption profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the decryption profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the decryption profile"), device: str = typer.Option(None, "--device", help="Device containing the decryption profile"), - name: str | None = typer.Option(None, "--name", help="Name of the decryption profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display decryption profiles. @@ -1425,7 +1412,7 @@ def show_decryption_profile( scm show security decryption-profile --folder Texas # Show a specific decryption profile by name - scm show security decryption-profile --folder Texas --name ssl-forward + scm show security decryption-profile ssl-forward --folder Texas # List profiles in snippet scm show security decryption-profile --snippet Security-Best-Practice @@ -1447,6 +1434,9 @@ def show_decryption_profile( kwargs = {location_type: location_value} profiles = scm_client.list_decryption_profiles(**kwargs, exact_match=False) + if max_results is not None: + profiles = profiles[:max_results] + if not profiles: emit([], output) return @@ -1528,23 +1518,23 @@ def backup_wildfire_antivirus_profile( @delete_app.command("wildfire-antivirus-profile") @handle_command_errors("deleting WildFire antivirus profile") def delete_wildfire_antivirus_profile( + name: str = typer.Argument(..., help="Name of the WildFire antivirus profile to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the WildFire antivirus profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the WildFire antivirus profile"), device: str = typer.Option(None, "--device", help="Device containing the WildFire antivirus profile"), - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a WildFire antivirus profile. Examples: # Delete from folder - scm delete security wildfire-antivirus-profile --folder Texas --name wf-strict + scm delete security wildfire-antivirus-profile wf-strict --folder Texas # Delete from snippet - scm delete security wildfire-antivirus-profile --snippet Security-Best-Practice --name wf-standard + scm delete security wildfire-antivirus-profile wf-standard --snippet Security-Best-Practice # Delete from device - scm delete security wildfire-antivirus-profile --device austin-01 --name wf-local + scm delete security wildfire-antivirus-profile wf-local --device austin-01 """ # Validate location parameters @@ -1687,10 +1677,10 @@ def _apply(profile_data: dict): @set_app.command("wildfire-antivirus-profile") @handle_command_errors("creating WildFire antivirus profile") def set_wildfire_antivirus_profile( + name: str = typer.Argument(..., help="Name of the WildFire antivirus profile"), folder: str = typer.Option(None, "--folder", help="Folder path for the WildFire antivirus profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the WildFire antivirus profile"), device: str = typer.Option(None, "--device", help="Device path for the WildFire antivirus profile"), - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, rules_json: str | None = typer.Option( None, @@ -1707,15 +1697,15 @@ def set_wildfire_antivirus_profile( Examples: # Create basic profile in folder with default rule - scm set security wildfire-antivirus-profile --folder Texas --name wf-basic \ + scm set security wildfire-antivirus-profile wf-basic --folder Texas \ --description "Basic WildFire profile" # Create profile with custom rules (JSON) - scm set security wildfire-antivirus-profile --folder Texas --name wf-custom \ + scm set security wildfire-antivirus-profile wf-custom --folder Texas \ --rules '[{"name":"Forward All","direction":"both","analysis":"public-cloud","application":["any"],"file_type":["any"]}]' # Create profile with packet capture - scm set security wildfire-antivirus-profile --folder Texas --name wf-capture \ + scm set security wildfire-antivirus-profile wf-capture --folder Texas \ --packet-capture """ @@ -1782,11 +1772,12 @@ def set_wildfire_antivirus_profile( @show_app.command("wildfire-antivirus-profile") @handle_command_errors("showing WildFire antivirus profile") def show_wildfire_antivirus_profile( + name: str | None = typer.Argument(None, help="Name of the WildFire antivirus profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the WildFire antivirus profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the WildFire antivirus profile"), device: str = typer.Option(None, "--device", help="Device containing the WildFire antivirus profile"), - name: str | None = typer.Option(None, "--name", help="Name of the WildFire antivirus profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display WildFire antivirus profiles. @@ -1795,7 +1786,7 @@ def show_wildfire_antivirus_profile( scm show security wildfire-antivirus-profile --folder Texas # Show a specific profile by name - scm show security wildfire-antivirus-profile --folder Texas --name wf-basic + scm show security wildfire-antivirus-profile wf-basic --folder Texas # List profiles in snippet scm show security wildfire-antivirus-profile --snippet Security-Best-Practice @@ -1817,6 +1808,9 @@ def show_wildfire_antivirus_profile( kwargs = {location_type: location_value} profiles = scm_client.list_wildfire_antivirus_profiles(**kwargs, exact_match=False) + if max_results is not None: + profiles = profiles[:max_results] + if not profiles: emit([], output) return @@ -1900,20 +1894,20 @@ def backup_dns_security_profile( @delete_app.command("dns-security-profile") @handle_command_errors("deleting DNS security profile") def delete_dns_security_profile( + name: str = typer.Argument(..., help="Name of the DNS security profile to delete"), folder: str = DNS_SEC_FOLDER_OPTION, snippet: str = DNS_SEC_SNIPPET_OPTION, device: str = DNS_SEC_DEVICE_OPTION, - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a DNS security profile. Examples: # Delete from folder - scm delete security dns-security-profile --folder Texas --name dns-sec-default + scm delete security dns-security-profile dns-sec-default --folder Texas # Delete from snippet - scm delete security dns-security-profile --snippet DNS-Best-Practice --name dns-sec-strict + scm delete security dns-security-profile dns-sec-strict --snippet DNS-Best-Practice """ # Validate location parameters @@ -2054,10 +2048,10 @@ def _apply(profile_data: dict): @set_app.command("dns-security-profile") @handle_command_errors("creating DNS security profile") def set_dns_security_profile( + name: str = typer.Argument(..., help="Name of the DNS security profile"), folder: str = typer.Option(None, "--folder", help="Folder path for the DNS security profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the DNS security profile"), device: str = typer.Option(None, "--device", help="Device path for the DNS security profile"), - name: str = NAME_OPTION, description: str | None = typer.Option( None, "--description", @@ -2073,11 +2067,11 @@ def set_dns_security_profile( Examples: # Create basic DNS security profile with sinkhole - scm set security dns-security-profile --folder Texas --name dns-sec-default \ + scm set security dns-security-profile dns-sec-default --folder Texas \ --botnet-domains '{"dns_security_categories": [{"name": "pan-dns-sec-malware", "action": "sinkhole"}]}' # Create profile with whitelist - scm set security dns-security-profile --folder Texas --name dns-sec-custom \ + scm set security dns-security-profile dns-sec-custom --folder Texas \ --botnet-domains '{"whitelist": [{"name": "example.com"}]}' """ @@ -2128,11 +2122,12 @@ def set_dns_security_profile( @show_app.command("dns-security-profile") @handle_command_errors("showing DNS security profile") def show_dns_security_profile( + name: str | None = typer.Argument(None, help="Name of the DNS security profile to show; omit to list all"), folder: str = DNS_SEC_FOLDER_OPTION, snippet: str = DNS_SEC_SNIPPET_OPTION, device: str = DNS_SEC_DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the DNS security profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display DNS security profiles. @@ -2141,7 +2136,7 @@ def show_dns_security_profile( scm show security dns-security-profile --folder Texas # Show a specific DNS security profile by name - scm show security dns-security-profile --folder Texas --name dns-sec-default + scm show security dns-security-profile dns-sec-default --folder Texas # List profiles in snippet scm show security dns-security-profile --snippet Security-Best-Practice @@ -2163,6 +2158,9 @@ def show_dns_security_profile( kwargs = {location_type: location_value} profiles = scm_client.list_dns_security_profiles(**kwargs, exact_match=False) + if max_results is not None: + profiles = profiles[:max_results] + if not profiles: emit([], output) return @@ -2244,23 +2242,23 @@ def backup_vulnerability_protection_profile( @delete_app.command("vulnerability-protection-profile") @handle_command_errors("deleting vulnerability protection profile") def delete_vulnerability_protection_profile( + name: str = typer.Argument(..., help="Name of the vulnerability protection profile to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the vulnerability protection profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the vulnerability protection profile"), device: str = typer.Option(None, "--device", help="Device containing the vulnerability protection profile"), - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a vulnerability protection profile. Examples: # Delete from folder - scm delete security vulnerability-protection-profile --folder Texas --name strict-vuln + scm delete security vulnerability-protection-profile strict-vuln --folder Texas # Delete from snippet - scm delete security vulnerability-protection-profile --snippet Security-Best-Practice --name vuln-protection + scm delete security vulnerability-protection-profile vuln-protection --snippet Security-Best-Practice # Delete from device - scm delete security vulnerability-protection-profile --device austin-01 --name local-profile + scm delete security vulnerability-protection-profile local-profile --device austin-01 """ # Validate location parameters @@ -2403,10 +2401,10 @@ def _apply(profile_data: dict): @set_app.command("vulnerability-protection-profile") @handle_command_errors("creating vulnerability protection profile") def set_vulnerability_protection_profile( + name: str = typer.Argument(..., help="Name of the vulnerability protection profile"), folder: str = typer.Option(None, "--folder", help="Folder path for the vulnerability protection profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the vulnerability protection profile"), device: str = typer.Option(None, "--device", help="Device path for the vulnerability protection profile"), - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, block_critical_high: bool = typer.Option( False, @@ -2418,16 +2416,16 @@ def set_vulnerability_protection_profile( Examples: # Create basic profile in folder - scm set security vulnerability-protection-profile --folder Texas --name strict-vuln \ + scm set security vulnerability-protection-profile strict-vuln --folder Texas \ --description "Block critical vulnerabilities" # Create profile with block critical/high rule - scm set security vulnerability-protection-profile --folder Texas --name vuln-protection \ + scm set security vulnerability-protection-profile vuln-protection --folder Texas \ --block-critical-high # Create profile in snippet - scm set security vulnerability-protection-profile --snippet Security-Best-Practice \ - --name standard-vuln + scm set security vulnerability-protection-profile standard-vuln \ + --snippet Security-Best-Practice """ # Validate location parameters @@ -2501,11 +2499,12 @@ def set_vulnerability_protection_profile( @show_app.command("vulnerability-protection-profile") @handle_command_errors("showing vulnerability protection profile") def show_vulnerability_protection_profile( + name: str | None = typer.Argument(None, help="Name of the vulnerability protection profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the vulnerability protection profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the vulnerability protection profile"), device: str = typer.Option(None, "--device", help="Device containing the vulnerability protection profile"), - name: str | None = typer.Option(None, "--name", help="Name of the vulnerability protection profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display vulnerability protection profiles. @@ -2514,7 +2513,7 @@ def show_vulnerability_protection_profile( scm show security vulnerability-protection-profile --folder Texas # Show a specific vulnerability protection profile by name - scm show security vulnerability-protection-profile --folder Texas --name strict-vuln + scm show security vulnerability-protection-profile strict-vuln --folder Texas # List profiles in snippet scm show security vulnerability-protection-profile --snippet Security-Best-Practice @@ -2536,6 +2535,9 @@ def show_vulnerability_protection_profile( kwargs = {location_type: location_value} profiles = scm_client.list_vulnerability_protection_profiles(**kwargs, exact_match=False) + if max_results is not None: + profiles = profiles[:max_results] + if not profiles: emit([], output) return @@ -2616,23 +2618,23 @@ def backup_url_category( @delete_app.command("url-category") @handle_command_errors("deleting URL category") def delete_url_category( + name: str = typer.Argument(..., help="Name of the URL category to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the URL category"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the URL category"), device: str = typer.Option(None, "--device", help="Device containing the URL category"), - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a URL category. Examples: # Delete from folder - scm delete security url-category --folder Texas --name custom-block-list + scm delete security url-category custom-block-list --folder Texas # Delete from snippet - scm delete security url-category --snippet DNS-Best-Practice --name phishing-urls + scm delete security url-category phishing-urls --snippet DNS-Best-Practice # Delete from device - scm delete security url-category --device austin-01 --name local-blocklist + scm delete security url-category local-blocklist --device austin-01 """ # Validate location parameters @@ -2775,10 +2777,10 @@ def _apply(category_data: dict): @set_app.command("url-category") @handle_command_errors("creating URL category") def set_url_category( + name: str = typer.Argument(..., help="Name of the URL category"), folder: str = typer.Option(None, "--folder", help="Folder path for the URL category"), snippet: str = typer.Option(None, "--snippet", help="Snippet path for the URL category"), device: str = typer.Option(None, "--device", help="Device path for the URL category"), - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, type: str = typer.Option("URL List", "--type", help="Type of URL category (URL List or Category Match)"), urls: list[str] | None = URL_CATEGORY_URLS_OPTION, @@ -2787,16 +2789,16 @@ def set_url_category( Examples: # Create URL list category in folder - scm set security url-category --folder Texas --name custom-block \ + scm set security url-category custom-block --folder Texas \ --url malware.example.com --url phishing.test.org # Create category match type - scm set security url-category --folder Texas --name match-category \ + scm set security url-category match-category --folder Texas \ --type "Category Match" --url gambling --url adult # Create in snippet - scm set security url-category --snippet Security-Best-Practice \ - --name blocked-sites --url bad-site.com + scm set security url-category blocked-sites \ + --snippet Security-Best-Practice --url bad-site.com """ # Validate location parameters @@ -2844,11 +2846,12 @@ def set_url_category( @show_app.command("url-category") @handle_command_errors("showing URL category") def show_url_category( + name: str | None = typer.Argument(None, help="Name of the URL category to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the URL category"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the URL category"), device: str = typer.Option(None, "--device", help="Device containing the URL category"), - name: str | None = typer.Option(None, "--name", help="Name of the URL category to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display URL categories. @@ -2857,7 +2860,7 @@ def show_url_category( scm show security url-category --folder Texas # Show a specific URL category by name - scm show security url-category --folder Texas --name custom-block + scm show security url-category custom-block --folder Texas # List URL categories in snippet scm show security url-category --snippet Security-Best-Practice @@ -2879,6 +2882,9 @@ def show_url_category( kwargs = {location_type: location_value} categories = scm_client.list_url_categories(**kwargs, exact_match=False) + if max_results is not None: + categories = categories[:max_results] + if not categories: emit([], output) return @@ -2947,17 +2953,17 @@ def backup_app_override_rule( @delete_app.command("app-override-rule") @handle_command_errors("deleting app override rule") def delete_app_override_rule( + name: str = typer.Argument(..., help="Name of the app override rule to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the rule"), device: str = typer.Option(None, "--device", help="Device containing the rule"), - name: str = NAME_OPTION, rulebase: str = RULEBASE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an app override rule. Examples: - scm delete security app-override-rule --folder Texas --name override-web --rulebase pre + scm delete security app-override-rule override-web --folder Texas --rulebase pre """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3063,10 +3069,10 @@ def load_app_override_rule( @set_app.command("app-override-rule") @handle_command_errors("creating app override rule") def set_app_override_rule( + name: str = typer.Argument(..., help="Name of the app override rule"), folder: str = typer.Option(None, "--folder", help="Folder path"), snippet: str = typer.Option(None, "--snippet", help="Snippet path"), device: str = typer.Option(None, "--device", help="Device path"), - name: str = NAME_OPTION, application: str = typer.Option(..., "--application", help="Application to override"), port: str = typer.Option(..., "--port", help="Port(s) for the rule"), protocol: str = typer.Option(..., "--protocol", help="Protocol (tcp or udp)"), @@ -3080,7 +3086,7 @@ def set_app_override_rule( r"""Create or update an app override rule. Examples: - scm set security app-override-rule --folder Texas --name override-https \ + scm set security app-override-rule override-https --folder Texas \ --application ssl --port 8443 --protocol tcp """ @@ -3130,18 +3136,19 @@ def set_app_override_rule( @show_app.command("app-override-rule") @handle_command_errors("showing app override rule") def show_app_override_rule( + name: str | None = typer.Argument(None, help="Name of the app override rule to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the rule"), device: str = typer.Option(None, "--device", help="Device containing the rule"), - name: str | None = typer.Option(None, "--name", help="Name of the rule to show"), rulebase: str = RULEBASE_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display app override rules. Examples: scm show security app-override-rule --folder Texas --rulebase pre - scm show security app-override-rule --folder Texas --name override-https + scm show security app-override-rule override-https --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3156,6 +3163,9 @@ def show_app_override_rule( kwargs = {location_type: location_value} rules = scm_client.list_app_override_rules(**kwargs, rulebase=rulebase, exact_match=False) + if max_results is not None: + rules = rules[:max_results] + if not rules: emit([], output) return @@ -3220,17 +3230,17 @@ def backup_authentication_rule( @delete_app.command("authentication-rule") @handle_command_errors("deleting authentication rule") def delete_authentication_rule( + name: str = typer.Argument(..., help="Name of the authentication rule to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the rule"), device: str = typer.Option(None, "--device", help="Device containing the rule"), - name: str = NAME_OPTION, rulebase: str = RULEBASE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete an authentication rule. Examples: - scm delete security authentication-rule --folder Texas --name auth-rule-1 + scm delete security authentication-rule auth-rule-1 --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3335,10 +3345,10 @@ def load_authentication_rule( @set_app.command("authentication-rule") @handle_command_errors("creating authentication rule") def set_authentication_rule( + name: str = typer.Argument(..., help="Name of the authentication rule"), folder: str = typer.Option(None, "--folder", help="Folder path"), snippet: str = typer.Option(None, "--snippet", help="Snippet path"), device: str = typer.Option(None, "--device", help="Device path"), - name: str = NAME_OPTION, rulebase: str = RULEBASE_OPTION, description: str | None = DESCRIPTION_OPTION, source_zones: list[str] | None = AUTH_RULE_SOURCE_ZONES_OPTION, @@ -3352,7 +3362,7 @@ def set_authentication_rule( r"""Create or update an authentication rule. Examples: - scm set security authentication-rule --folder Texas --name auth-web \ + scm set security authentication-rule auth-web --folder Texas \ --source-zones trust --destination-zones untrust """ @@ -3405,18 +3415,19 @@ def set_authentication_rule( @show_app.command("authentication-rule") @handle_command_errors("showing authentication rule") def show_authentication_rule( + name: str | None = typer.Argument(None, help="Name of the authentication rule to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the rule"), device: str = typer.Option(None, "--device", help="Device containing the rule"), - name: str | None = typer.Option(None, "--name", help="Name of the rule to show"), rulebase: str = RULEBASE_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display authentication rules. Examples: scm show security authentication-rule --folder Texas --rulebase pre - scm show security authentication-rule --folder Texas --name auth-web + scm show security authentication-rule auth-web --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3431,6 +3442,9 @@ def show_authentication_rule( kwargs = {location_type: location_value} rules = scm_client.list_authentication_rules(**kwargs, rulebase=rulebase, exact_match=False) + if max_results is not None: + rules = rules[:max_results] + if not rules: emit([], output) return @@ -3495,17 +3509,17 @@ def backup_decryption_rule( @delete_app.command("decryption-rule") @handle_command_errors("deleting decryption rule") def delete_decryption_rule( + name: str = typer.Argument(..., help="Name of the decryption rule to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the rule"), device: str = typer.Option(None, "--device", help="Device containing the rule"), - name: str = NAME_OPTION, rulebase: str = RULEBASE_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a decryption rule. Examples: - scm delete security decryption-rule --folder Texas --name decrypt-web + scm delete security decryption-rule decrypt-web --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3610,10 +3624,10 @@ def load_decryption_rule( @set_app.command("decryption-rule") @handle_command_errors("creating decryption rule") def set_decryption_rule( + name: str = typer.Argument(..., help="Name of the decryption rule"), folder: str = typer.Option(None, "--folder", help="Folder path"), snippet: str = typer.Option(None, "--snippet", help="Snippet path"), device: str = typer.Option(None, "--device", help="Device path"), - name: str = NAME_OPTION, action: str = typer.Option(..., "--action", help="Action (decrypt or no-decrypt)"), rulebase: str = RULEBASE_OPTION, description: str | None = DESCRIPTION_OPTION, @@ -3627,10 +3641,10 @@ def set_decryption_rule( r"""Create or update a decryption rule. Examples: - scm set security decryption-rule --folder Texas --name no-decrypt-internal \ + scm set security decryption-rule no-decrypt-internal --folder Texas \ --action no-decrypt --source-zones trust --destination-zones trust - scm set security decryption-rule --folder Texas --name decrypt-outbound \ + scm set security decryption-rule decrypt-outbound --folder Texas \ --action decrypt --type '{"ssl_forward_proxy": {}}' """ @@ -3682,18 +3696,19 @@ def set_decryption_rule( @show_app.command("decryption-rule") @handle_command_errors("showing decryption rule") def show_decryption_rule( + name: str | None = typer.Argument(None, help="Name of the decryption rule to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the rule"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the rule"), device: str = typer.Option(None, "--device", help="Device containing the rule"), - name: str | None = typer.Option(None, "--name", help="Name of the rule to show"), rulebase: str = RULEBASE_OPTION, output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display decryption rules. Examples: scm show security decryption-rule --folder Texas --rulebase pre - scm show security decryption-rule --folder Texas --name decrypt-outbound + scm show security decryption-rule decrypt-outbound --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3708,6 +3723,9 @@ def show_decryption_rule( kwargs = {location_type: location_value} rules = scm_client.list_decryption_rules(**kwargs, rulebase=rulebase, exact_match=False) + if max_results is not None: + rules = rules[:max_results] + if not rules: emit([], output) return @@ -3770,16 +3788,16 @@ def backup_url_access_profile( @delete_app.command("url-access-profile") @handle_command_errors("deleting URL access profile") def delete_url_access_profile( + name: str = typer.Argument(..., help="Name of the URL access profile to delete"), folder: str = typer.Option(None, "--folder", help="Folder containing the profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the profile"), device: str = typer.Option(None, "--device", help="Device containing the profile"), - name: str = NAME_OPTION, force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a URL access profile. Examples: - scm delete security url-access-profile --folder Texas --name strict-url-profile + scm delete security url-access-profile strict-url-profile --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3885,10 +3903,10 @@ def _apply(profile_data: dict): @set_app.command("url-access-profile") @handle_command_errors("creating URL access profile") def set_url_access_profile( + name: str = typer.Argument(..., help="Name of the URL access profile"), folder: str = typer.Option(None, "--folder", help="Folder path"), snippet: str = typer.Option(None, "--snippet", help="Snippet path"), device: str = typer.Option(None, "--device", help="Device path"), - name: str = NAME_OPTION, description: str | None = DESCRIPTION_OPTION, block: list[str] | None = URL_PROFILE_BLOCK_OPTION, alert: list[str] | None = URL_PROFILE_ALERT_OPTION, @@ -3900,7 +3918,7 @@ def set_url_access_profile( r"""Create or update a URL access profile. Examples: - scm set security url-access-profile --folder Texas --name strict-url \ + scm set security url-access-profile strict-url --folder Texas \ --block adult --block malware --alert hacking """ @@ -3950,17 +3968,18 @@ def set_url_access_profile( @show_app.command("url-access-profile") @handle_command_errors("showing URL access profile") def show_url_access_profile( + name: str | None = typer.Argument(None, help="Name of the URL access profile to show; omit to list all"), folder: str = typer.Option(None, "--folder", help="Folder containing the profile"), snippet: str = typer.Option(None, "--snippet", help="Snippet containing the profile"), device: str = typer.Option(None, "--device", help="Device containing the profile"), - name: str | None = typer.Option(None, "--name", help="Name of the profile to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display URL access profiles. Examples: scm show security url-access-profile --folder Texas - scm show security url-access-profile --folder Texas --name strict-url + scm show security url-access-profile strict-url --folder Texas """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -3975,6 +3994,9 @@ def show_url_access_profile( kwargs = {location_type: location_value} profiles = scm_client.list_url_access_profiles(**kwargs, exact_match=False) + if max_results is not None: + profiles = profiles[:max_results] + if not profiles: emit([], output) return @@ -3995,7 +4017,6 @@ def show_url_access_profile( MOVE_FOLDER_OPTION = typer.Option(None, "--folder", help="Folder containing the rule") MOVE_SNIPPET_OPTION = typer.Option(None, "--snippet", help="Snippet containing the rule") MOVE_DEVICE_OPTION = typer.Option(None, "--device", help="Device containing the rule") -MOVE_NAME_OPTION = typer.Option(..., "--name", help="Name of the rule to move") MOVE_DESTINATION_OPTION = typer.Option(..., "--destination", help="Where to move (top, bottom, before, after)") MOVE_RULEBASE_OPTION = typer.Option("pre", "--rulebase", help="Rulebase (pre or post)") MOVE_DESTINATION_RULE_OPTION = typer.Option(None, "--destination-rule", help="UUID of reference rule for before/after") @@ -4004,10 +4025,10 @@ def show_url_access_profile( @move_app.command("rule") @handle_command_errors("moving security rule") def move_security_rule_cmd( + name: str = typer.Argument(..., help="Name of the security rule to move"), folder: str = MOVE_FOLDER_OPTION, snippet: str = MOVE_SNIPPET_OPTION, device: str = MOVE_DEVICE_OPTION, - name: str = MOVE_NAME_OPTION, destination: str = MOVE_DESTINATION_OPTION, rulebase: str = MOVE_RULEBASE_OPTION, destination_rule: str = MOVE_DESTINATION_RULE_OPTION, @@ -4015,8 +4036,8 @@ def move_security_rule_cmd( """Move a security rule to a new position. Examples: - scm move security rule --folder Texas --name "Allow Web" --destination top --rulebase pre - scm move security rule --folder Texas --name "Allow Web" --destination after --destination-rule + scm move security rule "Allow Web" --folder Texas --destination top --rulebase pre + scm move security rule "Allow Web" --folder Texas --destination after --destination-rule """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -4039,10 +4060,10 @@ def move_security_rule_cmd( @move_app.command("app-override-rule") @handle_command_errors("moving app override rule") def move_app_override_rule_cmd( + name: str = typer.Argument(..., help="Name of the app override rule to move"), folder: str = MOVE_FOLDER_OPTION, snippet: str = MOVE_SNIPPET_OPTION, device: str = MOVE_DEVICE_OPTION, - name: str = MOVE_NAME_OPTION, destination: str = MOVE_DESTINATION_OPTION, rulebase: str = MOVE_RULEBASE_OPTION, destination_rule: str = MOVE_DESTINATION_RULE_OPTION, @@ -4050,8 +4071,8 @@ def move_app_override_rule_cmd( """Move an app override rule to a new position. Examples: - scm move security app-override-rule --folder Texas --name override-https --destination top - scm move security app-override-rule --folder Texas --name override-https --destination before --destination-rule + scm move security app-override-rule override-https --folder Texas --destination top + scm move security app-override-rule override-https --folder Texas --destination before --destination-rule """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -4074,10 +4095,10 @@ def move_app_override_rule_cmd( @move_app.command("authentication-rule") @handle_command_errors("moving authentication rule") def move_authentication_rule_cmd( + name: str = typer.Argument(..., help="Name of the authentication rule to move"), folder: str = MOVE_FOLDER_OPTION, snippet: str = MOVE_SNIPPET_OPTION, device: str = MOVE_DEVICE_OPTION, - name: str = MOVE_NAME_OPTION, destination: str = MOVE_DESTINATION_OPTION, rulebase: str = MOVE_RULEBASE_OPTION, destination_rule: str = MOVE_DESTINATION_RULE_OPTION, @@ -4085,7 +4106,7 @@ def move_authentication_rule_cmd( """Move an authentication rule to a new position. Examples: - scm move security authentication-rule --folder Texas --name auth-rule --destination bottom + scm move security authentication-rule auth-rule --folder Texas --destination bottom """ location_type, location_value = validate_location_params(folder, snippet, device) @@ -4108,10 +4129,10 @@ def move_authentication_rule_cmd( @move_app.command("decryption-rule") @handle_command_errors("moving decryption rule") def move_decryption_rule_cmd( + name: str = typer.Argument(..., help="Name of the decryption rule to move"), folder: str = MOVE_FOLDER_OPTION, snippet: str = MOVE_SNIPPET_OPTION, device: str = MOVE_DEVICE_OPTION, - name: str = MOVE_NAME_OPTION, destination: str = MOVE_DESTINATION_OPTION, rulebase: str = MOVE_RULEBASE_OPTION, destination_rule: str = MOVE_DESTINATION_RULE_OPTION, @@ -4119,7 +4140,7 @@ def move_decryption_rule_cmd( """Move a decryption rule to a new position. Examples: - scm move security decryption-rule --folder Texas --name decrypt-rule --destination top + scm move security decryption-rule decrypt-rule --folder Texas --destination top """ location_type, location_value = validate_location_params(folder, snippet, device) diff --git a/src/scm_cli/commands/setup.py b/src/scm_cli/commands/setup.py index 27d4ba5..86a960a 100644 --- a/src/scm_cli/commands/setup.py +++ b/src/scm_cli/commands/setup.py @@ -11,6 +11,7 @@ import yaml from pydantic import ValidationError +from ..utils import validate_location_params from ..utils.bulk import run_bulk from ..utils.config import load_from_yaml from ..utils.decorators import handle_command_errors @@ -34,11 +35,6 @@ # ============================================================================================================================================================================================= # Common options -NAME_OPTION = typer.Option( - ..., - "--name", - help="Name of the resource", -) DESCRIPTION_OPTION = typer.Option( None, "--description", @@ -59,6 +55,11 @@ "--file", help="Output filename for backup (defaults to {object-type}_{timestamp}.yaml)", ) +MAX_RESULTS_OPTION = typer.Option( + None, + "--max-results", + help="Maximum number of results to display", +) # Folder-specific options PARENT_OPTION = typer.Option( @@ -131,31 +132,6 @@ def get_default_backup_filename(object_type: str) -> str: return f"{object_type}_{timestamp}.yaml" -def validate_container_params(folder: str | None = None, snippet: str | None = None, device: str | None = None) -> tuple[str, str]: - """Validate that exactly one container parameter is provided. - - Returns: - tuple: (container_type, container_value) - - """ - container_count = sum(1 for c in [folder, snippet, device] if c is not None) - - if container_count == 0: - error("Error: One of --folder, --snippet, or --device must be specified") - raise typer.Exit(code=1) - elif container_count > 1: - error("Error: Only one of --folder, --snippet, or --device can be specified") - raise typer.Exit(code=1) - - if folder: - return "folder", folder - elif snippet: - return "snippet", snippet - else: - assert device is not None - return "device", device - - def get_child_folder_names(parent_name: str) -> list[str]: """Return direct child folder names for a parent folder.""" folders = scm_client.list_folders() @@ -170,7 +146,7 @@ def get_child_folder_names(parent_name: str) -> list[str]: @set_app.command("folder") @handle_command_errors("creating folder") def set_folder( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the folder"), parent: str = PARENT_OPTION, description: str | None = DESCRIPTION_OPTION, labels: list[str] | None = LABELS_OPTION, @@ -180,8 +156,8 @@ def set_folder( Examples -------- - scm set setup folder --name Texas --parent "All" - scm set setup folder --name Branch --parent Texas --description "Branch offices" + scm set setup folder Texas --parent "All" + scm set setup folder Branch --parent Texas --description "Branch offices" """ try: @@ -211,15 +187,16 @@ def set_folder( @show_app.command("folder") @handle_command_errors("showing folders") def show_folder( - name: str | None = typer.Option(None, "--name", help="Name of the folder to show"), + name: str | None = typer.Argument(None, help="Name of the folder to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display folders. Examples -------- scm show setup folder - scm show setup folder --name Texas + scm show setup folder Texas """ if name: @@ -228,18 +205,20 @@ def show_folder( return folders = scm_client.list_folders() + if max_results is not None: + folders = folders[:max_results] emit(folders, output, columns=["name", "display_name", "parent", "description"], title="Folders") @delete_app.command("folder") @handle_command_errors("deleting folder") def delete_folder( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the folder"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a folder. - Example: scm delete setup folder --name Branch + Example: scm delete setup folder Branch """ if not force: typer.confirm(f"Delete folder '{name}'?", abort=True) @@ -345,15 +324,15 @@ def backup_folder( @set_app.command("label") @handle_command_errors("creating label") def set_label( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the label"), description: str | None = DESCRIPTION_OPTION, ): """Create or update a label. Examples -------- - scm set setup label --name production - scm set setup label --name staging --description "Staging environment" + scm set setup label production + scm set setup label staging --description "Staging environment" """ try: @@ -380,15 +359,16 @@ def set_label( @show_app.command("label") @handle_command_errors("showing labels") def show_label( - name: str | None = typer.Option(None, "--name", help="Name of the label to show"), + name: str | None = typer.Argument(None, help="Name of the label to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display labels. Examples -------- scm show setup label - scm show setup label --name production + scm show setup label production """ if name: @@ -397,18 +377,20 @@ def show_label( return labels = scm_client.list_labels() + if max_results is not None: + labels = labels[:max_results] emit(labels, output, columns=["name", "description"], title="Labels") @delete_app.command("label") @handle_command_errors("deleting label") def delete_label( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the label"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a label. - Example: scm delete setup label --name staging + Example: scm delete setup label staging """ if not force: typer.confirm(f"Delete label '{name}'?", abort=True) @@ -510,7 +492,7 @@ def backup_label( @set_app.command("snippet") @handle_command_errors("creating snippet") def set_snippet( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the snippet"), description: str | None = DESCRIPTION_OPTION, labels: list[str] | None = LABELS_OPTION, enable_prefix: bool | None = ENABLE_PREFIX_OPTION, @@ -519,8 +501,8 @@ def set_snippet( Examples -------- - scm set setup snippet --name "DNS-Best-Practice" - scm set setup snippet --name "Web-Security" --description "Web security config" --labels prod + scm set setup snippet "DNS-Best-Practice" + scm set setup snippet "Web-Security" --description "Web security config" --labels prod """ try: @@ -549,15 +531,16 @@ def set_snippet( @show_app.command("snippet") @handle_command_errors("showing snippets") def show_snippet( - name: str | None = typer.Option(None, "--name", help="Name of the snippet to show"), + name: str | None = typer.Argument(None, help="Name of the snippet to show; omit to list all"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display snippets. Examples -------- scm show setup snippet - scm show setup snippet --name "DNS-Best-Practice" + scm show setup snippet "DNS-Best-Practice" """ if name: @@ -566,18 +549,20 @@ def show_snippet( return snippets = scm_client.list_snippets() + if max_results is not None: + snippets = snippets[:max_results] emit(snippets, output, columns=["name", "type", "description"], title="Snippets") @delete_app.command("snippet") @handle_command_errors("deleting snippet") def delete_snippet( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the snippet"), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt"), ): """Delete a snippet. - Example: scm delete setup snippet --name "DNS-Best-Practice" + Example: scm delete setup snippet "DNS-Best-Practice" """ if not force: typer.confirm(f"Delete snippet '{name}'?", abort=True) @@ -679,7 +664,7 @@ def backup_snippet( @set_app.command("variable") @handle_command_errors("creating variable") def set_variable( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the variable"), type: str = TYPE_OPTION, value: str = VALUE_OPTION, folder: str | None = FOLDER_OPTION, @@ -691,11 +676,11 @@ def set_variable( Examples -------- - scm set setup variable --name "\$egress-max" --type egress-max --value 1000 --folder Texas - scm set setup variable --name "\$dns-server" --type fqdn --value dns.example.com --snippet "DNS-Config" + scm set setup variable "\$egress-max" --type egress-max --value 1000 --folder Texas + scm set setup variable "\$dns-server" --type fqdn --value dns.example.com --snippet "DNS-Config" """ - container_type, container_value = validate_container_params(folder, snippet, device) + location_type, location_value = validate_location_params(folder, snippet, device) try: variable_model = Variable( @@ -715,46 +700,49 @@ def set_variable( action = result.get("__action__", "created") if action == "no_change": - info(f"No changes detected for variable: {name} in {container_type} {container_value}") + info(f"No changes detected for variable: {name} in {location_type} {location_value}") elif action == "updated": - success(f"Updated variable: {name} in {container_type} {container_value}") + success(f"Updated variable: {name} in {location_type} {location_value}") else: - success(f"Created variable: {name} in {container_type} {container_value}") + success(f"Created variable: {name} in {location_type} {location_value}") return result @show_app.command("variable") @handle_command_errors("showing variables") def show_variable( + name: str | None = typer.Argument(None, help="Name of the variable to show; omit to list all"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, - name: str | None = typer.Option(None, "--name", help="Name of the variable to show"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): r"""Display variables. Examples -------- scm show setup variable --folder Texas - scm show setup variable --folder Texas --name "\$egress-max" + scm show setup variable "\$egress-max" --folder Texas """ + _, location_value = validate_location_params(folder, snippet, device) + if name: variable = scm_client.get_variable(name=name, folder=folder, snippet=snippet, device=device) emit(variable, output, title=f"Variable: {name}") return variables = scm_client.list_variables(folder=folder, snippet=snippet, device=device) - location = folder or snippet or device - title = f"Variables in {location}" if location else "Variables" - emit(variables, output, columns=["name", "type", "value", "description"], title=title) + if max_results is not None: + variables = variables[:max_results] + emit(variables, output, columns=["name", "type", "value", "description"], title=f"Variables in {location_value}") @delete_app.command("variable") @handle_command_errors("deleting variable") def delete_variable( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name of the variable"), folder: str | None = FOLDER_OPTION, snippet: str | None = SNIPPET_OPTION, device: str | None = DEVICE_OPTION, @@ -762,15 +750,15 @@ def delete_variable( ): r"""Delete a variable. - Example: scm delete setup variable --name "\$egress-max" --folder Texas + Example: scm delete setup variable "\$egress-max" --folder Texas """ - container_type, container_value = validate_container_params(folder, snippet, device) + location_type, location_value = validate_location_params(folder, snippet, device) if not force: - typer.confirm(f"Delete variable '{name}' from {container_type} '{container_value}'?", abort=True) + typer.confirm(f"Delete variable '{name}' from {location_type} '{location_value}'?", abort=True) result = scm_client.delete_variable(name=name, folder=folder, snippet=snippet, device=device) if result: - success(f"Deleted variable: {name} from {container_type} {container_value}") + success(f"Deleted variable: {name} from {location_type} {location_value}") else: error(f"Variable not found: {name}") raise typer.Exit(code=1) @@ -879,16 +867,17 @@ def backup_variable( @show_app.command("device") @handle_command_errors("showing devices") def show_device( - name: str | None = typer.Option(None, "--name", help="Name or serial number of the device to show"), + name: str | None = typer.Argument(None, help="Name or serial number of the device to show; omit to list all"), folder: str | None = typer.Option(None, "--folder", help="Filter devices by folder"), output: OutputFormat = OUTPUT_OPTION, + max_results: int | None = MAX_RESULTS_OPTION, ): """Display devices. Examples -------- scm show setup device - scm show setup device --name "PA-VM-01" + scm show setup device "PA-VM-01" scm show setup device --folder Texas """ @@ -898,6 +887,8 @@ def show_device( return devices = scm_client.list_devices(folder=folder) + if max_results is not None: + devices = devices[:max_results] title = f"Devices in {folder}" if folder else "Devices" emit(devices, output, columns=["name", "serial_number", "model", "folder", "labels", "is_connected"], title=title) @@ -905,7 +896,7 @@ def show_device( @set_app.command("device") @handle_command_errors("updating device") def set_device( - name: str = NAME_OPTION, + name: str = typer.Argument(..., help="Name or serial number of the device"), display_name: str | None = DISPLAY_NAME_OPTION, folder: str | None = DEVICE_FOLDER_OPTION, description: str | None = DESCRIPTION_OPTION, @@ -920,9 +911,9 @@ def set_device( Examples -------- - scm set setup device --name PA-VM-01 --labels production --labels west - scm set setup device --name 0123456789 --folder Austin - scm set setup device --name PA-VM-01 --description "Edge firewall" + scm set setup device PA-VM-01 --labels production --labels west + scm set setup device 0123456789 --folder Austin + scm set setup device PA-VM-01 --description "Edge firewall" """ try: diff --git a/src/scm_cli/utils/sdk_client.py b/src/scm_cli/utils/sdk_client.py index c7c1be0..ef7ebe6 100644 --- a/src/scm_cli/utils/sdk_client.py +++ b/src/scm_cli/utils/sdk_client.py @@ -1188,8 +1188,10 @@ def list_remote_networks(self) -> list[dict[str, Any]]: def create_address( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", description: str = "", tags: list[str] | None = None, ip_netmask: str | None = None, @@ -1201,6 +1203,8 @@ def create_address( Args: folder: Folder to create the address in + snippet: Snippet to create the address in + device: Device to create the address in name: Name of the address description: Optional description tags: Optional list of tags @@ -1214,17 +1218,25 @@ def create_address( Note: Exactly one of ip_netmask, ip_range, ip_wildcard, or fqdn must be provided. - If an address with the same name already exists in the folder, it will be updated. + If an address with the same name already exists in the container, it will be updated. """ tags = tags or [] - self.logger.info(f"Creating or updating address: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating address: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"addr-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "description": description, "tags": tags, @@ -1238,10 +1250,10 @@ def create_address( # First, try to fetch the existing address existing_address = None try: - existing_address = self.client.address.fetch(name=name, folder=folder) - self.logger.info(f"Found existing address '{name}' in folder '{folder}', updating...") + existing_address = self.client.address.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing address '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Address '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Address '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching address '{name}': {str(fetch_error)}") @@ -1249,7 +1261,7 @@ def create_address( # Prepare address data address_data = { "name": name, - "folder": folder, + **container_kwargs, } # Only include description if it's provided and not empty @@ -1393,24 +1405,36 @@ def create_address( response["__action__"] = "created" return response except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_address( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an address object. Args: folder: Folder containing the address + snippet: Snippet containing the address + device: Device containing the address name: Name of the address to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting address: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting address: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -1418,36 +1442,48 @@ def delete_address( try: # Get the address first to retrieve its ID - address = self.client.address.fetch(name=name, folder=folder) + address = self.client.address.fetch(name=name, **container_kwargs) # Delete using the address's ID self.client.address.delete(object_id=str(address.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_address( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: - """Get an address object by name and folder. + """Get an address object by name and container. Args: folder: Folder containing the address + snippet: Snippet containing the address + device: Device containing the address name: Name of the address to get Returns: dict[str, Any]: The address object """ - self.logger.info(f"Getting address: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting address: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"addr-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "description": "Mock address object", "tags": [], @@ -1456,12 +1492,12 @@ def get_address( try: # Fetch the address using the SDK - result = self.client.address.fetch(name=name, folder=folder) + result = self.client.address.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_addresses( self, @@ -1543,9 +1579,11 @@ def list_addresses( def create_address_group( self, - folder: str, - name: str, - type: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + type: str = "", members: list[str] | None = None, filter: str | None = None, description: str = "", @@ -1555,6 +1593,8 @@ def create_address_group( Args: folder: Folder to create the address group in + snippet: Snippet to create the address group in + device: Device to create the address group in name: Name of the address group type: Type of address group ("static" or "dynamic") members: List of member addresses (for static groups) @@ -1572,13 +1612,21 @@ def create_address_group( """ members = members or [] tags = tags or [] - self.logger.info(f"Creating or updating address group: {name} of type {type} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating address group: {name} of type {type} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"ag-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "type": type, "members": members, @@ -1590,10 +1638,10 @@ def create_address_group( # First, try to fetch the existing address group existing_group = None try: - existing_group = self.client.address_group.fetch(name=name, folder=folder) - self.logger.info(f"Found existing address group '{name}' in folder '{folder}', updating...") + existing_group = self.client.address_group.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing address group '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Address group '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Address group '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching address group '{name}': {str(fetch_error)}") @@ -1601,7 +1649,7 @@ def create_address_group( # Prepare address group data group_data = { "name": name, - "folder": folder, + **container_kwargs, } if description: group_data["description"] = description @@ -1672,24 +1720,36 @@ def create_address_group( result_dict["__action__"] = "updated" if existing_group else "created" return result_dict except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_address_group( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an address group. Args: folder: Folder containing the address group + snippet: Snippet containing the address group + device: Device containing the address group name: Name of the address group to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting address group: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting address group: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -1697,36 +1757,48 @@ def delete_address_group( try: # Get the address group first to retrieve its ID - address_group = self.client.address_group.fetch(name=name, folder=folder) + address_group = self.client.address_group.fetch(name=name, **container_kwargs) # Delete using the address group's ID self.client.address_group.delete(object_id=str(address_group.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_address_group( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get an address group by name and folder. Args: folder: Folder containing the address group + snippet: Snippet containing the address group + device: Device containing the address group name: Name of the address group to get Returns: dict[str, Any]: The address group object """ - self.logger.info(f"Getting address group: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting address group: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"ag-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "description": "Mock address group", "type": "static", @@ -1736,12 +1808,12 @@ def get_address_group( try: # Fetch the address group using the SDK - result = self.client.address_group.fetch(name=name, folder=folder) + result = self.client.address_group.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_address_groups( self, @@ -1810,12 +1882,14 @@ def list_address_groups( def create_application( self, - folder: str, - name: str, - category: str, - subcategory: str, - technology: str, - risk: int, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + category: str = "", + subcategory: str = "", + technology: str = "", + risk: int = 0, description: str = "", ports: list[str] | None = None, evasive: bool = False, @@ -1832,6 +1906,8 @@ def create_application( Args: folder: Folder to create the application in + snippet: Snippet to create the application in + device: Device to create the application in name: Name of the application category: High-level category subcategory: Specific subcategory @@ -1857,13 +1933,21 @@ def create_application( """ ports = ports or [] - self.logger.info(f"Creating or updating application: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating application: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"app-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "category": category, "subcategory": subcategory, @@ -1877,10 +1961,10 @@ def create_application( # First, try to fetch the existing application existing_app = None try: - existing_app = self.client.application.fetch(name=name, folder=folder) - self.logger.info(f"Found existing application '{name}' in folder '{folder}', updating...") + existing_app = self.client.application.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing application '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Application '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Application '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching application '{name}': {str(fetch_error)}") @@ -1888,7 +1972,7 @@ def create_application( # Prepare application data app_data = { "name": name, - "folder": folder, + **container_kwargs, "category": category, "subcategory": subcategory, "technology": technology, @@ -1955,24 +2039,36 @@ def create_application( result_dict["__action__"] = "updated" if existing_app else "created" return result_dict except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_application( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an application. Args: folder: Folder containing the application + snippet: Snippet containing the application + device: Device containing the application name: Name of the application to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting application: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting application: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -1980,36 +2076,48 @@ def delete_application( try: # Get the application first to retrieve its ID - app = self.client.application.fetch(name=name, folder=folder) + app = self.client.application.fetch(name=name, **container_kwargs) # Delete using the application's ID self.client.application.delete(object_id=str(app.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_application( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get an application by name and folder. Args: folder: Folder containing the application + snippet: Snippet containing the application + device: Device containing the application name: Name of the application to get Returns: dict[str, Any]: The application object """ - self.logger.info(f"Getting application: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting application: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"app-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "category": "business-systems", "subcategory": "database", @@ -2021,12 +2129,12 @@ def get_application( try: # Fetch the application using the SDK - result = self.client.application.fetch(name=name, folder=folder) + result = self.client.application.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_applications( self, @@ -2099,14 +2207,18 @@ def list_applications( def create_application_group( self, - folder: str, - name: str, - members: list[str], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + members: list[str] | None = None, ) -> dict[str, Any]: """Create an application group. Args: folder: Folder to create the application group in + snippet: Snippet to create the application group in + device: Device to create the application group in name: Name of the application group members: List of application names @@ -2117,13 +2229,21 @@ def create_application_group( If an application group with the same name already exists in the folder, it will be updated. """ - self.logger.info(f"Creating or updating application group: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating application group: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"app-group-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "members": members, } @@ -2132,10 +2252,10 @@ def create_application_group( # First, try to fetch the existing application group existing_group = None try: - existing_group = self.client.application_group.fetch(name=name, folder=folder) - self.logger.info(f"Found existing application group '{name}' in folder '{folder}', updating...") + existing_group = self.client.application_group.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing application group '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Application group '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Application group '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching application group '{name}': {str(fetch_error)}") @@ -2143,7 +2263,7 @@ def create_application_group( # Prepare application group data group_data = { "name": name, - "folder": folder, + **container_kwargs, "members": members, } @@ -2165,24 +2285,36 @@ def create_application_group( result_dict["__action__"] = "updated" if existing_group else "created" return result_dict except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_application_group( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an application group. Args: folder: Folder containing the application group + snippet: Snippet containing the application group + device: Device containing the application group name: Name of the application group to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting application group: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting application group: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -2190,48 +2322,60 @@ def delete_application_group( try: # Get the application group first to retrieve its ID - group = self.client.application_group.fetch(name=name, folder=folder) + group = self.client.application_group.fetch(name=name, **container_kwargs) # Delete using the application group's ID self.client.application_group.delete(object_id=str(group.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_application_group( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get an application group by name and folder. Args: folder: Folder containing the application group + snippet: Snippet containing the application group + device: Device containing the application group name: Name of the application group to get Returns: dict[str, Any]: The application group object """ - self.logger.info(f"Getting application group: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting application group: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"app-group-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "members": ["ssl", "web-browsing"], } try: # Fetch the application group using the SDK - result = self.client.application_group.fetch(name=name, folder=folder) + result = self.client.application_group.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_application_groups( self, @@ -2294,12 +2438,14 @@ def list_application_groups( def create_application_filter( self, - folder: str, - name: str, - category: list[str], - subcategory: list[str], - technology: list[str], - risk: list[int], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + category: list[str] | None = None, + subcategory: list[str] | None = None, + technology: list[str] | None = None, + risk: list[int] | None = None, evasive: bool = False, pervasive: bool = False, excessive_bandwidth_use: bool = False, @@ -2314,6 +2460,8 @@ def create_application_filter( Args: folder: Folder to create the application filter in + snippet: Snippet to create the application filter in + device: Device to create the application filter in name: Name of the application filter category: List of category strings subcategory: List of subcategory strings @@ -2336,13 +2484,21 @@ def create_application_filter( If an application filter with the same name already exists in the folder, it will be updated. """ - self.logger.info(f"Creating or updating application filter: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating application filter: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"app-filter-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "category": category, "sub_category": subcategory, @@ -2363,10 +2519,10 @@ def create_application_filter( # First, try to fetch the existing application filter existing_filter = None try: - existing_filter = self.client.application_filter.fetch(name=name, folder=folder) - self.logger.info(f"Found existing application filter '{name}' in folder '{folder}', updating...") + existing_filter = self.client.application_filter.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing application filter '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Application filter '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Application filter '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching application filter '{name}': {str(fetch_error)}") @@ -2374,7 +2530,7 @@ def create_application_filter( # Prepare application filter data filter_data = { "name": name, - "folder": folder, + **container_kwargs, "category": category, "sub_category": subcategory, "technology": technology, @@ -2429,24 +2585,36 @@ def create_application_filter( # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_application_filter( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an application filter. Args: folder: Folder containing the application filter + snippet: Snippet containing the application filter + device: Device containing the application filter name: Name of the application filter to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting application filter: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting application filter: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -2454,36 +2622,48 @@ def delete_application_filter( try: # Get the application filter first to retrieve its ID - filter_obj = self.client.application_filter.fetch(name=name, folder=folder) + filter_obj = self.client.application_filter.fetch(name=name, **container_kwargs) # Delete using the application filter's ID self.client.application_filter.delete(object_id=str(filter_obj.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_application_filter( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get an application filter by name and folder. Args: folder: Folder containing the application filter + snippet: Snippet containing the application filter + device: Device containing the application filter name: Name of the application filter to get Returns: dict[str, Any]: The application filter object """ - self.logger.info(f"Getting application filter: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting application filter: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"app-filter-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "category": ["business-systems", "networking"], "sub_category": ["database", "routing"], @@ -2502,12 +2682,12 @@ def get_application_filter( try: # Fetch the application filter using the SDK - result = self.client.application_filter.fetch(name=name, folder=folder) + result = self.client.application_filter.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_application_filters( self, @@ -2594,9 +2774,11 @@ def list_application_filters( def create_dynamic_user_group( self, - folder: str, - name: str, - filter: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + filter: str = "", description: str = "", tags: list[str] | None = None, ) -> dict[str, Any]: @@ -2604,6 +2786,8 @@ def create_dynamic_user_group( Args: folder: Folder to create the dynamic user group in + snippet: Snippet to create the dynamic user group in + device: Device to create the dynamic user group in name: Name of the dynamic user group filter: Tag-based filter expression description: Optional description @@ -2617,13 +2801,21 @@ def create_dynamic_user_group( """ tags = tags or [] - self.logger.info(f"Creating or updating dynamic user group: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating dynamic user group: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"dug-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "filter": filter, "description": description, @@ -2634,10 +2826,10 @@ def create_dynamic_user_group( # First, try to fetch the existing dynamic user group existing_group = None try: - existing_group = self.client.dynamic_user_group.fetch(name=name, folder=folder) - self.logger.info(f"Found existing dynamic user group '{name}' in folder '{folder}', updating...") + existing_group = self.client.dynamic_user_group.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing dynamic user group '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Dynamic user group '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Dynamic user group '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching dynamic user group '{name}': {str(fetch_error)}") @@ -2645,7 +2837,7 @@ def create_dynamic_user_group( # Prepare dynamic user group data group_data = { "name": name, - "folder": folder, + **container_kwargs, "filter": filter, } if description: @@ -2676,24 +2868,36 @@ def create_dynamic_user_group( result_dict["__action__"] = "updated" if existing_group else "created" return result_dict except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_dynamic_user_group( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete a dynamic user group. Args: folder: Folder containing the dynamic user group + snippet: Snippet containing the dynamic user group + device: Device containing the dynamic user group name: Name of the dynamic user group to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting dynamic user group: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting dynamic user group: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -2701,36 +2905,48 @@ def delete_dynamic_user_group( try: # Get the dynamic user group first to retrieve its ID - group = self.client.dynamic_user_group.fetch(name=name, folder=folder) + group = self.client.dynamic_user_group.fetch(name=name, **container_kwargs) # Delete using the dynamic user group's ID self.client.dynamic_user_group.delete(object_id=str(group.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_dynamic_user_group( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get a dynamic user group by name and folder. Args: folder: Folder containing the dynamic user group + snippet: Snippet containing the dynamic user group + device: Device containing the dynamic user group name: Name of the dynamic user group to get Returns: dict[str, Any]: The dynamic user group object """ - self.logger.info(f"Getting dynamic user group: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting dynamic user group: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"dug-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "filter": "tag.Department='IT' and tag.Environment='Production'", "description": "Mock dynamic user group", @@ -2739,12 +2955,12 @@ def get_dynamic_user_group( try: # Fetch the dynamic user group using the SDK - result = self.client.dynamic_user_group.fetch(name=name, folder=folder) + result = self.client.dynamic_user_group.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_dynamic_user_groups( self, @@ -2815,14 +3031,18 @@ def list_dynamic_user_groups( def create_external_dynamic_list( self, - folder: str, - name: str, - type_config: dict[str, Any], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + type_config: dict[str, Any] | None = None, ) -> dict[str, Any]: """Create an external dynamic list. Args: folder: Folder to create the EDL in + snippet: Snippet to create the EDL in + device: Device to create the EDL in name: Name of the EDL type_config: Type configuration with EDL type and settings @@ -2833,13 +3053,21 @@ def create_external_dynamic_list( This uses smart upsert logic - if an EDL with the same name already exists, it will be updated. """ - self.logger.info(f"Creating or updating external dynamic list: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating external dynamic list: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"edl-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "type": type_config, } @@ -2848,10 +3076,10 @@ def create_external_dynamic_list( # First, try to fetch the existing EDL existing_edl = None try: - existing_edl = self.client.external_dynamic_list.fetch(name=name, folder=folder) - self.logger.info(f"Found existing EDL '{name}' in folder '{folder}', updating...") + existing_edl = self.client.external_dynamic_list.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing EDL '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"EDL '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"EDL '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching EDL '{name}': {str(fetch_error)}") @@ -2866,7 +3094,7 @@ def create_external_dynamic_list( else: # Create a new EDL edl_data = { - "folder": folder, + **container_kwargs, "name": name, "type": type_config, } @@ -2875,24 +3103,36 @@ def create_external_dynamic_list( # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_external_dynamic_list( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an external dynamic list. Args: folder: Folder containing the EDL + snippet: Snippet containing the EDL + device: Device containing the EDL name: Name of the EDL to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting external dynamic list: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting external dynamic list: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -2900,36 +3140,48 @@ def delete_external_dynamic_list( try: # Get the EDL first to retrieve its ID - edl = self.client.external_dynamic_list.fetch(name=name, folder=folder) + edl = self.client.external_dynamic_list.fetch(name=name, **container_kwargs) # Delete using the EDL's ID self.client.external_dynamic_list.delete(edl_id=str(edl.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_external_dynamic_list( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get an external dynamic list by name and folder. Args: folder: Folder containing the EDL + snippet: Snippet containing the EDL + device: Device containing the EDL name: Name of the EDL to get Returns: dict[str, Any]: The EDL object """ - self.logger.info(f"Getting external dynamic list: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting external dynamic list: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"edl-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "type": { "predefined_ip": { @@ -2942,12 +3194,12 @@ def get_external_dynamic_list( try: # Fetch the EDL using the SDK - result = self.client.external_dynamic_list.fetch(name=name, folder=folder) + result = self.client.external_dynamic_list.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_external_dynamic_lists( self, @@ -3035,8 +3287,10 @@ def list_external_dynamic_lists( def create_hip_object( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", description: str | None = None, host_info: dict[str, Any] | None = None, network_info: dict[str, Any] | None = None, @@ -3049,6 +3303,8 @@ def create_hip_object( Args: folder: Folder to create the HIP object in + snippet: Snippet to create the HIP object in + device: Device to create the HIP object in name: Name of the HIP object description: Description of the HIP object host_info: Host information criteria @@ -3065,13 +3321,21 @@ def create_hip_object( This uses smart upsert logic - if a HIP object with the same name already exists, it will be updated. """ - self.logger.info(f"Creating or updating HIP object: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating HIP object: {name} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"hip-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "description": description or "Mock HIP object", "host_info": host_info, @@ -3085,7 +3349,7 @@ def create_hip_object( try: # Prepare the HIP object data hip_data = { - "folder": folder, + **container_kwargs, "name": name, } @@ -3107,7 +3371,7 @@ def create_hip_object( # First, try to fetch the existing HIP object try: - existing_hip = self.client.hip_object.fetch(name=name, folder=folder) + existing_hip = self.client.hip_object.fetch(name=name, **container_kwargs) # Update and return an existing HIP object hip_data["id"] = str(existing_hip.id) result = self.client.hip_object.update(hip_data) @@ -3120,24 +3384,36 @@ def create_hip_object( # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_hip_object( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete a HIP object. Args: folder: Folder containing the HIP object + snippet: Snippet containing the HIP object + device: Device containing the HIP object name: Name of the HIP object to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting HIP object: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting HIP object: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -3145,36 +3421,48 @@ def delete_hip_object( try: # Get the HIP object first to retrieve its ID - hip_obj = self.client.hip_object.fetch(name=name, folder=folder) + hip_obj = self.client.hip_object.fetch(name=name, **container_kwargs) # Delete using the HIP object's ID self.client.hip_object.delete(object_id=str(hip_obj.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_hip_object( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get a HIP object by name and folder. Args: folder: Folder containing the HIP object + snippet: Snippet containing the HIP object + device: Device containing the HIP object name: Name of the HIP object to get Returns: dict[str, Any]: The HIP object """ - self.logger.info(f"Getting HIP object: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting HIP object: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"hip-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "description": "Mock Windows workstation policy", "host_info": { @@ -3199,12 +3487,12 @@ def get_hip_object( try: # Fetch the HIP object using the SDK - result = self.client.hip_object.fetch(name=name, folder=folder) + result = self.client.hip_object.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_hip_objects( self, @@ -3301,15 +3589,19 @@ def list_hip_objects( def create_hip_profile( self, - folder: str, - name: str, - match: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + match: str = "", description: str | None = None, ) -> dict[str, Any]: """Create or update a HIP profile. Args: folder: Folder where the HIP profile will be created + snippet: Snippet where the HIP profile will be created + device: Device where the HIP profile will be created name: Name of the HIP profile match: Match criteria for the HIP profile description: Optional description of the HIP profile @@ -3318,13 +3610,21 @@ def create_hip_profile( dict[str, Any]: Created HIP profile object """ - self.logger.info(f"Creating/updating HIP profile '{name}' in folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating/updating HIP profile '{name}' in {container}") if not self.client: # Return a mock response if no client is available return { "id": f"hip-profile-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "match": match, "description": description or f"Mock HIP profile for {name}", @@ -3333,7 +3633,7 @@ def create_hip_profile( try: # Check if a HIP profile already exists try: - existing = self.client.hip_profile.fetch(name=name, folder=folder) + existing = self.client.hip_profile.fetch(name=name, **container_kwargs) if existing: # Update existing HIP profile self.logger.info(f"HIP profile '{name}' already exists, updating...") @@ -3347,7 +3647,7 @@ def create_hip_profile( # Prepare the profile data profile_data = { - "folder": folder, + **container_kwargs, "name": name, "match": match, } @@ -3362,18 +3662,28 @@ def create_hip_profile( except Exception as e: self._handle_api_exception("creating/updating", "HIP profile", name, e) - def delete_hip_profile(self, folder: str, name: str) -> bool: + def delete_hip_profile(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> bool: """Delete a HIP profile. Args: folder: Folder containing the HIP profile + snippet: Snippet containing the HIP profile + device: Device containing the HIP profile name: Name of the HIP profile to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting HIP profile '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting HIP profile '{name}' from {container}") if not self.client: self.logger.info(f"Mock mode: Would delete HIP profile '{name}'") @@ -3381,31 +3691,41 @@ def delete_hip_profile(self, folder: str, name: str) -> bool: try: # First, fetch the HIP profile to get its ID - hip_profile = self.client.hip_profile.fetch(name=name, folder=folder) + hip_profile = self.client.hip_profile.fetch(name=name, **container_kwargs) self.client.hip_profile.delete(str(hip_profile.id)) self.logger.info(f"Successfully deleted HIP profile '{name}'") return True except Exception as e: self._handle_api_exception("deleting", "HIP profile", name, e) - def get_hip_profile(self, folder: str, name: str) -> dict[str, Any]: + def get_hip_profile(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> dict[str, Any]: """Get a specific HIP profile by name. Args: folder: Folder containing the HIP profile + snippet: Snippet containing the HIP profile + device: Device containing the HIP profile name: Name of the HIP profile Returns: dict[str, Any]: HIP profile object """ - self.logger.info(f"Getting HIP profile '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting HIP profile '{name}' from {container}") if not self.client: # Return mock data if no client is available return { "id": f"hip-profile-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "match": "'custom-check' and 'endpoint-management'", "description": f"Mock HIP profile for {name}", @@ -3413,7 +3733,7 @@ def get_hip_profile(self, folder: str, name: str) -> dict[str, Any]: try: # Fetch the HIP profile by name and folder - result = self.client.hip_profile.fetch(name=name, folder=folder) + result = self.client.hip_profile.fetch(name=name, **container_kwargs) return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: self._handle_api_exception("fetching", "HIP profile", name, e) @@ -3481,9 +3801,11 @@ def list_hip_profiles( def create_http_server_profile( self, - folder: str, - name: str, - servers: list[dict[str, Any]], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + servers: list[dict[str, Any]] | None = None, description: str | None = None, tag_registration: bool = False, format_config: dict[str, dict[str, Any]] | None = None, @@ -3492,6 +3814,8 @@ def create_http_server_profile( Args: folder: Folder where the HTTP server profile will be created + snippet: Snippet where the HTTP server profile will be created + device: Device where the HTTP server profile will be created name: Name of the HTTP server profile servers: List of server configurations description: Optional description of the HTTP server profile @@ -3502,13 +3826,21 @@ def create_http_server_profile( dict[str, Any]: Created an HTTP server profile object """ - self.logger.info(f"Creating/updating HTTP server profile '{name}' in folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating/updating HTTP server profile '{name}' in {container}") if not self.client: # Return a mock response if no client is available return { "id": f"http-server-profile-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "server": servers, "description": description or f"Mock HTTP server profile for {name}", @@ -3518,7 +3850,7 @@ def create_http_server_profile( try: # Check if an HTTP server profile already exists try: - existing = self.client.http_server_profile.fetch(name=name, folder=folder) + existing = self.client.http_server_profile.fetch(name=name, **container_kwargs) if existing: # Update an existing HTTP server profile self.logger.info(f"HTTP server profile '{name}' already exists, updating...") @@ -3535,7 +3867,7 @@ def create_http_server_profile( # Prepare the profile data profile_data = { - "folder": folder, + **container_kwargs, "name": name, "server": servers, } @@ -3556,18 +3888,28 @@ def create_http_server_profile( except Exception as e: self._handle_api_exception("creating/updating", "HTTP server profile", name, e) - def delete_http_server_profile(self, folder: str, name: str) -> bool: + def delete_http_server_profile(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> bool: """Delete an HTTP server profile. Args: folder: Folder containing the HTTP server profile + snippet: Snippet containing the HTTP server profile + device: Device containing the HTTP server profile name: Name of the HTTP server profile to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting HTTP server profile '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting HTTP server profile '{name}' from {container}") if not self.client: self.logger.info(f"Mock mode: Would delete HTTP server profile '{name}'") @@ -3575,31 +3917,41 @@ def delete_http_server_profile(self, folder: str, name: str) -> bool: try: # First, fetch the HTTP server profile to get its ID - http_server_profile = self.client.http_server_profile.fetch(name=name, folder=folder) + http_server_profile = self.client.http_server_profile.fetch(name=name, **container_kwargs) self.client.http_server_profile.delete(str(http_server_profile.id)) self.logger.info(f"Successfully deleted HTTP server profile '{name}'") return True except Exception as e: self._handle_api_exception("deleting", "HTTP server profile", name, e) - def get_http_server_profile(self, folder: str, name: str) -> dict[str, Any]: + def get_http_server_profile(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> dict[str, Any]: """Get a specific HTTP server profile by name. Args: folder: Folder containing the HTTP server profile + snippet: Snippet containing the HTTP server profile + device: Device containing the HTTP server profile name: Name of the HTTP server profile Returns: dict[str, Any]: HTTP server profile object """ - self.logger.info(f"Getting HTTP server profile '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting HTTP server profile '{name}' from {container}") if not self.client: # Return mock data if no client is available return { "id": f"http-server-profile-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "server": [ { @@ -3616,7 +3968,7 @@ def get_http_server_profile(self, folder: str, name: str) -> dict[str, Any]: try: # Fetch the HTTP server profile by name and folder - result = self.client.http_server_profile.fetch(name=name, folder=folder) + result = self.client.http_server_profile.fetch(name=name, **container_kwargs) return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: self._handle_api_exception("fetching", "HTTP server profile", name, e) @@ -3702,8 +4054,10 @@ def list_http_server_profiles( def create_log_forwarding_profile( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", match_list: list[dict[str, Any]] | None = None, description: str | None = None, enhanced_application_logging: bool = False, @@ -3712,6 +4066,8 @@ def create_log_forwarding_profile( Args: folder: Folder where the log-forwarding profile will be created + snippet: Snippet where the log-forwarding profile will be created + device: Device where the log-forwarding profile will be created name: Name of the log-forwarding profile match_list: List of match profile configurations description: Optional description of the log-forwarding profile @@ -3721,13 +4077,21 @@ def create_log_forwarding_profile( dict[str, Any]: Created a log-forwarding profile object """ - self.logger.info(f"Creating/updating log-forwarding profile '{name}' in folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating/updating log-forwarding profile '{name}' in {container}") if not self.client: # Return a mock response if no client is available return { "id": f"log-forwarding-profile-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "match_list": match_list or [ @@ -3744,7 +4108,7 @@ def create_log_forwarding_profile( try: # Check if a log-forwarding profile already exists try: - existing = self.client.log_forwarding_profile.fetch(name=name, folder=folder) + existing = self.client.log_forwarding_profile.fetch(name=name, **container_kwargs) if existing: # Update the existing log-forwarding profile self.logger.info(f"log-forwarding profile '{name}' already exists, updating...") @@ -3760,7 +4124,7 @@ def create_log_forwarding_profile( # Prepare the profile data profile_data = { - "folder": folder, + **container_kwargs, "name": name, } @@ -3784,56 +4148,76 @@ def create_log_forwarding_profile( except Exception as e: self._handle_api_exception("creating/updating", "log-forwarding profile", name, e) - def delete_log_forwarding_profile(self, folder: str, name: str) -> bool: + def delete_log_forwarding_profile(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> bool: """Delete a log-forwarding profile. Args: folder: Folder containing the log-forwarding profile + snippet: Snippet containing the log-forwarding profile + device: Device containing the log-forwarding profile name: Name of the log-forwarding profile to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting log-forwarding profile '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting log-forwarding profile '{name}' from {container}") if not self.client: # Mock deletion - self.logger.info(f"Mock mode: Would delete log-forwarding profile '{name}' from folder '{folder}'") + self.logger.info(f"Mock mode: Would delete log-forwarding profile '{name}' from '{container}'") return True try: # First, fetch the log-forwarding profile to get its ID - profile = self.client.log_forwarding_profile.fetch(name=name, folder=folder) + profile = self.client.log_forwarding_profile.fetch(name=name, **container_kwargs) if profile: # Delete using the ID self.client.log_forwarding_profile.delete(str(profile.id)) self.logger.info(f"Successfully deleted log-forwarding profile '{name}'") return True else: - self.logger.warning(f"log-forwarding profile '{name}' not found in folder '{folder}'") + self.logger.warning(f"log-forwarding profile '{name}' not found in '{container}'") return False except Exception as e: self._handle_api_exception("deleting", "log-forwarding profile", name, e) - def get_log_forwarding_profile(self, folder: str, name: str) -> dict[str, Any] | None: + def get_log_forwarding_profile(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> dict[str, Any] | None: """Get a specific log-forwarding profile by name. Args: folder: Folder containing the log-forwarding profile + snippet: Snippet containing the log-forwarding profile + device: Device containing the log-forwarding profile name: Name of the log-forwarding profile Returns: dict[str, Any] | None: Log a forwarding profile object if found, None otherwise """ - self.logger.info(f"Getting log-forwarding profile '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting log-forwarding profile '{name}' from {container}") if not self.client: # Return mock data if no client is available return { "id": f"log-forwarding-profile-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "match_list": [ { @@ -3849,7 +4233,7 @@ def get_log_forwarding_profile(self, folder: str, name: str) -> dict[str, Any] | try: # Fetch the log-forwarding profile - profile = self.client.log_forwarding_profile.fetch(name=name, folder=folder) + profile = self.client.log_forwarding_profile.fetch(name=name, **container_kwargs) return json.loads(profile.model_dump_json(exclude_unset=True)) if profile else None except Exception as e: self.logger.error(f"Failed to get log-forwarding profile '{name}': {str(e)}") @@ -4280,9 +4664,11 @@ def list_quarantined_devices( def create_service( self, - folder: str, - name: str, - protocol: dict[str, Any], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + protocol: dict[str, Any] | None = None, description: str | None = None, tag: list[str] | None = None, ) -> dict[str, Any]: @@ -4290,6 +4676,8 @@ def create_service( Args: folder: Folder where the service will be created + snippet: Snippet where the service will be created + device: Device where the service will be created name: Name of the service protocol: Protocol configuration (tcp or udp with port) description: Optional description @@ -4299,11 +4687,19 @@ def create_service( dict[str, Any]: Created/updated service object """ + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device if not self.client: # Return a mock response if no client is available return { "id": f"service-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "protocol": protocol, "description": description or f"Mock service for {name}", @@ -4314,10 +4710,10 @@ def create_service( # Step 1: Try to fetch the existing service existing_service = None try: - existing_service = self.client.service.fetch(name=name, folder=folder) - self.logger.info(f"Found existing service '{name}' in folder '{folder}'") + existing_service = self.client.service.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing service '{name}' in '{container}'") except NotFoundError: - self.logger.info(f"Service '{name}' not found in folder '{folder}', will create new") + self.logger.info(f"Service '{name}' not found in '{container}', will create new") except Exception as e: self.logger.warning(f"Error fetching service '{name}': {str(e)}") @@ -4355,7 +4751,7 @@ def create_service( if needs_update: self.logger.info(f"Updating service fields: {', '.join(update_fields)}") updated = self.client.service.update(existing_service) - self.logger.info(f"Successfully updated service '{name}' in folder '{folder}'") + self.logger.info(f"Successfully updated service '{name}' in '{container}'") result = json.loads(updated.model_dump_json(exclude_unset=True)) result["__action__"] = "updated" return result @@ -4367,7 +4763,7 @@ def create_service( else: # Step 4: Create new service service_data = { - "folder": folder, + **container_kwargs, "name": name, "protocol": protocol, } @@ -4379,26 +4775,36 @@ def create_service( service_data["tag"] = tag result = self.client.service.create(service_data) - self.logger.info(f"Successfully created service '{name}' in folder '{folder}'") + self.logger.info(f"Successfully created service '{name}' in '{container}'") response = json.loads(result.model_dump_json(exclude_unset=True)) response["__action__"] = "created" return response except Exception as e: - self._handle_api_exception("create/update", folder, name, e) + self._handle_api_exception("create/update", container, name, e) - def delete_service(self, folder: str, name: str) -> bool: + def delete_service(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> bool: """Delete a service. Args: folder: Folder containing the service + snippet: Snippet containing the service + device: Device containing the service name: Name of the service to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting service '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting service '{name}' from {container}") if not self.client: self.logger.info(f"Mock mode: Would delete service '{name}'") @@ -4406,31 +4812,41 @@ def delete_service(self, folder: str, name: str) -> bool: try: # First, fetch the service to get its ID - service = self.client.service.fetch(name=name, folder=folder) + service = self.client.service.fetch(name=name, **container_kwargs) self.client.service.delete(str(service.id)) self.logger.info(f"Successfully deleted service '{name}'") return True except Exception as e: self._handle_api_exception("deleting", "service", name, e) - def get_service(self, folder: str, name: str) -> dict[str, Any]: + def get_service(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> dict[str, Any]: """Get a specific service by name. Args: folder: Folder containing the service + snippet: Snippet containing the service + device: Device containing the service name: Name of the service Returns: dict[str, Any]: Service object """ - self.logger.info(f"Getting service '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting service '{name}' from {container}") if not self.client: # Return mock data if no client is available return { "id": f"service-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "protocol": { "tcp": { @@ -4448,7 +4864,7 @@ def get_service(self, folder: str, name: str) -> dict[str, Any]: try: # Fetch the service by name and folder - result = self.client.service.fetch(name=name, folder=folder) + result = self.client.service.fetch(name=name, **container_kwargs) return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: self._handle_api_exception("fetching", "service", name, e) @@ -4541,15 +4957,19 @@ def list_services( def create_service_group( self, - folder: str, - name: str, - members: list[str], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + members: list[str] | None = None, tag: list[str] | None = None, ) -> dict[str, Any]: """Create or update a service group. Args: folder: Folder where the service group will be created + snippet: Snippet where the service group will be created + device: Device where the service group will be created name: Name of the service group members: List of service or service group names tag: Optional list of tags @@ -4558,13 +4978,21 @@ def create_service_group( dict[str, Any]: The created service group object """ - self.logger.info(f"Creating/updating service group '{name}' in folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating/updating service group '{name}' in {container}") if not self.client: # Return a mock response if no client is available return { "id": f"service-group-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "members": members, "tag": tag or [], @@ -4573,7 +5001,7 @@ def create_service_group( try: # Check if the service group already exists try: - existing = self.client.service_group.fetch(name=name, folder=folder) + existing = self.client.service_group.fetch(name=name, **container_kwargs) if existing: # Update the existing service group self.logger.info(f"Service group '{name}' already exists, updating...") @@ -4588,7 +5016,7 @@ def create_service_group( # Prepare the service group data service_group_data = { - "folder": folder, + **container_kwargs, "name": name, "members": members, } @@ -4603,18 +5031,28 @@ def create_service_group( except Exception as e: self._handle_api_exception("creating/updating", "service group", name, e) - def delete_service_group(self, folder: str, name: str) -> bool: + def delete_service_group(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> bool: """Delete a service group. Args: folder: Folder containing the service group + snippet: Snippet containing the service group + device: Device containing the service group name: Name of the service group to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting service group '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting service group '{name}' from {container}") if not self.client: self.logger.info(f"Mock mode: Would delete service group '{name}'") @@ -4622,31 +5060,41 @@ def delete_service_group(self, folder: str, name: str) -> bool: try: # First, fetch the service group to get its ID - service_group = self.client.service_group.fetch(name=name, folder=folder) + service_group = self.client.service_group.fetch(name=name, **container_kwargs) self.client.service_group.delete(str(service_group.id)) self.logger.info(f"Successfully deleted service group '{name}'") return True except Exception as e: self._handle_api_exception("deleting", "service group", name, e) - def get_service_group(self, folder: str, name: str) -> dict[str, Any]: + def get_service_group(self, folder: str | None = None, snippet: str | None = None, device: str | None = None, name: str = "") -> dict[str, Any]: """Get a specific service group by name. Args: folder: Folder containing the service group + snippet: Snippet containing the service group + device: Device containing the service group name: Name of the service group Returns: dict[str, Any]: Service group object """ - self.logger.info(f"Getting service group '{name}' from folder: {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting service group '{name}' from {container}") if not self.client: # Return mock data if no client is available return { "id": f"service-group-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "members": ["web-browsing", "ssl", "custom-web"], "tag": ["production", "web"], @@ -4654,7 +5102,7 @@ def get_service_group(self, folder: str, name: str) -> dict[str, Any]: try: # Fetch the service group by name and folder - result = self.client.service_group.fetch(name=name, folder=folder) + result = self.client.service_group.fetch(name=name, **container_kwargs) return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: self._handle_api_exception("fetching", "service group", name, e) @@ -6581,9 +7029,11 @@ def list_nat_rules( def create_zone( self, - folder: str, - name: str, - mode: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + mode: str = "", interfaces: list[str] | None = None, enable_user_identification: bool | None = None, enable_device_identification: bool | None = None, @@ -6592,6 +7042,8 @@ def create_zone( Args: folder: Folder to create the zone in + snippet: Snippet to create the zone in + device: Device to create the zone in name: Name of the zone mode: Zone mode (L2, L3, external, virtual-wire, tunnel) interfaces: List of interfaces @@ -6608,13 +7060,21 @@ def create_zone( """ interfaces = interfaces or [] - self.logger.info(f"Creating or updating zone: {name} with mode {mode} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating zone: {name} with mode {mode} in {container}") if not self.client: # Return mock data if no client is available return { "id": f"zone-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "mode": mode, "interfaces": interfaces, @@ -6624,10 +7084,10 @@ def create_zone( # First, try to fetch the existing zone existing_zone = None try: - existing_zone = self.client.security_zone.fetch(name=name, folder=folder) - self.logger.info(f"Found existing security zone '{name}' in folder '{folder}', updating...") + existing_zone = self.client.security_zone.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing security zone '{name}' in '{container}', updating...") except NotFoundError: - self.logger.info(f"Security zone '{name}' not found in folder '{folder}', creating new...") + self.logger.info(f"Security zone '{name}' not found in '{container}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching security zone '{name}': {str(fetch_error)}") @@ -6635,7 +7095,7 @@ def create_zone( # Prepare zone data zone_data = { "name": name, - "folder": folder, + **container_kwargs, } # Note: The zone mode is typically stored within the network configuration @@ -6688,24 +7148,36 @@ def create_zone( # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_zone( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete a security zone. Args: folder: Folder containing the zone + snippet: Snippet containing the zone + device: Device containing the zone name: Name of the zone to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting zone: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting zone: {name} from {container}") if not self.client: # Return a mock result if no client is available @@ -6713,35 +7185,47 @@ def delete_zone( try: # First, fetch the security zone to get its ID - zone = self.client.security_zone.fetch(name=name, folder=folder) + zone = self.client.security_zone.fetch(name=name, **container_kwargs) self.client.security_zone.delete(str(zone.id)) self.logger.info(f"Successfully deleted security zone '{name}'") return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_security_zone( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get a security zone by name and folder. Args: folder: Folder containing the security zone + snippet: Snippet containing the security zone + device: Device containing the security zone name: Name of the security zone to get Returns: dict[str, Any]: The security zone object """ - self.logger.info(f"Getting security zone: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting security zone: {name} from {container}") if not self.client: # Return mock data if no client is available return { "id": f"zone-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "network": { "layer3": ["ethernet1/1", "ethernet1/2"], @@ -6755,12 +7239,12 @@ def get_security_zone( try: # Fetch the security zone using the SDK - result = self.client.security_zone.fetch(name=name, folder=folder) + result = self.client.security_zone.fetch(name=name, **container_kwargs) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_security_zones( self, @@ -6855,8 +7339,10 @@ def list_security_zones( def create_ipsec_crypto_profile( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", esp_encryption: list[str] | None = None, esp_authentication: list[str] | None = None, dh_group: str = "group14", @@ -6867,6 +7353,8 @@ def create_ipsec_crypto_profile( Args: folder: Folder to create the profile in + snippet: Snippet to create the profile in + device: Device to create the profile in name: Name of the IPsec crypto profile esp_encryption: List of ESP encryption algorithms esp_authentication: List of ESP authentication algorithms @@ -6882,13 +7370,21 @@ def create_ipsec_crypto_profile( esp_authentication = esp_authentication or ["sha256"] lifetime = lifetime or {"hours": 1} - self.logger.info(f"Creating or updating IPsec crypto profile: {name} in folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating IPsec crypto profile: {name} in {container}") if not self.client: # Return mock data if no client is available result = { "id": f"ipsec-crypto-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "esp": { "encryption": esp_encryption, @@ -6905,7 +7401,7 @@ def create_ipsec_crypto_profile( try: # Prepare the profile data profile_data: dict[str, Any] = { - "folder": folder, + **container_kwargs, "name": name, "esp": { "encryption": esp_encryption, @@ -6920,10 +7416,10 @@ def create_ipsec_crypto_profile( # Try to fetch existing profile for smart upsert existing_profile = None try: - existing_profile = self.client.ipsec_crypto_profile.fetch(name=name, folder=folder) - self.logger.info(f"Found existing IPsec crypto profile '{name}' in folder '{folder}'") + existing_profile = self.client.ipsec_crypto_profile.fetch(name=name, **container_kwargs) + self.logger.info(f"Found existing IPsec crypto profile '{name}' in '{container}'") except NotFoundError: - self.logger.info(f"IPsec crypto profile '{name}' not found in folder '{folder}', will create new") + self.logger.info(f"IPsec crypto profile '{name}' not found in '{container}', will create new") except Exception as fetch_error: self.logger.warning(f"Error fetching IPsec crypto profile '{name}': {str(fetch_error)}") @@ -6972,57 +7468,81 @@ def create_ipsec_crypto_profile( return response except Exception as e: - self._handle_api_exception("create/update", folder, name, e) + self._handle_api_exception("create/update", container, name, e) def delete_ipsec_crypto_profile( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> bool: """Delete an IPsec crypto profile. Args: folder: Folder containing the profile + snippet: Snippet containing the profile + device: Device containing the profile name: Name of the profile to delete Returns: bool: True if deletion was successful """ - self.logger.info(f"Deleting IPsec crypto profile: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting IPsec crypto profile: {name} from {container}") if not self.client: return True try: - profile = self.client.ipsec_crypto_profile.fetch(name=name, folder=folder) + profile = self.client.ipsec_crypto_profile.fetch(name=name, **container_kwargs) self.client.ipsec_crypto_profile.delete(str(profile.id)) self.logger.info(f"Successfully deleted IPsec crypto profile '{name}'") return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_ipsec_crypto_profile( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", ) -> dict[str, Any]: """Get an IPsec crypto profile by name and folder. Args: folder: Folder containing the profile + snippet: Snippet containing the profile + device: Device containing the profile name: Name of the profile to get Returns: dict[str, Any]: The IPsec crypto profile """ - self.logger.info(f"Getting IPsec crypto profile: {name} from folder {folder}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting IPsec crypto profile: {name} from {container}") if not self.client: return { "id": f"ipsec-crypto-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "esp": { "encryption": ["aes-256-cbc"], @@ -7033,10 +7553,10 @@ def get_ipsec_crypto_profile( } try: - result = self.client.ipsec_crypto_profile.fetch(name=name, folder=folder) + result = self.client.ipsec_crypto_profile.fetch(name=name, **container_kwargs) return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_ipsec_crypto_profiles( self, @@ -7120,10 +7640,12 @@ def list_ipsec_crypto_profiles( def create_security_rule( self, - folder: str, - name: str, - source_zones: list[str], - destination_zones: list[str], + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", + source_zones: list[str] | None = None, + destination_zones: list[str] | None = None, source_addresses: list[str] | None = None, destination_addresses: list[str] | None = None, applications: list[str] | None = None, @@ -7141,6 +7663,8 @@ def create_security_rule( Args: folder: Folder to create the rule in + snippet: Snippet to create the rule in + device: Device to create the rule in name: Name of the rule source_zones: List of source zones destination_zones: List of destination zones @@ -7170,13 +7694,21 @@ def create_security_rule( applications = applications or ["any"] services = services or ["any"] tags = tags or [] - self.logger.info(f"Creating or updating security rule: {name} with action {action} in folder {folder}, rulebase {rulebase}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Creating or updating security rule: {name} with action {action} in {container}, rulebase {rulebase}") if not self.client: # Return mock data if no client is available return { "id": f"sr-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "source_zones": source_zones, "destination_zones": destination_zones, @@ -7198,10 +7730,10 @@ def create_security_rule( # First, try to fetch the existing security rule existing_rule = None try: - existing_rule = self.client.security_rule.fetch(name=name, folder=folder, rulebase=rulebase) - self.logger.info(f"Found existing security rule '{name}' in folder '{folder}', rulebase '{rulebase}', updating...") + existing_rule = self.client.security_rule.fetch(name=name, **container_kwargs, rulebase=rulebase) + self.logger.info(f"Found existing security rule '{name}' in '{container}', rulebase '{rulebase}', updating...") except NotFoundError: - self.logger.info(f"Security rule '{name}' not found in folder '{folder}', rulebase '{rulebase}', creating new...") + self.logger.info(f"Security rule '{name}' not found in '{container}', rulebase '{rulebase}', creating new...") except Exception as fetch_error: # Log but continue - we'll try to create if fetch failed for other reasons self.logger.warning(f"Error fetching security rule '{name}': {str(fetch_error)}") @@ -7209,7 +7741,7 @@ def create_security_rule( # Prepare rule data - SDK uses different field names (from_, to_, etc.) rule_data = { "name": name, - "folder": folder, + **container_kwargs, "from_": source_zones, # SDK uses from_ instead of source_zones "to_": destination_zones, # SDK uses to_ instead of destination_zones "source": source_addresses, # SDK uses `source` for the source instead of source_addresses @@ -7272,18 +7804,22 @@ def create_security_rule( # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("creation/update", folder, name, e) + self._handle_api_exception("creation/update", container, name, e) def delete_security_rule( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", rulebase: str = "pre", ) -> bool: """Delete a security rule. Args: folder: Folder containing the security rule + snippet: Snippet containing the security rule + device: Device containing the security rule name: Name of the security rule to delete rulebase: Rulebase containing the rule (pre, post, or default) @@ -7291,7 +7827,15 @@ def delete_security_rule( bool: True if deletion was successful """ - self.logger.info(f"Deleting security rule: {name} from folder {folder}, rulebase {rulebase}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Deleting security rule: {name} from {container}, rulebase {rulebase}") if not self.client: # Return a mock result if no client is available @@ -7299,24 +7843,28 @@ def delete_security_rule( try: # First, fetch the rule to get its ID - rule = self.client.security_rule.fetch(name=name, folder=folder, rulebase=rulebase) + rule = self.client.security_rule.fetch(name=name, **container_kwargs, rulebase=rulebase) # Delete using the rule's ID self.client.security_rule.delete(str(rule.id)) return True except Exception as e: - self._handle_api_exception("deletion", folder, name, e) + self._handle_api_exception("deletion", container, name, e) def get_security_rule( self, - folder: str, - name: str, + folder: str | None = None, + snippet: str | None = None, + device: str | None = None, + name: str = "", rulebase: str = "pre", ) -> dict[str, Any]: """Get a security rule by name and folder. Args: folder: Folder containing the security rule + snippet: Snippet containing the security rule + device: Device containing the security rule name: Name of the security rule to get rulebase: Rulebase to use (pre, post, or default) @@ -7324,13 +7872,21 @@ def get_security_rule( dict[str, Any]: The security rule object """ - self.logger.info(f"Getting security rule: {name} from folder {folder} in rulebase {rulebase}") + container = folder or snippet or device or "" + container_kwargs: dict[str, str] = {} + if folder: + container_kwargs["folder"] = folder + elif snippet: + container_kwargs["snippet"] = snippet + elif device: + container_kwargs["device"] = device + self.logger.info(f"Getting security rule: {name} from {container} in rulebase {rulebase}") if not self.client: # Return mock data if no client is available return { "id": f"sr-{name}", - "folder": folder, + "folder": folder or snippet or device or "shared", "name": name, "from_": ["trust"], "to_": ["untrust"], @@ -7347,12 +7903,12 @@ def get_security_rule( try: # Fetch the security rule using the SDK - result = self.client.security_rule.fetch(name=name, folder=folder, rulebase=rulebase) + result = self.client.security_rule.fetch(name=name, **container_kwargs, rulebase=rulebase) # Convert SDK response to dict for compatibility return json.loads(result.model_dump_json(exclude_unset=True)) except Exception as e: - self._handle_api_exception("retrieval", folder, name, e) + self._handle_api_exception("retrieval", container, name, e) def list_security_rules( self, diff --git a/src/scm_cli/utils/validators.py b/src/scm_cli/utils/validators.py index 5cd46ae..d059806 100644 --- a/src/scm_cli/utils/validators.py +++ b/src/scm_cli/utils/validators.py @@ -269,10 +269,24 @@ def to_sdk_model(self) -> dict[str, Any]: # ============================================================================================================================================================================================= -class AddressGroup(BaseModel): - """Model for address group configurations with folder path.""" +class ContainerModel(BaseModel): + """Base model providing folder/snippet/device container fields with exactly-one validation.""" + + folder: str | None = Field(None, description="Folder location") + snippet: str | None = Field(None, description="Snippet location") + device: str | None = Field(None, description="Device location") + + @model_validator(mode="after") + def validate_container_location(self) -> "ContainerModel": + """Ensure exactly one of folder, snippet, or device is set.""" + if sum(1 for container in (self.folder, self.snippet, self.device) if container is not None) != 1: + raise ValueError("Exactly one of 'folder', 'snippet', or 'device' must be set") + return self + + +class AddressGroup(ContainerModel): + """Model for address group configurations with container location.""" - folder: str = Field(..., description="Folder path for the address group") name: str = Field(..., description="Name of the address group") type: str = Field(..., description="Type of address group (static or dynamic)") members: list[str] = Field(default_factory=list, description="List of addresses in the group (for static groups)") @@ -299,12 +313,14 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class Address(BaseModel): +class Address(ContainerModel): """Model for address objects with container information. Attributes ---------- - folder (str): The folder where the address object is located + folder (Optional[str]): The folder where the address object is located + snippet (Optional[str]): The snippet where the address object is located + device (Optional[str]): The device where the address object is located name (str): The name of the address object description (str): Description of the address object tags (List[str]): Tags associated with the address object @@ -315,7 +331,6 @@ class Address(BaseModel): """ - folder: str = Field(..., description="Folder containing the address object") name: str = Field(..., min_length=1, max_length=63, description="Name of the address object") description: str = Field("", description="Description of the address object") tags: list[str] = Field(default_factory=list, description="Tags associated with the address object") @@ -398,10 +413,9 @@ def to_sdk_model(self) -> dict[str, Any]: return data -class Application(BaseModel): - """Model for application configurations with folder path.""" +class Application(ContainerModel): + """Model for application configurations with container location.""" - folder: str = Field(..., description="Folder path for the application") name: str = Field(..., min_length=1, max_length=63, description="Name of the application") category: str = Field(..., max_length=50, description="High-level category") subcategory: str = Field(..., max_length=50, description="Specific sub-category") @@ -455,10 +469,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class ApplicationGroup(BaseModel): - """Model for application group configurations with folder path.""" +class ApplicationGroup(ContainerModel): + """Model for application group configurations with container location.""" - folder: str = Field(..., description="Folder path for the application group") name: str = Field(..., min_length=1, max_length=63, description="Name of the application group") members: list[str] = Field(..., min_length=1, description="List of application names") @@ -470,10 +483,9 @@ def to_sdk_model(self) -> dict[str, Any]: } -class ApplicationFilter(BaseModel): - """Model for application filter configurations with folder path.""" +class ApplicationFilter(ContainerModel): + """Model for application filter configurations with container location.""" - folder: str = Field(..., description="Folder path for the application filter") name: str = Field(..., min_length=1, max_length=63, description="Name of the application filter") category: list[str] = Field(..., min_length=1, description="List of category strings") subcategory: list[str] = Field(..., min_length=1, description="List of subcategory strings") @@ -538,10 +550,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class DynamicUserGroup(BaseModel): - """Model for dynamic user group configurations with folder path.""" +class DynamicUserGroup(ContainerModel): + """Model for dynamic user group configurations with container location.""" - folder: str = Field(..., description="Folder path for the dynamic user group") name: str = Field(..., min_length=1, max_length=63, description="Name of the dynamic user group") filter: str = Field(..., max_length=2047, description="Tag-based filter expression") description: str = Field("", max_length=1023, description="Description of the dynamic user group") @@ -561,10 +572,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class ExternalDynamicList(BaseModel): - """Model for external dynamic list configurations with folder path.""" +class ExternalDynamicList(ContainerModel): + """Model for external dynamic list configurations with container location.""" - folder: str = Field(..., description="Folder path for the external dynamic list") name: str = Field( ..., min_length=1, @@ -692,10 +702,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class HIPObject(BaseModel): - """Model for HIP object configurations with folder path.""" +class HIPObject(ContainerModel): + """Model for HIP object configurations with container location.""" - folder: str = Field(..., description="Folder path for the HIP object") name: str = Field( ..., min_length=1, @@ -940,10 +949,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class HIPProfile(BaseModel): - """Model for HIP profile configurations with folder path.""" +class HIPProfile(ContainerModel): + """Model for HIP profile configurations with container location.""" - folder: str = Field(..., description="Folder path for the HIP profile") name: str = Field( ..., min_length=1, @@ -957,21 +965,23 @@ class HIPProfile(BaseModel): def to_sdk_model(self) -> dict[str, Any]: """Convert CLI model to SDK model format.""" model_data = { - "folder": self.folder, "name": self.name, "match": self.match, } + for container in ("folder", "snippet", "device"): + if getattr(self, container) is not None: + model_data[container] = getattr(self, container) + if self.description: model_data["description"] = self.description return model_data -class HTTPServerProfile(BaseModel): - """Model for HTTP server profile configurations with folder path.""" +class HTTPServerProfile(ContainerModel): + """Model for HTTP server profile configurations with container location.""" - folder: str = Field(..., description="Folder path for the HTTP server profile") name: str = Field( ..., min_length=1, @@ -1038,11 +1048,14 @@ def validate_servers(self) -> "HTTPServerProfile": def to_sdk_model(self) -> dict[str, Any]: """Convert CLI model to SDK model format.""" model_data = { - "folder": self.folder, "name": self.name, "server": self.servers, } + for container in ("folder", "snippet", "device"): + if getattr(self, container) is not None: + model_data[container] = getattr(self, container) + if self.description: model_data["description"] = self.description @@ -1055,10 +1068,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class LogForwardingProfile(BaseModel): - """Model for log forwarding profile configurations with folder path.""" +class LogForwardingProfile(ContainerModel): + """Model for log forwarding profile configurations with container location.""" - folder: str = Field(..., description="Folder path for the log forwarding profile") name: str = Field( ..., min_length=1, @@ -1110,10 +1122,13 @@ def validate_match_list(self) -> "LogForwardingProfile": def to_sdk_model(self) -> dict[str, Any]: """Convert CLI model to SDK model format.""" model_data = { - "folder": self.folder, "name": self.name, } + for container in ("folder", "snippet", "device"): + if getattr(self, container) is not None: + model_data[container] = getattr(self, container) + if self.description: model_data["description"] = self.description @@ -1129,7 +1144,7 @@ def to_sdk_model(self) -> dict[str, Any]: class Region(BaseModel): """Model for region configurations with folder path.""" - folder: str = Field(..., description="Folder path for the region") + folder: str | None = Field(None, description="Folder path for the region") name: str = Field( ..., description="Name of the region", @@ -1216,10 +1231,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class Service(BaseModel): - """Model for service configurations with folder path.""" +class Service(ContainerModel): + """Model for service configurations with container location.""" - folder: str = Field(..., description="Folder path for the service") name: str = Field( ..., min_length=1, @@ -1337,10 +1351,9 @@ def to_sdk_model(self) -> dict[str, Any]: return model_data -class ServiceGroup(BaseModel): - """Model for service group configurations with folder path.""" +class ServiceGroup(ContainerModel): + """Model for service group configurations with container location.""" - folder: str = Field(..., description="Folder path for the service group") name: str = Field( ..., min_length=1, @@ -1374,11 +1387,14 @@ def validate_service_group(self) -> "ServiceGroup": def to_sdk_model(self) -> dict[str, Any]: """Convert to SDK model format.""" model_data = { - "folder": self.folder, "name": self.name, "members": self.members, } + for container in ("folder", "snippet", "device"): + if getattr(self, container) is not None: + model_data[container] = getattr(self, container) + if self.tag: model_data["tag"] = self.tag @@ -1388,7 +1404,7 @@ def to_sdk_model(self) -> dict[str, Any]: class SyslogServerProfile(BaseModel): """Model for syslog server profile configurations with folder path.""" - folder: str = Field(..., description="Folder path for the syslog server profile") + folder: str | None = Field(None, description="Folder path for the syslog server profile") name: str = Field(..., description="Name of the syslog server profile") description: str | None = Field(None, description="Description of the profile") server: list[dict[str, Any]] = Field(..., description="List of syslog servers") @@ -1512,7 +1528,7 @@ class Schedule(BaseModel): - non-recurring: One-time date/time ranges """ - folder: str = Field(..., description="Folder path for the schedule") + folder: str | None = Field(None, description="Folder path for the schedule") name: str = Field( ..., description="Name of the schedule", @@ -1623,7 +1639,7 @@ def to_sdk_model(self) -> dict[str, Any]: class Tag(BaseModel): """Model for tag configurations with folder path.""" - folder: str = Field(..., description="Folder path for the tag") + folder: str | None = Field(None, description="Folder path for the tag") name: str = Field( ..., description="Name of the tag", @@ -2718,9 +2734,9 @@ def to_sdk_model(self) -> dict[str, Any]: class Zone(BaseModel): - """Model for security zone configurations with folder path.""" + """Model for security zone configurations with container location.""" - folder: str = Field(..., description="Folder path for the zone") + folder: str | None = Field(None, description="Folder path for the zone") name: str = Field(..., description="Name of the zone") network: dict[str, Any] = Field(default_factory=dict, description="Network configuration") description: str | None = Field(None, description="Description of the zone") @@ -2730,6 +2746,14 @@ class Zone(BaseModel): enable_device_identification: bool | None = Field(None, description="Enable device identification") tags: list[str] | None = Field(None, description="List of tags") + @model_validator(mode="after") + def validate_container(self) -> "Zone": + """Validate that exactly one container is specified.""" + containers = [self.folder, self.snippet, self.device] + if sum(1 for c in containers if c is not None) != 1: + raise ValueError("Exactly one of 'folder', 'snippet', or 'device' must be set") + return self + def to_sdk_model(self) -> dict[str, Any]: """Convert CLI model to SDK model format.""" # Extract mode and interfaces from network config @@ -3394,9 +3418,11 @@ def to_sdk_model(self) -> dict[str, Any]: class SecurityRule(BaseModel): - """Model for security rule configurations with folder path.""" + """Model for security rule configurations with container location.""" - folder: str = Field(..., description="Folder path for the security rule") + folder: str | None = Field(None, description="Folder path for the security rule") + snippet: str | None = Field(None, description="Snippet path for the security rule") + device: str | None = Field(None, description="Device path for the security rule") name: str = Field(..., description="Name of the security rule") rulebase: str = Field("pre", description="Rulebase (pre, post, or default)") source_zones: list[str] = Field(default_factory=lambda: ["any"], description="List of source zones") @@ -3420,13 +3446,29 @@ class SecurityRule(BaseModel): log_end: bool | None = Field(None, description="Log at session end") log_setting: str | None = Field(None, description="Log forwarding profile") + @model_validator(mode="after") + def validate_container(self) -> "SecurityRule": + """Validate that exactly one container is specified.""" + containers = [self.folder, self.snippet, self.device] + if sum(1 for c in containers if c is not None) != 1: + raise ValueError("Exactly one of 'folder', 'snippet', or 'device' must be set") + return self + def to_sdk_model(self) -> dict[str, Any]: """Convert CLI model to SDK model format.""" # Use tag field if tags is not provided tags_list = self.tags if self.tags is not None else (self.tag or []) + container = {} + if self.folder: + container["folder"] = self.folder + elif self.snippet: + container["snippet"] = self.snippet + elif self.device: + container["device"] = self.device + return { - "folder": self.folder, + **container, "name": self.name, "source_zones": self.source_zones, "destination_zones": self.destination_zones, diff --git a/tests/test_deployment_commands.py b/tests/test_deployment_commands.py index 87bb651..a5c8e88 100644 --- a/tests/test_deployment_commands.py +++ b/tests/test_deployment_commands.py @@ -51,7 +51,10 @@ def test_set_bandwidth_allocation_command(self, runner, monkeypatch): # Mock the SCM client method to avoid actual API calls from scm_cli.utils.sdk_client import scm_client + captured = {} + def mock_create(*args, **kwargs): + captured.update(kwargs) return { "id": "ba-12345", "name": kwargs.get("name"), @@ -71,7 +74,6 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "test-allocation", "--bandwidth", "1000", @@ -80,7 +82,9 @@ def mock_create(*args, **kwargs): "--description", "Test allocation", "--tags", - "test,example", + "test", + "--tags", + "example", ], ) @@ -88,6 +92,8 @@ def mock_create(*args, **kwargs): assert "Created bandwidth allocation" in result.stdout assert "test-allocation" in result.stdout assert "1000" in result.stdout + # --tags is repeatable and reaches the client as a list + assert captured["tags"] == ["test", "example"] def test_set_bandwidth_allocation_error(self, runner, monkeypatch): """Test the set bandwidth allocation command with an error.""" @@ -107,7 +113,6 @@ def mock_create_error(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "test-allocation", "--bandwidth", "1000", @@ -138,7 +143,6 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "test-allocation", "--spn-name-list", "spn1", @@ -168,7 +172,6 @@ def mock_delete_error(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "test-allocation", "--spn-name-list", "spn1", @@ -288,7 +291,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "test-allocation", "--bandwidth", "1000", "--spn-name-list", "spn1,spn2", "--description", "Test"], + ["test-allocation", "--bandwidth", "1000", "--spn-name-list", "spn1,spn2", "--description", "Test"], ) assert result.exit_code == 0 @@ -312,7 +315,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "test-allocation", "--bandwidth", "1000", "--spn-name-list", "spn1,spn2", "--description", "Test"], + ["test-allocation", "--bandwidth", "1000", "--spn-name-list", "spn1,spn2", "--description", "Test"], ) assert result.exit_code == 0 @@ -453,7 +456,6 @@ def mock_create(**kwargs): result = runner.invoke( test_app, [ - "--name", "corp-dns", "--domain-name", "corp.example.com", @@ -483,7 +485,6 @@ def mock_create_error(**kwargs): result = runner.invoke( test_app, [ - "--name", "corp-dns", "--domain-name", "corp.example.com", @@ -513,7 +514,7 @@ def mock_get(name): test_app = typer.Typer() test_app.command()(show_internal_dns_server) - result = runner.invoke(test_app, ["--name", "corp-dns"]) + result = runner.invoke(test_app, ["corp-dns"]) assert result.exit_code == 0 assert "corp-dns" in result.stdout @@ -564,7 +565,7 @@ def mock_delete(name): test_app = typer.Typer() test_app.command()(delete_internal_dns_server) - result = runner.invoke(test_app, ["--name", "corp-dns", "--force"]) + result = runner.invoke(test_app, ["corp-dns", "--force"]) assert result.exit_code == 0 assert "Deleted internal DNS server" in result.stdout @@ -729,7 +730,7 @@ def test_show_service_connection_single_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_service_connection) - result = runner.invoke(test_app, ["--name", "primary-connection", "--output", "json"]) + result = runner.invoke(test_app, ["primary-connection", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == connection @@ -799,7 +800,7 @@ def test_show_internal_dns_server_single_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_internal_dns_server) - result = runner.invoke(test_app, ["--name", "corp-dns", "--output", "json"]) + result = runner.invoke(test_app, ["corp-dns", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == server @@ -876,3 +877,82 @@ def mock_create(**kwargs): assert active["max"] > 1, "create calls never overlapped" assert sorted(created) == [f"dns-{i}" for i in range(4)] assert "Loaded 4 internal DNS server(s)" in result.output + + +class TestMaxResults: + """--max-results slices list output client-side before emit.""" + + def test_show_bandwidth_allocation_max_results(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + allocations = [{"id": f"ba-{i}", "name": f"alloc-{i}", "allocated_bandwidth": 100 * i, "spn_name_list": [], "description": ""} for i in range(3)] + monkeypatch.setattr(scm_client, "list_bandwidth_allocations", lambda: allocations) + + test_app = typer.Typer() + test_app.command()(show_bandwidth_allocation) + + result = runner.invoke(test_app, ["--max-results", "2", "--output", "json"]) + + assert result.exit_code == 0, result.output + assert json.loads(result.stdout) == allocations[:2] + + def test_show_internal_dns_server_max_results(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + servers = [{"id": f"dns-{i}", "name": f"dns-{i}", "domain_name": ["example.com"], "primary": f"10.0.0.{i}"} for i in range(5)] + monkeypatch.setattr(scm_client, "list_internal_dns_servers", lambda: servers) + + test_app = typer.Typer() + test_app.command()(show_internal_dns_server) + + result = runner.invoke(test_app, ["--max-results", "1", "--output", "json"]) + + assert result.exit_code == 0, result.output + assert json.loads(result.stdout) == servers[:1] + + +class TestBackupFileOption: + """Every backup command accepts --file to override the default filename.""" + + def test_backup_bandwidth_allocation_custom_file(self, runner, monkeypatch, tmp_path): + import yaml + + from scm_cli.commands.deployment import backup_bandwidth_allocation + from scm_cli.utils.sdk_client import scm_client + + allocations = [{"id": "ba-1", "name": "primary", "allocated_bandwidth": 1000, "spn_name_list": ["spn1"]}] + monkeypatch.setattr(scm_client, "list_bandwidth_allocations", lambda: allocations) + + out_file = tmp_path / "custom-allocations.yaml" + test_app = typer.Typer() + test_app.command()(backup_bandwidth_allocation) + + result = runner.invoke(test_app, ["--file", str(out_file)]) + + assert result.exit_code == 0, result.output + assert out_file.exists() + data = yaml.safe_load(out_file.read_text()) + assert data["bandwidth_allocations"][0]["name"] == "primary" + # SDK field mapped back to CLI field and id stripped + assert data["bandwidth_allocations"][0]["bandwidth"] == 1000 + assert "id" not in data["bandwidth_allocations"][0] + + def test_backup_internal_dns_server_custom_file(self, runner, monkeypatch, tmp_path): + import yaml + + from scm_cli.commands.deployment import backup_internal_dns_server + from scm_cli.utils.sdk_client import scm_client + + servers = [{"id": "dns-1", "name": "corp-dns", "domain_name": ["corp.example.com"], "primary": "10.0.0.1"}] + monkeypatch.setattr(scm_client, "list_internal_dns_servers", lambda: servers) + + out_file = tmp_path / "custom-dns.yaml" + test_app = typer.Typer() + test_app.command()(backup_internal_dns_server) + + result = runner.invoke(test_app, ["--file", str(out_file)]) + + assert result.exit_code == 0, result.output + assert out_file.exists() + data = yaml.safe_load(out_file.read_text()) + assert data["internal_dns_servers"][0]["name"] == "corp-dns" diff --git a/tests/test_forwarding_subresource_commands.py b/tests/test_forwarding_subresource_commands.py index e4f6b0b..2329fa0 100644 --- a/tests/test_forwarding_subresource_commands.py +++ b/tests/test_forwarding_subresource_commands.py @@ -49,7 +49,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "office-apps", + "office-apps", "--application", "slack", "--application", "zoom", ], @@ -80,7 +80,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "office-apps", + "office-apps", "--application", "slack", ], ) @@ -109,7 +109,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "office-apps", + "office-apps", "--application", "slack", ], ) @@ -125,7 +125,7 @@ def test_set_source_application_missing_folder(self, runner, monkeypatch): result = runner.invoke( test_app, [ - "--name", "office-apps", + "office-apps", "--application", "slack", ], ) @@ -186,7 +186,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "office-apps"], + ["--folder", "Mobile Users", "office-apps"], ) assert result.exit_code == 0 @@ -227,7 +227,7 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "office-apps", "--force"], + ["--folder", "Mobile Users", "office-apps", "--force"], ) assert result.exit_code == 0 @@ -247,7 +247,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -360,7 +360,7 @@ def mock_error(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "fail-apps", + "fail-apps", "--application", "slack", ], ) @@ -393,7 +393,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "branch-network", + "branch-network", "--ip-address", "10.1.0.0/16", "--ip-address", "10.2.*.*", ], @@ -425,7 +425,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "corp-office", + "corp-office", "--internal-host-ip", "192.168.1.1", "--internal-host-fqdn", "intranet.example.com", ], @@ -455,7 +455,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "branch-network", + "branch-network", "--ip-address", "10.1.0.0/16", ], ) @@ -472,7 +472,7 @@ def test_set_user_location_choice_conflict(self, runner, monkeypatch): test_app, [ "--folder", "Mobile Users", - "--name", "bad-location", + "bad-location", "--ip-address", "10.1.0.0/16", "--internal-host-fqdn", "intranet.example.com", ], @@ -489,7 +489,7 @@ def test_set_user_location_choice_missing(self, runner, monkeypatch): test_app, [ "--folder", "Mobile Users", - "--name", "bad-location", + "bad-location", ], ) @@ -503,7 +503,7 @@ def test_set_user_location_missing_folder(self, runner, monkeypatch): result = runner.invoke( test_app, [ - "--name", "branch-network", + "branch-network", "--ip-address", "10.1.0.0/16", ], ) @@ -564,7 +564,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "branch-network"], + ["--folder", "Mobile Users", "branch-network"], ) assert result.exit_code == 0 @@ -605,7 +605,7 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "branch-network", "--force"], + ["--folder", "Mobile Users", "branch-network", "--force"], ) assert result.exit_code == 0 @@ -625,7 +625,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -736,7 +736,7 @@ def mock_error(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "fail-location", + "fail-location", "--ip-address", "10.1.0.0/16", ], ) @@ -769,7 +769,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "emea-proxy", + "emea-proxy", "--type", "gp-and-pac", "--proxy-1-fqdn", "proxy1.example.com", "--proxy-1-port", "8080", @@ -802,7 +802,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "emea-proxy", + "emea-proxy", "--proxy-1-fqdn", "proxy2.example.com", ], ) @@ -817,7 +817,7 @@ def test_set_regional_proxy_missing_folder(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--name", "emea-proxy"], + ["emea-proxy"], ) assert result.exit_code == 1 @@ -879,7 +879,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "emea-proxy"], + ["--folder", "Mobile Users", "emea-proxy"], ) assert result.exit_code == 0 @@ -920,7 +920,7 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "emea-proxy", "--force"], + ["--folder", "Mobile Users", "emea-proxy", "--force"], ) assert result.exit_code == 0 @@ -940,7 +940,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -1064,7 +1064,7 @@ def mock_error(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "fail-proxy", + "fail-proxy", ], ) @@ -1111,7 +1111,7 @@ def test_show_user_location_detail_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_forwarding_profile_user_location) - result = runner.invoke(test_app, ["--folder", "Mobile Users", "--name", "branch-network", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "Mobile Users", "branch-network", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == location @@ -1135,7 +1135,7 @@ def test_show_regional_proxy_detail_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_forwarding_profile_regional_and_custom_proxy) - result = runner.invoke(test_app, ["--folder", "Mobile Users", "--name", "emea-proxy", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "Mobile Users", "emea-proxy", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == proxy diff --git a/tests/test_grammar_conformance.py b/tests/test_grammar_conformance.py new file mode 100644 index 0000000..0917686 --- /dev/null +++ b/tests/test_grammar_conformance.py @@ -0,0 +1,177 @@ +"""2.0 CLI grammar conformance tests. + +One grammar, enforced by introspection: + +- set/delete/show take the object name as a positional argument (no --name). +- Containerized types accept --folder/--snippet/--device (exactly-one enforced + at runtime by validate_location_params; presence checked here). +- Every load command has --dry-run; every backup command has --file. +- Tag lists are `--tags` (repeatable); no CSV `--tag` variants. +- Show commands have --output and --max-results (list mode) and no dead --list. +- Job ids are `--id` everywhere. +""" + +import click +import pytest +import typer + +from src.scm_cli.commands import deployment, identity, mobile_agent, network, objects, security, setup + +CONFIG_MODULES = { + "objects": objects, + "network": network, + "security": security, + "identity": identity, + "mobile_agent": mobile_agent, + "deployment": deployment, + "setup": setup, +} + +# Types keyed by something other than a positional NAME, or singletons with no +# name at all. (module, command-name) pairs, per action app. +NO_POSITIONAL_NAME = { + ("objects", "quarantined-device"), # keyed by --host-id + ("deployment", "bgp-routing"), # singleton + ("deployment", "network-location"), # read-only list + ("mobile_agent", "agent-version"), # read-only list + ("mobile_agent", "global-setting"), # singleton + ("mobile_agent", "infrastructure-setting"), # singleton + ("mobile_agent", "location"), # read-only list (if present) + ("setup", "device"), # keyed by device id records + ("deployment", "bandwidth-allocation"), # deletes require --spn-name-list pairing; name positional still applies -> see NAME_STILL_POSITIONAL +} + +# Global resources: no container flags expected. +NO_CONTAINER = { + ("deployment", "bandwidth-allocation"), + ("deployment", "bgp-routing"), + ("deployment", "internal-dns-server"), + ("deployment", "remote-network"), + ("deployment", "service-connection"), + ("deployment", "network-location"), + ("setup", "folder"), + ("setup", "label"), + ("setup", "snippet"), + ("setup", "device"), + ("objects", "quarantined-device"), + ("mobile_agent", "agent-version"), +} + + +def _click_group(typer_app) -> click.Group: + return typer.main.get_command(typer_app) + + +def _commands(module, app_name: str) -> dict[str, click.Command]: + app = getattr(module, app_name, None) + if app is None: + return {} + group = _click_group(app) + ctx = click.Context(group) + return {name: group.get_command(ctx, name) for name in group.list_commands(ctx)} + + +def _param_names(cmd: click.Command) -> set[str]: + names = set() + for param in cmd.params: + names.update(param.opts) + names.update(param.secondary_opts) + return names + + +def _positional_params(cmd: click.Command) -> list[click.Argument]: + return [p for p in cmd.params if isinstance(p, click.Argument)] + + +def _iter_commands(app_attr: str): + for module_name, module in CONFIG_MODULES.items(): + for cmd_name, cmd in _commands(module, app_attr).items(): + yield module_name, cmd_name, cmd + + +class TestPositionalName: + @pytest.mark.parametrize("app_attr", ["set_app", "delete_app", "show_app"]) + def test_no_name_option_anywhere(self, app_attr): + offenders = [f"{m}:{n}" for m, n, cmd in _iter_commands(app_attr) if "--name" in _param_names(cmd)] + assert offenders == [], f"{app_attr} commands still using --name: {offenders}" + + @pytest.mark.parametrize("app_attr", ["set_app", "delete_app", "show_app"]) + def test_positional_name_present(self, app_attr): + offenders = [] + for module_name, cmd_name, cmd in _iter_commands(app_attr): + if (module_name, cmd_name) in NO_POSITIONAL_NAME: + continue + positionals = _positional_params(cmd) + if not positionals or positionals[0].name != "name": + offenders.append(f"{module_name}:{cmd_name}") + assert offenders == [], f"{app_attr} commands without positional NAME: {offenders}" + + +class TestContainerFlags: + @pytest.mark.parametrize("app_attr", ["set_app", "delete_app", "show_app"]) + def test_containerized_types_accept_all_three(self, app_attr): + offenders = [] + for module_name, cmd_name, cmd in _iter_commands(app_attr): + if (module_name, cmd_name) in NO_CONTAINER: + continue + if module_name == "mobile_agent": + # SDK mobile-agent services are folder-scoped ("Mobile Users"); + # snippet/device are not supported upstream. + continue + names = _param_names(cmd) + missing = {"--folder", "--snippet", "--device"} - names + if missing: + offenders.append(f"{module_name}:{cmd_name} missing {sorted(missing)}") + assert offenders == [], f"{app_attr} container-flag gaps: {offenders}" + + +class TestLoadAndBackup: + def test_every_load_has_dry_run(self): + offenders = [f"{m}:{n}" for m, n, cmd in _iter_commands("load_app") if "--dry-run" not in _param_names(cmd)] + assert offenders == [], f"load commands without --dry-run: {offenders}" + + def test_every_backup_has_file(self): + offenders = [f"{m}:{n}" for m, n, cmd in _iter_commands("backup_app") if "--file" not in _param_names(cmd)] + assert offenders == [], f"backup commands without --file: {offenders}" + + +class TestFlagHygiene: + @pytest.mark.parametrize("app_attr", ["set_app", "delete_app", "show_app", "load_app", "backup_app"]) + def test_no_csv_tag_flag(self, app_attr): + offenders = [f"{m}:{n}" for m, n, cmd in _iter_commands(app_attr) if "--tag" in _param_names(cmd)] + assert offenders == [], f"--tag (CSV or collision) remains: {offenders}" + + def test_tags_is_repeatable_list(self): + offenders = [] + for module_name, cmd_name, cmd in _iter_commands("set_app"): + for param in cmd.params: + if "--tags" in getattr(param, "opts", []) and not param.multiple: + offenders.append(f"{module_name}:{cmd_name}") + assert offenders == [], f"--tags not repeatable(list) on: {offenders}" + + def test_no_dead_list_flag_on_show(self): + offenders = [f"{m}:{n}" for m, n, cmd in _iter_commands("show_app") if "--list" in _param_names(cmd)] + assert offenders == [], f"dead --list on show commands: {offenders}" + + def test_show_commands_have_output_and_max_results(self): + offenders = [] + for module_name, cmd_name, cmd in _iter_commands("show_app"): + names = _param_names(cmd) + if "--output" not in names: + offenders.append(f"{module_name}:{cmd_name} (--output)") + if "--max-results" not in names: + offenders.append(f"{module_name}:{cmd_name} (--max-results)") + assert offenders == [], f"show flag gaps: {offenders}" + + +class TestJobIdFlag: + def test_operations_uses_id(self): + from src.scm_cli.commands import operations + + group = _click_group(operations.app) + ctx = click.Context(group) + status = group.get_command(ctx, "status") + names = _param_names(status) + assert "--id" in names + assert "--job-id" not in names + assert "-j" not in names # -j is reserved for --json in incidents diff --git a/tests/test_identity_commands.py b/tests/test_identity_commands.py index f7bd633..7651436 100644 --- a/tests/test_identity_commands.py +++ b/tests/test_identity_commands.py @@ -74,10 +74,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-auth", "--folder", "Texas", - "--name", - "test-auth", "--method", '{"local_database": {}}', ], @@ -99,7 +98,7 @@ def mock_list(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_authentication_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--list"]) + result = runner.invoke(test_app, ["--folder", "Texas"]) assert result.exit_code == 0 assert "test-auth" in result.output @@ -112,7 +111,7 @@ def test_delete_authentication_profile_command(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_authentication_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "test-auth", "--force"]) + result = runner.invoke(test_app, ["test-auth", "--folder", "Texas", "--force"]) assert result.exit_code == 0 assert "Deleted authentication profile" in result.output @@ -134,7 +133,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-kerberos", "--servers", '[{"name": "kdc1", "host": "kdc.example.com", "port": 88}]'], + ["test-kerberos", "--folder", "Texas", "--servers", '[{"name": "kdc1", "host": "kdc.example.com", "port": 88}]'], ) assert result.exit_code == 0 assert "Created Kerberos server profile" in result.output @@ -158,10 +157,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-ldap", "--folder", "Texas", - "--name", - "test-ldap", "--servers", '[{"name": "ldap1", "address": "ldap.example.com", "port": 389}]', "--base", @@ -192,10 +190,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-radius", "--folder", "Texas", - "--name", - "test-radius", "--servers", '[{"name": "rad1", "ip_address": "10.0.0.1", "port": 1812, "secret": "s3cret"}]', "--protocol", @@ -228,10 +225,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-saml", "--folder", "Texas", - "--name", - "test-saml", "--entity-id", "https://idp.example.com", "--certificate", @@ -264,10 +260,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-tacacs", "--folder", "Texas", - "--name", - "test-tacacs", "--servers", '[{"name": "tac1", "address": "10.0.0.1", "port": 49, "secret": "s3cret"}]', "--protocol", @@ -326,7 +321,7 @@ def test_show_authentication_profile_by_name_output_json(self, runner, monkeypat test_app = typer.Typer() test_app.command()(show_authentication_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "test-auth", "--output", "json"]) + result = runner.invoke(test_app, ["test-auth", "--folder", "Texas", "--output", "json"]) assert result.exit_code == 0, result.output assert json.loads(result.stdout) == record @@ -380,3 +375,103 @@ def mock_create(**kwargs): assert active["max"] > 1, "create calls never overlapped" assert sorted(created) == [f"auth-{i}" for i in range(4)] assert "Processed 4 authentication profiles" in result.output + + +class TestContainerEnforcement: + """Exactly one of --folder/--snippet/--device is required.""" + + def test_set_with_no_container_fails(self, runner): + test_app = typer.Typer() + test_app.command()(set_authentication_profile) + + result = runner.invoke(test_app, ["test-auth"]) + assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device must be specified" in result.output + + def test_set_with_two_containers_fails(self, runner): + test_app = typer.Typer() + test_app.command()(set_authentication_profile) + + result = runner.invoke(test_app, ["test-auth", "--folder", "Texas", "--snippet", "shared"]) + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device can be specified" in result.output + + def test_show_list_with_no_container_fails(self, runner): + test_app = typer.Typer() + test_app.command()(show_authentication_profile) + + result = runner.invoke(test_app, []) + assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device must be specified" in result.output + + def test_show_list_with_two_containers_fails(self, runner): + test_app = typer.Typer() + test_app.command()(show_authentication_profile) + + result = runner.invoke(test_app, ["--folder", "Texas", "--device", "fw01"]) + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device can be specified" in result.output + + +class TestMaxResults: + """--max-results slices list output client-side.""" + + def test_show_list_max_results_slices(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + records = [{"id": f"prof-{i}", "name": f"profile-{i}", "folder": "Texas"} for i in range(5)] + monkeypatch.setattr(scm_client, "list_authentication_profiles", lambda *a, **kw: records) + + test_app = typer.Typer() + test_app.command()(show_authentication_profile) + + result = runner.invoke(test_app, ["--folder", "Texas", "--output", "json", "--max-results", "2"]) + + assert result.exit_code == 0, result.output + assert json.loads(result.stdout) == records[:2] + + +class TestSnippetContainer: + """--snippet flows end-to-end to the SDK client.""" + + def test_set_authentication_profile_with_snippet(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_create(**kwargs): + captured.update(kwargs) + return {"id": "auth-1", "name": kwargs.get("name"), "__action__": "created"} + + monkeypatch.setattr(scm_client, "create_authentication_profile", mock_create) + + test_app = typer.Typer() + test_app.command()(set_authentication_profile) + + result = runner.invoke(test_app, ["snippet-auth", "--snippet", "shared-config"]) + + assert result.exit_code == 0, result.output + assert captured["snippet"] == "shared-config" + assert "folder" not in captured + assert "Created authentication profile: snippet-auth in snippet shared-config" in result.output + + def test_show_list_with_snippet_passes_snippet_kwarg(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_list(*args, **kwargs): + captured.update(kwargs) + return [{"name": "snippet-auth", "snippet": "shared-config"}] + + monkeypatch.setattr(scm_client, "list_authentication_profiles", mock_list) + + test_app = typer.Typer() + test_app.command()(show_authentication_profile) + + result = runner.invoke(test_app, ["--snippet", "shared-config"]) + + assert result.exit_code == 0, result.output + assert captured["snippet"] == "shared-config" + assert captured["folder"] is None + assert captured["device"] is None diff --git a/tests/test_infrastructure_global_settings_commands.py b/tests/test_infrastructure_global_settings_commands.py index 667bb80..6009c1a 100644 --- a/tests/test_infrastructure_global_settings_commands.py +++ b/tests/test_infrastructure_global_settings_commands.py @@ -42,7 +42,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "gp-infra", + "gp-infra", "--dns-servers", DNS_SERVERS_JSON, "--ip-pools", IP_POOLS_JSON, "--portal-hostname", PORTAL_HOSTNAME_JSON, @@ -73,7 +73,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "gp-infra", + "gp-infra", "--dns-servers", DNS_SERVERS_JSON, "--ip-pools", IP_POOLS_JSON, "--portal-hostname", PORTAL_HOSTNAME_JSON, @@ -104,7 +104,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "gp-infra", + "gp-infra", "--dns-servers", DNS_SERVERS_JSON, "--ip-pools", IP_POOLS_JSON, "--portal-hostname", PORTAL_HOSTNAME_JSON, @@ -122,7 +122,7 @@ def test_set_infrastructure_setting_invalid_json(self, runner, monkeypatch): result = runner.invoke( test_app, [ - "--name", "gp-infra", + "gp-infra", "--dns-servers", "not-json", "--ip-pools", IP_POOLS_JSON, "--portal-hostname", PORTAL_HOSTNAME_JSON, @@ -140,7 +140,7 @@ def test_set_infrastructure_setting_invalid_folder(self, runner, monkeypatch): test_app, [ "--folder", "Texas", - "--name", "gp-infra", + "gp-infra", "--dns-servers", DNS_SERVERS_JSON, "--ip-pools", IP_POOLS_JSON, "--portal-hostname", PORTAL_HOSTNAME_JSON, @@ -164,7 +164,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, [ - "--name", "gp-infra", + "gp-infra", "--dns-servers", DNS_SERVERS_JSON, "--ip-pools", IP_POOLS_JSON, "--portal-hostname", PORTAL_HOSTNAME_JSON, @@ -195,7 +195,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "gp-infra"], + ["gp-infra"], ) assert result.exit_code == 0 @@ -217,7 +217,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "nonexistent"], + ["nonexistent"], ) assert result.exit_code == 1 @@ -236,7 +236,7 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "gp-infra", "--force"], + ["gp-infra", "--force"], ) assert result.exit_code == 0 @@ -256,7 +256,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "nonexistent", "--force"], + ["nonexistent", "--force"], ) assert result.exit_code == 1 @@ -532,7 +532,7 @@ def test_show_infrastructure_setting_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_infrastructure_setting) - result = runner.invoke(test_app, ["--name", "gp-infra", "--output", "json"]) + result = runner.invoke(test_app, ["gp-infra", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == setting diff --git a/tests/test_mobile_agent_commands.py b/tests/test_mobile_agent_commands.py index 51b8ec7..81a61f9 100644 --- a/tests/test_mobile_agent_commands.py +++ b/tests/test_mobile_agent_commands.py @@ -103,7 +103,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "5.2.13"], + ["--folder", "Mobile Users", "5.2.13"], ) assert result.exit_code == 0 @@ -177,7 +177,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Mobile Users", - "--name", "saml-auth", "--authentication-profile", "best-practice", @@ -212,7 +211,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Mobile Users", - "--name", "saml-auth", "--authentication-profile", "best-practice", @@ -244,7 +242,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Mobile Users", - "--name", "saml-auth", ], ) @@ -309,7 +306,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "saml-auth"], + ["--folder", "Mobile Users", "saml-auth"], ) assert result.exit_code == 0 @@ -350,7 +347,7 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "saml-auth", "--force"], + ["--folder", "Mobile Users", "saml-auth", "--force"], ) assert result.exit_code == 0 @@ -370,7 +367,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -485,7 +482,6 @@ def mock_error(*args, **kwargs): [ "--folder", "Mobile Users", - "--name", "fail-auth", ], ) @@ -534,12 +530,99 @@ def test_show_auth_setting_detail_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_auth_setting) - result = runner.invoke(test_app, ["--folder", "Mobile Users", "--name", "saml-auth", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "Mobile Users", "saml-auth", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == setting +class TestV2Grammar: + """2.0 grammar behaviors: positional NAME and --max-results slicing.""" + + def test_show_auth_setting_max_results_slices_list(self, runner, monkeypatch): + """--max-results limits list output client-side.""" + import json + + from scm_cli.utils.sdk_client import scm_client + + settings = [{"id": f"as-{i}", "folder": "Mobile Users", "name": f"auth-{i}", "authentication_profile": "p", "os": "Any"} for i in range(5)] + monkeypatch.setattr(scm_client, "list_auth_settings", lambda *a, **k: settings) + + test_app = typer.Typer() + test_app.command()(show_auth_setting) + + result = runner.invoke(test_app, ["--folder", "Mobile Users", "--max-results", "2", "--output", "json"]) + + assert result.exit_code == 0 + assert json.loads(result.stdout) == settings[:2] + + def test_set_auth_setting_requires_positional_name(self, runner): + """Set without positional NAME fails.""" + test_app = typer.Typer() + test_app.command()(set_auth_setting) + + result = runner.invoke(test_app, ["--folder", "Mobile Users"]) + + assert result.exit_code != 0 + + def test_set_auth_setting_rejects_name_option(self, runner): + """--name is gone; only positional NAME is accepted.""" + test_app = typer.Typer() + test_app.command()(set_auth_setting) + + result = runner.invoke(test_app, ["--folder", "Mobile Users", "--name", "saml-auth"]) + + assert result.exit_code != 0 + + def test_delete_auth_setting_requires_positional_name(self, runner): + """Delete without positional NAME fails.""" + test_app = typer.Typer() + test_app.command()(delete_auth_setting) + + result = runner.invoke(test_app, ["--folder", "Mobile Users", "--force"]) + + assert result.exit_code != 0 + + def test_backup_auth_setting_requires_exactly_one_container(self, runner): + """Backup with no container or two containers fails.""" + from scm_cli.commands.mobile_agent import backup_auth_setting + + test_app = typer.Typer() + test_app.command()(backup_auth_setting) + + result = runner.invoke(test_app, []) + assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device" in result.output + + result = runner.invoke(test_app, ["--folder", "Mobile Users", "--snippet", "gp-snippet"]) + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device" in result.output + + def test_backup_auth_setting_snippet_reaches_client(self, runner, monkeypatch, tmp_path): + """--snippet is passed through to the SDK client.""" + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_list(**kwargs): + captured.update(kwargs) + return [{"id": "as-1", "snippet": "gp-snippet", "name": "saml-auth", "authentication_profile": "p", "os": "Any"}] + + monkeypatch.setattr(scm_client, "list_auth_settings", mock_list) + monkeypatch.chdir(tmp_path) + + from scm_cli.commands.mobile_agent import backup_auth_setting + + test_app = typer.Typer() + test_app.command()(backup_auth_setting) + + result = runner.invoke(test_app, ["--snippet", "gp-snippet"]) + + assert result.exit_code == 0, result.output + assert captured.get("snippet") == "gp-snippet" + assert "Successfully backed up" in result.stdout + + class TestMobileAgentBulkLoadConcurrency: """Bulk loads issue create calls concurrently (bounded thread pool).""" diff --git a/tests/test_mobile_agent_forwarding_commands.py b/tests/test_mobile_agent_forwarding_commands.py index 75e3031..f16ea8d 100644 --- a/tests/test_mobile_agent_forwarding_commands.py +++ b/tests/test_mobile_agent_forwarding_commands.py @@ -168,7 +168,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "ztna-profile", + "ztna-profile", "--profile-type", "ztna-agent", ], ) @@ -196,7 +196,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "proxy-profile", + "proxy-profile", "--profile-type", "global-protect-proxy", "--pac-upload", ], @@ -219,7 +219,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "ztna-profile"], + ["--folder", "Mobile Users", "ztna-profile"], ) assert result.exit_code == 0 @@ -239,7 +239,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "ztna-profile"], + ["--folder", "Mobile Users", "ztna-profile"], ) assert result.exit_code == 0 @@ -250,7 +250,7 @@ def test_set_forwarding_profile_missing_folder(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(set_forwarding_profile) - result = runner.invoke(test_app, ["--name", "ztna-profile"]) + result = runner.invoke(test_app, ["ztna-profile"]) assert result.exit_code == 1 @@ -268,7 +268,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "fail-profile"], + ["--folder", "Mobile Users", "fail-profile"], ) assert result.exit_code == 1 @@ -322,7 +322,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "ztna-profile"], + ["--folder", "Mobile Users", "ztna-profile"], ) assert result.exit_code == 0 @@ -403,12 +403,42 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "ztna-profile", "--force"], + ["--folder", "Mobile Users", "ztna-profile", "--force"], ) assert result.exit_code == 0 assert "Deleted forwarding profile" in result.stdout + def test_delete_forwarding_profile_requires_name_or_id(self, runner, monkeypatch): + """Delete without NAME or --id fails with a clear error.""" + test_app = typer.Typer() + test_app.command()(delete_forwarding_profile) + + result = runner.invoke( + test_app, + ["--folder", "Mobile Users", "--force"], + ) + + assert result.exit_code == 1 + assert "provide NAME or --id" in result.output + + def test_show_forwarding_profile_max_results(self, runner, monkeypatch): + """--max-results slices the profile list client-side.""" + import json + + from scm_cli.utils.sdk_client import scm_client + + profiles = [{"id": f"fp-{i}", "folder": "Mobile Users", "name": f"profile-{i}", "definition_method": "rules"} for i in range(4)] + monkeypatch.setattr(scm_client, "list_forwarding_profiles", lambda *a, **k: profiles) + + test_app = typer.Typer() + test_app.command()(show_forwarding_profile) + + result = runner.invoke(test_app, ["--folder", "Mobile Users", "--max-results", "3", "--output", "json"]) + + assert result.exit_code == 0 + assert json.loads(result.stdout) == profiles[:3] + def test_delete_forwarding_profile_error(self, runner, monkeypatch): """Test deleting a forwarding profile with an error.""" from scm_cli.utils.sdk_client import scm_client @@ -423,7 +453,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -545,7 +575,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "internal-apps", + "internal-apps", "--fqdn", "*.example.com:8080", "--fqdn", "app.internal", "--ip-address", "10.0.0.0/8", @@ -565,7 +595,7 @@ def test_set_forwarding_profile_destination_missing_folder(self, runner): test_app = typer.Typer() test_app.command()(set_forwarding_profile_destination) - result = runner.invoke(test_app, ["--name", "internal-apps"]) + result = runner.invoke(test_app, ["internal-apps"]) assert result.exit_code == 1 @@ -583,7 +613,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "fail-dest"], + ["--folder", "Mobile Users", "fail-dest"], ) assert result.exit_code == 1 @@ -636,7 +666,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "internal-apps"], + ["--folder", "Mobile Users", "internal-apps"], ) assert result.exit_code == 0 @@ -700,7 +730,7 @@ def mock_delete(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "internal-apps", "--force"], + ["--folder", "Mobile Users", "internal-apps", "--force"], ) assert result.exit_code == 0 @@ -720,7 +750,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -843,7 +873,7 @@ def test_show_forwarding_profile_destination_detail_json(self, runner, monkeypat test_app = typer.Typer() test_app.command()(show_forwarding_profile_destination) - result = runner.invoke(test_app, ["--folder", "Mobile Users", "--name", "internal-apps", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "Mobile Users", "internal-apps", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == destination diff --git a/tests/test_mobile_agent_profile_commands.py b/tests/test_mobile_agent_profile_commands.py index bc11ef2..fc09390 100644 --- a/tests/test_mobile_agent_profile_commands.py +++ b/tests/test_mobile_agent_profile_commands.py @@ -142,7 +142,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "corp-app-settings", + "corp-app-settings", "--connect-method", "user-logon", "--tunnel-mtu", "1400", "--os", "Windows", @@ -174,7 +174,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "corp-app-settings", "--save-user-credentials", "1"], + ["corp-app-settings", "--save-user-credentials", "1"], ) assert result.exit_code == 0 @@ -192,7 +192,7 @@ def mock_create(*args, **kwargs): test_app = typer.Typer() test_app.command()(set_agent_profile) - result = runner.invoke(test_app, ["--name", "corp-app-settings"]) + result = runner.invoke(test_app, ["corp-app-settings"]) assert result.exit_code == 0 assert "No changes needed" in result.stdout @@ -204,7 +204,7 @@ def test_set_agent_profile_invalid_folder(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Shared", "--name", "corp-app-settings"], + ["--folder", "Shared", "corp-app-settings"], ) assert result.exit_code == 1 @@ -222,7 +222,7 @@ def mock_error(*args, **kwargs): test_app = typer.Typer() test_app.command()(set_agent_profile) - result = runner.invoke(test_app, ["--name", "fail-profile"]) + result = runner.invoke(test_app, ["fail-profile"]) assert result.exit_code == 1 @@ -268,7 +268,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "corp-app-settings"], + ["--folder", "Mobile Users", "corp-app-settings"], ) assert result.exit_code == 0 @@ -301,7 +301,7 @@ def test_delete_agent_profile(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "corp-app-settings", "--force"], + ["--folder", "Mobile Users", "corp-app-settings", "--force"], ) assert result.exit_code == 0 @@ -321,7 +321,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -425,7 +425,7 @@ def mock_create(*args, **kwargs): test_app, [ "--folder", "Mobile Users", - "--name", "corp-tunnel", + "corp-tunnel", "--access-route", "10.0.0.0/8", "--no-direct-access-to-local-network", ], @@ -450,7 +450,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "corp-tunnel", "--retrieve-framed-ip-address"], + ["corp-tunnel", "--retrieve-framed-ip-address"], ) assert result.exit_code == 0 @@ -468,7 +468,7 @@ def mock_create(*args, **kwargs): test_app = typer.Typer() test_app.command()(set_tunnel_profile) - result = runner.invoke(test_app, ["--name", "corp-tunnel"]) + result = runner.invoke(test_app, ["corp-tunnel"]) assert result.exit_code == 0 assert "No changes needed" in result.stdout @@ -480,7 +480,7 @@ def test_set_tunnel_profile_invalid_folder(self, runner): result = runner.invoke( test_app, - ["--folder", "Shared", "--name", "corp-tunnel"], + ["--folder", "Shared", "corp-tunnel"], ) assert result.exit_code == 1 @@ -498,7 +498,7 @@ def mock_error(*args, **kwargs): test_app = typer.Typer() test_app.command()(set_tunnel_profile) - result = runner.invoke(test_app, ["--name", "fail-tunnel"]) + result = runner.invoke(test_app, ["fail-tunnel"]) assert result.exit_code == 1 @@ -543,7 +543,7 @@ def mock_get(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "corp-tunnel"], + ["--folder", "Mobile Users", "corp-tunnel"], ) assert result.exit_code == 0 @@ -575,7 +575,7 @@ def test_delete_tunnel_profile(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "corp-tunnel", "--force"], + ["--folder", "Mobile Users", "corp-tunnel", "--force"], ) assert result.exit_code == 0 @@ -595,7 +595,7 @@ def mock_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Mobile Users", "--name", "nonexistent", "--force"], + ["--folder", "Mobile Users", "nonexistent", "--force"], ) assert result.exit_code == 1 @@ -692,7 +692,7 @@ def test_show_agent_profile_detail_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_agent_profile) - result = runner.invoke(test_app, ["--folder", "Mobile Users", "--name", "corp-app-settings", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "Mobile Users", "corp-app-settings", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == profile diff --git a/tests/test_network_commands.py b/tests/test_network_commands.py index fd421b6..c27cbf1 100644 --- a/tests/test_network_commands.py +++ b/tests/test_network_commands.py @@ -164,7 +164,6 @@ def mock_create(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-zone", "--mode", "layer3", @@ -200,7 +199,6 @@ def mock_create_error(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-zone", "--mode", "layer3", @@ -231,7 +229,6 @@ def mock_delete(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-zone", "--force", ], @@ -262,7 +259,6 @@ def mock_delete_error(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-zone", "--force", ], @@ -348,7 +344,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "test-folder", "--name", "test-zone", "--mode", "layer3", "--interfaces", "ethernet1/1", "--interfaces", "ethernet1/2"], + ["--folder", "test-folder", "test-zone", "--mode", "layer3", "--interfaces", "ethernet1/1", "--interfaces", "ethernet1/2"], ) assert result.exit_code == 0 @@ -372,7 +368,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "test-folder", "--name", "test-zone", "--mode", "layer3", "--interfaces", "ethernet1/1", "--interfaces", "ethernet1/2"], + ["--folder", "test-folder", "test-zone", "--mode", "layer3", "--interfaces", "ethernet1/1", "--interfaces", "ethernet1/2"], ) assert result.exit_code == 0 @@ -458,7 +454,7 @@ def mock_get(**kwargs): monkeypatch.setattr(scm_client, "get_ike_crypto_profile", mock_get) test_app = typer.Typer() test_app.command()(show_ike_crypto_profile) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "profile-1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "profile-1"]) assert result.exit_code == 0 assert "profile-1" in result.stdout assert "sha256" in result.stdout @@ -644,7 +640,7 @@ def mock_get(**kwargs): monkeypatch.setattr(scm_client, "get_ike_gateway", mock_get) test_app = typer.Typer() test_app.command()(show_ike_gateway) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "gw-site-a"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "gw-site-a"]) assert result.exit_code == 0 assert "gw-site-a" in result.stdout assert "203.0.113.1" in result.stdout @@ -771,7 +767,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-profile", "--esp-encryption", "aes-256-cbc", @@ -803,7 +798,6 @@ def mock_create_error(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-profile", ], ) @@ -828,7 +822,6 @@ def mock_delete(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-profile", "--force", ], @@ -865,7 +858,6 @@ def mock_get(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-profile", ], ) @@ -996,7 +988,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-profile", "--esp-encryption", "aes-256-cbc", "--esp-authentication", "sha256", "--dh-group", "group14"], + ["--folder", "Texas", "test-profile", "--esp-encryption", "aes-256-cbc", "--esp-authentication", "sha256", "--dh-group", "group14"], ) assert result.exit_code == 0 @@ -1020,7 +1012,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-profile", "--esp-encryption", "aes-256-cbc", "--esp-authentication", "sha256", "--dh-group", "group14"], + ["--folder", "Texas", "test-profile", "--esp-encryption", "aes-256-cbc", "--esp-authentication", "sha256", "--dh-group", "group14"], ) assert result.exit_code == 0 @@ -1057,7 +1049,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Texas", - "--name", "outbound-nat", "--from-zone", "trust", @@ -1093,7 +1084,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Texas", - "--name", "outbound-nat", "--source-translation", '{"dynamic_ip_and_port": {"type": "dynamic_ip_and_port", "translated_address": ["10.0.0.1"]}}', @@ -1120,7 +1110,6 @@ def mock_create_error(*args, **kwargs): [ "--folder", "Texas", - "--name", "outbound-nat", ], ) @@ -1145,7 +1134,6 @@ def mock_delete(*args, **kwargs): [ "--folder", "Texas", - "--name", "outbound-nat", "--force", ], @@ -1172,7 +1160,6 @@ def mock_delete_error(*args, **kwargs): [ "--folder", "Texas", - "--name", "outbound-nat", "--force", ], @@ -1208,7 +1195,6 @@ def mock_get(*args, **kwargs): [ "--folder", "Texas", - "--name", "outbound-nat", ], ) @@ -1402,7 +1388,7 @@ def mock_get(**kwargs): monkeypatch.setattr(scm_client, "get_aggregate_interface", mock_get) test_app = typer.Typer() test_app.command()(show_aggregate_interface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "ae1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "ae1"]) assert result.exit_code == 0 assert "ae1" in result.stdout assert "9000" in result.stdout @@ -1541,7 +1527,7 @@ def test_show_dhcp_interface_specific(self, runner, monkeypatch): monkeypatch.setattr(scm_client, "get_dhcp_interface", lambda **kwargs: {"id": "dhcp-1", "name": "ethernet1/1", "folder": "test-folder", "server": {"mode": "auto"}}) test_app = typer.Typer() test_app.command()(show_dhcp_interface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "ethernet1/1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "ethernet1/1"]) assert result.exit_code == 0 assert "ethernet1/1" in result.stdout @@ -1635,7 +1621,7 @@ def test_show_ethernet_interface_specific(self, runner, monkeypatch): ) test_app = typer.Typer() test_app.command()(show_ethernet_interface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "$eth1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "$eth1"]) assert result.exit_code == 0 assert "$eth1" in result.stdout assert "9000" in result.stdout @@ -1730,7 +1716,7 @@ def test_show_layer2_subinterface_specific(self, runner, monkeypatch): ) test_app = typer.Typer() test_app.command()(show_layer2_subinterface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "ethernet1/1.100"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "ethernet1/1.100"]) assert result.exit_code == 0 assert "ethernet1/1.100" in result.stdout @@ -1790,7 +1776,7 @@ def test_set_layer3_subinterface_created(self, runner, monkeypatch): monkeypatch.setattr(scm_client, "create_layer3_subinterface", lambda d: {**d, "id": "l3sub-12345", "__action__": "created"}) test_app = typer.Typer() test_app.command()(set_layer3_subinterface) - result = runner.invoke(test_app, ["ethernet1/1.100", "--folder", "test-folder", "--tag", "100", "--mtu", "1500"]) + result = runner.invoke(test_app, ["ethernet1/1.100", "--folder", "test-folder", "--vlan-tag", "100", "--mtu", "1500"]) assert result.exit_code == 0 assert "Created layer3 subinterface" in result.stdout @@ -1824,7 +1810,7 @@ def test_show_layer3_subinterface_specific(self, runner, monkeypatch): ) test_app = typer.Typer() test_app.command()(show_layer3_subinterface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "ethernet1/1.100"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "ethernet1/1.100"]) assert result.exit_code == 0 assert "ethernet1/1.100" in result.stdout assert "9000" in result.stdout @@ -1919,7 +1905,7 @@ def test_show_loopback_interface_specific(self, runner, monkeypatch): ) test_app = typer.Typer() test_app.command()(show_loopback_interface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "$lo1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "$lo1"]) assert result.exit_code == 0 assert "$lo1" in result.stdout @@ -2011,7 +1997,7 @@ def test_show_tunnel_interface_specific(self, runner, monkeypatch): monkeypatch.setattr(scm_client, "get_tunnel_interface", lambda **kwargs: {"id": "tun-1", "name": "tunnel1", "folder": "test-folder", "mtu": 1400, "ip": [{"name": "10.0.0.1/30"}]}) test_app = typer.Typer() test_app.command()(show_tunnel_interface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "tunnel1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "tunnel1"]) assert result.exit_code == 0 assert "tunnel1" in result.stdout assert "1400" in result.stdout @@ -2104,7 +2090,7 @@ def test_show_vlan_interface_specific(self, runner, monkeypatch): monkeypatch.setattr(scm_client, "get_vlan_interface", lambda **kwargs: {"id": "vlan-1", "name": "vlan1", "folder": "test-folder", "vlan_tag": "100", "ip": [{"name": "10.0.10.1/24"}]}) test_app = typer.Typer() test_app.command()(show_vlan_interface) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "vlan1"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "vlan1"]) assert result.exit_code == 0 assert "vlan1" in result.stdout assert "100" in result.stdout @@ -2973,7 +2959,7 @@ def test_prompt_decline_aborts_without_deleting(self, runner, monkeypatch): calls = [] test_app = self._make_app(command, client_method, monkeypatch, calls) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", name], input="n\n") + result = runner.invoke(test_app, ["--folder", "Texas", name], input="n\n") assert result.exit_code != 0, f"{client_method}: declining must not exit 0" assert calls == [], f"{client_method}: delete must not be called on decline" @@ -2983,7 +2969,7 @@ def test_prompt_accept_deletes(self, runner, monkeypatch): calls = [] test_app = self._make_app(command, client_method, monkeypatch, calls) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", name], input="y\n") + result = runner.invoke(test_app, ["--folder", "Texas", name], input="y\n") assert result.exit_code == 0, f"{client_method}: accepting must delete" assert len(calls) == 1, f"{client_method}: delete must be called once" @@ -2993,7 +2979,7 @@ def test_force_skips_prompt(self, runner, monkeypatch): calls = [] test_app = self._make_app(command, client_method, monkeypatch, calls) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", name, "--force"]) + result = runner.invoke(test_app, ["--folder", "Texas", name, "--force"]) assert result.exit_code == 0, f"{client_method}: --force must delete" assert len(calls) == 1 @@ -3072,7 +3058,7 @@ def test_show_single_json_round_trip(self, runner, monkeypatch): monkeypatch.setattr(scm_client, "get_ike_crypto_profile", lambda **kw: dict(obj)) test_app = typer.Typer() test_app.command()(show_ike_crypto_profile) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "profile-1", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "profile-1", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == obj @@ -3108,7 +3094,7 @@ def test_show_single_not_found_errors(self, runner, monkeypatch): monkeypatch.setattr(scm_client, "get_ike_crypto_profile", lambda **kw: None) test_app = typer.Typer() test_app.command()(show_ike_crypto_profile) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "missing"]) + result = runner.invoke(test_app, ["--folder", "test-folder", "missing"]) assert result.exit_code == 1 assert "not found" in result.output @@ -3154,3 +3140,106 @@ def mock_create(sdk_data): assert sorted(created) == ["$eth1", "$eth2", "$eth3", "$eth4"] assert result.stdout.count("Created ethernet interface") == 4 assert "Summary: Processed 4 ethernet interfaces" in result.stdout + + +class TestGrammarV2: + """2.0 grammar behaviors: container enforcement, --max-results, snippet end-to-end.""" + + def test_set_zone_no_container_errors(self, runner): + """Set with no container exits non-zero with a clear error.""" + test_app = typer.Typer() + test_app.command()(set_zone) + result = runner.invoke(test_app, ["test-zone", "--mode", "layer3"]) + assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device must be specified" in result.output + + def test_set_zone_two_containers_errors(self, runner): + """Set with two containers exits non-zero with a clear error.""" + test_app = typer.Typer() + test_app.command()(set_zone) + result = runner.invoke(test_app, ["test-zone", "--mode", "layer3", "--folder", "Texas", "--snippet", "shared"]) + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device can be specified" in result.output + + def test_show_nat_rule_max_results_slices(self, runner, monkeypatch): + """--max-results limits list output client-side.""" + from scm_cli.utils.sdk_client import scm_client + + def mock_list(**kwargs): + return [{"id": f"nat-{i}", "name": f"rule-{i}", "folder": "Texas"} for i in range(5)] + + monkeypatch.setattr(scm_client, "list_nat_rules", mock_list) + test_app = typer.Typer() + test_app.command()(show_nat_rule) + result = runner.invoke(test_app, ["--folder", "Texas", "--max-results", "2", "--output", "json"]) + assert result.exit_code == 0 + items = json.loads(result.stdout) + assert len(items) == 2 + assert [i["name"] for i in items] == ["rule-0", "rule-1"] + + def test_set_zone_snippet_end_to_end(self, runner, monkeypatch): + """--snippet reaches the SDK client for a previously folder-only type.""" + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_create(**kwargs): + captured.update(kwargs) + return {"id": "zone-1", "name": kwargs.get("name"), "snippet": kwargs.get("snippet"), "__action__": "created"} + + monkeypatch.setattr(scm_client, "create_zone", mock_create) + test_app = typer.Typer() + test_app.command()(set_zone) + result = runner.invoke(test_app, ["test-zone", "--snippet", "shared-snippet", "--mode", "layer3", "--interfaces", "ethernet1/1"]) + assert result.exit_code == 0 + assert captured["snippet"] == "shared-snippet" + assert captured["folder"] is None + assert captured["device"] is None + assert "Created zone" in result.stdout + assert "shared-snippet" in result.stdout + + def test_delete_ipsec_crypto_profile_snippet_end_to_end(self, runner, monkeypatch): + """--snippet reaches the SDK client on delete for a previously folder-only type.""" + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_delete(**kwargs): + captured.update(kwargs) + return True + + monkeypatch.setattr(scm_client, "delete_ipsec_crypto_profile", mock_delete) + test_app = typer.Typer() + test_app.command()(delete_ipsec_crypto_profile) + result = runner.invoke(test_app, ["my-profile", "--snippet", "shared-snippet", "--force"]) + assert result.exit_code == 0 + assert captured["snippet"] == "shared-snippet" + assert captured["name"] == "my-profile" + + def test_load_ike_crypto_profile_dry_run(self, runner, monkeypatch, tmp_path): + """load ike-crypto-profile --dry-run previews without SDK calls.""" + import yaml + + from scm_cli.utils.sdk_client import scm_client + + yaml_data = { + "ike_crypto_profiles": [{"name": "test-profile", "folder": "test-folder", "hash": ["sha256"], "dh_group": ["group14"], "encryption": ["aes-256-cbc"]}] + } + yaml_file = tmp_path / "ike-profiles.yaml" + with yaml_file.open("w") as f: + yaml.dump(yaml_data, f) + + mock_called = False + + def mock_create(profile_data): + nonlocal mock_called + mock_called = True + return {} + + monkeypatch.setattr(scm_client, "create_ike_crypto_profile", mock_create) + test_app = typer.Typer() + test_app.command()(load_ike_crypto_profile) + result = runner.invoke(test_app, ["--file", str(yaml_file), "--dry-run"]) + assert result.exit_code == 0 + assert "Dry run mode" in result.stdout + assert not mock_called diff --git a/tests/test_network_commands_env.py b/tests/test_network_commands_env.py index d4ba0c9..b112450 100644 --- a/tests/test_network_commands_env.py +++ b/tests/test_network_commands_env.py @@ -30,7 +30,7 @@ def test_set_zone(self, runner, env_name, env): result = runner.invoke( app, - ["set", "network", "zone", "--folder", "Shared", "--name", "test-zone", "--mode", "layer3", "--interfaces", "ethernet1/1"], + ["set", "network", "zone", "test-zone", "--folder", "Shared", "--mode", "layer3", "--interfaces", "ethernet1/1"], ) assert result.exit_code == 0 @@ -52,7 +52,7 @@ def test_set_zone_with_mock(self, runner, env_name, env): result = runner.invoke( app, - ["set", "network", "zone", "--folder", "Shared", "--name", "test-zone", "--mode", "layer3"], + ["set", "network", "zone", "test-zone", "--folder", "Shared", "--mode", "layer3"], ) assert result.exit_code == 0 @@ -66,7 +66,7 @@ def test_delete_zone(self, runner, env_name, env): with patch("scm_cli.commands.network.scm_client") as mock_client: mock_client.delete_zone.return_value = True - result = runner.invoke(app, ["delete", "network", "zone", "--folder", "Shared", "--name", "test-zone", "--force"]) + result = runner.invoke(app, ["delete", "network", "zone", "test-zone", "--folder", "Shared", "--force"]) assert result.exit_code == 0 assert "Deleted zone" in result.stdout diff --git a/tests/test_objects_commands.py b/tests/test_objects_commands.py index 300e38e..cf1b46d 100644 --- a/tests/test_objects_commands.py +++ b/tests/test_objects_commands.py @@ -86,10 +86,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-group", "--folder", "test-folder", - "--name", - "test-group", "--type", "static", "--members", @@ -125,10 +124,9 @@ def mock_create_error(*args, **kwargs): result = runner.invoke( test_app, [ + "test-group", "--folder", "test-folder", - "--name", - "test-group", "--type", "static", "--members", @@ -153,7 +151,7 @@ def mock_delete(*args, **kwargs): test_app = typer.Typer() test_app.command()(delete_address_group) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "test-group", "--force"]) + result = runner.invoke(test_app, ["test-group", "--folder", "test-folder", "--force"]) assert result.exit_code == 0 assert "Deleted address group: test-group" in result.stdout @@ -170,7 +168,7 @@ def mock_delete_error(*args, **kwargs): test_app = typer.Typer() test_app.command()(delete_address_group) - result = runner.invoke(test_app, ["--folder", "test-folder", "--name", "test-group", "--force"]) + result = runner.invoke(test_app, ["test-group", "--folder", "test-folder", "--force"]) assert result.exit_code == 1 assert "Error" in result.output @@ -253,7 +251,7 @@ def mock_get_address_group(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_address_group) - result = runner.invoke(test_app, ["--folder", "Shared", "--name", "test-group"]) + result = runner.invoke(test_app, ["test-group", "--folder", "Shared"]) assert result.exit_code == 0 assert "Address Group: test-group" in result.stdout @@ -280,7 +278,7 @@ def mock_get_address_group(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_address_group) - result = runner.invoke(test_app, ["--folder", "Shared", "--name", "dynamic-endpoints"]) + result = runner.invoke(test_app, ["dynamic-endpoints", "--folder", "Shared"]) assert result.exit_code == 0 assert "Address Group: dynamic-endpoints" in result.stdout @@ -385,6 +383,8 @@ def mock_create_error(*args, **kwargs): test_app, [ "bad-sched", + "--folder", + "Texas", "--schedule-type", "recurring-daily", "--time-range", @@ -436,7 +436,7 @@ def mock_get(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_schedule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "BusinessHours"]) + result = runner.invoke(test_app, ["BusinessHours", "--folder", "Texas"]) assert result.exit_code == 0 assert "Schedule: BusinessHours" in result.stdout @@ -602,7 +602,7 @@ def mock_get(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_region) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "US-South"]) + result = runner.invoke(test_app, ["US-South", "--folder", "Texas"]) assert result.exit_code == 0 assert "Region: US-South" in result.stdout @@ -673,7 +673,7 @@ def mock_create(device_data): test_app = typer.Typer() test_app.command()(set_quarantined_device) - result = runner.invoke(test_app, ["host-123", "--serial-number", "SN-456"]) + result = runner.invoke(test_app, ["--host-id", "host-123", "--serial-number", "SN-456"]) assert result.exit_code == 0 assert "Created quarantined device: host-123" in result.stdout @@ -690,7 +690,7 @@ def mock_create(device_data): test_app = typer.Typer() test_app.command()(set_quarantined_device) - result = runner.invoke(test_app, ["host-789"]) + result = runner.invoke(test_app, ["--host-id", "host-789"]) assert result.exit_code == 0 assert "Created quarantined device: host-789" in result.stdout @@ -707,7 +707,7 @@ def mock_delete(host_id): test_app = typer.Typer() test_app.command()(delete_quarantined_device) - result = runner.invoke(test_app, ["host-123", "--force"]) + result = runner.invoke(test_app, ["--host-id", "host-123", "--force"]) assert result.exit_code == 0 assert "Deleted quarantined device: host-123" in result.stdout @@ -887,7 +887,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "high-risk-apps", "--category", "business-systems", "--subcategory", "database", "--technology", "client-server", "--risk", "4"], + ["high-risk-apps", "--folder", "Texas", "--category", "business-systems", "--subcategory", "database", "--technology", "client-server", "--risk", "4"], ) assert result.exit_code == 0 @@ -911,7 +911,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "high-risk-apps", "--category", "business-systems", "--subcategory", "database", "--technology", "client-server", "--risk", "4"], + ["high-risk-apps", "--folder", "Texas", "--category", "business-systems", "--subcategory", "database", "--technology", "client-server", "--risk", "4"], ) assert result.exit_code == 0 @@ -940,10 +940,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-edl", "--folder", "Texas", - "--name", - "test-edl", "--type", "ip", "--url", @@ -975,10 +974,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-edl", "--folder", "Texas", - "--name", - "test-edl", "--type", "ip", "--url", @@ -1014,10 +1012,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "wifi-only", "--folder", "Texas", - "--name", - "wifi-only", "--network-info-type", "is", "--network-info-value", @@ -1047,10 +1044,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "wifi-only", "--folder", "Texas", - "--name", - "wifi-only", "--network-info-type", "is", "--network-info-value", @@ -1083,7 +1079,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-profile", "--match", '"wifi-only" is'], + ["test-profile", "--folder", "Texas", "--match", '"wifi-only" is'], ) assert result.exit_code == 0 @@ -1107,7 +1103,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-profile", "--match", '"wifi-only" is'], + ["test-profile", "--folder", "Texas", "--match", '"wifi-only" is'], ) assert result.exit_code == 0 @@ -1136,10 +1132,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-http-profile", "--folder", "Texas", - "--name", - "test-http-profile", "--servers", '[{"name": "srv1", "address": "192.168.1.100", "protocol": "HTTPS", "port": 443, "http_method": "POST"}]', ], @@ -1167,10 +1162,9 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, [ + "test-http-profile", "--folder", "Texas", - "--name", - "test-http-profile", "--servers", '[{"name": "srv1", "address": "192.168.1.100", "protocol": "HTTPS", "port": 443, "http_method": "POST"}]', ], @@ -1201,7 +1195,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-lfp"], + ["test-lfp", "--folder", "Texas"], ) assert result.exit_code == 0 @@ -1225,7 +1219,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-lfp"], + ["test-lfp", "--folder", "Texas"], ) assert result.exit_code == 0 @@ -1253,7 +1247,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "web-services", "--members", "http,https"], + ["web-services", "--folder", "Texas", "--members", "http,https"], ) assert result.exit_code == 0 @@ -1277,7 +1271,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "web-services", "--members", "http,https"], + ["web-services", "--folder", "Texas", "--members", "http,https"], ) assert result.exit_code == 0 @@ -1437,7 +1431,7 @@ def test_show_by_name_output_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_address_group) - result = runner.invoke(test_app, ["--folder", "Shared", "--name", "test-group", "--output", "json"]) + result = runner.invoke(test_app, ["test-group", "--folder", "Shared", "--output", "json"]) assert result.exit_code == 0 assert self._parse_json(result.output) == record @@ -1475,7 +1469,7 @@ def mock_create(*args, **kwargs): monkeypatch.setattr(scm_client, "create_service", mock_create) test_app = typer.Typer() test_app.command()(set_service) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "test-svc", "--protocol", "tcp", "--port", "80", "--tag", "web, production"]) + result = runner.invoke(test_app, ["test-svc", "--folder", "Texas", "--protocol", "tcp", "--port", "80", "--tags", "web, production"]) assert result.exit_code == 0 assert captured.get("tag") == ["web", "production"] @@ -1492,11 +1486,207 @@ def mock_create(*args, **kwargs): monkeypatch.setattr(scm_client, "create_service_group", mock_create) test_app = typer.Typer() test_app.command()(set_service_group) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "test-sg", "--members", "HTTP, HTTPS, SSH"]) + result = runner.invoke(test_app, ["test-sg", "--folder", "Texas", "--members", "HTTP, HTTPS, SSH"]) assert result.exit_code == 0 assert captured.get("members") == ["HTTP", "HTTPS", "SSH"] +class TestContainerEnforcement: + """Exactly one of --folder/--snippet/--device must be provided on config commands.""" + + def test_set_address_no_container_fails(self, runner, monkeypatch): + from scm_cli.commands.objects import set_address + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "create_address", lambda *a, **k: pytest.fail("SDK must not be called")) + + test_app = typer.Typer() + test_app.command()(set_address) + + result = runner.invoke(test_app, ["web1", "--ip-netmask", "10.0.0.1/32"]) + + assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device must be specified" in result.output + + def test_set_address_two_containers_fails(self, runner, monkeypatch): + from scm_cli.commands.objects import set_address + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "create_address", lambda *a, **k: pytest.fail("SDK must not be called")) + + test_app = typer.Typer() + test_app.command()(set_address) + + result = runner.invoke( + test_app, + ["web1", "--folder", "Texas", "--snippet", "Shared", "--ip-netmask", "10.0.0.1/32"], + ) + + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device can be specified" in result.output + + +class TestMaxResults: + """--max-results must slice list-mode output client-side.""" + + def test_show_address_max_results_slices(self, runner, monkeypatch): + from scm_cli.commands.objects import show_address + from scm_cli.utils.sdk_client import scm_client + + rows = [{"name": f"addr-{i}", "folder": "Texas"} for i in range(5)] + monkeypatch.setattr(scm_client, "list_addresses", lambda *a, **k: rows) + + test_app = typer.Typer() + test_app.command()(show_address) + + result = runner.invoke(test_app, ["--folder", "Texas", "--max-results", "2", "--output", "json"]) + + assert result.exit_code == 0 + assert TestShowJsonOutput._parse_json(result.output) == rows[:2] + + def test_show_address_without_max_results_returns_all(self, runner, monkeypatch): + from scm_cli.commands.objects import show_address + from scm_cli.utils.sdk_client import scm_client + + rows = [{"name": f"addr-{i}", "folder": "Texas"} for i in range(5)] + monkeypatch.setattr(scm_client, "list_addresses", lambda *a, **k: rows) + + test_app = typer.Typer() + test_app.command()(show_address) + + result = runner.invoke(test_app, ["--folder", "Texas", "--output", "json"]) + + assert result.exit_code == 0 + assert TestShowJsonOutput._parse_json(result.output) == rows + + +class TestSnippetContainer: + """--snippet must flow end-to-end to the SDK client for previously folder-only types.""" + + def test_set_address_snippet_reaches_client(self, runner, monkeypatch): + from scm_cli.commands.objects import set_address + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_create(*args, **kwargs): + captured.update(kwargs) + return {"id": "addr-1", "name": kwargs.get("name"), "snippet": kwargs.get("snippet"), "__action__": "created"} + + monkeypatch.setattr(scm_client, "create_address", mock_create) + + test_app = typer.Typer() + test_app.command()(set_address) + + result = runner.invoke(test_app, ["web1", "--snippet", "Shared-Snippet", "--ip-netmask", "10.0.0.1/32"]) + + assert result.exit_code == 0 + assert captured.get("snippet") == "Shared-Snippet" + assert captured.get("folder") is None + assert captured.get("device") is None + + def test_show_address_snippet_reaches_client(self, runner, monkeypatch): + from scm_cli.commands.objects import show_address + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_list(*args, **kwargs): + captured.update(kwargs) + return [{"name": "web1", "snippet": "Shared-Snippet"}] + + monkeypatch.setattr(scm_client, "list_addresses", mock_list) + + test_app = typer.Typer() + test_app.command()(show_address) + + result = runner.invoke(test_app, ["--snippet", "Shared-Snippet"]) + + assert result.exit_code == 0 + assert captured.get("snippet") == "Shared-Snippet" + assert captured.get("folder") is None + + +class TestLoadDryRun: + """Newly added --dry-run flags must preview without calling the SDK.""" + + def test_load_tag_dry_run(self, runner, monkeypatch, tmp_path): + from scm_cli.commands.objects import load_tag + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "create_tag", lambda *a, **k: pytest.fail("SDK must not be called in dry-run")) + + yaml_content = "tags:\n - name: production\n folder: Texas\n color: Red\n" + test_file = tmp_path / "tags.yml" + test_file.write_text(yaml_content) + + test_app = typer.Typer() + test_app.command()(load_tag) + + result = runner.invoke(test_app, ["--file", str(test_file), "--dry-run"]) + + assert result.exit_code == 0 + assert "Dry run" in result.output + assert "production" in result.output + + def test_load_region_dry_run(self, runner, monkeypatch, tmp_path): + from scm_cli.commands.objects import load_region + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "create_region", lambda *a, **k: pytest.fail("SDK must not be called in dry-run")) + + yaml_content = "regions:\n - name: US-South\n folder: Texas\n" + test_file = tmp_path / "regions.yml" + test_file.write_text(yaml_content) + + test_app = typer.Typer() + test_app.command()(load_region) + + result = runner.invoke(test_app, ["--file", str(test_file), "--dry-run"]) + + assert result.exit_code == 0 + assert "Dry run" in result.output + assert "US-South" in result.output + + def test_load_schedule_dry_run(self, runner, monkeypatch, tmp_path): + from scm_cli.commands.objects import load_schedule + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "create_schedule", lambda *a, **k: pytest.fail("SDK must not be called in dry-run")) + + yaml_content = "schedules:\n - name: business-hours\n folder: Texas\n" + test_file = tmp_path / "schedules.yml" + test_file.write_text(yaml_content) + + test_app = typer.Typer() + test_app.command()(load_schedule) + + result = runner.invoke(test_app, ["--file", str(test_file), "--dry-run"]) + + assert result.exit_code == 0 + assert "Dry run" in result.output + assert "business-hours" in result.output + + def test_load_quarantined_device_dry_run(self, runner, monkeypatch, tmp_path): + from scm_cli.commands.objects import load_quarantined_device + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "create_quarantined_device", lambda *a, **k: pytest.fail("SDK must not be called in dry-run")) + + yaml_content = "quarantined_devices:\n - host_id: host-001\n" + test_file = tmp_path / "quarantined.yml" + test_file.write_text(yaml_content) + + test_app = typer.Typer() + test_app.command()(load_quarantined_device) + + result = runner.invoke(test_app, ["--file", str(test_file), "--dry-run"]) + + assert result.exit_code == 0 + assert "Dry run" in result.output + assert "host-001" in result.output + + class TestLoadConcurrency: """Bulk load commands must apply items through the bounded thread pool (run_bulk).""" diff --git a/tests/test_objects_commands_env.py b/tests/test_objects_commands_env.py index a7203de..6bba01c 100644 --- a/tests/test_objects_commands_env.py +++ b/tests/test_objects_commands_env.py @@ -29,7 +29,7 @@ def test_set_address(self, runner, env_name, env): result = runner.invoke( app, - ["set", "object", "address", "--folder", "Shared", "--name", "test-address", "--ip-netmask", "192.168.1.1/32"], + ["set", "object", "address", "test-address", "--folder", "Shared", "--ip-netmask", "192.168.1.1/32"], ) assert result.exit_code == 0 @@ -49,7 +49,7 @@ def test_set_address_with_mock(self, runner, env_name, env): result = runner.invoke( app, - ["set", "object", "address", "--folder", "Shared", "--name", "test-address", "--ip-netmask", "192.168.1.1/32"], + ["set", "object", "address", "test-address", "--folder", "Shared", "--ip-netmask", "192.168.1.1/32"], ) assert result.exit_code == 0 @@ -63,7 +63,7 @@ def test_delete_address(self, runner, env_name, env): with patch("scm_cli.commands.objects.scm_client") as mock_client: mock_client.delete_address.return_value = True - result = runner.invoke(app, ["delete", "object", "address", "--folder", "Shared", "--name", "test-address", "--force"]) + result = runner.invoke(app, ["delete", "object", "address", "test-address", "--folder", "Shared", "--force"]) assert result.exit_code == 0 assert "Deleted address" in result.stdout @@ -92,10 +92,9 @@ def test_set_address_group(self, runner, env_name, env): "set", "object", "address-group", + "test-group", "--folder", "Shared", - "--name", - "test-group", "--type", "static", "--description", diff --git a/tests/test_operations_commands.py b/tests/test_operations_commands.py index b4156f3..95409e4 100644 --- a/tests/test_operations_commands.py +++ b/tests/test_operations_commands.py @@ -95,7 +95,7 @@ class TestOperationsStatus: """Test operations status command.""" def test_status_check(self, runner, mock_ops_env): - result = runner.invoke(app, ["operations", "status", "--job-id", "job-abc"]) + result = runner.invoke(app, ["operations", "status", "--id", "job-abc"]) if result.exit_code != 0: print(f"Output: {result.output}") print(f"Exception: {result.exception}") @@ -105,7 +105,7 @@ def test_status_check(self, runner, mock_ops_env): def test_status_output_json(self, mock_ops_env): """operations status --output json emits machine-readable data on stdout.""" - result = CliRunner(mix_stderr=False).invoke(app, ["operations", "status", "--job-id", "job-abc", "--output", "json"]) + result = CliRunner(mix_stderr=False).invoke(app, ["operations", "status", "--id", "job-abc", "--output", "json"]) assert result.exit_code == 0 data = json.loads(result.stdout) assert data["job_id"] == "job-abc" diff --git a/tests/test_security_commands.py b/tests/test_security_commands.py index 75068fc..248e5c9 100644 --- a/tests/test_security_commands.py +++ b/tests/test_security_commands.py @@ -103,7 +103,6 @@ def mock_create(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-rule", "--source-zones", "trust", @@ -154,7 +153,6 @@ def mock_create_error(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-rule", "--source-zones", "trust", @@ -189,7 +187,6 @@ def mock_delete(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-rule", "--force", ], @@ -220,7 +217,6 @@ def mock_delete_error(*args, **kwargs): [ "--folder", "test-folder", - "--name", "test-rule", "--force", ], @@ -311,7 +307,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "test-folder", "--name", "test-rule", "--source-zones", "trust", "--destination-zones", "untrust", "--action", "allow"], + ["--folder", "test-folder", "test-rule", "--source-zones", "trust", "--destination-zones", "untrust", "--action", "allow"], ) assert result.exit_code == 0 @@ -335,7 +331,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "test-folder", "--name", "test-rule", "--source-zones", "trust", "--destination-zones", "untrust", "--action", "allow"], + ["--folder", "test-folder", "test-rule", "--source-zones", "trust", "--destination-zones", "untrust", "--action", "allow"], ) assert result.exit_code == 0 @@ -373,7 +369,6 @@ def test_set_wildfire_antivirus_profile_command(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "wf-test", "--description", "Test WildFire profile", @@ -403,7 +398,6 @@ def test_set_wildfire_antivirus_profile_with_rules_json(self, runner, monkeypatc [ "--folder", "Texas", - "--name", "wf-custom", "--rules", rules, @@ -426,7 +420,6 @@ def test_set_wildfire_antivirus_profile_error(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "wf-test", ], ) @@ -447,7 +440,6 @@ def test_delete_wildfire_antivirus_profile_command(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "wf-test", "--force", ], @@ -508,7 +500,7 @@ def test_show_wildfire_antivirus_profile_single(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_wildfire_antivirus_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "WF Test"]) + result = runner.invoke(test_app, ["--folder", "Texas", "WF Test"]) assert result.exit_code == 0 assert "WF Test" in result.stdout @@ -647,7 +639,7 @@ def test_set_wildfire_antivirus_profile_updated(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "wf-test", "--description", "Test"], + ["--folder", "Texas", "wf-test", "--description", "Test"], ) assert result.exit_code == 0 @@ -667,7 +659,7 @@ def test_set_wildfire_antivirus_profile_no_change(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "wf-test", "--description", "Test"], + ["--folder", "Texas", "wf-test", "--description", "Test"], ) assert result.exit_code == 0 @@ -706,7 +698,6 @@ def test_set_dns_security_profile_command(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "dns-sec-test", "--description", "Test DNS security profile", @@ -732,7 +723,6 @@ def test_set_dns_security_profile_error(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "dns-sec-test", ], ) @@ -753,7 +743,6 @@ def test_delete_dns_security_profile_command(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "dns-sec-test", "--force", ], @@ -822,7 +811,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-vuln-profile", "--description", "Test vulnerability protection", @@ -858,7 +846,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Texas", - "--name", "strict-vuln", "--block-critical-high", ], @@ -887,7 +874,6 @@ def mock_create_error(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-vuln", ], ) @@ -912,7 +898,6 @@ def mock_delete(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-vuln", "--force", ], @@ -939,7 +924,6 @@ def mock_delete_error(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-vuln", "--force", ], @@ -980,7 +964,6 @@ def mock_get(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-vuln", ], ) @@ -1123,7 +1106,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-vuln-profile", "--description", "Test"], + ["--folder", "Texas", "test-vuln-profile", "--description", "Test"], ) assert result.exit_code == 0 @@ -1147,7 +1130,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "test-vuln-profile", "--description", "Test"], + ["--folder", "Texas", "test-vuln-profile", "--description", "Test"], ) assert result.exit_code == 0 @@ -1180,7 +1163,7 @@ def test_set_anti_spyware_profile_updated(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "strict-security"], + ["--folder", "Texas", "strict-security"], ) assert result.exit_code == 0 @@ -1200,7 +1183,7 @@ def test_set_anti_spyware_profile_no_change(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "strict-security"], + ["--folder", "Texas", "strict-security"], ) assert result.exit_code == 0 @@ -1233,7 +1216,7 @@ def test_set_decryption_profile_updated(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "ssl-forward", "--ssl-forward-proxy", '{"block_expired_certificate": true}'], + ["--folder", "Texas", "ssl-forward", "--ssl-forward-proxy", '{"block_expired_certificate": true}'], ) assert result.exit_code == 0 @@ -1253,7 +1236,7 @@ def test_set_decryption_profile_no_change(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "ssl-forward", "--ssl-forward-proxy", '{"block_expired_certificate": true}'], + ["--folder", "Texas", "ssl-forward", "--ssl-forward-proxy", '{"block_expired_certificate": true}'], ) assert result.exit_code == 0 @@ -1287,7 +1270,6 @@ def mock_create(*args, **kwargs): [ "--folder", "Texas", - "--name", "custom-block", "--url", "malware.example.com", @@ -1317,7 +1299,6 @@ def mock_create_error(*args, **kwargs): [ "--folder", "Texas", - "--name", "test-category", ], ) @@ -1342,7 +1323,6 @@ def mock_delete(*args, **kwargs): [ "--folder", "Texas", - "--name", "custom-block", "--force", ], @@ -1407,7 +1387,6 @@ def mock_get(*args, **kwargs): [ "--folder", "Texas", - "--name", "Custom-Block-List", ], ) @@ -1451,7 +1430,6 @@ def test_set_app_override_rule(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "override-https", "--application", "ssl", @@ -1479,7 +1457,6 @@ def test_set_app_override_rule_error(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "test", "--application", "ssl", @@ -1500,7 +1477,7 @@ def test_delete_app_override_rule(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_app_override_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "override-https", "--force"]) + result = runner.invoke(test_app, ["--folder", "Texas", "override-https", "--force"]) assert result.exit_code == 0 assert "Deleted app override rule" in result.stdout @@ -1535,7 +1512,7 @@ def test_show_app_override_rule_single(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_app_override_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "override-https"]) + result = runner.invoke(test_app, ["--folder", "Texas", "override-https"]) assert result.exit_code == 0 assert "App Override Rule" in result.stdout @@ -1556,7 +1533,7 @@ def test_set_app_override_rule_updated(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "override-https", "--application", "ssl", "--port", "8443", "--protocol", "tcp"], + ["--folder", "Texas", "override-https", "--application", "ssl", "--port", "8443", "--protocol", "tcp"], ) assert result.exit_code == 0 @@ -1576,7 +1553,7 @@ def test_set_app_override_rule_no_change(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "override-https", "--application", "ssl", "--port", "8443", "--protocol", "tcp"], + ["--folder", "Texas", "override-https", "--application", "ssl", "--port", "8443", "--protocol", "tcp"], ) assert result.exit_code == 0 @@ -1608,7 +1585,7 @@ def test_set_authentication_rule(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(set_authentication_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "auth-web"]) + result = runner.invoke(test_app, ["--folder", "Texas", "auth-web"]) assert result.exit_code == 0 assert "Created authentication rule" in result.stdout @@ -1621,7 +1598,7 @@ def test_set_authentication_rule_error(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(set_authentication_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "test"]) + result = runner.invoke(test_app, ["--folder", "Texas", "test"]) assert result.exit_code == 1 @@ -1633,7 +1610,7 @@ def test_delete_authentication_rule(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_authentication_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "auth-web", "--force"]) + result = runner.invoke(test_app, ["--folder", "Texas", "auth-web", "--force"]) assert result.exit_code == 0 assert "Deleted authentication rule" in result.stdout @@ -1668,7 +1645,7 @@ def test_show_authentication_rule_single(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_authentication_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "auth-web"]) + result = runner.invoke(test_app, ["--folder", "Texas", "auth-web"]) assert result.exit_code == 0 assert "Authentication Rule: auth-web" in result.stdout @@ -1685,7 +1662,7 @@ def test_set_authentication_rule_updated(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(set_authentication_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "auth-web"]) + result = runner.invoke(test_app, ["--folder", "Texas", "auth-web"]) assert result.exit_code == 0 assert "Updated authentication rule" in result.stdout @@ -1702,7 +1679,7 @@ def test_set_authentication_rule_no_change(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(set_authentication_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "auth-web"]) + result = runner.invoke(test_app, ["--folder", "Texas", "auth-web"]) assert result.exit_code == 0 assert "No changes needed" in result.stdout @@ -1739,7 +1716,6 @@ def test_set_decryption_rule(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "no-decrypt-internal", "--action", "no-decrypt", @@ -1762,7 +1738,6 @@ def test_set_decryption_rule_error(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "test", "--action", "decrypt", @@ -1779,7 +1754,7 @@ def test_delete_decryption_rule(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_decryption_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "decrypt-web", "--force"]) + result = runner.invoke(test_app, ["--folder", "Texas", "decrypt-web", "--force"]) assert result.exit_code == 0 assert "Deleted decryption rule" in result.stdout @@ -1814,7 +1789,7 @@ def test_show_decryption_rule_single(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_decryption_rule) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "decrypt-outbound"]) + result = runner.invoke(test_app, ["--folder", "Texas", "decrypt-outbound"]) assert result.exit_code == 0 assert "Decryption Rule" in result.stdout @@ -1834,7 +1809,7 @@ def test_set_decryption_rule_updated(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "no-decrypt-internal", "--action", "no-decrypt"], + ["--folder", "Texas", "no-decrypt-internal", "--action", "no-decrypt"], ) assert result.exit_code == 0 @@ -1854,7 +1829,7 @@ def test_set_decryption_rule_no_change(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "no-decrypt-internal", "--action", "no-decrypt"], + ["--folder", "Texas", "no-decrypt-internal", "--action", "no-decrypt"], ) assert result.exit_code == 0 @@ -1891,7 +1866,6 @@ def test_set_url_access_profile(self, runner, monkeypatch): [ "--folder", "Texas", - "--name", "strict-url", "--block", "adult", @@ -1911,7 +1885,7 @@ def test_set_url_access_profile_error(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(set_url_access_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "test"]) + result = runner.invoke(test_app, ["--folder", "Texas", "test"]) assert result.exit_code == 1 @@ -1923,7 +1897,7 @@ def test_delete_url_access_profile(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_url_access_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "strict-url", "--force"]) + result = runner.invoke(test_app, ["--folder", "Texas", "strict-url", "--force"]) assert result.exit_code == 0 assert "Deleted URL access profile" in result.stdout @@ -1957,7 +1931,7 @@ def test_show_url_access_profile_single(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_url_access_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "strict-url"]) + result = runner.invoke(test_app, ["--folder", "Texas", "strict-url"]) assert result.exit_code == 0 assert "URL Access Profile" in result.stdout @@ -1977,7 +1951,7 @@ def test_set_url_access_profile_updated(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "strict-url", "--block", "adult", "--block", "malware"], + ["--folder", "Texas", "strict-url", "--block", "adult", "--block", "malware"], ) assert result.exit_code == 0 @@ -1997,7 +1971,7 @@ def test_set_url_access_profile_no_change(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "strict-url", "--block", "adult", "--block", "malware"], + ["--folder", "Texas", "strict-url", "--block", "adult", "--block", "malware"], ) assert result.exit_code == 0 @@ -2036,7 +2010,7 @@ def mock_move(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Allow Web", "--destination", "top"], + ["--folder", "Texas", "Allow Web", "--destination", "top"], ) assert result.exit_code == 0 @@ -2058,7 +2032,7 @@ def mock_move(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Allow Web", "--destination", "bottom"], + ["--folder", "Texas", "Allow Web", "--destination", "bottom"], ) assert result.exit_code == 0 @@ -2084,7 +2058,6 @@ def mock_move(*args, **kwargs): [ "--folder", "Texas", - "--name", "Allow Web", "--destination", "before", @@ -2116,7 +2089,6 @@ def mock_move(*args, **kwargs): [ "--folder", "Texas", - "--name", "Allow Web", "--destination", "after", @@ -2136,7 +2108,7 @@ def test_move_security_rule_before_missing_reference(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Allow Web", "--destination", "before"], + ["--folder", "Texas", "Allow Web", "--destination", "before"], ) assert result.exit_code == 1 @@ -2149,7 +2121,7 @@ def test_move_security_rule_after_missing_reference(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Allow Web", "--destination", "after"], + ["--folder", "Texas", "Allow Web", "--destination", "after"], ) assert result.exit_code == 1 @@ -2169,7 +2141,7 @@ def mock_move_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Nonexistent", "--destination", "top"], + ["--folder", "Texas", "Nonexistent", "--destination", "top"], ) assert result.exit_code == 1 @@ -2194,7 +2166,6 @@ def mock_move(*args, **kwargs): [ "--folder", "Texas", - "--name", "Allow Web", "--destination", "top", @@ -2224,7 +2195,7 @@ def mock_move(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "override-https", "--destination", "top"], + ["--folder", "Texas", "override-https", "--destination", "top"], ) assert result.exit_code == 0 @@ -2238,7 +2209,7 @@ def test_move_app_override_rule_before_missing_reference(self, runner, monkeypat result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "override-https", "--destination", "before"], + ["--folder", "Texas", "override-https", "--destination", "before"], ) assert result.exit_code == 1 @@ -2258,7 +2229,7 @@ def mock_move_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Nonexistent", "--destination", "top"], + ["--folder", "Texas", "Nonexistent", "--destination", "top"], ) assert result.exit_code == 1 @@ -2282,7 +2253,7 @@ def mock_move(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "auth-rule", "--destination", "bottom"], + ["--folder", "Texas", "auth-rule", "--destination", "bottom"], ) assert result.exit_code == 0 @@ -2296,7 +2267,7 @@ def test_move_authentication_rule_after_missing_reference(self, runner, monkeypa result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "auth-rule", "--destination", "after"], + ["--folder", "Texas", "auth-rule", "--destination", "after"], ) assert result.exit_code == 1 @@ -2316,7 +2287,7 @@ def mock_move_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Nonexistent", "--destination", "top"], + ["--folder", "Texas", "Nonexistent", "--destination", "top"], ) assert result.exit_code == 1 @@ -2340,7 +2311,7 @@ def mock_move(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "decrypt-rule", "--destination", "top"], + ["--folder", "Texas", "decrypt-rule", "--destination", "top"], ) assert result.exit_code == 0 @@ -2366,7 +2337,6 @@ def mock_move(*args, **kwargs): [ "--folder", "Texas", - "--name", "decrypt-rule", "--destination", "before", @@ -2386,7 +2356,7 @@ def test_move_decryption_rule_before_missing_reference(self, runner, monkeypatch result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "decrypt-rule", "--destination", "before"], + ["--folder", "Texas", "decrypt-rule", "--destination", "before"], ) assert result.exit_code == 1 @@ -2406,7 +2376,7 @@ def mock_move_error(*args, **kwargs): result = runner.invoke( test_app, - ["--folder", "Texas", "--name", "Nonexistent", "--destination", "top"], + ["--folder", "Texas", "Nonexistent", "--destination", "top"], ) assert result.exit_code == 1 @@ -2421,7 +2391,7 @@ def test_move_security_rule_no_location(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--name", "Allow Web", "--destination", "top"], + ["Allow Web", "--destination", "top"], ) assert result.exit_code == 1 @@ -2489,7 +2459,7 @@ def test_show_single_output_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_wildfire_antivirus_profile) - result = runner.invoke(test_app, ["--folder", "Texas", "--name", "wf-test", "--output", "json"]) + result = runner.invoke(test_app, ["--folder", "Texas", "wf-test", "--output", "json"]) assert result.exit_code == 0 assert json.loads(result.stdout) == sample @@ -2506,3 +2476,83 @@ def test_show_list_empty_output_json(self, runner, monkeypatch): assert result.exit_code == 0 assert json.loads(result.stdout) == [] + + +class TestV2GrammarSecurity: + """v2 grammar behaviors: container enforcement, --max-results, snippet passthrough.""" + + def _mock_scm_client(self, monkeypatch): + from unittest.mock import MagicMock + + import scm_cli.commands.security as sec_module + + mock_client = MagicMock() + monkeypatch.setattr(sec_module, "scm_client", mock_client) + return mock_client + + def test_set_requires_exactly_one_container_none(self, runner, monkeypatch): + """A set command with no container exits non-zero with a clear error.""" + self._mock_scm_client(monkeypatch) + + test_app = typer.Typer() + test_app.command()(set_url_category) + + result = runner.invoke(test_app, ["custom-block", "--url", "bad.example.com"]) + + assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device must be specified" in result.output + + def test_set_requires_exactly_one_container_two(self, runner, monkeypatch): + """A set command with two containers exits non-zero.""" + self._mock_scm_client(monkeypatch) + + test_app = typer.Typer() + test_app.command()(set_url_category) + + result = runner.invoke( + test_app, + ["custom-block", "--folder", "Texas", "--snippet", "Best-Practice", "--url", "bad.example.com"], + ) + + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device can be specified" in result.output + + def test_show_max_results_slices_list(self, runner, monkeypatch): + """--max-results slices the list output client-side.""" + mock_client = self._mock_scm_client(monkeypatch) + mock_client.list_url_categories.return_value = [{"name": f"cat-{i}", "folder": "Texas", "type": "URL List"} for i in range(5)] + + test_app = typer.Typer() + test_app.command()(show_url_category) + + result = runner.invoke(test_app, ["--folder", "Texas", "--output", "json", "--max-results", "2"]) + + assert result.exit_code == 0 + data = json.loads(result.stdout) + assert len(data) == 2 + assert [item["name"] for item in data] == ["cat-0", "cat-1"] + + def test_set_security_rule_snippet_reaches_client(self, runner, monkeypatch): + """--snippet on the previously folder-only security rule set reaches scm_client.""" + mock_client = self._mock_scm_client(monkeypatch) + mock_client.create_security_rule.return_value = { + "id": "sr-1", + "name": "snippet-rule", + "snippet": "Best-Practice", + "__action__": "created", + } + + test_app = typer.Typer() + test_app.command()(set_security_rule) + + result = runner.invoke( + test_app, + ["snippet-rule", "--snippet", "Best-Practice", "--source-zones", "trust", "--destination-zones", "untrust", "--action", "allow"], + ) + + assert result.exit_code == 0 + assert "Created security rule" in result.stdout + kwargs = mock_client.create_security_rule.call_args.kwargs + assert kwargs["snippet"] == "Best-Practice" + assert "folder" not in kwargs + assert kwargs["name"] == "snippet-rule" diff --git a/tests/test_security_commands_env.py b/tests/test_security_commands_env.py index ab01261..db1b004 100644 --- a/tests/test_security_commands_env.py +++ b/tests/test_security_commands_env.py @@ -35,7 +35,6 @@ def test_set_security_rule(self, runner, env_name, env): "rule", "--folder", "Shared", - "--name", "test-rule", "--source-zones", "trust", @@ -76,7 +75,6 @@ def test_set_security_rule_with_mock(self, runner, env_name, env): "rule", "--folder", "Shared", - "--name", "test-rule", "--source-zones", "trust", @@ -98,7 +96,7 @@ def test_delete_security_rule(self, runner, env_name, env): with patch("scm_cli.commands.security.scm_client") as mock_client: mock_client.delete_security_rule.return_value = True - result = runner.invoke(app, ["delete", "security", "rule", "--folder", "Shared", "--name", "test-rule", "--force"]) + result = runner.invoke(app, ["delete", "security", "rule", "--folder", "Shared", "test-rule", "--force"]) assert result.exit_code == 0 assert "Deleted" in result.stdout diff --git a/tests/test_setup_commands.py b/tests/test_setup_commands.py index 63127d0..bebce07 100644 --- a/tests/test_setup_commands.py +++ b/tests/test_setup_commands.py @@ -71,7 +71,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "Texas", "--parent", "All", "--description", "Texas offices"], + ["Texas", "--parent", "All", "--description", "Texas offices"], ) assert result.exit_code == 0 @@ -109,7 +109,7 @@ def mock_get(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_folder) - result = runner.invoke(test_app, ["--name", "Texas"]) + result = runner.invoke(test_app, ["Texas"]) assert result.exit_code == 0 assert "Texas" in result.stdout @@ -123,7 +123,7 @@ def test_delete_folder_command(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_folder) - result = runner.invoke(test_app, ["--name", "Texas", "--force"]) + result = runner.invoke(test_app, ["Texas", "--force"]) assert result.exit_code == 0 assert "Deleted folder" in result.stdout @@ -146,7 +146,7 @@ def mock_delete(*args, **kwargs): test_app = typer.Typer() test_app.command()(delete_folder) - result = runner.invoke(test_app, ["--name", "Texas", "--force"]) + result = runner.invoke(test_app, ["Texas", "--force"]) assert result.exit_code == 1 assert "Cannot delete folder 'Texas'" in result.output @@ -174,7 +174,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "production", "--description", "Prod environment"], + ["production", "--description", "Prod environment"], ) assert result.exit_code == 0 @@ -207,7 +207,7 @@ def test_delete_label_command(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_label) - result = runner.invoke(test_app, ["--name", "staging", "--force"]) + result = runner.invoke(test_app, ["staging", "--force"]) assert result.exit_code == 0 assert "Deleted label" in result.stdout @@ -234,7 +234,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "DNS-Best-Practice", "--description", "DNS config"], + ["DNS-Best-Practice", "--description", "DNS config"], ) assert result.exit_code == 0 @@ -267,7 +267,7 @@ def test_delete_snippet_command(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_snippet) - result = runner.invoke(test_app, ["--name", "Web-Security", "--force"]) + result = runner.invoke(test_app, ["Web-Security", "--force"]) assert result.exit_code == 0 assert "Deleted snippet" in result.stdout @@ -296,7 +296,7 @@ def mock_create(*args, **kwargs): result = runner.invoke( test_app, - ["--name", "$egress-max", "--type", "egress-max", "--value", "1000", "--folder", "Texas"], + ["$egress-max", "--type", "egress-max", "--value", "1000", "--folder", "Texas"], ) assert result.exit_code == 0 @@ -328,7 +328,7 @@ def test_delete_variable_command(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(delete_variable) - result = runner.invoke(test_app, ["--name", "$egress-max", "--folder", "Texas", "--force"]) + result = runner.invoke(test_app, ["$egress-max", "--folder", "Texas", "--force"]) assert result.exit_code == 0 assert "Deleted variable" in result.stdout @@ -343,11 +343,96 @@ def test_variable_requires_container(self, runner, monkeypatch): result = runner.invoke( test_app, - ["--name", "$test", "--type", "fqdn", "--value", "example.com"], + ["$test", "--type", "fqdn", "--value", "example.com"], ) # Should fail because no container is specified assert result.exit_code != 0 + assert "One of --folder, --snippet, or --device" in result.output + + def test_variable_rejects_multiple_containers(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + def mock_create(*a, **kw): + pytest.fail("create_variable must not be called with two containers") + + monkeypatch.setattr(scm_client, "create_variable", mock_create) + + test_app = typer.Typer() + test_app.command()(set_variable) + + result = runner.invoke( + test_app, + ["$test", "--type", "fqdn", "--value", "example.com", "--folder", "Texas", "--snippet", "DNS-Config"], + ) + + # Should fail because two containers are specified + assert result.exit_code != 0 + assert "Only one of --folder, --snippet, or --device" in result.output + + def test_set_variable_snippet_container_reaches_client(self, runner, monkeypatch): + """--snippet flows end-to-end to the scm_client call.""" + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_create(*args, **kwargs): + captured.update(kwargs) + return { + "id": "var-123", + "name": kwargs.get("name"), + "type": kwargs.get("type"), + "value": kwargs.get("value"), + "snippet": kwargs.get("snippet"), + "__action__": "created", + } + + monkeypatch.setattr(scm_client, "create_variable", mock_create) + + test_app = typer.Typer() + test_app.command()(set_variable) + + result = runner.invoke( + test_app, + ["$dns-server", "--type", "fqdn", "--value", "dns.example.com", "--snippet", "DNS-Config"], + ) + + assert result.exit_code == 0, result.output + assert "Created variable" in result.stdout + assert captured["snippet"] == "DNS-Config" + assert captured.get("folder") is None + + def test_show_variable_requires_container(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + monkeypatch.setattr(scm_client, "list_variables", lambda *a, **kw: []) + + test_app = typer.Typer() + test_app.command()(show_variable) + + result = runner.invoke(test_app, []) + + assert result.exit_code != 0 + + def test_show_variable_snippet_container_reaches_client(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + captured = {} + + def mock_list(*args, **kwargs): + captured.update(kwargs) + return [{"id": "v1", "name": "$dns-server", "type": "fqdn", "value": "dns.example.com"}] + + monkeypatch.setattr(scm_client, "list_variables", mock_list) + + test_app = typer.Typer() + test_app.command()(show_variable) + + result = runner.invoke(test_app, ["--snippet", "DNS-Config"]) + + assert result.exit_code == 0, result.output + assert "$dns-server" in result.stdout + assert captured["snippet"] == "DNS-Config" class TestSetupValidators: @@ -509,7 +594,7 @@ def mock_get(*args, **kwargs): test_app = typer.Typer() test_app.command()(show_device) - result = runner.invoke(test_app, ["--name", "PA-VM-01"]) + result = runner.invoke(test_app, ["PA-VM-01"]) assert result.exit_code == 0 assert "PA-VM-01" in result.stdout @@ -536,7 +621,7 @@ def mock_update(**kwargs): result = runner.invoke( test_app, - ["--name", "PA-VM-01", "--labels", "production", "--labels", "west"], + ["PA-VM-01", "--labels", "production", "--labels", "west"], ) assert result.exit_code == 0, result.stdout @@ -561,7 +646,6 @@ def mock_update(**kwargs): result = runner.invoke( test_app, [ - "--name", "PA-VM-01", "--display-name", "Edge-FW", @@ -594,7 +678,7 @@ def mock_update(**kwargs): test_app = typer.Typer() test_app.command()(set_device) - result = runner.invoke(test_app, ["--name", "PA-VM-01", "--labels", "production"]) + result = runner.invoke(test_app, ["PA-VM-01", "--labels", "production"]) assert result.exit_code == 0, result.stdout assert "No changes detected" in result.stdout @@ -610,7 +694,7 @@ def mock_update(**kwargs): test_app = typer.Typer() test_app.command()(set_device) - result = runner.invoke(test_app, ["--name", "missing", "--labels", "x"]) + result = runner.invoke(test_app, ["missing", "--labels", "x"]) assert result.exit_code != 0 assert "not found" in result.output @@ -638,7 +722,7 @@ def mock_get(name): test_app = typer.Typer() test_app.command()(show_device) - result = runner.invoke(test_app, ["--name", "PA-VM-01", "--output", "json"]) + result = runner.invoke(test_app, ["PA-VM-01", "--output", "json"]) assert result.exit_code == 0, result.stdout data = json.loads(result.stdout) @@ -819,7 +903,7 @@ def test_show_folder_by_name_output_json(self, runner, monkeypatch): test_app = typer.Typer() test_app.command()(show_folder) - result = runner.invoke(test_app, ["--name", "Texas", "--output", "json"]) + result = runner.invoke(test_app, ["Texas", "--output", "json"]) assert result.exit_code == 0, result.output assert json.loads(result.stdout) == record @@ -873,3 +957,35 @@ def mock_create(**kwargs): assert active["max"] > 1, "create calls never overlapped" assert sorted(created) == [f"label-{i}" for i in range(4)] assert "Processed 4 labels" in result.output + + +class TestMaxResults: + """--max-results slices list output client-side before emit.""" + + def test_show_folder_max_results(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + folders = [{"id": f"f{i}", "name": f"folder-{i}", "parent": "All"} for i in range(5)] + monkeypatch.setattr(scm_client, "list_folders", lambda *a, **kw: folders) + + test_app = typer.Typer() + test_app.command()(show_folder) + + result = runner.invoke(test_app, ["--max-results", "2", "--output", "json"]) + + assert result.exit_code == 0, result.output + assert json.loads(result.stdout) == folders[:2] + + def test_show_variable_max_results(self, runner, monkeypatch): + from scm_cli.utils.sdk_client import scm_client + + variables = [{"id": f"v{i}", "name": f"$var-{i}", "type": "fqdn", "value": f"host{i}.example.com"} for i in range(4)] + monkeypatch.setattr(scm_client, "list_variables", lambda *a, **kw: variables) + + test_app = typer.Typer() + test_app.command()(show_variable) + + result = runner.invoke(test_app, ["--folder", "Texas", "--max-results", "3", "--output", "json"]) + + assert result.exit_code == 0, result.output + assert json.loads(result.stdout) == variables[:3]