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
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Jobs\VirtualMachine\Provision;
use App\Http\Facade\SignRSA;
use App\Models\ModuleVmware;
use App\Models\vCenter;
use App\Services\VmProvisionService;
use App\Services\VsphereHostSshService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
/**
* ChangePasswordViaVsphereJob
*
* Resets the VM guest OS password via:
* Laravel → SSH (10.10.10.173) → PowerShell PS1 → Invoke-VMScript (guest OS)
*
* Flow:
* [1] VM already exists, vm_id known
* [2] Generate random 16-char password (plain text)
* [3] Load vCenter credentials from module_vcenter
* [4] SSH into vSphere host
* [5] Execute changePassword{Linux|Windows}.ps1 with 5 params
* [6] Parse "SUCCESS" / "FAIL" from script output
* [7] On SUCCESS → save encrypted password to DB, dispatch email
* [8] On FAIL → throw exception → job retries
*/
class ChangePasswordViaVsphereJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5;
public int $timeout = 180;
public int $backoff = 60;
public function __construct(
public readonly int $moduleVmwareId,
public readonly string $vmId,
public readonly string $osFamily, // VmProvisionService::OS_LINUX | OS_WINDOWS
) {}
// ─────────────────────────────────────────────────────────────────────────
public function handle(VmProvisionService $provision, VsphereHostSshService $ssh): void
{
Log::info('ChangePasswordViaVsphereJob: starting', [
'module_vmware_id' => $this->moduleVmwareId,
'vm_id' => $this->vmId,
'os_family' => $this->osFamily,
]);
// [2] Generate plain-text password (not hashed)
$newPassword = $provision->generatePassword(16);
// [3] Load vCenter credentials from database
$vcenter = vCenter::first();
if (!$vcenter) {
throw new \RuntimeException('No vCenter record found in module_vcenter');
}
$vcenterHost = $vcenter->host;
$vcenterUser = $vcenter->user;
$vcenterPass = SignRSA::DecryptFunc($vcenter->password);
// [4–5] SSH → vSphere host → run PS1 with 5 params
$output = $ssh->changePassword(
$this->vmId,
$newPassword,
$this->osFamily,
$vcenterHost,
$vcenterUser,
$vcenterPass,
);
// [6] Parse output — throw on FAIL so job retries
$this->assertSuccess($output);
// [7] Save encrypted + plain password; IP already set during VM creation
$provision->updatePassword($this->moduleVmwareId, $newPassword);
// [8] Send credentials email (loads fresh VM data from DB)
SendVmCredentialsEmailJob::dispatch($this->moduleVmwareId);
Log::info('ChangePasswordViaVsphereJob: completed', [
'module_vmware_id' => $this->moduleVmwareId,
'vm_id' => $this->vmId,
]);
}
// ─────────────────────────────────────────────────────────────────────────
public function failed(\Throwable $e): void
{
Log::error('ChangePasswordViaVsphereJob: all retries exhausted', [
'module_vmware_id' => $this->moduleVmwareId,
'vm_id' => $this->vmId,
'error' => $e->getMessage(),
]);
ModuleVmware::where('id', $this->moduleVmwareId)->update(['status' => 0]);
}
// ─────────────────────────────────────────────────────────────────────────
/**
* PS1 scripts output exactly "SUCCESS" or "FAIL".
* Throw RuntimeException on FAIL → triggers job retry.
*
* @throws \RuntimeException
*/
private function assertSuccess(string $output): void
{
$trimmed = trim($output);
if (str_contains(strtoupper($trimmed), 'SUCCESS')) {
return;
}
throw new \RuntimeException(
"PS1 script returned failure for VM {$this->vmId}. Output: {$trimmed}",
);
}
}