From c6253658ca61f4323d76c4ed46106ad30fc585f1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 1 Nov 2023 12:19:08 +0100 Subject: [PATCH] feat: restart application fix: a few things in application deployment job --- .../Livewire/Project/Application/Heading.php | 14 +++ app/Jobs/ApplicationDeploymentJob.php | 90 +++++++++++++------ bootstrap/helpers/applications.php | 3 +- ...100437_add_restart_to_deployment_queue.php | 28 ++++++ .../components/applications/navbar.blade.php | 11 ++- 5 files changed, 117 insertions(+), 29 deletions(-) create mode 100644 database/migrations/2023_11_01_100437_add_restart_to_deployment_queue.php diff --git a/app/Http/Livewire/Project/Application/Heading.php b/app/Http/Livewire/Project/Application/Heading.php index 0cb25b172..be96d4ffa 100644 --- a/app/Http/Livewire/Project/Application/Heading.php +++ b/app/Http/Livewire/Project/Application/Heading.php @@ -65,4 +65,18 @@ public function stop() $this->application->save(); $this->application->refresh(); } + public function restart() { + $this->setDeploymentUuid(); + queue_application_deployment( + application_id: $this->application->id, + deployment_uuid: $this->deploymentUuid, + restart_only: true, + ); + return redirect()->route('project.application.deployment', [ + 'project_uuid' => $this->parameters['project_uuid'], + 'application_uuid' => $this->parameters['application_uuid'], + 'deployment_uuid' => $this->deploymentUuid, + 'environment_name' => $this->parameters['environment_name'], + ]); + } } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 583342015..c056fb650 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -44,6 +44,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private int $pull_request_id; private string $commit; private bool $force_rebuild; + private bool $restart_only; private ?string $dockerImage = null; private ?string $dockerImageTag = null; @@ -94,6 +95,7 @@ public function __construct(int $application_deployment_queue_id) $this->pull_request_id = $this->application_deployment_queue->pull_request_id; $this->commit = $this->application_deployment_queue->commit; $this->force_rebuild = $this->application_deployment_queue->force_rebuild; + $this->restart_only = $this->application_deployment_queue->restart_only; $source = data_get($this->application, 'source'); if ($source) { @@ -182,7 +184,9 @@ public function handle(): void $this->application->git_repository = "$gitHost:$gitRepo"; } try { - if ($this->application->dockerfile) { + if ($this->restart_only) { + $this->just_restart(); + } else if ($this->application->dockerfile) { $this->deploy_simple_dockerfile(); } else if ($this->application->build_pack === 'dockerimage') { $this->deploy_dockerimage_buildpack(); @@ -264,6 +268,49 @@ public function handle(): void // [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true], // ); // } + private function generate_image_names() + { + if ($this->application->dockerfile) { + $this->build_image_name = Str::lower("{$this->application->git_repository}:build"); + $this->production_image_name = Str::lower("{$this->application->uuid}:latest"); + } else if ($this->application->build_pack === 'dockerimage') { + $this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}"); + } else if ($this->pull_request_id !== 0) { + $this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build"); + $this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}"); + } else { + $tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}"); + if (strlen($tag) > 128) { + $tag = $tag->substr(0, 128); + } + $this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build"); + $this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}"); + } + ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green(); + } + private function just_restart() + { + $this->execute_remote_command( + [ + "echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'" + ], + ); + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->set_base_dir(); + $this->generate_image_names(); + $this->execute_remote_command([ + "docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found" + ]); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { + $this->generate_compose_file(); + $this->rolling_update(); + return; + } + $this->execute_remote_command([ + "echo 'Cannot find image {$this->production_image_name} locally. Please redeploy the application.'", + ]); + } private function save_environment_variables() { $envs = collect([]); @@ -291,9 +338,7 @@ private function deploy_simple_dockerfile() executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile") ], ); - $this->build_image_name = Str::lower("{$this->application->git_repository}:build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:latest"); - // ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green(); + $this->generate_image_names(); $this->generate_compose_file(); $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); @@ -311,7 +356,7 @@ private function deploy_dockerimage_buildpack() "echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'" ], ); - $this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}"); + $this->generate_image_names(); $this->prepare_builder_image(); $this->generate_compose_file(); $this->rolling_update(); @@ -330,14 +375,7 @@ private function deploy_dockerfile_buildpack() $this->prepare_builder_image(); $this->clone_repository(); $this->set_base_dir(); - $tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}"); - if (strlen($tag) > 128) { - $tag = $tag->substr(0, 128); - } - - $this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}"); - // ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green(); + $this->generate_image_names(); $this->cleanup_git(); $this->generate_compose_file(); $this->generate_build_env_variables(); @@ -355,15 +393,7 @@ private function deploy_nixpacks_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->set_base_dir(); - $tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}"); - if (strlen($tag) > 128) { - $tag = $tag->substr(0, 128); - } - - $this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}"); - // ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green(); - + $this->generate_image_names(); if (!$this->force_rebuild) { $this->execute_remote_command([ "docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found" @@ -396,7 +426,7 @@ private function rolling_update() { if (count($this->application->ports_mappings_array) > 0) { $this->execute_remote_command( - ["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"], + ["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"], ); $this->stop_running_container(force: true); $this->start_by_compose_file(); @@ -545,7 +575,9 @@ private function check_git_if_build_needed() ); } - $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); + if ($this->saved_outputs->get('git_commit_sha')) { + $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); + } } private function clone_repository() { @@ -596,7 +628,11 @@ private function generate_git_import_commands() } if ($this->application->deploymentType() === 'deploy_key') { $this->fullRepoUrl = $this->application->git_repository; - $private_key = base64_encode($this->application->private_key->private_key); + $private_key = data_get($this->application, 'private_key.private_key'); + if (is_null($private_key)) { + throw new Exception('Private key not found. Please add a private key to the application and try again.'); + } + $private_key = base64_encode($private_key); $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}"; $git_clone_command = $this->set_git_import_settings($git_clone_command); $commands = collect([ @@ -907,12 +943,12 @@ private function stop_running_container(bool $force = false) if ($this->newVersionIsHealthy || $force) { $this->execute_remote_command( ["echo -n 'Removing old version of your application.'"], - [executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true], ); } else { $this->execute_remote_command( ["echo -n 'New version is not healthy, rolling back to the old version.'"], - [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true], ); } } diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 427ef5a57..d78d19992 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -4,7 +4,7 @@ use App\Models\Application; use App\Models\ApplicationDeploymentQueue; -function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false) +function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false) { $deployment = ApplicationDeploymentQueue::create([ 'application_id' => $application_id, @@ -12,6 +12,7 @@ function queue_application_deployment(int $application_id, string $deployment_uu 'pull_request_id' => $pull_request_id, 'force_rebuild' => $force_rebuild, 'is_webhook' => $is_webhook, + 'restart_only' => $restart_only, 'commit' => $commit, ]); $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at'); diff --git a/database/migrations/2023_11_01_100437_add_restart_to_deployment_queue.php b/database/migrations/2023_11_01_100437_add_restart_to_deployment_queue.php new file mode 100644 index 000000000..608fdd924 --- /dev/null +++ b/database/migrations/2023_11_01_100437_add_restart_to_deployment_queue.php @@ -0,0 +1,28 @@ +boolean('restart_only')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('restart_only'); + }); + } +}; diff --git a/resources/views/components/applications/navbar.blade.php b/resources/views/components/applications/navbar.blade.php index d38790789..cab687b98 100644 --- a/resources/views/components/applications/navbar.blade.php +++ b/resources/views/components/applications/navbar.blade.php @@ -17,7 +17,7 @@ @if ($application->status !== 'exited') +