Files
portal-api/app/Jobs/VirtualMachine/Provision/ChangePasswordViaVsphereJob.php
T
pbao9 5043bea79a 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.
2026-04-16 23:21:27 +07:00

132 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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}",
);
}
}