refactor: implement centralized cache key management and add utility traits for vCenter SSH operations.

This commit is contained in:
pbao9
2026-05-16 16:52:21 +07:00
parent 06dbd54ecd
commit 8a115c5175
36 changed files with 1694 additions and 517 deletions
+98 -254
View File
@@ -13,9 +13,12 @@ use App\Http\Facade\SignRSA;
use App\Models\InfraManagement;
use Illuminate\Support\Str;
use phpseclib3\Net\SSH2;
use App\Http\Services\vCenter\Concerns\ManagesSsh;
class vCenter
{
use ManagesSsh;
public ?string $token = null;
public string $apiUri;
private string $username;
@@ -364,224 +367,7 @@ class vCenter
return $this->getAllNetwork($host);
}
protected function Command($cmd)
{
$auth = $this->LoginAuth();
if (!$auth) {
return "Login_Fail";
}
try {
// Build PowerShell command inline
$psCommand = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "' .
'Import-Module VMware.PowerCLI; ' .
'Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false -Scope Session; ' .
'$cred = Import-Clixml -Path \'C:\secure\vcenter.cred\'; ' .
'Connect-VIServer -Server \'' . $this->apiUri . '\' -Credential $cred; ' .
$cmd . '; ' .
'Disconnect-VIServer -Confirm:$false" 2>&1';
Log::channel('Custom')->info('PowerShell Command: ' . substr($psCommand, 0, 200) . '...');
$out = $auth->exec($psCommand);
$err = $auth->getStdError();
Log::channel('Custom')->info('PowerShell Output length: ' . strlen($out));
return $out . ($err ? "\n[STDERR]\n" . $err : '');
} catch (\Exception $e) {
Log::channel('Custom')->error('Command Exception: ' . $e->getMessage());
return $e->getMessage();
}
}
private function LoginAuth()
{
try {
$infra = InfraManagement::where('type', 'jumphost')
->where('status', true)
->first();
if (!$infra) {
Log::error('SSH Login Failed: No active jumphost configuration found in InfraManagement.');
return false;
}
$sshHost = $infra->host;
$sshPort = 22;
if (isset($infra->meta_data['port'])) {
$sshPort = $infra->meta_data['port'];
}
$sshUser = $infra->username;
$sshPass = SignRSA::DecryptFunc($infra->password);
$connection = new SSH2($sshHost, $sshPort);
if ($connection->login($sshUser, $sshPass)) {
$connection->setTimeout(0);
return $connection;
}
Log::error('SSH Login Failed: Invalid credentials for jumphost.');
return false;
} catch (Exception $error) {
Log::error('SSH Login Failed: ' . $error->getMessage());
return false;
}
}
// Login SSH
public function loginSSH(): bool
{
$conn = $this->LoginAuth();
if ($conn) {
Log::info("✅ Connected to vCenter via SSH successfully");
return true;
}
Log::error("❌ Failed to connect to vCenter via SSH");
return false;
}
public function runPsCommand(string $psCommand): string
{
$auth = $this->LoginAuth();
if (!$auth)
return "Login_Fail";
// gọi trực tiếp -Command, thu cả STDERR
$action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass '
. '-Command "' . str_replace('"', '\"', $psCommand) . '"';
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
protected function CommandRSP(string $cmd)
{
$auth = $this->LoginAuth();
if (!$auth)
return "Login_Fail";
// Escape để dùng trong chuỗi single-quoted của PowerShell
$esc = fn(string $v) => str_replace("'", "''", $v);
$server = $esc($this->apiUri ?? '');
$user = $esc($this->username ?? '');
$pass = $esc($this->password ?? '');
// Ghép script PowerShell hoàn chỉnh, chạy trong 1 scriptblock
// Lưu ý: dùng placeholder rồi strtr để tránh lẫn dấu ngoặc
$psTemplate = <<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
# Thực thi lệnh công việc (đã được xây dựng sẵn, ví dụ: & 'C:\path\script.ps1' -Param 'Value' ... )
& {
<CMD>
} | Out-String
PS;
// Chèn dữ liệu
$psScript = strtr($psTemplate, [
'<SERVER>' => $server,
'<USER>' => $user,
'<PASS>' => $pass,
'<CMD>' => $cmd, // ở đây $cmd KHÔNG cần ngoặc kép bao ngoài
]);
// Encode UTF-16LE → Base64 cho -EncodedCommand
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
// Dùng -EncodedCommand để tránh mọi lỗi escape/quote
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
protected function CommandChangePort(string $cmd, bool $expectJson = false)
{
$auth = $this->LoginAuth();
if (!$auth)
return "Login_Fail";
$esc = fn(string $v) => str_replace("'", "''", $v);
$server = $esc($this->apiUri ?? '');
$user = $esc($this->username ?? '');
$pass = $esc($this->password ?? '');
// PowerShell script template
$psTemplate = <<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
& {
<CMD>
}
PS;
$psScript = strtr($psTemplate, [
'<SERVER>' => $server,
'<USER>' => $user,
'<PASS>' => $pass,
'<CMD>' => $cmd,
]);
// Encode UTF-16LE cho -EncodedCommand
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
if ($expectJson) {
// CẮT JSON cuối: {...} hoặc [...]
$trimmed = trim($out);
// đơn giản: bắt khối { ... } hoặc [ ... ] ở cuối chuỗi
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $trimmed, $m)) {
return $m[1];
}
// không tìm thấy → trả nguyên STDOUT (để caller log raw)
return $trimmed;
}
// Trường hợp không kỳ vọng JSON: giữ nguyên hành vi cũ (append STDERR)
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
// Moved to App\Http\Services\vCenter\Concerns\ManagesSsh
public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string
{
@@ -1769,51 +1555,72 @@ PS;
public function StartVirtualMachine($product, $vmware_id = ''): array|string
{
$check = $this->loginServer($product->ipaddress->host->datacenter->vCenter);
if ($check) {
$response = Http::withoutVerifying()
->withHeaders([
'vmware-api-session-id' => $this->token
])
->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/power/start");
if ($response->status() === 200) {
return [
'error' => false,
'data' => 'success'
];
try {
$vmId = $vmware_id ?: ($product->vmware_id ?? null);
if (!$vmId) {
return ['error' => true, 'data' => 'Không tìm thấy vmware_id'];
}
return [
'error' => true,
'data' => $response['value']
];
$vcenter = $product->vCenter ?? null;
if (!$vcenter && $product->ipAddress?->host?->datacenter?->vCenter) {
$vcenter = $product->ipAddress->host->datacenter->vCenter;
}
if (!$vcenter) {
return ['error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM'];
}
if (!$this->loginServer($vcenter)) {
return 'Login_Again';
}
$response = Http::withoutVerifying()
->withHeaders(['vmware-api-session-id' => $this->token])
->post("https://$this->apiUri/rest/vcenter/vm/$vmId/power/start");
if ($response->status() === 200) {
return ['error' => false, 'data' => 'success'];
}
return ['error' => true, 'data' => $response->json('value') ?? $response->body()];
} catch (\Throwable $e) {
return ['error' => true, 'data' => $e->getMessage()];
}
return "Login_Again";
}
public function ShutdownGuestVirtualMachine($product, $vmware_id = ''): array|string
{
$check = $this->loginServer($product->ipaddress->host->datacenter->vCenter);
if ($check) {
$response = Http::withoutVerifying()
->withHeaders([
'vmware-api-session-id' => $this->token
])
->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/power?action=shutdown");
if ($response->status() === 200) {
return [
'error' => false,
'data' => 'success'
];
try {
$vmId = $vmware_id ?: ($product->vmware_id ?? null);
if (!$vmId) {
return ['error' => true, 'data' => 'Không tìm thấy vmware_id'];
}
return [
'error' => true,
'data' => $response['value']
];
$vcenter = $product->vCenter ?? null;
if (!$vcenter && $product->ipAddress?->host?->datacenter?->vCenter) {
$vcenter = $product->ipAddress->host->datacenter->vCenter;
}
if (!$vcenter) {
return ['error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM'];
}
if (!$this->loginServer($vcenter)) {
return 'Login_Again';
}
$response = Http::withoutVerifying()
->withHeaders(['vmware-api-session-id' => $this->token])
->post("https://$this->apiUri/rest/vcenter/vm/$vmId/guest/power?action=shutdown");
if ($response->status() === 200) {
return ['error' => false, 'data' => 'success'];
}
return ['error' => true, 'data' => $response->json('value') ?? $response->body()];
} catch (\Throwable $e) {
return ['error' => true, 'data' => $e->getMessage()];
}
return "Login_Again";
}
public function PowerOffVirtualMachine($vmware_id = ''): array|string
@@ -1839,6 +1646,43 @@ PS;
return "Login_Again";
}
public function getVmPowerState($product, string $vmware_id = ''): array|string
{
try {
$vmId = $vmware_id ?: ($product->vmware_id ?? null);
if (!$vmId) {
return ['error' => true, 'data' => 'Không tìm thấy vmware_id'];
}
$vcenter = $product->vCenter ?? null;
if (!$vcenter && $product->ipAddress?->host?->datacenter?->vCenter) {
$vcenter = $product->ipAddress->host->datacenter->vCenter;
}
if (!$vcenter) {
return ['error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM'];
}
if (!$this->loginServer($vcenter)) {
return 'Login_Again';
}
$response = Http::withoutVerifying()
->withHeaders(['vmware-api-session-id' => $this->token])
->timeout(8)
->get("https://$this->apiUri/rest/vcenter/vm/$vmId/power");
if ($response->successful()) {
$data = $response->json();
return ['error' => false, 'data' => $data['value'] ?? $data];
}
return ['error' => true, 'data' => $response->json('value') ?? 'Không thể lấy trạng thái VM'];
} catch (\Throwable $e) {
return ['error' => true, 'data' => $e->getMessage()];
}
}
public function DeleteVirtualMachine($vmware_id = ''): array|string
{
$check = $this->checkApiExits();