proxcli 0.3.0__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.3.0 → proxcli-0.4.0}/.github/workflows/ci.yml +4 -4
- {proxcli-0.3.0 → proxcli-0.4.0}/CHANGELOG.md +7 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/PKG-INFO +1 -1
- proxcli-0.4.0/proxmox/cli/container.py +316 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/pyproject.toml +1 -1
- {proxcli-0.3.0 → proxcli-0.4.0}/uv.lock +1 -1
- proxcli-0.3.0/proxmox/cli/container.py +0 -157
- {proxcli-0.3.0 → proxcli-0.4.0}/.env.example +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/.gitignore +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/.python-version +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/AGENTS.md +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/PLAN.md +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/PROJECT.md +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/PROMPT.md +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/README.md +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/TODO.md +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/auth.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/cluster.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/firewall_helpers.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/main.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/node.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/storage.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/tasks.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/cli/vm.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/client/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/client/auth.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/client/client.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/client/exceptions.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/config/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/config/config.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/config/models.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/output/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/output/formatter.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/output/json_fmt.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/output/table_fmt.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/output/yaml_fmt.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/utils/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/utils/helpers.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/proxmox/utils/logging.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/conftest.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_auth.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_cli/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_cli/test_main.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_client.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_config.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_integration/__init__.py +0 -0
- {proxcli-0.3.0 → proxcli-0.4.0}/tests/test_output/__init__.py +0 -0
- {proxcli-0.3.0 → 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
|
|
|
@@ -61,7 +61,7 @@ jobs:
|
|
|
61
61
|
steps:
|
|
62
62
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
63
63
|
|
|
64
|
-
- uses: astral-sh/setup-uv@
|
|
64
|
+
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
65
65
|
with:
|
|
66
66
|
enable-cache: true
|
|
67
67
|
|
|
@@ -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.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
|
+
|
|
10
16
|
## [0.3.0] - 2026-06-20
|
|
11
17
|
|
|
12
18
|
### Added
|
|
@@ -55,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
55
61
|
- CSRF ticket auto-refresh on 401.
|
|
56
62
|
- AI-agent-friendly: default JSON output, strict exit codes, `--dry-run` mode.
|
|
57
63
|
|
|
64
|
+
[0.4.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.4.0
|
|
58
65
|
[0.3.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.3.0
|
|
59
66
|
[0.2.1]: https://github.com/xezpeleta/proxcli/releases/tag/v0.2.1
|
|
60
67
|
[0.2.0]: https://github.com/xezpeleta/proxcli/releases/tag/v0.2.0
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""`proxmox container` subcommand — LXC container 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
|
+
from proxmox.utils.helpers import resolve_vmid, vmid_type
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register_container_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
13
|
+
"""Register the `proxmox container` subcommand tree."""
|
|
14
|
+
ct_parser = subparsers.add_parser("container", help="Manage LXC containers")
|
|
15
|
+
ct_sub = ct_parser.add_subparsers(dest="action", title="actions", required=True)
|
|
16
|
+
|
|
17
|
+
# --- container list ---
|
|
18
|
+
ct_list = ct_sub.add_parser("list", help="List containers")
|
|
19
|
+
ct_list.add_argument("--node", help="Filter by node name")
|
|
20
|
+
ct_list.set_defaults(func=_ct_list)
|
|
21
|
+
|
|
22
|
+
# --- container show ---
|
|
23
|
+
ct_show = ct_sub.add_parser("show", help="Show container details")
|
|
24
|
+
ct_show.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
25
|
+
ct_show.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
26
|
+
ct_show.set_defaults(func=_ct_show)
|
|
27
|
+
|
|
28
|
+
# --- container create ---
|
|
29
|
+
ct_create = ct_sub.add_parser("create", help="Create a new container")
|
|
30
|
+
ct_create.add_argument("--node", required=True, help="Target node")
|
|
31
|
+
ct_create.add_argument("--vmid", type=vmid_type, default=None, help="Container ID (auto-assigned if omitted)")
|
|
32
|
+
ct_create.add_argument("--ostemplate", required=True, help="OS template")
|
|
33
|
+
ct_create.add_argument("--storage", default=None, help="Storage for the container")
|
|
34
|
+
ct_create.add_argument("--memory", type=int, default=512, help="Memory in MB")
|
|
35
|
+
ct_create.add_argument("--cores", type=int, default=1, help="CPU cores")
|
|
36
|
+
ct_create.add_argument("--net", default=None, help="Network config (e.g. name=eth0,bridge=vmbr0,ip=dhcp)")
|
|
37
|
+
ct_create.add_argument("--password", default=None, help="Root password")
|
|
38
|
+
ct_create.set_defaults(func=_ct_create)
|
|
39
|
+
|
|
40
|
+
# --- container start ---
|
|
41
|
+
ct_start = ct_sub.add_parser("start", help="Start a container")
|
|
42
|
+
ct_start.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
43
|
+
ct_start.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
44
|
+
ct_start.set_defaults(func=_ct_start)
|
|
45
|
+
|
|
46
|
+
# --- container stop ---
|
|
47
|
+
ct_stop = ct_sub.add_parser("stop", help="Stop a container")
|
|
48
|
+
ct_stop.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
49
|
+
ct_stop.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
50
|
+
ct_stop.set_defaults(func=_ct_stop)
|
|
51
|
+
|
|
52
|
+
# --- container delete ---
|
|
53
|
+
ct_delete = ct_sub.add_parser("delete", help="Delete a container")
|
|
54
|
+
ct_delete.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
55
|
+
ct_delete.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
56
|
+
ct_delete.add_argument("--force", action="store_true", help="Force removal")
|
|
57
|
+
ct_delete.add_argument("--purge", action="store_true", help="Purge from all configurations")
|
|
58
|
+
ct_delete.set_defaults(func=_ct_delete)
|
|
59
|
+
|
|
60
|
+
# --- firewall ---
|
|
61
|
+
fw = ct_sub.add_parser("firewall", help="Manage container firewall")
|
|
62
|
+
fw_sub = fw.add_subparsers(dest="fw_resource", title="resources", required=True)
|
|
63
|
+
|
|
64
|
+
fw_opts = fw_sub.add_parser("options", help="Show container firewall options")
|
|
65
|
+
fw_opts.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
66
|
+
fw_opts.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
67
|
+
fw_opts.set_defaults(func=_ct_fw_options)
|
|
68
|
+
|
|
69
|
+
fw_enable = fw_sub.add_parser("enable", help="Enable container firewall")
|
|
70
|
+
fw_enable.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
71
|
+
fw_enable.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
72
|
+
fw_enable.set_defaults(func=_ct_fw_enable)
|
|
73
|
+
|
|
74
|
+
fw_disable = fw_sub.add_parser("disable", help="Disable container firewall")
|
|
75
|
+
fw_disable.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
76
|
+
fw_disable.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
77
|
+
fw_disable.set_defaults(func=_ct_fw_disable)
|
|
78
|
+
|
|
79
|
+
fw_policy = fw_sub.add_parser("policy", help="Set default input/output policy for container")
|
|
80
|
+
fw_policy.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
81
|
+
fw_policy.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
82
|
+
fw_policy.add_argument("--in-policy", choices=["ACCEPT", "DENY", "REJECT"], default=None,
|
|
83
|
+
help="Default input policy")
|
|
84
|
+
fw_policy.add_argument("--out-policy", choices=["ACCEPT", "DENY", "REJECT"], default=None,
|
|
85
|
+
help="Default output policy")
|
|
86
|
+
fw_policy.set_defaults(func=_ct_fw_policy)
|
|
87
|
+
|
|
88
|
+
# firewall rules
|
|
89
|
+
rules = fw_sub.add_parser("rules", help="Manage container firewall rules")
|
|
90
|
+
rules_sub = rules.add_subparsers(dest="fw_action", title="rule actions", required=False)
|
|
91
|
+
|
|
92
|
+
rules_list = rules_sub.add_parser("list", help="List rules")
|
|
93
|
+
rules_list.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
94
|
+
rules_list.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
95
|
+
rules_list.set_defaults(func=_ct_fw_rules)
|
|
96
|
+
|
|
97
|
+
rules_add = rules_sub.add_parser("add", help="Add a rule")
|
|
98
|
+
rules_add.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
99
|
+
rules_add.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
100
|
+
add_firewall_rule_args(rules_add)
|
|
101
|
+
rules_add.set_defaults(func=_ct_fw_rule_add)
|
|
102
|
+
|
|
103
|
+
rules_show = rules_sub.add_parser("show", help="Show a rule by position")
|
|
104
|
+
rules_show.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
105
|
+
rules_show.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
106
|
+
rules_show.add_argument("pos", type=int, help="Rule position")
|
|
107
|
+
rules_show.set_defaults(func=_ct_fw_rule_show)
|
|
108
|
+
|
|
109
|
+
rules_upd = rules_sub.add_parser("update", help="Update a rule by position")
|
|
110
|
+
rules_upd.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
111
|
+
rules_upd.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
112
|
+
rules_upd.add_argument("pos", type=int, help="Rule position")
|
|
113
|
+
add_firewall_rule_args(rules_upd)
|
|
114
|
+
for action in rules_upd._actions:
|
|
115
|
+
if action.dest == "action":
|
|
116
|
+
action.required = False
|
|
117
|
+
action.default = None
|
|
118
|
+
rules_upd.set_defaults(func=_ct_fw_rule_upd)
|
|
119
|
+
|
|
120
|
+
rules_del = rules_sub.add_parser("delete", help="Delete a rule by position")
|
|
121
|
+
rules_del.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
122
|
+
rules_del.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
123
|
+
rules_del.add_argument("pos", type=int, help="Rule position")
|
|
124
|
+
rules_del.set_defaults(func=_ct_fw_rule_del)
|
|
125
|
+
|
|
126
|
+
# firewall refs
|
|
127
|
+
fw_refs = fw_sub.add_parser("refs", help="List container firewall references")
|
|
128
|
+
fw_refs.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
129
|
+
fw_refs.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
130
|
+
fw_refs.add_argument("--type", default=None, choices=["alias", "ipset", "group"],
|
|
131
|
+
help="Filter by reference type")
|
|
132
|
+
fw_refs.set_defaults(func=_ct_fw_refs)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# Helpers
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _resolve_ct_node(client: ProxmoxClient, node: str | None, vmid: int) -> str | None:
|
|
140
|
+
if node:
|
|
141
|
+
return node
|
|
142
|
+
try:
|
|
143
|
+
resources = client.get("/cluster/resources", params={"type": "vm"})
|
|
144
|
+
if isinstance(resources, list):
|
|
145
|
+
for r in resources:
|
|
146
|
+
if r.get("vmid") == vmid:
|
|
147
|
+
return r.get("node")
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# Command handlers
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def _ct_list(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
158
|
+
if args.node:
|
|
159
|
+
return client.get(f"/nodes/{args.node}/lxc")
|
|
160
|
+
nodes = client.get("/nodes")
|
|
161
|
+
if isinstance(nodes, dict):
|
|
162
|
+
nodes = nodes.get("data", [])
|
|
163
|
+
cts: list[dict] = []
|
|
164
|
+
for n in (nodes if isinstance(nodes, list) else []):
|
|
165
|
+
node_name = n.get("node") if isinstance(n, dict) else n
|
|
166
|
+
try:
|
|
167
|
+
node_cts = client.get(f"/nodes/{node_name}/lxc")
|
|
168
|
+
if isinstance(node_cts, list):
|
|
169
|
+
for ct in node_cts:
|
|
170
|
+
if isinstance(ct, dict):
|
|
171
|
+
ct["_node"] = node_name
|
|
172
|
+
cts.append(ct)
|
|
173
|
+
elif isinstance(node_cts, dict):
|
|
174
|
+
for ct in node_cts.get("data", []):
|
|
175
|
+
if isinstance(ct, dict):
|
|
176
|
+
ct["_node"] = node_name
|
|
177
|
+
cts.append(ct)
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
return cts
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _ct_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
184
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
185
|
+
if not node:
|
|
186
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
187
|
+
result = client.get(f"/nodes/{node}/lxc/{args.vmid}/status/current")
|
|
188
|
+
if isinstance(result, dict):
|
|
189
|
+
result["_node"] = node
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _ct_create(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
194
|
+
data: dict = {
|
|
195
|
+
"vmid": resolve_vmid(client, args.vmid),
|
|
196
|
+
"ostemplate": args.ostemplate,
|
|
197
|
+
"memory": args.memory,
|
|
198
|
+
"cores": args.cores,
|
|
199
|
+
}
|
|
200
|
+
if args.storage:
|
|
201
|
+
data["storage"] = args.storage
|
|
202
|
+
if args.net:
|
|
203
|
+
data["net0"] = args.net
|
|
204
|
+
if args.password:
|
|
205
|
+
data["password"] = args.password
|
|
206
|
+
return client.post(f"/nodes/{args.node}/lxc", data=data)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _ct_start(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
210
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
211
|
+
if not node:
|
|
212
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
213
|
+
return client.post(f"/nodes/{node}/lxc/{args.vmid}/status/start")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _ct_stop(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
217
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
218
|
+
if not node:
|
|
219
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
220
|
+
return client.post(f"/nodes/{node}/lxc/{args.vmid}/status/stop")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _ct_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
224
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
225
|
+
if not node:
|
|
226
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
227
|
+
params: dict = {}
|
|
228
|
+
if args.force:
|
|
229
|
+
params["force"] = 1
|
|
230
|
+
if args.purge:
|
|
231
|
+
params["purge"] = 1
|
|
232
|
+
return client.delete(f"/nodes/{node}/lxc/{args.vmid}", params=params or None)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
# Container firewall handlers
|
|
237
|
+
# ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
def _ct_fw_options(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
240
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
241
|
+
if not node:
|
|
242
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
243
|
+
return client.get(f"/nodes/{node}/lxc/{args.vmid}/firewall/options")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _ct_fw_enable(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
247
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
248
|
+
if not node:
|
|
249
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
250
|
+
return client.put(f"/nodes/{node}/lxc/{args.vmid}/firewall/options", data={"enable": 1})
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _ct_fw_disable(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
254
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
255
|
+
if not node:
|
|
256
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
257
|
+
return client.put(f"/nodes/{node}/lxc/{args.vmid}/firewall/options", data={"enable": 0})
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _ct_fw_policy(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
261
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
262
|
+
if not node:
|
|
263
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
264
|
+
data: dict = {}
|
|
265
|
+
if args.in_policy:
|
|
266
|
+
data["policy_in"] = args.in_policy
|
|
267
|
+
if args.out_policy:
|
|
268
|
+
data["policy_out"] = args.out_policy
|
|
269
|
+
if not data:
|
|
270
|
+
return {"error": "No policy specified. Use --in-policy or --out-policy"}
|
|
271
|
+
return client.put(f"/nodes/{node}/lxc/{args.vmid}/firewall/options", data=data)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _ct_fw_rules(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
275
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
276
|
+
if not node:
|
|
277
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
278
|
+
return client.get(f"/nodes/{node}/lxc/{args.vmid}/firewall/rules")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _ct_fw_rule_add(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
282
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
283
|
+
if not node:
|
|
284
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
285
|
+
return client.post(f"/nodes/{node}/lxc/{args.vmid}/firewall/rules",
|
|
286
|
+
data=build_rule_data(args))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _ct_fw_rule_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
290
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
291
|
+
if not node:
|
|
292
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
293
|
+
return client.get(f"/nodes/{node}/lxc/{args.vmid}/firewall/rules/{args.pos}")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _ct_fw_rule_upd(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
297
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
298
|
+
if not node:
|
|
299
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
300
|
+
data = {k: v for k, v in build_rule_data(args).items() if v is not None and v != 0}
|
|
301
|
+
return client.put(f"/nodes/{node}/lxc/{args.vmid}/firewall/rules/{args.pos}", data=data)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _ct_fw_rule_del(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
305
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
306
|
+
if not node:
|
|
307
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
308
|
+
return client.delete(f"/nodes/{node}/lxc/{args.vmid}/firewall/rules/{args.pos}")
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _ct_fw_refs(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
312
|
+
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
313
|
+
if not node:
|
|
314
|
+
return {"error": f"Container {args.vmid} not found"}
|
|
315
|
+
params: dict | None = {"type": args.type} if args.type else None
|
|
316
|
+
return client.get(f"/nodes/{node}/lxc/{args.vmid}/firewall/refs", params=params)
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""`proxmox container` subcommand — LXC container management."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
|
|
7
|
-
from proxmox.client.client import ProxmoxClient
|
|
8
|
-
from proxmox.utils.helpers import resolve_vmid, vmid_type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def register_container_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
12
|
-
"""Register the `proxmox container` subcommand tree."""
|
|
13
|
-
ct_parser = subparsers.add_parser("container", help="Manage LXC containers")
|
|
14
|
-
ct_sub = ct_parser.add_subparsers(dest="action", title="actions", required=True)
|
|
15
|
-
|
|
16
|
-
# --- container list ---
|
|
17
|
-
ct_list = ct_sub.add_parser("list", help="List containers")
|
|
18
|
-
ct_list.add_argument("--node", help="Filter by node name")
|
|
19
|
-
ct_list.set_defaults(func=_ct_list)
|
|
20
|
-
|
|
21
|
-
# --- container show ---
|
|
22
|
-
ct_show = ct_sub.add_parser("show", help="Show container details")
|
|
23
|
-
ct_show.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
24
|
-
ct_show.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
25
|
-
ct_show.set_defaults(func=_ct_show)
|
|
26
|
-
|
|
27
|
-
# --- container create ---
|
|
28
|
-
ct_create = ct_sub.add_parser("create", help="Create a new container")
|
|
29
|
-
ct_create.add_argument("--node", required=True, help="Target node")
|
|
30
|
-
ct_create.add_argument("--vmid", type=vmid_type, default=None, help="Container ID (auto-assigned if omitted)")
|
|
31
|
-
ct_create.add_argument("--ostemplate", required=True, help="OS template")
|
|
32
|
-
ct_create.add_argument("--storage", default=None, help="Storage for the container")
|
|
33
|
-
ct_create.add_argument("--memory", type=int, default=512, help="Memory in MB")
|
|
34
|
-
ct_create.add_argument("--cores", type=int, default=1, help="CPU cores")
|
|
35
|
-
ct_create.add_argument("--net", default=None, help="Network config (e.g. name=eth0,bridge=vmbr0,ip=dhcp)")
|
|
36
|
-
ct_create.add_argument("--password", default=None, help="Root password")
|
|
37
|
-
ct_create.set_defaults(func=_ct_create)
|
|
38
|
-
|
|
39
|
-
# --- container start ---
|
|
40
|
-
ct_start = ct_sub.add_parser("start", help="Start a container")
|
|
41
|
-
ct_start.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
42
|
-
ct_start.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
43
|
-
ct_start.set_defaults(func=_ct_start)
|
|
44
|
-
|
|
45
|
-
# --- container stop ---
|
|
46
|
-
ct_stop = ct_sub.add_parser("stop", help="Stop a container")
|
|
47
|
-
ct_stop.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
48
|
-
ct_stop.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
49
|
-
ct_stop.set_defaults(func=_ct_stop)
|
|
50
|
-
|
|
51
|
-
# --- container delete ---
|
|
52
|
-
ct_delete = ct_sub.add_parser("delete", help="Delete a container")
|
|
53
|
-
ct_delete.add_argument("vmid", type=vmid_type, help="Container ID")
|
|
54
|
-
ct_delete.add_argument("--node", help="Node name (auto-detected if omitted)")
|
|
55
|
-
ct_delete.add_argument("--force", action="store_true", help="Force removal")
|
|
56
|
-
ct_delete.add_argument("--purge", action="store_true", help="Purge from all configurations")
|
|
57
|
-
ct_delete.set_defaults(func=_ct_delete)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# ---------------------------------------------------------------------------
|
|
61
|
-
# Helpers
|
|
62
|
-
# ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
def _resolve_ct_node(client: ProxmoxClient, node: str | None, vmid: int) -> str | None:
|
|
65
|
-
if node:
|
|
66
|
-
return node
|
|
67
|
-
try:
|
|
68
|
-
resources = client.get("/cluster/resources", params={"type": "vm"})
|
|
69
|
-
if isinstance(resources, list):
|
|
70
|
-
for r in resources:
|
|
71
|
-
if r.get("vmid") == vmid:
|
|
72
|
-
return r.get("node")
|
|
73
|
-
except Exception:
|
|
74
|
-
pass
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# ---------------------------------------------------------------------------
|
|
79
|
-
# Command handlers
|
|
80
|
-
# ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
def _ct_list(args: argparse.Namespace, client: ProxmoxClient) -> dict | list:
|
|
83
|
-
if args.node:
|
|
84
|
-
return client.get(f"/nodes/{args.node}/lxc")
|
|
85
|
-
nodes = client.get("/nodes")
|
|
86
|
-
if isinstance(nodes, dict):
|
|
87
|
-
nodes = nodes.get("data", [])
|
|
88
|
-
cts: list[dict] = []
|
|
89
|
-
for n in (nodes if isinstance(nodes, list) else []):
|
|
90
|
-
node_name = n.get("node") if isinstance(n, dict) else n
|
|
91
|
-
try:
|
|
92
|
-
node_cts = client.get(f"/nodes/{node_name}/lxc")
|
|
93
|
-
if isinstance(node_cts, list):
|
|
94
|
-
for ct in node_cts:
|
|
95
|
-
if isinstance(ct, dict):
|
|
96
|
-
ct["_node"] = node_name
|
|
97
|
-
cts.append(ct)
|
|
98
|
-
elif isinstance(node_cts, dict):
|
|
99
|
-
for ct in node_cts.get("data", []):
|
|
100
|
-
if isinstance(ct, dict):
|
|
101
|
-
ct["_node"] = node_name
|
|
102
|
-
cts.append(ct)
|
|
103
|
-
except Exception:
|
|
104
|
-
pass
|
|
105
|
-
return cts
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _ct_show(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
109
|
-
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
110
|
-
if not node:
|
|
111
|
-
return {"error": f"Container {args.vmid} not found"}
|
|
112
|
-
result = client.get(f"/nodes/{node}/lxc/{args.vmid}/status/current")
|
|
113
|
-
if isinstance(result, dict):
|
|
114
|
-
result["_node"] = node
|
|
115
|
-
return result
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _ct_create(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
119
|
-
data: dict = {
|
|
120
|
-
"vmid": resolve_vmid(client, args.vmid),
|
|
121
|
-
"ostemplate": args.ostemplate,
|
|
122
|
-
"memory": args.memory,
|
|
123
|
-
"cores": args.cores,
|
|
124
|
-
}
|
|
125
|
-
if args.storage:
|
|
126
|
-
data["storage"] = args.storage
|
|
127
|
-
if args.net:
|
|
128
|
-
data["net0"] = args.net
|
|
129
|
-
if args.password:
|
|
130
|
-
data["password"] = args.password
|
|
131
|
-
return client.post(f"/nodes/{args.node}/lxc", data=data)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _ct_start(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
135
|
-
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
136
|
-
if not node:
|
|
137
|
-
return {"error": f"Container {args.vmid} not found"}
|
|
138
|
-
return client.post(f"/nodes/{node}/lxc/{args.vmid}/status/start")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _ct_stop(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
142
|
-
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
143
|
-
if not node:
|
|
144
|
-
return {"error": f"Container {args.vmid} not found"}
|
|
145
|
-
return client.post(f"/nodes/{node}/lxc/{args.vmid}/status/stop")
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def _ct_delete(args: argparse.Namespace, client: ProxmoxClient) -> dict:
|
|
149
|
-
node = _resolve_ct_node(client, args.node, args.vmid)
|
|
150
|
-
if not node:
|
|
151
|
-
return {"error": f"Container {args.vmid} not found"}
|
|
152
|
-
params: dict = {}
|
|
153
|
-
if args.force:
|
|
154
|
-
params["force"] = 1
|
|
155
|
-
if args.purge:
|
|
156
|
-
params["purge"] = 1
|
|
157
|
-
return client.delete(f"/nodes/{node}/lxc/{args.vmid}", params=params or None)
|
|
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
|