diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index 2c74f09dc..3cde4a80b 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -3,6 +3,8 @@ namespace App\Actions\Application; use App\Models\Application; +use App\Models\StandaloneDocker; +use App\Notifications\Application\StatusChanged; use Lorisleiva\Actions\Concerns\AsAction; class StopApplication @@ -10,13 +12,20 @@ class StopApplication use AsAction; public function handle(Application $application) { - $server = $application->destination->server; - if (!$server->isFunctional()) { - return 'Server is not functional'; + if ($application->destination->server->isSwarm()) { + instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server); + return; } - if ($server->isSwarm()) { - instant_remote_process(["docker stack rm {$application->uuid}" ], $server); - } else { + + $servers = collect([]); + $servers->push($application->destination->server); + $application->additional_networks->map(function ($network) use ($servers) { + $servers->push($network->server); + }); + foreach ($servers as $server) { + if (!$server->isFunctional()) { + return 'Server is not functional'; + } $containers = getCurrentApplicationContainerStatus($server, $application->id, 0); if ($containers->count() > 0) { foreach ($containers as $container) { @@ -28,20 +37,18 @@ public function handle(Application $application) ); } } - // TODO: make notification for application // $application->environment->project->team->notify(new StatusChanged($application)); } - // Delete Preview Deployments - $previewDeployments = $application->previews; - foreach ($previewDeployments as $previewDeployment) { - $containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id); - foreach ($containers as $container) { - $name = str_replace('/', '', $container['Names']); - instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false); - } - } } - + // // Delete Preview Deployments + // $previewDeployments = $application->previews; + // foreach ($previewDeployments as $previewDeployment) { + // $containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id); + // foreach ($containers as $container) { + // $name = str_replace('/', '', $container['Names']); + // instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false); + // } + // } } } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 9d0e069ce..4edf1b537 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -249,7 +249,11 @@ public function handle(): void } // Otherwise built image needs to be pushed before from the build server. if (!$this->use_build_server) { - $this->push_to_docker_registry(); + if ($this->application->additional_networks->count() > 0) { + $this->push_to_docker_registry(forceFail: true); + } else { + $this->push_to_docker_registry(); + } } $this->next(ApplicationDeploymentStatus::FINISHED->value); if ($this->pull_request_id !== 0) { @@ -349,7 +353,7 @@ private function push_to_docker_registry($forceFail = false) } catch (Exception $e) { $this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information."); if ($forceFail) { - throw $e; + throw new RuntimeException($e->getMessage(), 69420); } ray($e); } @@ -388,7 +392,7 @@ private function generate_image_names() } private function just_restart() { - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}."); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->set_base_dir(); @@ -443,7 +447,7 @@ private function deploy_simple_dockerfile() $this->server = $this->build_server; } $dockerfile_base64 = base64_encode($this->application->dockerfile); - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}."); $this->prepare_builder_image(); $this->execute_remote_command( [ @@ -451,6 +455,16 @@ private function deploy_simple_dockerfile() ], ); $this->generate_image_names(); + if (!$this->force_rebuild) { + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + $this->create_workdir(); + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->rolling_update(); + return; + } + } $this->generate_compose_file(); $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); @@ -462,8 +476,8 @@ private function deploy_dockerimage_buildpack() { $this->dockerImage = $this->application->docker_registry_image_name; $this->dockerImageTag = $this->application->docker_registry_image_tag; - ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"); - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}."); + ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.'"); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}."); $this->generate_image_names(); $this->prepare_builder_image(); $this->generate_compose_file(); @@ -481,9 +495,9 @@ private function deploy_docker_compose_buildpack() $this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command; } if ($this->pull_request_id === 0) { - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}."); } else { - $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); + $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); } $this->prepare_builder_image(); $this->check_git_if_build_needed(); @@ -551,12 +565,22 @@ private function deploy_dockerfile_buildpack() if (data_get($this->application, 'dockerfile_location')) { $this->dockerfile_location = $this->application->dockerfile_location; } - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}."); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); - $this->clone_repository(); $this->set_base_dir(); $this->generate_image_names(); + if (!$this->force_rebuild) { + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + $this->create_workdir(); + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->rolling_update(); + return; + } + } + $this->clone_repository(); $this->cleanup_git(); $this->generate_compose_file(); $this->generate_build_env_variables(); @@ -569,7 +593,7 @@ private function deploy_nixpacks_buildpack() if ($this->use_build_server) { $this->server = $this->build_server; } - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}."); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->set_base_dir(); @@ -601,11 +625,21 @@ private function deploy_static_buildpack() if ($this->use_build_server) { $this->server = $this->build_server; } - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}."); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->set_base_dir(); $this->generate_image_names(); + if (!$this->force_rebuild) { + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + $this->create_workdir(); + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->rolling_update(); + return; + } + } $this->clone_repository(); $this->cleanup_git(); $this->build_image(); @@ -787,10 +821,10 @@ private function prepare_builder_image() } private function deploy_to_additional_destinations() { - if (str($this->application->additional_destinations)->isEmpty()) { + if ($this->application->additional_networks->count() === 0) { return; } - $destination_ids = collect(str($this->application->additional_destinations)->explode(',')); + $destination_ids = $this->application->additional_networks->pluck('id'); if ($this->server->isSwarm()) { $this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode."); return; @@ -1529,7 +1563,7 @@ private function next(string $status) return; } if ($status === ApplicationDeploymentStatus::FINISHED->value) { - // $this->deploy_to_additional_destinations(); + $this->deploy_to_additional_destinations(); $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); } } @@ -1542,10 +1576,14 @@ public function failed(Throwable $exception): void } if ($this->application->build_pack !== 'dockercompose') { - $this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr'); - $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true] - ); + $code = $exception->getCode(); + if ($code !== 69420) { + // 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one + $this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr'); + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true] + ); + } } $this->next(ApplicationDeploymentStatus::FAILED->value); diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 8f7a22ec8..070a215d1 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -87,22 +87,6 @@ public function stop() $this->application->save(); $this->application->refresh(); } - public function restartNew() - { - $this->setDeploymentUuid(); - queue_application_deployment( - application: $this->application, - deployment_uuid: $this->deploymentUuid, - restart_only: true, - is_new_deployment: true, - ); - return redirect()->route('project.application.deployment.show', [ - 'project_uuid' => $this->parameters['project_uuid'], - 'application_uuid' => $this->parameters['application_uuid'], - 'deployment_uuid' => $this->deploymentUuid, - 'environment_name' => $this->parameters['environment_name'], - ]); - } public function restart() { $this->setDeploymentUuid(); diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index 5c99630ee..5cc906667 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -2,11 +2,43 @@ namespace App\Livewire\Project\Shared; +use App\Models\Server; use Livewire\Component; class Destination extends Component { public $resource; public $servers = []; - public $additional_servers = []; + public $networks = []; + + public function mount() + { + $this->loadData(); + } + public function loadData() + { + $all_networks = collect([]); + $all_networks = $all_networks->push($this->resource->destination); + $all_networks = $all_networks->merge($this->resource->additional_networks); + + $this->networks = Server::isUsable()->get()->map(function ($server) { + return $server->standaloneDockers; + })->flatten(); + $this->networks = $this->networks->reject(function ($network) use ($all_networks) { + return $all_networks->pluck('id')->contains($network->id); + }); + } + public function addServer(int $network_id, int $server_id) + { + $this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]); + $this->resource->load(['additional_networks']); + $this->loadData(); + + } + public function removeServer(int $network_id, int $server_id) + { + $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); + $this->resource->load(['additional_networks']); + $this->loadData(); + } } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index c30011a4a..28aac7ce3 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -171,7 +171,7 @@ public function submit($data) } $environment->save(); $this->refreshEnvs(); - $this->dispatch('success', 'Environment variable added successfully.'); + $this->dispatch('success', 'Environment variable added.'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Models/Application.php b/app/Models/Application.php index cf3141ed7..0a43f7723 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -15,7 +15,6 @@ class Application extends BaseModel { use SoftDeletes; protected $guarded = []; - protected static function booted() { static::saving(function ($application) { @@ -53,6 +52,16 @@ protected static function booted() }); } + public function additional_servers() + { + return $this->belongsToMany(Server::class, 'additional_destinations') + ->withPivot('standalone_docker_id'); + } + public function additional_networks() + { + return $this->belongsToMany(StandaloneDocker::class, 'additional_destinations') + ->withPivot('server_id'); + } public function is_github_based(): bool { if (data_get($this, 'source')) { @@ -216,7 +225,8 @@ public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } - public function project() { + public function project() + { return data_get($this, 'environment.project'); } public function team() @@ -435,7 +445,7 @@ function generateBaseDir(string $uuid) { return "/artifacts/{$uuid}"; } - function setGitImportSettings(string $deployment_uuid, string $git_clone_command) + function setGitImportSettings(string $deployment_uuid, string $git_clone_command) { $baseDir = $this->generateBaseDir($deployment_uuid); if ($this->git_commit_sha !== 'HEAD') { diff --git a/database/migrations/2024_02_01_111228_create_tags_table.php b/database/migrations/2024_02_01_111228_create_tags_table.php index c922d8fa9..259a0c30d 100644 --- a/database/migrations/2024_02_01_111228_create_tags_table.php +++ b/database/migrations/2024_02_01_111228_create_tags_table.php @@ -24,7 +24,6 @@ public function up(): void $table->string('taggable_type'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); $table->unique(['tag_id', 'taggable_id', 'taggable_type'], 'taggable_unique'); // Composite unique index - }); } diff --git a/database/migrations/2024_02_06_132748_add_additional_destinations.php b/database/migrations/2024_02_06_132748_add_additional_destinations.php new file mode 100644 index 000000000..d751fcc40 --- /dev/null +++ b/database/migrations/2024_02_06_132748_add_additional_destinations.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('application_id')->constrained()->onDelete('cascade'); + $table->foreignId('server_id')->constrained()->onDelete('cascade'); + $table->foreignId('standalone_docker_id')->constrained()->onDelete('cascade'); + $table->timestamps(); + }); + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('additional_destinations'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('additional_destinations'); + Schema::table('applications', function (Blueprint $table) { + $table->string('additional_destinations')->nullable()->after('destination'); + }); + } +}; diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 3de42f428..62e9063bb 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -77,6 +77,11 @@ class="hover:no-underline"> Manual @endif + @if (data_get($deployment, 'server_name')) +
+ Server: {{ data_get($deployment, 'server_name') }} +
+ @endif
diff --git a/resources/views/livewire/project/shared/destination.blade.php b/resources/views/livewire/project/shared/destination.blade.php index 469439254..f3f5f7245 100644 --- a/resources/views/livewire/project/shared/destination.blade.php +++ b/resources/views/livewire/project/shared/destination.blade.php @@ -1,38 +1,48 @@

Server

Server related configurations.
-

Destination Server & Network

-
- + {{-- On server {{ data_get($resource, 'destination.server.name') }} - in {{ data_get($resource, 'destination.network') }} network. + in {{ data_get($resource, 'destination.network') }} network + @if (count($additional_destinations) > 0) + @foreach ($additional_destinations as $destination) + On server + {{ data_get($destination, 'server.name') }} in {{ data_get($destination, 'network') }} network + @endforeach + @endif --}} +
+ On + server {{ data_get($resource, 'destination.server.name') }} + in {{ data_get($resource, 'destination.network') }} network
+ @if (count($resource->additional_networks) > 0) + @foreach ($resource->additional_networks as $destination) +
+ On + server + {{ data_get($destination, 'server.name') }} in {{ data_get($destination, 'network') }} network +
+ @endforeach + @endif
- {{-- Additional Destinations: - {{$resource->additional_destinations}} --}} - {{-- @if (count($servers) > 0) -
-

Additional Servers

- @foreach ($servers as $server) -
-

{{ $server->name }}

-
{{ $server->description }}
- - - - @foreach ($server->destinations() as $destination) - @if ($loop->first) - - - @else - - - @endif - @endforeach - - Save -
+

Attach to a Server

+ @if (count($networks) > 0) +
+ @foreach ($networks as $network) +
+ {{ data_get($network, 'server.name') }} + {{ $network->name }} +
@endforeach
- @endif --}} + @else +
No additional servers available to attach.
+ @endif