feat(api): support netN add/remove in VM and LXC config updates
- update_vm_config: whitelist netN/scsiN/virtioN/ideN/sataN/ipconfigN by regex instead of hardcoded 0-3; accept validated delete='netN' key; raise with real Proxmox error text instead of returning False - update_container_config: refactor to dict signature with whitelist + netN + delete (fixes LXC config PUT endpoint that passed the whole dict as hostname) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1290,32 +1290,38 @@ class ProxmoxClient(VmMixin, LxcMixin, ClusterMixin, StorageMixin, NetworkMixin,
|
|||||||
if not self.proxmox:
|
if not self.proxmox:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Фильтруем разрешенные параметры
|
||||||
|
allowed_params = {
|
||||||
|
'cores', 'sockets', 'memory', 'balloon', 'name', 'description',
|
||||||
|
'cpu', 'cpulimit', 'cpuunits', 'onboot', 'boot', 'bootdisk',
|
||||||
|
'agent', 'ostype', 'tablet', 'hotplug',
|
||||||
|
'ciuser', 'cipassword', 'sshkeys',
|
||||||
|
}
|
||||||
|
indexed_re = re.compile(r'^(net|scsi|virtio|ide|sata|ipconfig)\d+$')
|
||||||
|
delete_re = re.compile(r'^net\d+(,net\d+)*$')
|
||||||
|
|
||||||
|
filtered_config = {
|
||||||
|
k: v for k, v in config.items()
|
||||||
|
if k in allowed_params or indexed_re.match(k)
|
||||||
|
}
|
||||||
|
# Удаление интерфейсов: delete='net1' (или 'net1,net2')
|
||||||
|
delete_value = config.get('delete')
|
||||||
|
if isinstance(delete_value, str) and delete_re.match(delete_value):
|
||||||
|
filtered_config['delete'] = delete_value
|
||||||
|
|
||||||
|
if not filtered_config:
|
||||||
|
logger.warning(f"Нет разрешенных параметров для обновления VM {vmid}")
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Фильтруем разрешенные параметры
|
self.proxmox.nodes(node).qemu(vmid).config.put(**filtered_config)
|
||||||
allowed_params = {
|
logger.info(f"Конфигурация VM {vmid} обновлена: {list(filtered_config.keys())}")
|
||||||
'cores', 'sockets', 'memory', 'balloon', 'name', 'description',
|
return True
|
||||||
'cpu', 'cpulimit', 'cpuunits', 'onboot', 'boot', 'bootdisk',
|
|
||||||
'net0', 'net1', 'net2', 'net3',
|
|
||||||
'scsi0', 'scsi1', 'scsi2', 'scsi3',
|
|
||||||
'virtio0', 'virtio1', 'virtio2', 'virtio3',
|
|
||||||
'ide0', 'ide1', 'ide2', 'ide3',
|
|
||||||
'sata0', 'sata1', 'sata2', 'sata3',
|
|
||||||
'agent', 'ostype', 'tablet', 'hotplug',
|
|
||||||
'ciuser', 'cipassword', 'sshkeys', 'ipconfig0', 'ipconfig1',
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered_config = {k: v for k, v in config.items() if k in allowed_params}
|
|
||||||
|
|
||||||
if filtered_config:
|
|
||||||
self.proxmox.nodes(node).qemu(vmid).config.put(**filtered_config)
|
|
||||||
logger.info(f"Конфигурация VM {vmid} обновлена: {list(filtered_config.keys())}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.warning(f"Нет разрешенных параметров для обновления VM {vmid}")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка обновления конфигурации VM {vmid}: {e}")
|
logger.error(f"Ошибка обновления конфигурации VM {vmid}: {e}")
|
||||||
return False
|
raise RuntimeError(f"Proxmox: {e}") from e
|
||||||
|
|
||||||
def get_container_config(self, node: str, vmid: int) -> Optional[Dict]:
|
def get_container_config(self, node: str, vmid: int) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
@@ -1338,68 +1344,51 @@ class ProxmoxClient(VmMixin, LxcMixin, ClusterMixin, StorageMixin, NetworkMixin,
|
|||||||
logger.error(f"Ошибка получения конфигурации LXC {vmid}: {e}")
|
logger.error(f"Ошибка получения конфигурации LXC {vmid}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update_container_config(
|
def update_container_config(self, node: str, vmid: int, config: Dict) -> bool:
|
||||||
self,
|
|
||||||
node: str,
|
|
||||||
vmid: int,
|
|
||||||
hostname: str = None,
|
|
||||||
memory: int = None,
|
|
||||||
swap: int = None,
|
|
||||||
cores: int = None,
|
|
||||||
cpulimit: float = None,
|
|
||||||
onboot: bool = None,
|
|
||||||
description: str = None,
|
|
||||||
net0: str = None
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Обновить конфигурацию LXC контейнера
|
Обновить конфигурацию LXC контейнера
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
node: Имя ноды
|
node: Имя ноды
|
||||||
vmid: ID контейнера
|
vmid: ID контейнера
|
||||||
hostname: Новое имя хоста
|
config: Словарь с параметрами для обновления
|
||||||
memory: Память в MB
|
|
||||||
swap: Swap в MB
|
|
||||||
cores: Количество ядер
|
|
||||||
cpulimit: Лимит CPU (0.0-128.0)
|
|
||||||
onboot: Автозапуск
|
|
||||||
description: Описание
|
|
||||||
net0: Конфигурация сети
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True при успехе
|
True при успехе
|
||||||
"""
|
"""
|
||||||
if not self.proxmox:
|
if not self.proxmox:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
import re
|
||||||
params = {}
|
|
||||||
|
allowed_params = {
|
||||||
if hostname:
|
'hostname', 'memory', 'swap', 'cores', 'cpulimit', 'cpuunits',
|
||||||
params['hostname'] = hostname
|
'onboot', 'description', 'nameserver', 'searchdomain', 'tags',
|
||||||
if memory is not None:
|
}
|
||||||
params['memory'] = memory
|
net_re = re.compile(r'^net\d+$')
|
||||||
if swap is not None:
|
delete_re = re.compile(r'^net\d+(,net\d+)*$')
|
||||||
params['swap'] = swap
|
|
||||||
if cores is not None:
|
filtered_config = {}
|
||||||
params['cores'] = cores
|
for k, v in config.items():
|
||||||
if cpulimit is not None:
|
if k == 'onboot':
|
||||||
params['cpulimit'] = cpulimit
|
filtered_config['onboot'] = 1 if v in (True, 1, '1') else 0
|
||||||
if onboot is not None:
|
elif k in allowed_params or net_re.match(k):
|
||||||
params['onboot'] = 1 if onboot else 0
|
filtered_config[k] = v
|
||||||
if description is not None:
|
delete_value = config.get('delete')
|
||||||
params['description'] = description
|
if isinstance(delete_value, str) and delete_re.match(delete_value):
|
||||||
if net0:
|
filtered_config['delete'] = delete_value
|
||||||
params['net0'] = net0
|
|
||||||
|
if not filtered_config:
|
||||||
if params:
|
logger.warning(f"Нет разрешенных параметров для обновления LXC {vmid}")
|
||||||
self.proxmox.nodes(node).lxc(vmid).config.put(**params)
|
|
||||||
logger.info(f"Конфигурация LXC {vmid} обновлена: {list(params.keys())}")
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.proxmox.nodes(node).lxc(vmid).config.put(**filtered_config)
|
||||||
|
logger.info(f"Конфигурация LXC {vmid} обновлена: {list(filtered_config.keys())}")
|
||||||
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка обновления конфигурации LXC {vmid}: {e}")
|
logger.error(f"Ошибка обновления конфигурации LXC {vmid}: {e}")
|
||||||
return False
|
raise RuntimeError(f"Proxmox: {e}") from e
|
||||||
|
|
||||||
def resize_container_disk(self, node: str, vmid: int, disk: str, size: str) -> bool:
|
def resize_container_disk(self, node: str, vmid: int, disk: str, size: str) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user