proxcli 0.8.0__tar.gz → 0.8.2__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.
Files changed (52) hide show
  1. {proxcli-0.8.0 → proxcli-0.8.2}/.github/workflows/ci.yml +1 -1
  2. {proxcli-0.8.0 → proxcli-0.8.2}/CHANGELOG.md +20 -0
  3. {proxcli-0.8.0 → proxcli-0.8.2}/PKG-INFO +14 -2
  4. {proxcli-0.8.0 → proxcli-0.8.2}/README.md +13 -1
  5. {proxcli-0.8.0 → proxcli-0.8.2}/TODO.md +4 -3
  6. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/vm.py +141 -0
  7. {proxcli-0.8.0 → proxcli-0.8.2}/pyproject.toml +1 -1
  8. {proxcli-0.8.0 → proxcli-0.8.2}/uv.lock +1 -1
  9. {proxcli-0.8.0 → proxcli-0.8.2}/.env.example +0 -0
  10. {proxcli-0.8.0 → proxcli-0.8.2}/.gitignore +0 -0
  11. {proxcli-0.8.0 → proxcli-0.8.2}/.python-version +0 -0
  12. {proxcli-0.8.0 → proxcli-0.8.2}/AGENTS.md +0 -0
  13. {proxcli-0.8.0 → proxcli-0.8.2}/PLAN.md +0 -0
  14. {proxcli-0.8.0 → proxcli-0.8.2}/PROJECT.md +0 -0
  15. {proxcli-0.8.0 → proxcli-0.8.2}/PROMPT.md +0 -0
  16. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/__init__.py +0 -0
  17. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/__init__.py +0 -0
  18. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/auth.py +0 -0
  19. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/cluster.py +0 -0
  20. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/completion.py +0 -0
  21. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/container.py +0 -0
  22. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/firewall_helpers.py +0 -0
  23. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/main.py +0 -0
  24. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/node.py +0 -0
  25. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/pool.py +0 -0
  26. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/storage.py +0 -0
  27. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/cli/tasks.py +0 -0
  28. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/client/__init__.py +0 -0
  29. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/client/auth.py +0 -0
  30. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/client/client.py +0 -0
  31. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/client/exceptions.py +0 -0
  32. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/config/__init__.py +0 -0
  33. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/config/config.py +0 -0
  34. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/config/models.py +0 -0
  35. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/output/__init__.py +0 -0
  36. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/output/formatter.py +0 -0
  37. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/output/json_fmt.py +0 -0
  38. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/output/table_fmt.py +0 -0
  39. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/output/yaml_fmt.py +0 -0
  40. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/utils/__init__.py +0 -0
  41. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/utils/helpers.py +0 -0
  42. {proxcli-0.8.0 → proxcli-0.8.2}/proxmox/utils/logging.py +0 -0
  43. {proxcli-0.8.0 → proxcli-0.8.2}/tests/__init__.py +0 -0
  44. {proxcli-0.8.0 → proxcli-0.8.2}/tests/conftest.py +0 -0
  45. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_auth.py +0 -0
  46. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_cli/__init__.py +0 -0
  47. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_cli/test_main.py +0 -0
  48. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_client.py +0 -0
  49. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_config.py +0 -0
  50. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_integration/__init__.py +0 -0
  51. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_output/__init__.py +0 -0
  52. {proxcli-0.8.0 → proxcli-0.8.2}/tests/test_output/test_formatter.py +0 -0
@@ -70,4 +70,4 @@ jobs:
70
70
  name: dist
71
71
  path: dist/
72
72
 
73
- - run: uv publish --token "${{ secrets.PYPI_TOKEN }}"
73
+ - run: uv publish --token "${{ secrets.PYPI_TOKEN }}" --check-url https://pypi.org/simple
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.2] - 2026-06-20
11
+
12
+ ### Added
13
+ - **vm agent interfaces** — query QEMU guest agent for network interface
14
+ and IP information via ``proxmox vm agent interfaces <vmid>``.
15
+ Requires ``qemu-guest-agent`` in the VM.
16
+
17
+ ### Fixed
18
+ - CI publish job now uses ``uv publish --check-url https://pypi.org/simple``
19
+ to skip already-published versions instead of failing with "File already
20
+ exists".
21
+
22
+ ## [0.8.1] - 2026-06-20
23
+
24
+ ### Added
25
+ - **vm snapshot** management: ``list``, ``create``, ``show``, ``rollback``,
26
+ ``delete``. Wraps ``/nodes/{node}/qemu/{vmid}/snapshot`` endpoints.
27
+
10
28
  ## [0.8.0] - 2026-06-20
11
29
 
12
30
  ### Fixed
@@ -118,6 +136,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
118
136
  - CSRF ticket auto-refresh on 401.
119
137
  - AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
120
138
 
139
+ [0.8.2]: https://github.com/xezpeleta/proxcli/releases/tag/v0.8.2
140
+ [0.8.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.8.1
121
141
  [0.8.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.8.0
122
142
  [0.7.2]: https://github.com/xezpeleta/proxcli/releases/tag/v0.7.2
123
143
  [0.7.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.7.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxcli
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: A CLI tool to interact with Proxmox VE nodes and clusters via the REST API
5
5
  Author-email: Xabi Ezpeleta <xezpeleta@gmail.com>
6
6
  License: MIT
@@ -177,7 +177,9 @@ proxmox completion fish > ~/.config/fish/completions/proxmox.fish
177
177
  ```bash
178
178
  proxmox vm list [--node <node>]
179
179
  proxmox vm show <vmid> [--node <node>]
180
- proxmox vm create --node <node> --vmid <id> --memory <mb> [--cores <n>] [--name <name>] [--storage <name>] [--net <config>]
180
+ proxmox vm create --node <node> --memory <mb> [--vmid <id>] [--cores <n>] \
181
+ [--name <name>] [--cdrom <iso>] [--net <config>] [--disk <size>] \
182
+ [--scsihw <type>] [--bios seabios|ovmf] [--machine <type>] [--boot <order>]
181
183
  proxmox vm start <vmid> [--node <node>]
182
184
  proxmox vm stop <vmid> [--node <node>]
183
185
  proxmox vm reboot <vmid> [--node <node>]
@@ -185,6 +187,16 @@ proxmox vm suspend <vmid> [--node <node>]
185
187
  proxmox vm resume <vmid> [--node <node>]
186
188
  proxmox vm delete <vmid> [--node <node>] [--force] [--purge]
187
189
 
190
+ # VM snapshots
191
+ proxmox vm snapshot list <vmid> [--node <node>]
192
+ proxmox vm snapshot create <vmid> <snapname> [--description <text>] [--vmstate 1]
193
+ proxmox vm snapshot show <vmid> <snapname> [--node <node>]
194
+ proxmox vm snapshot rollback <vmid> <snapname> [--start 1]
195
+ proxmox vm snapshot delete <vmid> <snapname> [--force 1]
196
+
197
+ # VM guest agent
198
+ proxmox vm agent interfaces <vmid> [--node <node>]
199
+
188
200
  # VM firewall
189
201
  proxmox vm firewall options <vmid> [--node <node>]
190
202
  proxmox vm firewall enable <vmid> [--node <node>]
@@ -154,7 +154,9 @@ proxmox completion fish > ~/.config/fish/completions/proxmox.fish
154
154
  ```bash
155
155
  proxmox vm list [--node <node>]
156
156
  proxmox vm show <vmid> [--node <node>]
157
- proxmox vm create --node <node> --vmid <id> --memory <mb> [--cores <n>] [--name <name>] [--storage <name>] [--net <config>]
157
+ proxmox vm create --node <node> --memory <mb> [--vmid <id>] [--cores <n>] \
158
+ [--name <name>] [--cdrom <iso>] [--net <config>] [--disk <size>] \
159
+ [--scsihw <type>] [--bios seabios|ovmf] [--machine <type>] [--boot <order>]
158
160
  proxmox vm start <vmid> [--node <node>]
159
161
  proxmox vm stop <vmid> [--node <node>]
160
162
  proxmox vm reboot <vmid> [--node <node>]
@@ -162,6 +164,16 @@ proxmox vm suspend <vmid> [--node <node>]
162
164
  proxmox vm resume <vmid> [--node <node>]
163
165
  proxmox vm delete <vmid> [--node <node>] [--force] [--purge]
164
166
 
167
+ # VM snapshots
168
+ proxmox vm snapshot list <vmid> [--node <node>]
169
+ proxmox vm snapshot create <vmid> <snapname> [--description <text>] [--vmstate 1]
170
+ proxmox vm snapshot show <vmid> <snapname> [--node <node>]
171
+ proxmox vm snapshot rollback <vmid> <snapname> [--start 1]
172
+ proxmox vm snapshot delete <vmid> <snapname> [--force 1]
173
+
174
+ # VM guest agent
175
+ proxmox vm agent interfaces <vmid> [--node <node>]
176
+
165
177
  # VM firewall
166
178
  proxmox vm firewall options <vmid> [--node <node>]
167
179
  proxmox vm firewall enable <vmid> [--node <node>]
@@ -11,12 +11,13 @@ Completed items are marked with a check. Implementation notes are preserved for
11
11
  - [x] **Firewall management** — cluster, node, VM, and container. Options, enable/disable, policy, rules (CRUD), aliases (cluster), ipsets with CIDR mgmt (cluster), refs.
12
12
  - [x] **Pool management** — `proxmox pool`: list, show, create, update, delete. Wraps `/pools`.
13
13
  - [x] **Shell completions** — `proxmox completion bash|zsh|fish`. Dynamic, introspects the parser tree.
14
+ - [x] **VM snapshot management** — `proxmox vm snapshot`: list, create, show, rollback, delete. Wraps `/nodes/{node}/qemu/{vmid}/snapshot`.
15
+ - [x] **QEMU guest agent interfaces** — `proxmox vm agent interfaces <vmid>`. Wraps `/nodes/{node}/qemu/{vmid}/agent/network-get-interfaces`.
16
+ - [x] **Streaming task logs** — `proxmox task log <upid> [--follow]`. Polls `/nodes/{node}/tasks/{upid}/log`.
17
+ - [x] **Global flag hint** — If user places `--output` / `--dry-run` / etc. after the resource, a hint suggests the correct order.
14
18
 
15
19
  ## v1.1 — Polish & Usability
16
20
 
17
- - [ ] **Streaming task logs (`--follow`)**
18
- - `proxmox task log <upid> --follow` that streams task output in real time (like `tail -f`). Requires httpx streaming.
19
-
20
21
  - [ ] **Startup time optimization**
21
22
  - Current `proxmox --help` takes ~350ms. Lazy-load subcommand modules so only the requested resource's code is imported. Move `import rich`, `import yaml` inside formatter functions. Target: <200ms.
22
23
 
@@ -84,6 +84,55 @@ 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
+
127
+ # --- agent ---
128
+ agent = vm_sub.add_parser("agent", help="Query QEMU guest agent")
129
+ agent_sub = agent.add_subparsers(dest="agent_action", title="agent actions", required=True)
130
+
131
+ agent_ifaces = agent_sub.add_parser("interfaces", help="List network interfaces via guest agent")
132
+ agent_ifaces.add_argument("vmid", type=vmid_type, help="VM ID")
133
+ agent_ifaces.add_argument("--node", help="Node name (auto-detected if omitted)")
134
+ agent_ifaces.set_defaults(func=_vm_agent_interfaces)
135
+
87
136
  # --- firewall ---
88
137
  fw = vm_sub.add_parser("firewall", help="Manage VM firewall")
89
138
  fw_sub = fw.add_subparsers(dest="fw_resource", title="resources", required=True)
@@ -325,6 +374,98 @@ def _vm_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
325
374
  return result if isinstance(result, dict) else {"data": result}
326
375
 
327
376
 
377
+ # ---------------------------------------------------------------------------
378
+ # VM guest agent handlers
379
+ # ---------------------------------------------------------------------------
380
+
381
+ def _vm_agent_interfaces(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
382
+ """Retrieve network interfaces via QEMU guest agent.
383
+
384
+ Requires qemu-guest-agent installed in the VM and agent enabled in VM options.
385
+ """
386
+ node = _resolve_node(client, args.node, args.vmid)
387
+ if not node:
388
+ return {"error": f"VM {args.vmid} not found"}
389
+ result = client.get(f"/nodes/{node}/qemu/{args.vmid}/agent/network-get-interfaces")
390
+ # The result is a list of interfaces, each with 'name', 'ip-addresses', etc.
391
+ if isinstance(result, list):
392
+ for iface in result:
393
+ if isinstance(iface, dict):
394
+ iface["_node"] = node
395
+ iface["_vmid"] = args.vmid
396
+ return result
397
+
398
+
399
+ # ---------------------------------------------------------------------------
400
+ # VM snapshot handlers
401
+ # ---------------------------------------------------------------------------
402
+
403
+ def _vm_snapshot_list(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
404
+ node = _resolve_node(client, args.node, args.vmid)
405
+ if not node:
406
+ return {"error": f"VM {args.vmid} not found"}
407
+ result = client.get(f"/nodes/{node}/qemu/{args.vmid}/snapshot")
408
+ # Add _node for consistency
409
+ if isinstance(result, list):
410
+ for snap in result:
411
+ if isinstance(snap, dict):
412
+ snap["_node"] = node
413
+ snap["_vmid"] = args.vmid
414
+ return result
415
+
416
+
417
+ def _vm_snapshot_create(args: argparse.Namespace, client: ProxmoxClient) -> dict:
418
+ node = _resolve_node(client, args.node, args.vmid)
419
+ if not node:
420
+ return {"error": f"VM {args.vmid} not found"}
421
+ data: dict = {"snapname": args.snapname}
422
+ if args.description:
423
+ data["description"] = args.description
424
+ if args.vmstate:
425
+ data["vmstate"] = args.vmstate
426
+ result = client.post(f"/nodes/{node}/qemu/{args.vmid}/snapshot", data=data)
427
+ return result if isinstance(result, dict) else {"data": result}
428
+
429
+
430
+ def _vm_snapshot_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
431
+ node = _resolve_node(client, args.node, args.vmid)
432
+ if not node:
433
+ return {"error": f"VM {args.vmid} not found"}
434
+ result = client.get(f"/nodes/{node}/qemu/{args.vmid}/snapshot/{args.snapname}")
435
+ if isinstance(result, dict):
436
+ result["_node"] = node
437
+ result["_vmid"] = args.vmid
438
+ return result
439
+
440
+
441
+ def _vm_snapshot_rollback(args: argparse.Namespace, client: ProxmoxClient) -> dict:
442
+ node = _resolve_node(client, args.node, args.vmid)
443
+ if not node:
444
+ return {"error": f"VM {args.vmid} not found"}
445
+ data: dict = {}
446
+ if args.start:
447
+ data["start"] = args.start
448
+ result = client.post(
449
+ f"/nodes/{node}/qemu/{args.vmid}/snapshot/{args.snapname}/rollback",
450
+ data=data or None,
451
+ )
452
+ return result if isinstance(result, dict) else {"data": result}
453
+
454
+
455
+ def _vm_snapshot_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
456
+ node = _resolve_node(client, args.node, args.vmid)
457
+ if not node:
458
+ return {"error": f"VM {args.vmid} not found"}
459
+ params: dict = {}
460
+ if args.force:
461
+ params["force"] = args.force
462
+ result = client.delete(
463
+ f"/nodes/{node}/qemu/{args.vmid}/snapshot/{args.snapname}",
464
+ params=params or None,
465
+ )
466
+ return result if isinstance(result, dict) else {"data": result}
467
+
468
+
328
469
  # ---------------------------------------------------------------------------
329
470
  # VM firewall handlers
330
471
  # ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "proxcli"
3
- version = "0.8.0"
3
+ version = "0.8.2"
4
4
  description = "A CLI tool to interact with Proxmox VE nodes and clusters via the REST API"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -254,7 +254,7 @@ wheels = [
254
254
 
255
255
  [[package]]
256
256
  name = "proxcli"
257
- version = "0.8.0"
257
+ version = "0.8.2"
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