feat: Add IP address and DNS fields to module_vmware table

- Introduced `ip` and `dns` columns to the `module_vmware` table to store assigned IP addresses and DNS configurations.
- Implemented `ChangePasswordViaSshJob` for secure password changes via SSH.
- Created `ChangePasswordJob` for handling password changes during VM provisioning.
- Developed `ChangePasswordViaVsphereJob` to reset VM guest OS passwords using PowerShell scripts executed on the vSphere host.
- Added `SendVmCredentialsEmailJob` to send final VM credentials to customers.
- Implemented `WaitVmReadyJob` to poll vSphere API until VMware Tools are running and guest IP is available.
- Created `VmProvisionService` for managing VM provisioning tasks, including password generation and updates.
- Developed `VsphereHostSshService` for executing PowerShell scripts on the vSphere host via SSH.
- Added `IpPoolSeeder` to populate IP pools for VM service initialization.
This commit is contained in:
pbao9
2026-04-16 23:21:27 +07:00
parent d68fda2cf0
commit 5043bea79a
16 changed files with 1599 additions and 172 deletions
@@ -23,13 +23,16 @@ class VmController extends Controller
/**
* POST /v1/system/vm-power-on
*
* Body: { "product": <ModuleVmware.id> }
* Body: { "product": <ModuleVmware.id|vmware_id|service_uuid> }
*/
public function powerOn(Request $request, PowerOnVMAction $action): JsonResponse
{
$validated = $request->validate(['product' => ['required', 'integer']]);
$productId = $request->input('product');
if (!$productId) {
return response()->json(['success' => false, 'message' => 'Thiếu product id.'], 422);
}
$vm = $this->resolveVm((int) $validated['product'], $request);
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
@@ -46,13 +49,16 @@ class VmController extends Controller
/**
* POST /v1/system/vm-power-off
*
* Body: { "product": <ModuleVmware.id> }
* Body: { "product": <ModuleVmware.id|vmware_id|service_uuid> }
*/
public function powerOff(Request $request, PowerOffVMAction $action): JsonResponse
{
$validated = $request->validate(['product' => ['required', 'integer']]);
$productId = $request->input('product');
if (!$productId) {
return response()->json(['success' => false, 'message' => 'Thiếu product id.'], 422);
}
$vm = $this->resolveVm((int) $validated['product'], $request);
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
@@ -190,17 +196,17 @@ class VmController extends Controller
* POST /v1/system/vm-change-port
*
* Body:
* product int ModuleVmware.id
* product int|string ModuleVmware.id|vmware_id|service_uuid
* new_port int New RDP port (1024–65535)
*/
public function changePort(Request $request, ChangePortVMAction $action): JsonResponse
{
$validated = $request->validate([
'product' => ['required', 'integer'],
'product' => ['required'],
'new_port' => ['required', 'integer', 'min:1024', 'max:65535'],
]);
$vm = $this->resolveVm((int) $validated['product'], $request);
$vm = $this->resolveVmById($validated['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
@@ -239,25 +245,57 @@ class VmController extends Controller
// -------------------------------------------------------------------------
/**
* Load a ModuleVmware by ID, verify the authenticated user owns it.
* Load a ModuleVmware by ID (int), vmware_id (string), or Service UUID.
* Verify the authenticated user owns it.
* Returns JsonResponse on error, ModuleVmware on success.
*/
private function resolveVm(int $id, Request $request): ModuleVmware|JsonResponse
private function resolveVmById(int|string $productId, Request $request): ModuleVmware|JsonResponse
{
$vm = ModuleVmware::with(['vCenter', 'ipAddress.host.datacenter.vCenter'])
->find($id);
$customer = $request->user();
$vm = null;
// Check if it's a UUID (Service UUID)
if (is_string($productId) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $productId)) {
$service = Service::with('vmware.ipAddress.host.datacenter.vCenter', 'vmware.vCenter')
->where('uuid', $productId)
->where('customer_id', $customer->id)
->first();
if ($service && $service->vmware) {
$vm = $service->vmware;
}
} elseif (is_numeric($productId)) {
// Numeric ID
$vm = ModuleVmware::with(['vCenter', 'ipAddress.host.datacenter.vCenter'])
->find((int) $productId);
} else {
// vmware_id string (e.g., "vm-3238")
$vm = ModuleVmware::with(['vCenter', 'ipAddress.host.datacenter.vCenter'])
->where('vmware_id', $productId)
->where('customer_id', $customer->id)
->first();
}
if (!$vm) {
return response()->json(['success' => false, 'message' => 'Không tìm thấy VM.'], 404);
}
if ((int) $vm->customer_id !== (int) $request->user()->id) {
if ((int) $vm->customer_id !== (int) $customer->id) {
return response()->json(['success' => false, 'message' => 'Bạn không có quyền thao tác VM này.'], 403);
}
return $vm;
}
/**
* Load a ModuleVmware by ID, verify the authenticated user owns it.
* Returns JsonResponse on error, ModuleVmware on success.
* @deprecated Use resolveVmById instead for better ID format support
*/
private function resolveVm(int $id, Request $request): ModuleVmware|JsonResponse
{
return $this->resolveVmById($id, $request);
}
/**
* Normalise the array|string return value from Power ON/OFF actions.
*/