From c431a7b617c983066ede9a1c471ba6c677182937 Mon Sep 17 00:00:00 2001 From: Anush <33168386+AnushK-Fro@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:57:28 -0500 Subject: [PATCH 1/4] Add support for setting passwords on Windows Server and utilize QEMU Guest Agent for setting passwords on Linux --- app/Jobs/Server/SyncWindowsSettings.php | 71 +++++++++++++++++++ .../Server/ProxmoxGuestAgentRepository.php | 61 ++++++++++++++++ app/Services/Servers/ServerAuthService.php | 28 +++++--- .../Servers/ServerBuildDispatchService.php | 16 ++++- 4 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 app/Jobs/Server/SyncWindowsSettings.php create mode 100644 app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php diff --git a/app/Jobs/Server/SyncWindowsSettings.php b/app/Jobs/Server/SyncWindowsSettings.php new file mode 100644 index 00000000000..71521b21417 --- /dev/null +++ b/app/Jobs/Server/SyncWindowsSettings.php @@ -0,0 +1,71 @@ +serverId}", + )]; + } + + public function handle(ServerAuthService $service): void + { + $server = Server::findOrFail($this->serverId); + + $service->updateWindowsPassword($server, $this->password); + } + + /** + * Determine the time at which the job should retry. + * + * @return \DateTime + */ + public function retryAfter(): \DateTime + { + return now()->addSeconds(20); + } + + /** + * Determine the time at which the job should timeout. + * + * @return \DateTime + */ + public function retryUntil(): \DateTime + { + return now()->addSeconds($this->timeout * $this->tries); + } + + /** + * Handle a job failure. + * + * @return void + */ + public function failed(): void + { + // Mark the job as completed + $this->delete(); + } +} \ No newline at end of file diff --git a/app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php b/app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php new file mode 100644 index 00000000000..d9752f5f2b4 --- /dev/null +++ b/app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php @@ -0,0 +1,61 @@ +server, Server::class); + + $response = $this->getHttpClient() + ->withUrlParameters([ + 'node' => $this->node->cluster, + 'server' => $this->server->vmid, + ]) + ->get('/api2/json/nodes/{node}/qemu/{server}/agent/get-osinfo') + ->json(); + + return $this->getData($response); + } + + /** + * Update Guest Agent password for Administrator user. + * + * @param string $password + * @return mixed + * + * @throws ProxmoxConnectionException + */ + public function updateGuestAgentPassword(string $username, string $password) + { + Assert::isInstanceOf($this->server, Server::class); + + $params = [ + 'username' => $username, + 'password' => $password, + ]; + + $response = $this->getHttpClient() + ->withUrlParameters([ + 'node' => $this->node->cluster, + 'server' => $this->server->vmid, + ]) + ->post('/api2/json/nodes/{node}/qemu/{server}/agent/set-user-password', $params) + ->json(); + + return $this->getData($response); + } +} \ No newline at end of file diff --git a/app/Services/Servers/ServerAuthService.php b/app/Services/Servers/ServerAuthService.php index 590948888e1..e53f2defad6 100644 --- a/app/Services/Servers/ServerAuthService.php +++ b/app/Services/Servers/ServerAuthService.php @@ -4,20 +4,32 @@ use Convoy\Models\Server; use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository; +use Convoy\Repositories\Proxmox\Server\ProxmoxGuestAgentRepository; class ServerAuthService { - public function __construct(private ProxmoxConfigRepository $configRepository) + public function __construct(private ProxmoxConfigRepository $configRepository, private ProxmoxGuestAgentRepository $guestAgentRepository) { } public function updatePassword(Server $server, string $password) { - // if (!empty($password)) { - $this->configRepository->setServer($server)->update(['cipassword' => $password]); - // } else { - // $this->configRepository->setServer($server)->update(['delete' => 'cipassword']); - // } + try { + $OsInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); + if (str_contains($OsInfo["result"]["name"], "Windows")) { + $username = "Administrator"; + } else { + $username = "root"; + } + $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); + $this->configRepository->setServer($server)->update(['cipassword' => $password]); + } catch (\Exception $e) { + $this->configRepository->setServer($server)->update(['cipassword' => $password]); + } + } + + public function updateWindowsPassword(Server $server, string $password) { + $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword("Administrator", $password); } public function getSSHKeys(Server $server) @@ -29,10 +41,10 @@ public function getSSHKeys(Server $server) public function updateSSHKeys(Server $server, ?string $keys) { - if (! empty($keys)) { + if (!empty($keys)) { $this->configRepository->setServer($server)->update(['sshkeys' => rawurlencode($keys)]); } else { $this->configRepository->setServer($server)->update(['delete' => 'sshkeys']); } } -} +} \ No newline at end of file diff --git a/app/Services/Servers/ServerBuildDispatchService.php b/app/Services/Servers/ServerBuildDispatchService.php index 528d81e78fb..bf2e4a312d7 100644 --- a/app/Services/Servers/ServerBuildDispatchService.php +++ b/app/Services/Servers/ServerBuildDispatchService.php @@ -15,6 +15,7 @@ use Convoy\Jobs\Server\SendPowerCommandJob; use Convoy\Jobs\Server\WaitUntilVmIsCreatedJob; use Convoy\Jobs\Server\WaitUntilVmIsDeletedJob; +use Convoy\Jobs\Server\SyncWindowsSettings; use Convoy\Data\Server\Deployments\ServerDeploymentData; class ServerBuildDispatchService @@ -67,11 +68,20 @@ private function getChainedBuildJobs(ServerDeploymentData $deployment): array ]; } - if (! empty($deployment->account_password)) { + $startOnce = false; + + // TODO: Readd the start_on_completion check + + if (!empty($deployment->account_password)) { $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); + if (str_contains($deployment->template->name, "Windows")) { + $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); + $startOnce = true; + $jobs[] = new SyncWindowsSettings($deployment->server->id, $deployment->account_password); + } } - if ($deployment->start_on_completion) { + if (!$startOnce) { $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); } @@ -91,4 +101,4 @@ public function getChainedDeleteJobs(Server $server): array new WaitUntilVmIsDeletedJob($server->id), ]; } -} +} \ No newline at end of file From 3c6200ca7dad14b5a80e9e768a1295e5e8d31545 Mon Sep 17 00:00:00 2001 From: Anush <33168386+AnushK-Fro@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:06:26 -0500 Subject: [PATCH 2/4] Increases pagination limit to 999999 for IP Addresses --- app/Http/Controllers/Admin/AddressPools/AddressController.php | 2 +- app/Http/Controllers/Admin/Nodes/AddressController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/AddressPools/AddressController.php b/app/Http/Controllers/Admin/AddressPools/AddressController.php index e2c4f2409e1..5f287fc98e1 100644 --- a/app/Http/Controllers/Admin/AddressPools/AddressController.php +++ b/app/Http/Controllers/Admin/AddressPools/AddressController.php @@ -48,7 +48,7 @@ public function index(Request $request, AddressPool $addressPool) AllowedFilter::exact('server_id')->nullable(), ], ) - ->paginate(min($request->query('per_page', 50), 100))->appends( + ->paginate(min($request->query('per_page', 50), 999999))->appends( $request->query(), ); diff --git a/app/Http/Controllers/Admin/Nodes/AddressController.php b/app/Http/Controllers/Admin/Nodes/AddressController.php index c7bd5afb942..ff633ec02b3 100644 --- a/app/Http/Controllers/Admin/Nodes/AddressController.php +++ b/app/Http/Controllers/Admin/Nodes/AddressController.php @@ -25,7 +25,7 @@ public function index(Request $request, Node $node) new FiltersAddressWildcard(), ), AllowedFilter::exact('server_id')->nullable()], ) - ->paginate(min($request->query('per_page', 50), 100))->appends( + ->paginate(min($request->query('per_page', 50), 999999))->appends( $request->query(), ); From 059a6557a2a2b8ed5435c6c12db3f2678bd56ae5 Mon Sep 17 00:00:00 2001 From: Anush <33168386+AnushK-Fro@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:00:28 -0500 Subject: [PATCH 3/4] Fixes problem with test not passing --- app/Services/Servers/ServerAuthService.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/Services/Servers/ServerAuthService.php b/app/Services/Servers/ServerAuthService.php index e53f2defad6..7c2caac9070 100644 --- a/app/Services/Servers/ServerAuthService.php +++ b/app/Services/Servers/ServerAuthService.php @@ -16,12 +16,15 @@ public function updatePassword(Server $server, string $password) { try { $OsInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); - if (str_contains($OsInfo["result"]["name"], "Windows")) { - $username = "Administrator"; - } else { - $username = "root"; + if (is_array($OsInfo) && isset($OsInfo["result"]["name"])) { + if (str_contains($OsInfo["result"]["name"], "Windows")) { + $username = "Administrator"; + } else { + $username = "root"; + } + + $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); } - $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); $this->configRepository->setServer($server)->update(['cipassword' => $password]); } catch (\Exception $e) { $this->configRepository->setServer($server)->update(['cipassword' => $password]); From bc9aada574d3c52c35bd48666161808364dc9c55 Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Tue, 31 Dec 2024 22:43:44 -0600 Subject: [PATCH 4/4] Refactor Guest Agent Password Changing --- app/Jobs/Server/SyncWindowsSettings.php | 71 ------------------- app/Services/Servers/ServerAuthService.php | 40 +++++------ .../Servers/ServerBuildDispatchService.php | 56 ++++++++------- 3 files changed, 50 insertions(+), 117 deletions(-) delete mode 100644 app/Jobs/Server/SyncWindowsSettings.php diff --git a/app/Jobs/Server/SyncWindowsSettings.php b/app/Jobs/Server/SyncWindowsSettings.php deleted file mode 100644 index 71521b21417..00000000000 --- a/app/Jobs/Server/SyncWindowsSettings.php +++ /dev/null @@ -1,71 +0,0 @@ -serverId}", - )]; - } - - public function handle(ServerAuthService $service): void - { - $server = Server::findOrFail($this->serverId); - - $service->updateWindowsPassword($server, $this->password); - } - - /** - * Determine the time at which the job should retry. - * - * @return \DateTime - */ - public function retryAfter(): \DateTime - { - return now()->addSeconds(20); - } - - /** - * Determine the time at which the job should timeout. - * - * @return \DateTime - */ - public function retryUntil(): \DateTime - { - return now()->addSeconds($this->timeout * $this->tries); - } - - /** - * Handle a job failure. - * - * @return void - */ - public function failed(): void - { - // Mark the job as completed - $this->delete(); - } -} \ No newline at end of file diff --git a/app/Services/Servers/ServerAuthService.php b/app/Services/Servers/ServerAuthService.php index 7c2caac9070..cac708546eb 100644 --- a/app/Services/Servers/ServerAuthService.php +++ b/app/Services/Servers/ServerAuthService.php @@ -5,6 +5,7 @@ use Convoy\Models\Server; use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository; use Convoy\Repositories\Proxmox\Server\ProxmoxGuestAgentRepository; +use Illuminate\Support\Str; class ServerAuthService { @@ -12,42 +13,41 @@ public function __construct(private ProxmoxConfigRepository $configRepository, p { } - public function updatePassword(Server $server, string $password) + public function updatePassword(Server $server, string $password): void { + // Always store CIPassword first + $this->configRepository->setServer($server)->update(['cipassword' => $password]); + try { - $OsInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); - if (is_array($OsInfo) && isset($OsInfo["result"]["name"])) { - if (str_contains($OsInfo["result"]["name"], "Windows")) { - $username = "Administrator"; - } else { - $username = "root"; - } - - $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); + $osInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); + + // If we have valid OS info, decide which username to use + if (is_array($osInfo) && isset($osInfo['result']['name'])) { + $osName = $osInfo['result']['name']; + $username = Str::contains(Str::lower($osName), 'windows') ? 'Administrator' : 'root'; + + $this->guestAgentRepository + ->setServer($server) + ->updateGuestAgentPassword($username, $password); } - $this->configRepository->setServer($server)->update(['cipassword' => $password]); } catch (\Exception $e) { - $this->configRepository->setServer($server)->update(['cipassword' => $password]); + // Optionally log or handle exceptions } } - public function updateWindowsPassword(Server $server, string $password) { - $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword("Administrator", $password); - } - - public function getSSHKeys(Server $server) + public function getSSHKeys(Server $server): string { $raw = collect($this->configRepository->setServer($server)->getConfig())->where('key', '=', 'sshkeys')->first()['value'] ?? ''; return rawurldecode($raw); } - public function updateSSHKeys(Server $server, ?string $keys) + public function updateSSHKeys(Server $server, ?string $keys): void { - if (!empty($keys)) { + if (! empty($keys)) { $this->configRepository->setServer($server)->update(['sshkeys' => rawurlencode($keys)]); } else { $this->configRepository->setServer($server)->update(['delete' => 'sshkeys']); } } -} \ No newline at end of file +} diff --git a/app/Services/Servers/ServerBuildDispatchService.php b/app/Services/Servers/ServerBuildDispatchService.php index bf2e4a312d7..42a6357f2a0 100644 --- a/app/Services/Servers/ServerBuildDispatchService.php +++ b/app/Services/Servers/ServerBuildDispatchService.php @@ -2,21 +2,21 @@ namespace Convoy\Services\Servers; -use Convoy\Models\Server; +use Convoy\Data\Server\Deployments\ServerDeploymentData; +use Convoy\Enums\Server\PowerAction; use Convoy\Enums\Server\State; use Convoy\Enums\Server\Status; -use Illuminate\Support\Facades\Bus; -use Convoy\Enums\Server\PowerAction; -use Convoy\Jobs\Server\SyncBuildJob; use Convoy\Jobs\Server\BuildServerJob; use Convoy\Jobs\Server\DeleteServerJob; use Convoy\Jobs\Server\MonitorStateJob; -use Convoy\Jobs\Server\UpdatePasswordJob; use Convoy\Jobs\Server\SendPowerCommandJob; +use Convoy\Jobs\Server\SyncBuildJob; +use Convoy\Jobs\Server\UpdatePasswordJob; use Convoy\Jobs\Server\WaitUntilVmIsCreatedJob; use Convoy\Jobs\Server\WaitUntilVmIsDeletedJob; -use Convoy\Jobs\Server\SyncWindowsSettings; -use Convoy\Data\Server\Deployments\ServerDeploymentData; +use Convoy\Models\Server; +use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Str; class ServerBuildDispatchService { @@ -56,35 +56,39 @@ public function rebuild(ServerDeploymentData $deployment): void private function getChainedBuildJobs(ServerDeploymentData $deployment): array { - if ($deployment->should_create_server) { - $jobs = [ + // Base jobs: either create a new server or sync an existing one + $jobs = $deployment->should_create_server + ? [ new BuildServerJob($deployment->server->id, $deployment->template->id), new WaitUntilVmIsCreatedJob($deployment->server->id), new SyncBuildJob($deployment->server->id), - ]; - } else { - $jobs = [ + ] + : [ new SyncBuildJob($deployment->server->id), ]; - } - $startOnce = false; - - // TODO: Readd the start_on_completion check + if (Str::contains(Str::lower($deployment->template->name), 'windows')) { + $jobs = [ + ...$jobs, + new SendPowerCommandJob($deployment->server->id, PowerAction::START), + new MonitorStateJob($deployment->server->id, State::RUNNING), + ]; - if (!empty($deployment->account_password)) { - $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); - if (str_contains($deployment->template->name, "Windows")) { + if (! empty($deployment->account_password)) { + $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); + } + } else { + // For non-Windows, update password first if provided + if (! empty($deployment->account_password)) { + $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); + } + // Then power on if user wants to start on completion + if ($deployment->start_on_completion) { $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); - $startOnce = true; - $jobs[] = new SyncWindowsSettings($deployment->server->id, $deployment->account_password); } } - if (!$startOnce) { - $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); - } - + // Final callback to clear the status $jobs[] = function () use ($deployment) { Server::findOrFail($deployment->server->id)->update(['status' => null]); }; @@ -101,4 +105,4 @@ public function getChainedDeleteJobs(Server $server): array new WaitUntilVmIsDeletedJob($server->id), ]; } -} \ No newline at end of file +}