proxcli 0.2.1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {proxcli-0.2.1 → proxcli-0.4.0}/.github/workflows/ci.yml +22 -3
- proxcli-0.4.0/AGENTS.md +123 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/CHANGELOG.md +22 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/PKG-INFO +1 -1
- proxcli-0.4.0/proxmox/cli/cluster.py +239 -0
- proxcli-0.4.0/proxmox/cli/container.py +316 -0
- proxcli-0.4.0/proxmox/cli/firewall_helpers.py +60 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/cli/main.py +16 -2
- proxcli-0.4.0/proxmox/cli/node.py +177 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/cli/vm.py +159 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/pyproject.toml +1 -1
- {proxcli-0.2.1 → proxcli-0.4.0}/uv.lock +2 -2
- proxcli-0.2.1/proxmox/cli/cluster.py +0 -21
- proxcli-0.2.1/proxmox/cli/container.py +0 -157
- proxcli-0.2.1/proxmox/cli/node.py +0 -55
- {proxcli-0.2.1 → proxcli-0.4.0}/.env.example +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/.gitignore +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/.python-version +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/PLAN.md +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/PROJECT.md +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/PROMPT.md +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/README.md +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/TODO.md +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/cli/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/cli/auth.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/cli/storage.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/cli/tasks.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/client/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/client/auth.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/client/client.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/client/exceptions.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/config/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/config/config.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/config/models.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/output/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/output/formatter.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/output/json_fmt.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/output/table_fmt.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/output/yaml_fmt.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/utils/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/utils/helpers.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/proxmox/utils/logging.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/conftest.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_auth.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_cli/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_cli/test_main.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_client.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_config.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_integration/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_output/__init__.py +0 -0
- {proxcli-0.2.1 → proxcli-0.4.0}/tests/test_output/test_formatter.py +0 -0
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
steps:
|
|
13
13
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
14
14
|
|
|
15
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
16
16
|
with:
|
|
17
17
|
enable-cache: true
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
steps:
|
|
28
28
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
29
29
|
|
|
30
|
-
- uses: astral-sh/setup-uv@
|
|
30
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
31
31
|
with:
|
|
32
32
|
enable-cache: true
|
|
33
33
|
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
steps:
|
|
42
42
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
43
43
|
|
|
44
|
-
- uses: astral-sh/setup-uv@
|
|
44
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
45
45
|
with:
|
|
46
46
|
enable-cache: true
|
|
47
47
|
|
|
@@ -52,3 +52,22 @@ jobs:
|
|
|
52
52
|
with:
|
|
53
53
|
name: dist
|
|
54
54
|
path: dist/
|
|
55
|
+
|
|
56
|
+
publish:
|
|
57
|
+
runs-on: ubuntu-24.04
|
|
58
|
+
if: github.ref == 'refs/heads/main'
|
|
59
|
+
needs: [build]
|
|
60
|
+
environment: pypi
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
63
|
+
|
|
64
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
65
|
+
with:
|
|
66
|
+
enable-cache: true
|
|
67
|
+
|
|
68
|
+
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/
|
|
72
|
+
|
|
73
|
+
- run: uv publish --token "${{ secrets.PYPI_TOKEN }}"
|
proxcli-0.4.0/AGENTS.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Agent Guidelines for proxmox CLI
|
|
2
|
+
|
|
3
|
+
## CLI Command Convention
|
|
4
|
+
|
|
5
|
+
All commands follow a strict **`<resource> <action> [positional_id] [--flags]`** pattern:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
proxmox <resource> <action> [id_or_name] [--options]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Resource-level (top-level subcommands)
|
|
12
|
+
|
|
13
|
+
Every resource is a noun: `vm`, `container`, `node`, `storage`, `cluster`, `task`, `auth`.
|
|
14
|
+
|
|
15
|
+
Each has actions as verbs: `list`, `show`, `create`, `start`, `stop`, `delete`, etc.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
proxmox vm list
|
|
19
|
+
proxmox vm show 100
|
|
20
|
+
proxmox vm start 100
|
|
21
|
+
proxmox node show pve01
|
|
22
|
+
proxmox container list
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Nested resources (firewall, etc.)
|
|
26
|
+
|
|
27
|
+
When a resource has sub-resources, use the same **`<resource> <action> <subresource> [subaction]`** pattern:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
proxmox cluster firewall rules # list (shorthand)
|
|
31
|
+
proxmox cluster firewall rules list # list (explicit)
|
|
32
|
+
proxmox cluster firewall rules add --action ACCEPT ...
|
|
33
|
+
proxmox cluster firewall rules show <pos>
|
|
34
|
+
proxmox cluster firewall rules delete <pos>
|
|
35
|
+
proxmox vm firewall rules list <vmid>
|
|
36
|
+
proxmox vm firewall rules add <vmid> --action ACCEPT ...
|
|
37
|
+
proxmox node firewall rules list <node_name>
|
|
38
|
+
proxmox node firewall rules add <node_name> --action ACCEPT ...
|
|
39
|
+
proxmox cluster firewall aliases # list (shorthand)
|
|
40
|
+
proxmox cluster firewall aliases add <name> --cidr ...
|
|
41
|
+
proxmox cluster firewall ipsets # list (shorthand)
|
|
42
|
+
proxmox cluster firewall ipsets add <name> ...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Key rules
|
|
46
|
+
|
|
47
|
+
1. **Nouns before verbs** — `proxmox vm firewall rules list`, NOT `proxmox vm firewall list-rules`.
|
|
48
|
+
2. **Resource identifiers are positional arguments**, placed after the action verb:
|
|
49
|
+
- `proxmox vm show 100` (vmid is positional)
|
|
50
|
+
- `proxmox node show pve01` (node_name is positional)
|
|
51
|
+
- `proxmox vm firewall rules show 100 3` (vmid then pos)
|
|
52
|
+
3. **`--node` is always an optional flag** for VM/container commands (auto-detected if omitted), except on `create` where it's required.
|
|
53
|
+
4. **No shorthand noun squeezing** — `proxmox vm fw` is NOT acceptable. Always use full resource names.
|
|
54
|
+
5. **Subcommands inherit `--flags` from parents** via `set_defaults(func=handler)`. Each handler function receives the merged `Namespace`.
|
|
55
|
+
|
|
56
|
+
## Parser Registration
|
|
57
|
+
|
|
58
|
+
Each CLI module has a `register_<resource>_parser(subparsers)` function that adds subparsers to the passed-in `_SubParsersAction`. Example:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
def register_vm_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
62
|
+
vm_parser = subparsers.add_parser("vm", help="Manage QEMU virtual machines")
|
|
63
|
+
vm_sub = vm_parser.add_subparsers(dest="action", title="actions", required=True)
|
|
64
|
+
|
|
65
|
+
vm_list = vm_sub.add_parser("list", help="List virtual machines")
|
|
66
|
+
vm_list.add_argument("--node", help="Filter by node name")
|
|
67
|
+
vm_list.set_defaults(func=_vm_list)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For nested resources (like firewall), use a second `dest` name to track the sub-resource:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
fw = vm_sub.add_parser("firewall", help="Manage VM firewall")
|
|
74
|
+
fw_sub = fw.add_subparsers(dest="fw_resource", title="resources", required=True)
|
|
75
|
+
|
|
76
|
+
rules = fw_sub.add_parser("rules", help="Manage VM firewall rules")
|
|
77
|
+
rules_sub = rules.add_subparsers(dest="fw_action", title="rule actions", required=False)
|
|
78
|
+
rules_list = rules_sub.add_parser("list", help="List rules")
|
|
79
|
+
rules_list.add_argument("vmid", type=vmid_type, help="VM ID")
|
|
80
|
+
rules_list.set_defaults(func=_vm_fw_rules)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Handler Functions
|
|
84
|
+
|
|
85
|
+
Every handler has the signature:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
def _handler(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `args` contains all parsed arguments (global + resource-specific)
|
|
92
|
+
- `client` is the authenticated `ProxmoxClient` instance
|
|
93
|
+
- Returns a dict or list that will be formatted by the output system
|
|
94
|
+
- For errors, return `{"error": "message"}` dict
|
|
95
|
+
|
|
96
|
+
## Shared Helpers
|
|
97
|
+
|
|
98
|
+
Shared argument definitions go in helper modules (e.g., `firewall_helpers.py`). They export two functions:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
def add_firewall_rule_args(parser: argparse.ArgumentParser) -> None:
|
|
102
|
+
"""Add common rule-form arguments to a parser."""
|
|
103
|
+
|
|
104
|
+
def build_rule_data(args: argparse.Namespace) -> dict[str, Any]:
|
|
105
|
+
"""Convert parsed args into the POST/PUT body dict."""
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Testing
|
|
109
|
+
|
|
110
|
+
- Unit tests use `pytest-httpx` to mock API responses
|
|
111
|
+
- Integration tests use `subprocess.run` to invoke the CLI binary
|
|
112
|
+
- Dry-run tests verify the URL, method, and body without real network calls
|
|
113
|
+
|
|
114
|
+
## Versioning
|
|
115
|
+
|
|
116
|
+
The version string is read from `importlib.metadata.version('proxcli')` — never hardcoded in source.
|
|
117
|
+
|
|
118
|
+
## PyPI
|
|
119
|
+
|
|
120
|
+
- Package name: `proxcli`
|
|
121
|
+
- CLI binary: `proxmox`
|
|
122
|
+
- Publish via `uv publish --token $PYPI_TOKEN`
|
|
123
|
+
- CI auto-publishes on push to `main` (after lint + test + build)
|
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-06-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Container firewall management: options, enable/disable, policy, rules (CRUD), refs.
|
|
14
|
+
Uses `/nodes/{node}/lxc/{vmid}/firewall/*` endpoints.
|
|
15
|
+
|
|
16
|
+
## [0.3.0] - 2026-06-20
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Cluster firewall management: options, enable/disable, policy, rules (CRUD), aliases, ipsets (with CIDR management), refs.
|
|
20
|
+
- Node firewall management: options, enable/disable, policy, rules (CRUD), refs.
|
|
21
|
+
- VM firewall management: options, enable/disable, policy, rules (CRUD), refs.
|
|
22
|
+
- Shared `firewall_helpers.py` for consistent rule argument building across all levels.
|
|
23
|
+
- CI `publish` job: auto-publishes to PyPI on push to main (uses `PYPI_TOKEN` repo secret with `environment: pypi`).
|
|
24
|
+
- `AGENTS.md` with CLI convention and contribution guidelines.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Removed `.env` file with PyPI token; now uses GitHub Actions secrets.
|
|
28
|
+
- Firewall subcommands refactored to consistent `<resource> <action> <subresource> [subaction]` pattern.
|
|
29
|
+
|
|
10
30
|
## [0.2.1] - 2026-06-20
|
|
11
31
|
|
|
12
32
|
### Fixed
|
|
@@ -41,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
61
|
- CSRF ticket auto-refresh on 401.
|
|
42
62
|
- AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
|
|
43
63
|
|
|
64
|
+
[0.4.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.4.0
|
|
65
|
+
[0.3.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.3.0
|
|
44
66
|
[0.2.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.2.1
|
|
45
67
|
[0.2.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.2.0
|
|
46
68
|
[0.1.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.1.1
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""`proxmox cluster` subcommand — cluster management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from proxmox.cli.firewall_helpers import add_firewall_rule_args, build_rule_data
|
|
8
|
+
from proxmox.client.client import ProxmoxClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_cluster_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
12
|
+
"""Register the `proxmox cluster` subcommand tree."""
|
|
13
|
+
cl_parser = subparsers.add_parser("cluster", help="Manage Proxmox cluster")
|
|
14
|
+
cl_sub = cl_parser.add_subparsers(dest="action", title="actions", required=True)
|
|
15
|
+
|
|
16
|
+
# --- cluster status ---
|
|
17
|
+
cl_status = cl_sub.add_parser("status", help="Show cluster status")
|
|
18
|
+
cl_status.set_defaults(func=_cl_status)
|
|
19
|
+
|
|
20
|
+
# --- firewall ---
|
|
21
|
+
fw = cl_sub.add_parser("firewall", help="Manage cluster firewall")
|
|
22
|
+
fw_sub = fw.add_subparsers(dest="fw_resource", title="resources", required=True)
|
|
23
|
+
|
|
24
|
+
# firewall options
|
|
25
|
+
fw_opts = fw_sub.add_parser("options", help="Show cluster firewall options")
|
|
26
|
+
fw_opts.set_defaults(func=_cl_fw_options)
|
|
27
|
+
|
|
28
|
+
fw_opts_set = fw_sub.add_parser("enable", help="Enable cluster firewall")
|
|
29
|
+
fw_opts_set.set_defaults(func=_cl_fw_enable)
|
|
30
|
+
|
|
31
|
+
fw_opts_disable = fw_sub.add_parser("disable", help="Disable cluster firewall")
|
|
32
|
+
fw_opts_disable.set_defaults(func=_cl_fw_disable)
|
|
33
|
+
|
|
34
|
+
fw_policy = fw_sub.add_parser("policy", help="Set default firewall policy")
|
|
35
|
+
fw_policy.add_argument("--in-policy", choices=["ACCEPT", "DENY", "REJECT"], default=None,
|
|
36
|
+
help="Default input policy")
|
|
37
|
+
fw_policy.add_argument("--out-policy", choices=["ACCEPT", "DENY", "REJECT"], default=None,
|
|
38
|
+
help="Default output policy")
|
|
39
|
+
fw_policy.set_defaults(func=_cl_fw_policy)
|
|
40
|
+
|
|
41
|
+
# firewall rules
|
|
42
|
+
rules = fw_sub.add_parser("rules", help="Manage cluster firewall rules")
|
|
43
|
+
rules.set_defaults(func=_cl_fw_rules)
|
|
44
|
+
rules_sub = rules.add_subparsers(dest="fw_action", title="rule actions", required=False)
|
|
45
|
+
|
|
46
|
+
rules_list = rules_sub.add_parser("list", help="List rules")
|
|
47
|
+
rules_list.set_defaults(func=_cl_fw_rules)
|
|
48
|
+
|
|
49
|
+
rules_add = rules_sub.add_parser("add", help="Add a rule")
|
|
50
|
+
add_firewall_rule_args(rules_add)
|
|
51
|
+
rules_add.set_defaults(func=_cl_fw_rule_add)
|
|
52
|
+
|
|
53
|
+
rules_show = rules_sub.add_parser("show", help="Show a rule by position")
|
|
54
|
+
rules_show.add_argument("pos", type=int, help="Rule position")
|
|
55
|
+
rules_show.set_defaults(func=_cl_fw_rule_show)
|
|
56
|
+
|
|
57
|
+
rules_upd = rules_sub.add_parser("update", help="Update a rule by position")
|
|
58
|
+
rules_upd.add_argument("pos", type=int, help="Rule position")
|
|
59
|
+
add_firewall_rule_args(rules_upd)
|
|
60
|
+
for action in rules_upd._actions:
|
|
61
|
+
if action.dest == "action":
|
|
62
|
+
action.required = False
|
|
63
|
+
action.default = None
|
|
64
|
+
rules_upd.set_defaults(func=_cl_fw_rule_upd)
|
|
65
|
+
|
|
66
|
+
rules_del = rules_sub.add_parser("delete", help="Delete a rule by position")
|
|
67
|
+
rules_del.add_argument("pos", type=int, help="Rule position")
|
|
68
|
+
rules_del.set_defaults(func=_cl_fw_rule_del)
|
|
69
|
+
|
|
70
|
+
# firewall aliases
|
|
71
|
+
aliases = fw_sub.add_parser("aliases", help="Manage firewall aliases")
|
|
72
|
+
aliases.set_defaults(func=_cl_fw_aliases)
|
|
73
|
+
aliases_sub = aliases.add_subparsers(dest="fw_action", title="alias actions", required=False)
|
|
74
|
+
|
|
75
|
+
aliases_list = aliases_sub.add_parser("list", help="List aliases")
|
|
76
|
+
aliases_list.set_defaults(func=_cl_fw_aliases)
|
|
77
|
+
|
|
78
|
+
aliases_add = aliases_sub.add_parser("add", help="Add an alias")
|
|
79
|
+
aliases_add.add_argument("name", help="Alias name")
|
|
80
|
+
aliases_add.add_argument("--cidr", required=True, help="CIDR notation (e.g. 10.0.0.0/8)")
|
|
81
|
+
aliases_add.add_argument("--comment", default=None, help="Comment / description")
|
|
82
|
+
aliases_add.set_defaults(func=_cl_fw_alias_add)
|
|
83
|
+
|
|
84
|
+
aliases_del = aliases_sub.add_parser("delete", help="Delete an alias")
|
|
85
|
+
aliases_del.add_argument("name", help="Alias name")
|
|
86
|
+
aliases_del.set_defaults(func=_cl_fw_alias_del)
|
|
87
|
+
|
|
88
|
+
# firewall ipsets
|
|
89
|
+
ipsets = fw_sub.add_parser("ipsets", help="Manage firewall ipsets")
|
|
90
|
+
ipsets.set_defaults(func=_cl_fw_ipsets)
|
|
91
|
+
ipsets_sub = ipsets.add_subparsers(dest="fw_action", title="ipset actions", required=False)
|
|
92
|
+
|
|
93
|
+
ipsets_list = ipsets_sub.add_parser("list", help="List ipsets")
|
|
94
|
+
ipsets_list.set_defaults(func=_cl_fw_ipsets)
|
|
95
|
+
|
|
96
|
+
ipsets_add = ipsets_sub.add_parser("add", help="Add an ipset")
|
|
97
|
+
ipsets_add.add_argument("name", help="IPset name")
|
|
98
|
+
ipsets_add.add_argument("--comment", default=None, help="Comment / description")
|
|
99
|
+
ipsets_add.set_defaults(func=_cl_fw_ipset_add)
|
|
100
|
+
|
|
101
|
+
ipsets_show = ipsets_sub.add_parser("show", help="Show ipset contents")
|
|
102
|
+
ipsets_show.add_argument("name", help="IPset name")
|
|
103
|
+
ipsets_show.set_defaults(func=_cl_fw_ipset_show)
|
|
104
|
+
|
|
105
|
+
ipsets_del = ipsets_sub.add_parser("delete", help="Delete an ipset")
|
|
106
|
+
ipsets_del.add_argument("name", help="IPset name")
|
|
107
|
+
ipsets_del.set_defaults(func=_cl_fw_ipset_del)
|
|
108
|
+
|
|
109
|
+
ipsets_add_cidr = ipsets_sub.add_parser("add-cidr", help="Add a CIDR to an ipset")
|
|
110
|
+
ipsets_add_cidr.add_argument("name", help="IPset name")
|
|
111
|
+
ipsets_add_cidr.add_argument("--cidr", required=True, help="CIDR to add")
|
|
112
|
+
ipsets_add_cidr.add_argument("--comment", default=None, help="Comment")
|
|
113
|
+
ipsets_add_cidr.add_argument("--nomatch", action="store_true", help="Exclude match")
|
|
114
|
+
ipsets_add_cidr.set_defaults(func=_cl_fw_ipset_add_cidr)
|
|
115
|
+
|
|
116
|
+
ipsets_del_cidr = ipsets_sub.add_parser("delete-cidr", help="Remove a CIDR from an ipset")
|
|
117
|
+
ipsets_del_cidr.add_argument("name", help="IPset name")
|
|
118
|
+
ipsets_del_cidr.add_argument("--cidr", required=True, help="CIDR to remove")
|
|
119
|
+
ipsets_del_cidr.set_defaults(func=_cl_fw_ipset_del_cidr)
|
|
120
|
+
|
|
121
|
+
# firewall refs
|
|
122
|
+
fw_refs = fw_sub.add_parser("refs", help="List firewall references")
|
|
123
|
+
fw_refs.add_argument("--type", default=None, choices=["alias", "ipset", "group"],
|
|
124
|
+
help="Filter by reference type")
|
|
125
|
+
fw_refs.set_defaults(func=_cl_fw_refs)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Handlers
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def _cl_status(_args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
133
|
+
return client.get("/cluster/status")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# --- Firewall options ---
|
|
137
|
+
|
|
138
|
+
def _cl_fw_options(_args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
139
|
+
return client.get("/cluster/firewall/options")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _cl_fw_enable(_args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
143
|
+
return client.put("/cluster/firewall/options", data={"enable": 1})
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _cl_fw_disable(_args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
147
|
+
return client.put("/cluster/firewall/options", data={"enable": 0})
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _cl_fw_policy(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
151
|
+
data: dict = {}
|
|
152
|
+
if args.in_policy is not None:
|
|
153
|
+
data["policy_in"] = args.in_policy
|
|
154
|
+
if args.out_policy is not None:
|
|
155
|
+
data["policy_out"] = args.out_policy
|
|
156
|
+
if not data:
|
|
157
|
+
return {"error": "No policy specified. Use --in-policy or --out-policy"}
|
|
158
|
+
return client.put("/cluster/firewall/options", data=data)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# --- Firewall rules ---
|
|
162
|
+
|
|
163
|
+
def _cl_fw_rules(_args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
164
|
+
return client.get("/cluster/firewall/rules")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _cl_fw_rule_add(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
168
|
+
return client.post("/cluster/firewall/rules", data=build_rule_data(args))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _cl_fw_rule_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
172
|
+
return client.get(f"/cluster/firewall/rules/{args.pos}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _cl_fw_rule_upd(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
176
|
+
data = {k: v for k, v in build_rule_data(args).items() if v is not None and v != 0}
|
|
177
|
+
return client.put(f"/cluster/firewall/rules/{args.pos}", data=data)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _cl_fw_rule_del(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
181
|
+
return client.delete(f"/cluster/firewall/rules/{args.pos}")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# --- Firewall aliases ---
|
|
185
|
+
|
|
186
|
+
def _cl_fw_aliases(_args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
187
|
+
return client.get("/cluster/firewall/aliases")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _cl_fw_alias_add(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
191
|
+
data = {"name": args.name, "cidr": args.cidr}
|
|
192
|
+
if args.comment:
|
|
193
|
+
data["comment"] = args.comment
|
|
194
|
+
return client.post("/cluster/firewall/aliases", data=data)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _cl_fw_alias_del(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
198
|
+
return client.delete(f"/cluster/firewall/aliases/{args.name}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# --- Firewall ipsets ---
|
|
202
|
+
|
|
203
|
+
def _cl_fw_ipsets(_args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
204
|
+
return client.get("/cluster/firewall/ipset")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _cl_fw_ipset_add(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
208
|
+
data: dict = {"name": args.name}
|
|
209
|
+
if args.comment:
|
|
210
|
+
data["comment"] = args.comment
|
|
211
|
+
return client.post("/cluster/firewall/ipset", data=data)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _cl_fw_ipset_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
215
|
+
return client.get(f"/cluster/firewall/ipset/{args.name}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _cl_fw_ipset_del(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
219
|
+
return client.delete(f"/cluster/firewall/ipset/{args.name}")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _cl_fw_ipset_add_cidr(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
223
|
+
data: dict = {"cidr": args.cidr}
|
|
224
|
+
if args.comment:
|
|
225
|
+
data["comment"] = args.comment
|
|
226
|
+
if args.nomatch:
|
|
227
|
+
data["nomatch"] = 1
|
|
228
|
+
return client.post(f"/cluster/firewall/ipset/{args.name}", data=data)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _cl_fw_ipset_del_cidr(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
232
|
+
return client.delete(f"/cluster/firewall/ipset/{args.name}/{args.cidr}")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# --- Firewall refs ---
|
|
236
|
+
|
|
237
|
+
def _cl_fw_refs(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
238
|
+
params = {"type": args.type} if args.type else None
|
|
239
|
+
return client.get("/cluster/firewall/refs", params=params)
|