fix(vm_provision_mixin): upload Cloud-Init snippet as a real multipart file

proxmoxer only builds a multipart request for io.IOBase values passed
as kwargs; a plain filename string (plus a nonexistent "data" field,
as the old code sent) goes out as an ordinary form-urlencoded POST
instead. Real Proxmox's /storage/{s}/upload endpoint expects an actual
file upload for "filename" and responds to anything else by closing
the connection with no HTTP response at all.

Found live: the VM shell, disk import, and node-scoped storage
selection all succeeded, then create_vm_from_cloud_init failed with
requests.exceptions.ConnectionError / RemoteDisconnected right at the
snippet upload step.
This commit is contained in:
Christian Manivong
2026-07-07 23:01:27 +02:00
parent fe4f5e84d8
commit 80d9c7335f
2 changed files with 94 additions and 3 deletions
+11 -3
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import io
import logging
import time
import yaml
@@ -337,11 +338,18 @@ class ProxmoxVMProvisionMixin:
filename = f"{vmid}-user-data.yaml"
_logger.debug(f"Uploading Cloud-Init snippet {filename} to {snippet_storage}")
# Upload to snippet storage
# Upload to snippet storage. Proxmox's upload endpoint expects the
# "filename" parameter to BE the file (multipart), not a name
# string with separate content — proxmoxer only builds a
# multipart request when the value is an io.IOBase instance,
# otherwise it silently sends everything as a plain
# form-urlencoded POST, which real Proxmox rejects by dropping
# the connection (RemoteDisconnected, no HTTP response at all).
file_obj = io.BytesIO(user_data_yaml.encode("utf-8"))
file_obj.name = filename
self._node_api().storage(snippet_storage).upload.post(
content="snippets",
filename=filename,
data=user_data_yaml,
filename=file_obj,
)
# Step 7: Configure Cloud-Init references and SSH keys