refactor: implement centralized cache key management and add utility traits for vCenter SSH operations.
This commit is contained in:
+98
-254
@@ -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();
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services\vCenter\Concerns;
|
||||
|
||||
use App\Http\Facade\SignRSA;
|
||||
use App\Models\InfraManagement;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use phpseclib3\Net\SSH2;
|
||||
|
||||
/**
|
||||
* SSH / PowerShell execution via jumphost.
|
||||
*
|
||||
* Requires on the host class:
|
||||
* public ?string $token
|
||||
* public string $apiUri
|
||||
* private string $username
|
||||
* private string $password
|
||||
*/
|
||||
trait ManagesSsh
|
||||
{
|
||||
// ─── Auth ────────────────────────────────────────────────────────────────
|
||||
|
||||
private function LoginAuth(): SSH2|false
|
||||
{
|
||||
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 = (int) $infra->meta_data['port'];
|
||||
}
|
||||
|
||||
$connection = new SSH2($sshHost, $sshPort);
|
||||
|
||||
if ($connection->login($infra->username, SignRSA::DecryptFunc($infra->password))) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ─── Command variants ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Run a PowerShell command using a stored credential file on the jumphost.
|
||||
* Uses C:\secure\vcenter.cred for vCenter auth — no plain-text password in command.
|
||||
*/
|
||||
protected function Command($cmd): string
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return 'Login_Fail';
|
||||
}
|
||||
|
||||
try {
|
||||
$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);
|
||||
$errOut = $auth->getStdError();
|
||||
|
||||
Log::channel('Custom')->info('PowerShell Output length: ' . strlen($out));
|
||||
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('Custom')->error('Command Exception: ' . $e->getMessage());
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a raw PowerShell command directly — no vCenter connect wrapper.
|
||||
*/
|
||||
public function runPsCommand(string $psCommand): string
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return 'Login_Fail';
|
||||
}
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command using EncodedCommand — connects to vCenter with plain credentials.
|
||||
* Safer for complex scripts that would break with inline quoting.
|
||||
*/
|
||||
protected function CommandRSP(string $cmd): string
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return 'Login_Fail';
|
||||
}
|
||||
|
||||
$esc = fn(string $v) => str_replace("'", "''", $v);
|
||||
$script = strtr(
|
||||
<<<'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
|
||||
|
||||
& {
|
||||
<CMD>
|
||||
} | Out-String
|
||||
PS,
|
||||
[
|
||||
'<SERVER>' => $esc($this->apiUri ?? ''),
|
||||
'<USER>' => $esc($this->username ?? ''),
|
||||
'<PASS>' => $esc($this->password ?? ''),
|
||||
'<CMD>' => $cmd,
|
||||
]
|
||||
);
|
||||
|
||||
$encoded = base64_encode(mb_convert_encoding($script, 'UTF-16LE', 'UTF-8'));
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command using EncodedCommand — variant used for RDP port changes.
|
||||
* Optionally strips raw output down to the first JSON block found.
|
||||
*/
|
||||
protected function CommandChangePort(string $cmd, bool $expectJson = false): string
|
||||
{
|
||||
$auth = $this->LoginAuth();
|
||||
if (!$auth) {
|
||||
return 'Login_Fail';
|
||||
}
|
||||
|
||||
$esc = fn(string $v) => str_replace("'", "''", $v);
|
||||
$script = strtr(
|
||||
<<<'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,
|
||||
[
|
||||
'<SERVER>' => $esc($this->apiUri ?? ''),
|
||||
'<USER>' => $esc($this->username ?? ''),
|
||||
'<PASS>' => $esc($this->password ?? ''),
|
||||
'<CMD>' => $cmd,
|
||||
]
|
||||
);
|
||||
|
||||
$encoded = base64_encode(mb_convert_encoding($script, 'UTF-16LE', 'UTF-8'));
|
||||
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
|
||||
|
||||
try {
|
||||
$out = $auth->exec($action);
|
||||
$errOut = $auth->getStdError();
|
||||
|
||||
if ($expectJson) {
|
||||
$trimmed = trim($out);
|
||||
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $trimmed, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
return $trimmed;
|
||||
}
|
||||
|
||||
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
|
||||
} catch (\Throwable $e) {
|
||||
return 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace App\Http\Services\vCenter;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Support\Cache\CacheKey;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Http\Facade\SignRSA;
|
||||
|
||||
class SnapshotService
|
||||
@@ -68,10 +69,10 @@ class SnapshotService
|
||||
} catch (\Exception $e) {
|
||||
return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
|
||||
$vmware_name = (string) $product->name;
|
||||
// In original code, it checks checkApiExits(), if true proceed.
|
||||
|
||||
|
||||
if ($this->client->checkApiExits()) {
|
||||
try {
|
||||
$Snapshot = $vmware_name . "_" . date('d_m_Y_H_i_s');
|
||||
@@ -102,24 +103,23 @@ class SnapshotService
|
||||
|
||||
private function clearSnapshotCache($product): void
|
||||
{
|
||||
$vmware_name = (string) $product->name;
|
||||
$cacheKey = "snapshots_{$product->id}_{$vmware_name}";
|
||||
$cacheKey = CacheKey::build('snapshot', 'list', ['vm_id' => $product->id]);
|
||||
Cache::forget($cacheKey);
|
||||
Log::channel('Custom')->info("🗑️ Cleared snapshot cache for VM: {$vmware_name}");
|
||||
Log::channel('Custom')->info("🗑️ Cleared snapshot cache for VM: {$product->name}");
|
||||
}
|
||||
|
||||
public function getSnapShot($product): string|array
|
||||
{
|
||||
// ... (Similar logic, using SOAP)
|
||||
// Since SOAP logic is quite complex and self-contained in vCenter.php,
|
||||
// Since SOAP logic is quite complex and self-contained in vCenter.php,
|
||||
// I'll copy it here but need to adapt $this->callSoap etc.
|
||||
// Actually, callSoap uses curl with manual implementation.
|
||||
// I should probably simplify or copy that helper too.
|
||||
|
||||
|
||||
// This method heavily relies on internal SOAP helpers.
|
||||
// For simplicity in this 'splitting' task, I will implement the public method
|
||||
// For simplicity in this 'splitting' task, I will implement the public method
|
||||
// and add the private helpers it depends on.
|
||||
|
||||
|
||||
try {
|
||||
$vcenterModel = $this->resolveVCenterFromProduct($product);
|
||||
|
||||
@@ -132,10 +132,8 @@ class SnapshotService
|
||||
return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
$vmware_name = (string) $product->name;
|
||||
$vmId = $product->vmware_id;
|
||||
|
||||
$cacheKey = "snapshots_{$product->id}_{$vmware_name}";
|
||||
$vmId = $product->vmware_id;
|
||||
$cacheKey = CacheKey::build('snapshot', 'list', ['vm_id' => $product->id]);
|
||||
$cachedResult = Cache::get($cacheKey);
|
||||
|
||||
if ($cachedResult !== null) {
|
||||
@@ -234,7 +232,7 @@ class SnapshotService
|
||||
try {
|
||||
$xml = simplexml_load_string($result['body']);
|
||||
if ($xml === false) return [];
|
||||
|
||||
|
||||
$xml->registerXPathNamespace('u', 'urn:vim25');
|
||||
$json = json_encode($xml);
|
||||
$array = json_decode($json, true);
|
||||
@@ -271,14 +269,14 @@ class SnapshotService
|
||||
|
||||
if (isset($node['childSnapshotList'])) {
|
||||
$children = $node['childSnapshotList'];
|
||||
|
||||
|
||||
// Check if 'snapshot' key exists directly (single child wrapped)
|
||||
if (isset($children['snapshot'])) {
|
||||
// If it's an array of snapshots or single object?
|
||||
// XML conversion: if strictly one child, might be direct. if multiple, array.
|
||||
// But simplexml usually produces array if multiple same tags.
|
||||
// However, structure is usually childSnapshotList -> snapshot (array or single)
|
||||
|
||||
|
||||
$snaps = $children['snapshot'];
|
||||
// If $snaps has numeric keys, it's array. Else single.
|
||||
if (array_keys($snaps) !== range(0, count($snaps) - 1)) {
|
||||
@@ -288,7 +286,7 @@ class SnapshotService
|
||||
foreach ($snaps as $child) {
|
||||
$list = array_merge($list, $this->flattenSnapshotTree($child));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
@@ -296,7 +294,7 @@ class SnapshotService
|
||||
private function callSoap($soapBody, $cookie = null)
|
||||
{
|
||||
$curl = curl_init();
|
||||
$host = $this->client->apiUri;
|
||||
$host = $this->client->apiUri;
|
||||
|
||||
$headers = [
|
||||
'Content-Type: text/xml',
|
||||
@@ -327,11 +325,11 @@ class SnapshotService
|
||||
));
|
||||
|
||||
$response = curl_exec($curl);
|
||||
|
||||
|
||||
$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($response, 0, $header_size);
|
||||
$body = substr($response, $header_size);
|
||||
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
// Parse cookie
|
||||
@@ -344,7 +342,7 @@ class SnapshotService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ['body' => $body, 'cookie' => $cookieValue];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user