139 lines
4.9 KiB
PHP
139 lines
4.9 KiB
PHP
<?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 App\Jobs\VirtualMachine\Provision\AssignIpToGuestJob;
|
||
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
|
||
* [8] Dispatch AssignIpToGuestJob with the new guest password
|
||
* [9] 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 will be set later by AssignIpToGuestJob)
|
||
$provision->updatePassword($this->moduleVmwareId, $newPassword);
|
||
|
||
// [8] Assign real IP into guest OS using the new password
|
||
AssignIpToGuestJob::dispatch(
|
||
$this->moduleVmwareId,
|
||
$this->vmId,
|
||
$this->osFamily,
|
||
$newPassword, // guest_password — MUST use new password, not template
|
||
);
|
||
|
||
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}",
|
||
);
|
||
}
|
||
}
|