379 lines
12 KiB
PHP
379 lines
12 KiB
PHP
<?php
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
final class SPP_Repository
|
|
{
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function templates(): array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('templates');
|
|
$rows = $wpdb->get_results("SELECT * FROM {$table} WHERE is_active = 1 ORDER BY name ASC", ARRAY_A);
|
|
|
|
return array_map([$this, 'template_dto'], is_array($rows) ? $rows : []);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
public function template(int $id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('templates');
|
|
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d AND is_active = 1", $id), ARRAY_A);
|
|
|
|
return is_array($row) ? $row : null;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function deployments(): array
|
|
{
|
|
global $wpdb;
|
|
|
|
$deployments = SPP_Activator::table('deployments');
|
|
$templates = SPP_Activator::table('templates');
|
|
$users = $wpdb->users;
|
|
|
|
$rows = $wpdb->get_results(
|
|
"SELECT d.*, t.name AS template_name, t.cpu_cores, t.memory_mb, t.disk_gb, u.display_name AS requested_by_name
|
|
FROM {$deployments} d
|
|
INNER JOIN {$templates} t ON t.id = d.template_id
|
|
INNER JOIN {$users} u ON u.ID = d.requested_by
|
|
WHERE d.status <> 'DELETED'
|
|
ORDER BY d.created_at DESC",
|
|
ARRAY_A
|
|
);
|
|
|
|
return array_map([$this, 'deployment_summary_dto'], is_array($rows) ? $rows : []);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
public function deployment(int $id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$deployments = SPP_Activator::table('deployments');
|
|
$templates = SPP_Activator::table('templates');
|
|
$users = $wpdb->users;
|
|
|
|
$row = $wpdb->get_row(
|
|
$wpdb->prepare(
|
|
"SELECT d.*, t.name AS template_name, t.cpu_cores, t.memory_mb, t.disk_gb, u.display_name AS requested_by_name
|
|
FROM {$deployments} d
|
|
INNER JOIN {$templates} t ON t.id = d.template_id
|
|
INNER JOIN {$users} u ON u.ID = d.requested_by
|
|
WHERE d.id = %d",
|
|
$id
|
|
),
|
|
ARRAY_A
|
|
);
|
|
|
|
return is_array($row) ? $this->deployment_detail_dto($row) : null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $template
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function create_deployment(array $template, string $name, ?int $ttl_hours, int $vm_id, array $ip_addresses, int $actor_id): array
|
|
{
|
|
global $wpdb;
|
|
|
|
$now = current_time('mysql');
|
|
$expires_at = $ttl_hours === null ? null : date('Y-m-d H:i:s', current_time('timestamp') + ($ttl_hours * HOUR_IN_SECONDS));
|
|
$table = SPP_Activator::table('deployments');
|
|
|
|
$wpdb->insert($table, [
|
|
'name' => $name,
|
|
'status' => 'STOPPED',
|
|
'proxmox_vm_id' => $vm_id,
|
|
'ip_addresses' => wp_json_encode(array_values($ip_addresses)),
|
|
'expires_at' => $expires_at,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
'template_id' => (int) $template['id'],
|
|
'requested_by' => $actor_id,
|
|
]);
|
|
|
|
$deployment_id = (int) $wpdb->insert_id;
|
|
$this->audit('DEPLOYMENT_CREATED', 'deployment', $deployment_id, $actor_id, [
|
|
'template_id' => (int) $template['id'],
|
|
'proxmox_vm_id' => $vm_id,
|
|
'ip_addresses' => $ip_addresses,
|
|
'ttl_hours' => $ttl_hours,
|
|
'never_expire' => $ttl_hours === null,
|
|
]);
|
|
|
|
return $this->deployment($deployment_id);
|
|
}
|
|
|
|
public function update_deployment_status(int $id, string $status, string $action, int $actor_id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('deployments');
|
|
$wpdb->update($table, [
|
|
'status' => $status,
|
|
'updated_at' => current_time('mysql'),
|
|
], ['id' => $id]);
|
|
|
|
$this->audit($action, 'deployment', $id, $actor_id, ['status' => $status]);
|
|
|
|
return $this->deployment($id);
|
|
}
|
|
|
|
public function update_deployment_status_and_ips(int $id, string $status, array $ip_addresses, string $action, int $actor_id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('deployments');
|
|
$wpdb->update($table, [
|
|
'status' => $status,
|
|
'ip_addresses' => wp_json_encode(array_values($ip_addresses)),
|
|
'updated_at' => current_time('mysql'),
|
|
], ['id' => $id]);
|
|
|
|
$this->audit($action, 'deployment', $id, $actor_id, [
|
|
'status' => $status,
|
|
'ip_addresses' => $ip_addresses,
|
|
]);
|
|
|
|
return $this->deployment($id);
|
|
}
|
|
|
|
public function update_deployment_ips(int $id, array $ip_addresses, int $actor_id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('deployments');
|
|
$wpdb->update($table, [
|
|
'ip_addresses' => wp_json_encode(array_values($ip_addresses)),
|
|
'updated_at' => current_time('mysql'),
|
|
], ['id' => $id]);
|
|
|
|
$this->audit('DEPLOYMENT_IPS_REFRESHED', 'deployment', $id, $actor_id, [
|
|
'ip_addresses' => $ip_addresses,
|
|
]);
|
|
|
|
return $this->deployment($id);
|
|
}
|
|
|
|
public function prolong_deployment(int $id, ?int $ttl_hours, int $actor_id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$expires_at = $ttl_hours === null ? null : date('Y-m-d H:i:s', current_time('timestamp') + ($ttl_hours * HOUR_IN_SECONDS));
|
|
$table = SPP_Activator::table('deployments');
|
|
|
|
$wpdb->update($table, [
|
|
'status' => 'STOPPED',
|
|
'expires_at' => $expires_at,
|
|
'error_message' => null,
|
|
'updated_at' => current_time('mysql'),
|
|
], ['id' => $id]);
|
|
|
|
$this->audit('DEPLOYMENT_PROLONGED', 'deployment', $id, $actor_id, [
|
|
'ttl_hours' => $ttl_hours,
|
|
'never_expire' => $ttl_hours === null,
|
|
]);
|
|
|
|
return $this->deployment($id);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
public function deployment_record(int $id): ?array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('deployments');
|
|
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
|
|
|
|
return is_array($row) ? $row : null;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function due_for_expiration(): array
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('deployments');
|
|
$now = current_time('mysql');
|
|
$rows = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"SELECT * FROM {$table}
|
|
WHERE expires_at IS NOT NULL
|
|
AND expires_at <= %s
|
|
AND status IN ('PROVISIONING', 'STOPPED', 'RUNNING')",
|
|
$now
|
|
),
|
|
ARRAY_A
|
|
);
|
|
|
|
return is_array($rows) ? $rows : [];
|
|
}
|
|
|
|
public function mark_deployment_expired(int $id, ?string $stop_error): void
|
|
{
|
|
global $wpdb;
|
|
|
|
$table = SPP_Activator::table('deployments');
|
|
$wpdb->update($table, [
|
|
'status' => 'EXPIRED',
|
|
'error_message' => $stop_error,
|
|
'updated_at' => current_time('mysql'),
|
|
], ['id' => $id]);
|
|
|
|
$this->audit('DEPLOYMENT_EXPIRED', 'deployment', $id, 0, [
|
|
'stopped' => $stop_error === null,
|
|
'stop_error' => $stop_error,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array{userUsedMb:int,userLimitMb:int,globalUsedMb:int,globalLimitMb:int}
|
|
*/
|
|
public function quota(int $actor_id): array
|
|
{
|
|
return [
|
|
'userUsedMb' => $this->allocated_memory_mb($actor_id),
|
|
'userLimitMb' => $this->user_memory_limit_mb($actor_id),
|
|
'globalUsedMb' => $this->allocated_memory_mb(null),
|
|
'globalLimitMb' => max(0, (int) get_option('spp_quota_global_memory_mb', 0)),
|
|
];
|
|
}
|
|
|
|
private function user_memory_limit_mb(int $actor_id): int
|
|
{
|
|
$user_limit = get_user_meta($actor_id, 'spp_memory_quota_mb', true);
|
|
|
|
if ($user_limit !== '') {
|
|
return max(0, (int) $user_limit);
|
|
}
|
|
|
|
return max(0, (int) get_option('spp_quota_user_memory_mb', 0));
|
|
}
|
|
|
|
private function allocated_memory_mb(?int $actor_id): int
|
|
{
|
|
global $wpdb;
|
|
|
|
$deployments = SPP_Activator::table('deployments');
|
|
$templates = SPP_Activator::table('templates');
|
|
$active_statuses = ['PROVISIONING', 'STOPPED', 'RUNNING', 'DELETING'];
|
|
$placeholders = implode(',', array_fill(0, count($active_statuses), '%s'));
|
|
|
|
$sql = "SELECT COALESCE(SUM(t.memory_mb), 0)
|
|
FROM {$deployments} d
|
|
INNER JOIN {$templates} t ON t.id = d.template_id
|
|
WHERE d.status IN ({$placeholders})";
|
|
$params = $active_statuses;
|
|
|
|
if ($actor_id !== null) {
|
|
$sql .= ' AND d.requested_by = %d';
|
|
$params[] = $actor_id;
|
|
}
|
|
|
|
return (int) $wpdb->get_var($wpdb->prepare($sql, $params));
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $metadata
|
|
*/
|
|
private function audit(string $action, string $entity_type, int $entity_id, int $actor_id, array $metadata): void
|
|
{
|
|
global $wpdb;
|
|
|
|
$wpdb->insert(SPP_Activator::table('audit_logs'), [
|
|
'action' => $action,
|
|
'entity_type' => $entity_type,
|
|
'entity_id' => $entity_id,
|
|
'actor_id' => $actor_id,
|
|
'metadata' => wp_json_encode($metadata),
|
|
'created_at' => current_time('mysql'),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function template_dto(array $row): array
|
|
{
|
|
return [
|
|
'id' => (int) $row['id'],
|
|
'key' => (string) $row['template_key'],
|
|
'name' => (string) $row['name'],
|
|
'description' => (string) $row['description'],
|
|
'osType' => (string) $row['os_type'],
|
|
'cpuCores' => (int) $row['cpu_cores'],
|
|
'memoryMb' => (int) $row['memory_mb'],
|
|
'diskGb' => (int) $row['disk_gb'],
|
|
'defaultTtlHours' => (int) $row['default_ttl_hours'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function deployment_summary_dto(array $row): array
|
|
{
|
|
return [
|
|
'id' => (int) $row['id'],
|
|
'name' => (string) $row['name'],
|
|
'status' => (string) $row['status'],
|
|
'templateName' => (string) $row['template_name'],
|
|
'requestedByName' => (string) $row['requested_by_name'],
|
|
'ipAddresses' => $this->ip_addresses_from_row($row),
|
|
'expiresAt' => $row['expires_at'] === null ? null : (string) $row['expires_at'],
|
|
'createdAt' => (string) $row['created_at'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function deployment_detail_dto(array $row): array
|
|
{
|
|
return array_merge($this->deployment_summary_dto($row), [
|
|
'templateId' => (int) $row['template_id'],
|
|
'proxmoxVmId' => isset($row['proxmox_vm_id']) ? (int) $row['proxmox_vm_id'] : null,
|
|
'cpuCores' => (int) $row['cpu_cores'],
|
|
'memoryMb' => (int) $row['memory_mb'],
|
|
'diskGb' => (int) $row['disk_gb'],
|
|
'errorMessage' => $row['error_message'] === null ? null : (string) $row['error_message'],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array<int, string>
|
|
*/
|
|
private function ip_addresses_from_row(array $row): array
|
|
{
|
|
if (empty($row['ip_addresses'])) {
|
|
return [];
|
|
}
|
|
|
|
$decoded = json_decode((string) $row['ip_addresses'], true);
|
|
|
|
return is_array($decoded) ? array_values(array_map('strval', $decoded)) : [];
|
|
}
|
|
}
|