proxcli 0.8.0__tar.gz → 0.8.1__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.8.0 → proxcli-0.8.1}/CHANGELOG.md +7 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/PKG-INFO +1 -1
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/vm.py +110 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/pyproject.toml +1 -1
- {proxcli-0.8.0 → proxcli-0.8.1}/uv.lock +2 -2
- {proxcli-0.8.0 → proxcli-0.8.1}/.env.example +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/.github/workflows/ci.yml +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/.gitignore +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/.python-version +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/AGENTS.md +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/PLAN.md +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/PROJECT.md +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/PROMPT.md +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/README.md +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/TODO.md +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/auth.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/cluster.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/completion.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/container.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/firewall_helpers.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/main.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/node.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/pool.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/storage.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/cli/tasks.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/client/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/client/auth.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/client/client.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/client/exceptions.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/config/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/config/config.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/config/models.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/output/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/output/formatter.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/output/json_fmt.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/output/table_fmt.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/output/yaml_fmt.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/utils/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/utils/helpers.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/proxmox/utils/logging.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/conftest.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_auth.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_cli/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_cli/test_main.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_client.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_config.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_integration/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_output/__init__.py +0 -0
- {proxcli-0.8.0 → proxcli-0.8.1}/tests/test_output/test_formatter.py +0 -0
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.1] - 2026-06-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **vm snapshot** management: ``list``, ``create``, ``show``, ``rollback``,
|
|
14
|
+
``delete``. Wraps ``/nodes/{node}/qemu/{vmid}/snapshot`` endpoints.
|
|
15
|
+
|
|
10
16
|
## [0.8.0] - 2026-06-20
|
|
11
17
|
|
|
12
18
|
### Fixed
|
|
@@ -118,6 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
118
124
|
- CSRF ticket auto-refresh on 401.
|
|
119
125
|
- AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
|
|
120
126
|
|
|
127
|
+
[0.8.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.8.1
|
|
121
128
|
[0.8.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.8.0
|
|
122
129
|
[0.7.2]: https://github.com/xezpeleta/proxcli/releases/tag/v0.7.2
|
|
123
130
|
[0.7.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.7.1
|
|
@@ -84,6 +84,46 @@ def register_vm_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
84
84
|
vm_delete.add_argument("--purge", action="store_true", help="Purge VM from all configurations")
|
|
85
85
|
vm_delete.set_defaults(func=_vm_delete)
|
|
86
86
|
|
|
87
|
+
# --- snapshot ---
|
|
88
|
+
snap = vm_sub.add_parser("snapshot", help="Manage VM snapshots")
|
|
89
|
+
snap_sub = snap.add_subparsers(dest="snap_action", title="snapshot actions", required=True)
|
|
90
|
+
|
|
91
|
+
snap_list = snap_sub.add_parser("list", help="List snapshots")
|
|
92
|
+
snap_list.add_argument("vmid", type=vmid_type, help="VM ID")
|
|
93
|
+
snap_list.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
94
|
+
snap_list.set_defaults(func=_vm_snapshot_list)
|
|
95
|
+
|
|
96
|
+
snap_create = snap_sub.add_parser("create", help="Create a snapshot")
|
|
97
|
+
snap_create.add_argument("vmid", type=vmid_type, help="VM ID")
|
|
98
|
+
snap_create.add_argument("snapname", help="Snapshot name")
|
|
99
|
+
snap_create.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
100
|
+
snap_create.add_argument("--description", default=None, help="Snapshot description")
|
|
101
|
+
snap_create.add_argument("--vmstate", type=int, choices=[0, 1], default=0,
|
|
102
|
+
help="Include RAM state (1=yes, 0=no, default: 0)")
|
|
103
|
+
snap_create.set_defaults(func=_vm_snapshot_create)
|
|
104
|
+
|
|
105
|
+
snap_show = snap_sub.add_parser("show", help="Show snapshot details")
|
|
106
|
+
snap_show.add_argument("vmid", type=vmid_type, help="VM ID")
|
|
107
|
+
snap_show.add_argument("snapname", help="Snapshot name")
|
|
108
|
+
snap_show.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
109
|
+
snap_show.set_defaults(func=_vm_snapshot_show)
|
|
110
|
+
|
|
111
|
+
snap_rollback = snap_sub.add_parser("rollback", help="Rollback to a snapshot")
|
|
112
|
+
snap_rollback.add_argument("vmid", type=vmid_type, help="VM ID")
|
|
113
|
+
snap_rollback.add_argument("snapname", help="Snapshot name")
|
|
114
|
+
snap_rollback.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
115
|
+
snap_rollback.add_argument("--start", type=int, choices=[0, 1], default=0,
|
|
116
|
+
help="Start VM after rollback (1=yes, 0=no, default: 0)")
|
|
117
|
+
snap_rollback.set_defaults(func=_vm_snapshot_rollback)
|
|
118
|
+
|
|
119
|
+
snap_delete = snap_sub.add_parser("delete", help="Delete a snapshot")
|
|
120
|
+
snap_delete.add_argument("vmid", type=vmid_type, help="VM ID")
|
|
121
|
+
snap_delete.add_argument("snapname", help="Snapshot name")
|
|
122
|
+
snap_delete.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
123
|
+
snap_delete.add_argument("--force", type=int, choices=[0, 1], default=0,
|
|
124
|
+
help="Force removal (1=yes, 0=no, default: 0)")
|
|
125
|
+
snap_delete.set_defaults(func=_vm_snapshot_delete)
|
|
126
|
+
|
|
87
127
|
# --- firewall ---
|
|
88
128
|
fw = vm_sub.add_parser("firewall", help="Manage VM firewall")
|
|
89
129
|
fw_sub = fw.add_subparsers(dest="fw_resource", title="resources", required=True)
|
|
@@ -325,6 +365,76 @@ def _vm_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
|
325
365
|
return result if isinstance(result, dict) else {"data": result}
|
|
326
366
|
|
|
327
367
|
|
|
368
|
+
# ---------------------------------------------------------------------------
|
|
369
|
+
# VM snapshot handlers
|
|
370
|
+
# ---------------------------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
def _vm_snapshot_list(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
373
|
+
node = _resolve_node(client, args.node, args.vmid)
|
|
374
|
+
if not node:
|
|
375
|
+
return {"error": f"VM {args.vmid} not found"}
|
|
376
|
+
result = client.get(f"/nodes/{node}/qemu/{args.vmid}/snapshot")
|
|
377
|
+
# Add _node for consistency
|
|
378
|
+
if isinstance(result, list):
|
|
379
|
+
for snap in result:
|
|
380
|
+
if isinstance(snap, dict):
|
|
381
|
+
snap["_node"] = node
|
|
382
|
+
snap["_vmid"] = args.vmid
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _vm_snapshot_create(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
387
|
+
node = _resolve_node(client, args.node, args.vmid)
|
|
388
|
+
if not node:
|
|
389
|
+
return {"error": f"VM {args.vmid} not found"}
|
|
390
|
+
data: dict = {"snapname": args.snapname}
|
|
391
|
+
if args.description:
|
|
392
|
+
data["description"] = args.description
|
|
393
|
+
if args.vmstate:
|
|
394
|
+
data["vmstate"] = args.vmstate
|
|
395
|
+
result = client.post(f"/nodes/{node}/qemu/{args.vmid}/snapshot", data=data)
|
|
396
|
+
return result if isinstance(result, dict) else {"data": result}
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _vm_snapshot_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
400
|
+
node = _resolve_node(client, args.node, args.vmid)
|
|
401
|
+
if not node:
|
|
402
|
+
return {"error": f"VM {args.vmid} not found"}
|
|
403
|
+
result = client.get(f"/nodes/{node}/qemu/{args.vmid}/snapshot/{args.snapname}")
|
|
404
|
+
if isinstance(result, dict):
|
|
405
|
+
result["_node"] = node
|
|
406
|
+
result["_vmid"] = args.vmid
|
|
407
|
+
return result
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _vm_snapshot_rollback(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
411
|
+
node = _resolve_node(client, args.node, args.vmid)
|
|
412
|
+
if not node:
|
|
413
|
+
return {"error": f"VM {args.vmid} not found"}
|
|
414
|
+
data: dict = {}
|
|
415
|
+
if args.start:
|
|
416
|
+
data["start"] = args.start
|
|
417
|
+
result = client.post(
|
|
418
|
+
f"/nodes/{node}/qemu/{args.vmid}/snapshot/{args.snapname}/rollback",
|
|
419
|
+
data=data or None,
|
|
420
|
+
)
|
|
421
|
+
return result if isinstance(result, dict) else {"data": result}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _vm_snapshot_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
425
|
+
node = _resolve_node(client, args.node, args.vmid)
|
|
426
|
+
if not node:
|
|
427
|
+
return {"error": f"VM {args.vmid} not found"}
|
|
428
|
+
params: dict = {}
|
|
429
|
+
if args.force:
|
|
430
|
+
params["force"] = args.force
|
|
431
|
+
result = client.delete(
|
|
432
|
+
f"/nodes/{node}/qemu/{args.vmid}/snapshot/{args.snapname}",
|
|
433
|
+
params=params or None,
|
|
434
|
+
)
|
|
435
|
+
return result if isinstance(result, dict) else {"data": result}
|
|
436
|
+
|
|
437
|
+
|
|
328
438
|
# ---------------------------------------------------------------------------
|
|
329
439
|
# VM firewall handlers
|
|
330
440
|
# ---------------------------------------------------------------------------
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 3
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -254,7 +254,7 @@ wheels = [
|
|
|
254
254
|
|
|
255
255
|
[[package]]
|
|
256
256
|
name = "proxcli"
|
|
257
|
-
version = "0.8.
|
|
257
|
+
version = "0.8.1"
|
|
258
258
|
source = { editable = "." }
|
|
259
259
|
dependencies = [
|
|
260
260
|
{ name = "httpx" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|