- Added template typing so approved templates can represent either QEMU VMs or LXC containers.
- Added LXC template discovery from Proxmox storage `vztmpl` content in the admin template manager. - Added live LXC container provisioning through the Proxmox API with configurable rootfs storage and optional DHCP bridge. - Routed start, stop, delete, expiration, status, and IP refresh operations through typed Proxmox VM/LXC API paths. - Added Proxmox tags to newly created VMs and containers, including a sanitized per-user tag for easier PVE administration. - Updated the admin and portal UI to show VM versus LXC template/deployment types and generic Proxmox resource IDs. - Added schema upgrades for template provisioning type, LXC template references, and deployment resource type. - Documented LXC setup, storage permissions, and the new Proxmox settings.
This commit is contained in:
@@ -100,7 +100,7 @@ final class SPP_Admin_Page
|
||||
'settings_saved' => __('Settings saved.', 'support-provisioning-portal'),
|
||||
'template_saved' => __('Template saved.', 'support-provisioning-portal'),
|
||||
'template_removed' => __('Template removed from new provisioning.', 'support-provisioning-portal'),
|
||||
'template_error' => __('Template could not be saved. Check the fields and confirm the VMID exists as a Proxmox QEMU template on the configured node.', 'support-provisioning-portal'),
|
||||
'template_error' => __('Template could not be saved. Check the fields and confirm the selected Proxmox VM or LXC template exists on the configured node.', 'support-provisioning-portal'),
|
||||
'user_access_saved' => __('User rights saved.', 'support-provisioning-portal'),
|
||||
'manager_required' => __('At least one user must keep the Manage user rights permission.', 'support-provisioning-portal'),
|
||||
];
|
||||
@@ -169,6 +169,14 @@ final class SPP_Admin_Page
|
||||
<span>Node</span>
|
||||
<input name="spp_proxmox_node" type="text" value="<?php echo esc_attr(get_option('spp_proxmox_node', 'pve-01')); ?>">
|
||||
</label>
|
||||
<label>
|
||||
<span>LXC rootfs storage</span>
|
||||
<input name="spp_lxc_rootfs_storage" type="text" value="<?php echo esc_attr(get_option('spp_lxc_rootfs_storage', '')); ?>" placeholder="local-lvm">
|
||||
</label>
|
||||
<label>
|
||||
<span>LXC network bridge</span>
|
||||
<input name="spp_lxc_bridge" type="text" value="<?php echo esc_attr(get_option('spp_lxc_bridge', 'vmbr0')); ?>" placeholder="vmbr0">
|
||||
</label>
|
||||
<h2><?php echo esc_html__('RAM Contingents', 'support-provisioning-portal'); ?></h2>
|
||||
<p class="description"><?php echo esc_html__('Set 0 for unlimited. Active allocations include provisioning, stopped, running, and deleting deployments.', 'support-provisioning-portal'); ?></p>
|
||||
<label>
|
||||
@@ -187,7 +195,7 @@ final class SPP_Admin_Page
|
||||
private function render_template_management(): void
|
||||
{
|
||||
$approved_templates = $this->repository->admin_templates();
|
||||
$active_proxmox_ids = $this->repository->active_proxmox_template_ids();
|
||||
$active_template_keys = $this->repository->active_template_identity_keys();
|
||||
$proxmox_error = null;
|
||||
|
||||
try {
|
||||
@@ -220,7 +228,7 @@ final class SPP_Admin_Page
|
||||
<div class="spp-template-row-head">
|
||||
<div>
|
||||
<strong><?php echo esc_html((string) $template['name']); ?></strong>
|
||||
<span><?php echo esc_html(sprintf('PVE VMID %d', (int) $template['proxmoxTemplateId'])); ?></span>
|
||||
<span><?php echo esc_html($this->template_identity_label($template)); ?></span>
|
||||
</div>
|
||||
<?php if (!empty($template['isActive'])) : ?>
|
||||
<span class="spp-badge RUNNING"><?php echo esc_html__('Active', 'support-provisioning-portal'); ?></span>
|
||||
@@ -229,12 +237,24 @@ final class SPP_Admin_Page
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="spp-template-fields">
|
||||
<input type="hidden" name="spp_provisioning_type" value="<?php echo esc_attr((string) $template['provisioningType']); ?>">
|
||||
<label>Name
|
||||
<input name="spp_template_name" type="text" required maxlength="160" value="<?php echo esc_attr((string) $template['name']); ?>">
|
||||
</label>
|
||||
<label>PVE template VMID
|
||||
<input name="spp_proxmox_template_id" type="number" min="1" required value="<?php echo esc_attr((string) $template['proxmoxTemplateId']); ?>">
|
||||
<label>Type
|
||||
<input type="text" readonly value="<?php echo esc_attr($this->template_type_label((string) $template['provisioningType'])); ?>">
|
||||
</label>
|
||||
<?php if ((string) $template['provisioningType'] === 'lxc') : ?>
|
||||
<input type="hidden" name="spp_proxmox_template_id" value="0">
|
||||
<label>LXC template ref
|
||||
<input name="spp_proxmox_template_ref" type="text" readonly required value="<?php echo esc_attr((string) $template['proxmoxTemplateRef']); ?>">
|
||||
</label>
|
||||
<?php else : ?>
|
||||
<input type="hidden" name="spp_proxmox_template_ref" value="">
|
||||
<label>PVE template VMID
|
||||
<input name="spp_proxmox_template_id" type="number" min="1" required value="<?php echo esc_attr((string) $template['proxmoxTemplateId']); ?>">
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<label>OS type
|
||||
<?php $this->render_os_type_select((string) $template['osType']); ?>
|
||||
</label>
|
||||
@@ -271,17 +291,27 @@ final class SPP_Admin_Page
|
||||
<p class="spp-error"><?php echo esc_html($proxmox_error); ?></p>
|
||||
<?php elseif (empty($proxmox_templates)) : ?>
|
||||
<div class="spp-panel spp-admin-notice">
|
||||
<p><?php echo esc_html__('No QEMU templates were returned by Proxmox for the configured node.', 'support-provisioning-portal'); ?></p>
|
||||
<p><?php echo esc_html__('No QEMU VM or LXC templates were returned by Proxmox for the configured node.', 'support-provisioning-portal'); ?></p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="spp-pve-template-grid">
|
||||
<?php foreach ($proxmox_templates as $template) : ?>
|
||||
<?php $is_imported = in_array((int) $template['vmId'], $active_proxmox_ids, true); ?>
|
||||
<?php
|
||||
$provisioning_type = $this->normalise_template_type((string) ($template['provisioningType'] ?? 'qemu'));
|
||||
$template_ref = (string) ($template['templateRef'] ?? '');
|
||||
$is_imported = in_array(
|
||||
$this->template_identity_key($provisioning_type, (int) ($template['vmId'] ?? 0), $template_ref),
|
||||
$active_template_keys,
|
||||
true
|
||||
);
|
||||
?>
|
||||
<form class="spp-pve-template" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<input type="hidden" name="action" value="spp_save_template">
|
||||
<input type="hidden" name="spp_template_action" value="import">
|
||||
<input type="hidden" name="spp_provisioning_type" value="<?php echo esc_attr($provisioning_type); ?>">
|
||||
<input type="hidden" name="spp_template_name" value="<?php echo esc_attr((string) $template['name']); ?>">
|
||||
<input type="hidden" name="spp_proxmox_template_id" value="<?php echo esc_attr((string) $template['vmId']); ?>">
|
||||
<input type="hidden" name="spp_proxmox_template_id" value="<?php echo esc_attr((string) ($template['vmId'] ?? 0)); ?>">
|
||||
<input type="hidden" name="spp_proxmox_template_ref" value="<?php echo esc_attr($template_ref); ?>">
|
||||
<input type="hidden" name="spp_cpu_cores" value="<?php echo esc_attr((string) $template['cpuCores']); ?>">
|
||||
<input type="hidden" name="spp_memory_mb" value="<?php echo esc_attr((string) $template['memoryMb']); ?>">
|
||||
<input type="hidden" name="spp_disk_gb" value="<?php echo esc_attr((string) $template['diskGb']); ?>">
|
||||
@@ -289,13 +319,18 @@ final class SPP_Admin_Page
|
||||
<div class="spp-template-row-head">
|
||||
<div>
|
||||
<strong><?php echo esc_html((string) $template['name']); ?></strong>
|
||||
<span><?php echo esc_html(sprintf('PVE VMID %d', (int) $template['vmId'])); ?></span>
|
||||
<span><?php echo esc_html($this->template_identity_label([
|
||||
'provisioningType' => $provisioning_type,
|
||||
'proxmoxTemplateId' => (int) ($template['vmId'] ?? 0),
|
||||
'proxmoxTemplateRef' => $template_ref,
|
||||
])); ?></span>
|
||||
</div>
|
||||
<?php if ($is_imported) : ?>
|
||||
<span class="spp-badge RUNNING"><?php echo esc_html__('Imported', 'support-provisioning-portal'); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="spp-meta">
|
||||
<span>Type<strong><?php echo esc_html($this->template_type_label($provisioning_type)); ?></strong></span>
|
||||
<span>CPU<strong><?php echo esc_html((string) $template['cpuCores']); ?> cores</strong></span>
|
||||
<span>Memory<strong><?php echo esc_html((string) $template['memoryMb']); ?> MB</strong></span>
|
||||
<span>Disk<strong><?php echo esc_html((string) $template['diskGb']); ?> GB</strong></span>
|
||||
@@ -309,7 +344,7 @@ final class SPP_Admin_Page
|
||||
<input name="spp_default_ttl_hours" type="number" min="1" max="720" value="168" required>
|
||||
</label>
|
||||
<label>Description
|
||||
<textarea name="spp_template_description" rows="2" required><?php echo esc_textarea(sprintf('Imported from Proxmox template VMID %d.', (int) $template['vmId'])); ?></textarea>
|
||||
<textarea name="spp_template_description" rows="2" required><?php echo esc_textarea($provisioning_type === 'lxc' ? sprintf('Imported from Proxmox LXC template %s.', $template_ref) : sprintf('Imported from Proxmox template VMID %d.', (int) $template['vmId'])); ?></textarea>
|
||||
</label>
|
||||
<button class="button button-primary" type="submit"><?php echo esc_html__('Import Template', 'support-provisioning-portal'); ?></button>
|
||||
<?php endif; ?>
|
||||
@@ -318,10 +353,12 @@ final class SPP_Admin_Page
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3><?php echo esc_html__('Add Template Manually', 'support-provisioning-portal'); ?></h3>
|
||||
<h3><?php echo esc_html__('Add QEMU Template Manually', 'support-provisioning-portal'); ?></h3>
|
||||
<form class="spp-template-row" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<input type="hidden" name="action" value="spp_save_template">
|
||||
<input type="hidden" name="spp_template_action" value="import">
|
||||
<input type="hidden" name="spp_provisioning_type" value="qemu">
|
||||
<input type="hidden" name="spp_proxmox_template_ref" value="">
|
||||
<?php wp_nonce_field('spp_save_template'); ?>
|
||||
<div class="spp-template-fields">
|
||||
<label>Name
|
||||
@@ -464,6 +501,8 @@ final class SPP_Admin_Page
|
||||
update_option('spp_proxmox_token_secret', $token_secret);
|
||||
}
|
||||
update_option('spp_proxmox_node', sanitize_text_field($this->posted_string('spp_proxmox_node')));
|
||||
update_option('spp_lxc_rootfs_storage', $this->sanitize_proxmox_identifier($this->posted_string('spp_lxc_rootfs_storage')));
|
||||
update_option('spp_lxc_bridge', $this->sanitize_proxmox_identifier($this->posted_string('spp_lxc_bridge')));
|
||||
update_option('spp_quota_user_memory_mb', max(0, absint($this->posted_string('spp_quota_user_memory_mb'))));
|
||||
update_option('spp_quota_global_memory_mb', max(0, absint($this->posted_string('spp_quota_global_memory_mb'))));
|
||||
|
||||
@@ -495,7 +534,11 @@ final class SPP_Admin_Page
|
||||
$this->redirect_to_admin_page('template_error');
|
||||
}
|
||||
|
||||
if (!$this->proxmox_template_exists((int) $data['proxmox_template_id'])) {
|
||||
if (!$this->proxmox_template_exists(
|
||||
(string) $data['provisioning_type'],
|
||||
(int) $data['proxmox_template_id'],
|
||||
(string) $data['proxmox_template_ref']
|
||||
)) {
|
||||
$this->redirect_to_admin_page('template_error');
|
||||
}
|
||||
|
||||
@@ -579,7 +622,13 @@ final class SPP_Admin_Page
|
||||
|
||||
private function posted_string(string $key): string
|
||||
{
|
||||
return isset($_POST[$key]) ? (string) wp_unslash($_POST[$key]) : '';
|
||||
if (!isset($_POST[$key])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = wp_unslash($_POST[$key]);
|
||||
|
||||
return is_scalar($value) ? (string) $value : '';
|
||||
}
|
||||
|
||||
private function sanitize_proxmox_base_url(string $value): string
|
||||
@@ -593,11 +642,30 @@ final class SPP_Admin_Page
|
||||
return wp_parse_url($url, PHP_URL_SCHEME) === 'https' ? $url : '';
|
||||
}
|
||||
|
||||
private function proxmox_template_exists(int $vm_id): bool
|
||||
private function sanitize_proxmox_identifier(string $value): string
|
||||
{
|
||||
$value = sanitize_text_field($value);
|
||||
$value = preg_replace('/[^A-Za-z0-9_.:-]/', '', $value);
|
||||
|
||||
return $value === null ? '' : $value;
|
||||
}
|
||||
|
||||
private function proxmox_template_exists(string $provisioning_type, int $vm_id, string $template_ref): bool
|
||||
{
|
||||
$provisioning_type = $this->normalise_template_type($provisioning_type);
|
||||
|
||||
try {
|
||||
foreach ($this->proxmox->list_templates() as $template) {
|
||||
if ((int) $template['vmId'] === $vm_id) {
|
||||
$candidate_type = $this->normalise_template_type((string) ($template['provisioningType'] ?? 'qemu'));
|
||||
if ($candidate_type !== $provisioning_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($candidate_type === 'lxc' && (string) ($template['templateRef'] ?? '') === $template_ref) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($candidate_type === 'qemu' && (int) ($template['vmId'] ?? 0) === $vm_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -614,15 +682,25 @@ final class SPP_Admin_Page
|
||||
private function posted_template_data(): ?array
|
||||
{
|
||||
$name = sanitize_text_field($this->posted_string('spp_template_name'));
|
||||
$provisioning_type = $this->normalise_template_type($this->posted_string('spp_provisioning_type'));
|
||||
$proxmox_template_id = absint($this->posted_string('spp_proxmox_template_id'));
|
||||
$proxmox_template_ref = sanitize_text_field($this->posted_string('spp_proxmox_template_ref'));
|
||||
$description = sanitize_textarea_field($this->posted_string('spp_template_description'));
|
||||
|
||||
if ($name === '' || $proxmox_template_id < 1 || $description === '') {
|
||||
if ($name === '' || $description === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($provisioning_type === 'qemu' && $proxmox_template_id < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($provisioning_type === 'lxc' && $proxmox_template_ref === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'template_key' => 'pve-template-' . $proxmox_template_id . '-' . sanitize_title($name),
|
||||
'template_key' => $this->template_identity_key($provisioning_type, $proxmox_template_id, $proxmox_template_ref) . '-' . sanitize_title($name),
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'os_type' => $this->posted_os_type(),
|
||||
@@ -630,7 +708,9 @@ final class SPP_Admin_Page
|
||||
'memory_mb' => max(128, absint($this->posted_string('spp_memory_mb'))),
|
||||
'disk_gb' => max(1, absint($this->posted_string('spp_disk_gb'))),
|
||||
'default_ttl_hours' => max(1, min(720, absint($this->posted_string('spp_default_ttl_hours')))),
|
||||
'provisioning_type' => $provisioning_type,
|
||||
'proxmox_template_id' => $proxmox_template_id,
|
||||
'proxmox_template_ref' => $proxmox_template_ref,
|
||||
'is_active' => $this->posted_string('spp_is_active') === '1',
|
||||
];
|
||||
}
|
||||
@@ -642,6 +722,37 @@ final class SPP_Admin_Page
|
||||
return in_array($os_type, ['LINUX', 'WINDOWS', 'APPLIANCE', 'OTHER'], true) ? $os_type : 'OTHER';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $template
|
||||
*/
|
||||
private function template_identity_label(array $template): string
|
||||
{
|
||||
$type = $this->normalise_template_type((string) ($template['provisioningType'] ?? 'qemu'));
|
||||
|
||||
if ($type === 'lxc') {
|
||||
return 'LXC ' . (string) ($template['proxmoxTemplateRef'] ?? $template['templateRef'] ?? '');
|
||||
}
|
||||
|
||||
return sprintf('PVE VMID %d', (int) ($template['proxmoxTemplateId'] ?? $template['vmId'] ?? 0));
|
||||
}
|
||||
|
||||
private function template_type_label(string $type): string
|
||||
{
|
||||
return $this->normalise_template_type($type) === 'lxc' ? 'LXC container' : 'QEMU VM';
|
||||
}
|
||||
|
||||
private function template_identity_key(string $provisioning_type, int $proxmox_template_id, string $proxmox_template_ref): string
|
||||
{
|
||||
return $this->normalise_template_type($provisioning_type) === 'lxc'
|
||||
? 'lxc:' . $proxmox_template_ref
|
||||
: 'qemu:' . $proxmox_template_id;
|
||||
}
|
||||
|
||||
private function normalise_template_type(string $type): string
|
||||
{
|
||||
return strtolower($type) === 'lxc' ? 'lxc' : 'qemu';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int>
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user