Merge branch 'next' into fix-ssh-keys
This commit is contained in:
@@ -3,50 +3,40 @@
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplication
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Application $application, bool $previewDeployments = false)
|
||||
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
|
||||
{
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$servers = collect([]);
|
||||
$servers->push($application->destination->server);
|
||||
$application->additional_servers->map(function ($server) use ($servers) {
|
||||
$servers->push($server);
|
||||
});
|
||||
foreach ($servers as $server) {
|
||||
if (! $server->isFunctional()) {
|
||||
try {
|
||||
$server = $application->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
if ($previewDeployments) {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
}
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
|
||||
}
|
||||
}
|
||||
ray('Stopping application: ' . $application->name);
|
||||
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
|
||||
return;
|
||||
}
|
||||
|
||||
$containersToStop = $application->getContainersToStop($previewDeployments);
|
||||
$application->stopContainers($containersToStop, $server);
|
||||
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
// remove network
|
||||
$uuid = $application->uuid;
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
$application->delete_connected_networks($application->uuid);
|
||||
}
|
||||
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
@@ -10,25 +11,65 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
if (! $server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
|
||||
instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
|
||||
$this->stopContainer($database, $database->uuid, 300);
|
||||
if (! $isDeleteOperation) {
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($database->is_public) {
|
||||
StopDatabaseProxy::run($database);
|
||||
}
|
||||
|
||||
return 'Database stopped successfully';
|
||||
}
|
||||
|
||||
private function stopContainer($database, string $containerName, int $timeout = 300): void
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
|
||||
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
|
||||
$startTime = time();
|
||||
while ($process->running()) {
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName, $server);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
|
||||
private function forceStopContainer(string $containerName, $server): void
|
||||
{
|
||||
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
private function removeContainer(string $containerName, $server): void
|
||||
{
|
||||
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
private function deleteConnectedNetworks($uuid, $server)
|
||||
{
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class DeleteService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Service $service)
|
||||
public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
|
||||
{
|
||||
try {
|
||||
$server = data_get($service, 'server');
|
||||
if ($server->isFunctional()) {
|
||||
if ($deleteVolumes && $server->isFunctional()) {
|
||||
$storagesToDelete = collect([]);
|
||||
|
||||
$service->environment_variables()->delete();
|
||||
@@ -33,13 +34,29 @@ class DeleteService
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
|
||||
if (!empty($commands)) {
|
||||
foreach ($commands as $command) {
|
||||
$result = instant_remote_process([$command], $server, false);
|
||||
if ($result !== 0) {
|
||||
ray("Failed to execute: $command");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleteConnectedNetworks) {
|
||||
$service->delete_connected_networks($service->uuid);
|
||||
}
|
||||
|
||||
instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
} finally {
|
||||
if ($deleteConfigurations) {
|
||||
$service->delete_configurations();
|
||||
}
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$application->forceDelete();
|
||||
}
|
||||
@@ -50,6 +67,11 @@ class DeleteService
|
||||
$task->delete();
|
||||
}
|
||||
$service->tags()->detach();
|
||||
$service->forceDelete();
|
||||
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,46 +3,33 @@
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Service $service)
|
||||
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
|
||||
{
|
||||
try {
|
||||
$server = $service->destination->server;
|
||||
if (! $server->isFunctional()) {
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
ray('Stopping service: '.$service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
if ($applications->count() < 6) {
|
||||
instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
}
|
||||
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
if ($dbs->count() < 6) {
|
||||
|
||||
instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$containersToStop = $service->getContainersToStop();
|
||||
$service->stopContainers($containersToStop, $server);
|
||||
|
||||
if (!$isDeleteOperation) {
|
||||
$service->delete_connected_networks($service->uuid);
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
@@ -2200,20 +2201,40 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout in seconds
|
||||
*/
|
||||
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
|
||||
private function graceful_shutdown_container(string $containerName, int $timeout = 300)
|
||||
{
|
||||
try {
|
||||
$this->execute_remote_command(
|
||||
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
||||
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
|
||||
$startTime = time();
|
||||
while ($process->running()) {
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->execute_remote_command(
|
||||
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
$isRunning = $this->execute_remote_command(
|
||||
["docker inspect -f '{{.State.Running}}' $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
) === 'true';
|
||||
|
||||
if ($isRunning) {
|
||||
$this->execute_remote_command(
|
||||
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
}
|
||||
} catch (\Exception $error) {
|
||||
// report error if needed
|
||||
$this->application_deployment_queue->addLogEntry("Error stopping container $containerName: " . $error->getMessage(), 'stderr');
|
||||
}
|
||||
|
||||
$this->remove_container($containerName);
|
||||
}
|
||||
|
||||
private function remove_container(string $containerName)
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
@@ -2229,7 +2250,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($this->pull_request_id === 0) {
|
||||
$containers = $containers->filter(function ($container) {
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
|
||||
});
|
||||
}
|
||||
$containers->each(function ($container) {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use App\Actions\Service\DeleteService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Models\Application;
|
||||
@@ -30,8 +31,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public function __construct(
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
||||
public bool $deleteConfigurations = false,
|
||||
public bool $deleteVolumes = false) {}
|
||||
public bool $deleteConfigurations,
|
||||
public bool $deleteVolumes,
|
||||
public bool $dockerCleanup,
|
||||
public bool $deleteConnectedNetworks
|
||||
) {}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
@@ -51,11 +55,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
case 'standalone-dragonfly':
|
||||
case 'standalone-clickhouse':
|
||||
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||
StopDatabase::run($this->resource);
|
||||
StopDatabase::run($this->resource, true);
|
||||
break;
|
||||
case 'service':
|
||||
StopService::run($this->resource);
|
||||
DeleteService::run($this->resource);
|
||||
StopService::run($this->resource, true);
|
||||
DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,12 +69,31 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->deleteConfigurations) {
|
||||
$this->resource?->delete_configurations();
|
||||
}
|
||||
|
||||
$isDatabase = $this->resource instanceof StandalonePostgresql
|
||||
|| $this->resource instanceof StandaloneRedis
|
||||
|| $this->resource instanceof StandaloneMongodb
|
||||
|| $this->resource instanceof StandaloneMysql
|
||||
|| $this->resource instanceof StandaloneMariadb
|
||||
|| $this->resource instanceof StandaloneKeydb
|
||||
|| $this->resource instanceof StandaloneDragonfly
|
||||
|| $this->resource instanceof StandaloneClickhouse;
|
||||
$server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server');
|
||||
if (($this->dockerCleanup || $isDatabase) && $server) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
|
||||
if ($this->deleteConnectedNetworks && ! $isDatabase) {
|
||||
$this->resource?->delete_connected_networks($this->resource->uuid);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->forceDelete();
|
||||
if ($this->dockerCleanup) {
|
||||
CleanupDocker::run($server, true);
|
||||
}
|
||||
Artisan::queue('cleanup:stucked-resources');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ class Dashboard extends Component
|
||||
|
||||
public function cleanup_queue()
|
||||
{
|
||||
$this->dispatch('success', 'Cleanup started.');
|
||||
Artisan::queue('cleanup:application-deployment-queue', [
|
||||
'--team-id' => currentTeam()->id,
|
||||
]);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Destination;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class Form extends Component
|
||||
@@ -38,7 +37,7 @@ class Form extends Component
|
||||
}
|
||||
$this->destination->delete();
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
return redirect()->route('destination.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,25 @@ namespace App\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class NavbarDeleteTeam extends Component
|
||||
{
|
||||
public function delete()
|
||||
public $team;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = currentTeam()->name;
|
||||
}
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
$currentTeam = currentTeam();
|
||||
$currentTeam->delete();
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ class Heading extends Component
|
||||
|
||||
protected string $deploymentUuid;
|
||||
|
||||
public bool $docker_cleanup = true;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
@@ -102,7 +104,7 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
StopApplication::run($this->application);
|
||||
StopApplication::run($this->application, false, $this->docker_cleanup);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
if ($this->application->additional_servers->count() > 0) {
|
||||
@@ -135,4 +137,13 @@ class Heading extends Component
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.heading', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
class Previews extends Component
|
||||
{
|
||||
@@ -184,17 +186,20 @@ class Previews extends Component
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$server = $this->application->destination->server;
|
||||
$timeout = 300;
|
||||
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
|
||||
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||
}
|
||||
$containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray();
|
||||
$this->stopContainers($containers, $server, $timeout);
|
||||
}
|
||||
GetContainersStatus::dispatchSync($this->application->destination->server)->onQueue('high');
|
||||
$this->dispatch('reloadWindow');
|
||||
|
||||
GetContainersStatus::run($server);
|
||||
$this->application->refresh();
|
||||
$this->dispatch('containerStatusUpdated');
|
||||
$this->dispatch('success', 'Preview Deployment stopped.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -203,16 +208,21 @@ class Previews extends Component
|
||||
public function delete(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$server = $this->application->destination->server;
|
||||
$timeout = 300;
|
||||
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
|
||||
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||
}
|
||||
$containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray();
|
||||
$this->stopContainers($containers, $server, $timeout);
|
||||
}
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
|
||||
|
||||
ApplicationPreview::where('application_id', $this->application->id)
|
||||
->where('pull_request_id', $pull_request_id)
|
||||
->first()
|
||||
->delete();
|
||||
|
||||
$this->application->refresh();
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Preview deleted.');
|
||||
@@ -220,4 +230,49 @@ class Previews extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
private function stopContainers(array $containers, $server, int $timeout)
|
||||
{
|
||||
$processes = [];
|
||||
foreach ($containers as $container) {
|
||||
$containerName = str_replace('/', '', $container['Names']);
|
||||
$processes[$containerName] = $this->stopContainer($containerName, $timeout);
|
||||
}
|
||||
|
||||
$startTime = time();
|
||||
while (count($processes) > 0) {
|
||||
$finishedProcesses = array_filter($processes, function ($process) {
|
||||
return !$process->running();
|
||||
});
|
||||
foreach (array_keys($finishedProcesses) as $containerName) {
|
||||
unset($processes[$containerName]);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopRemainingContainers(array_keys($processes), $server);
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
private function stopContainer(string $containerName, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
private function removeContainer(string $containerName, $server)
|
||||
{
|
||||
instant_remote_process(["docker rm -f $containerName"], $server, throwError: false);
|
||||
}
|
||||
|
||||
private function forceStopRemainingContainers(array $containerNames, $server)
|
||||
{
|
||||
foreach ($containerNames as $containerName) {
|
||||
instant_remote_process(["docker kill $containerName"], $server, throwError: false);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Livewire\Project\Database;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class BackupEdit extends Component
|
||||
{
|
||||
@@ -12,6 +14,10 @@ class BackupEdit extends Component
|
||||
|
||||
public $s3s;
|
||||
|
||||
public bool $delete_associated_backups_locally = false;
|
||||
public bool $delete_associated_backups_s3 = false;
|
||||
public bool $delete_associated_backups_sftp = false;
|
||||
|
||||
public ?string $status = null;
|
||||
|
||||
public array $parameters;
|
||||
@@ -46,16 +52,29 @@ class BackupEdit extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->delete_associated_backups_locally) {
|
||||
$this->deleteAssociatedBackupsLocally();
|
||||
}
|
||||
if ($this->delete_associated_backups_s3) {
|
||||
$this->deleteAssociatedBackupsS3();
|
||||
}
|
||||
|
||||
$this->backup->delete();
|
||||
|
||||
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
$previousUrl = url()->previous();
|
||||
$url = Url::fromString($previousUrl);
|
||||
$url = $url->withoutQueryParameter('selectedBackupId');
|
||||
$url = $url->withFragment('backups');
|
||||
$url = $url->getPath()."#{$url->getFragment()}";
|
||||
$url = $url->getPath() . "#{$url->getFragment()}";
|
||||
|
||||
return redirect($url);
|
||||
} else {
|
||||
@@ -104,4 +123,66 @@ class BackupEdit extends Component
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAssociatedBackupsLocally()
|
||||
{
|
||||
$executions = $this->backup->executions;
|
||||
$backupFolder = null;
|
||||
|
||||
foreach ($executions as $execution) {
|
||||
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
$server = $this->backup->database->service->destination->server;
|
||||
} else {
|
||||
$server = $this->backup->database->destination->server;
|
||||
}
|
||||
|
||||
if (!$backupFolder) {
|
||||
$backupFolder = dirname($execution->filename);
|
||||
}
|
||||
|
||||
delete_backup_locally($execution->filename, $server);
|
||||
$execution->delete();
|
||||
}
|
||||
|
||||
if ($backupFolder) {
|
||||
$this->deleteEmptyBackupFolder($backupFolder, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAssociatedBackupsS3()
|
||||
{
|
||||
//Add function to delete backups from S3
|
||||
}
|
||||
|
||||
public function deleteAssociatedBackupsSftp()
|
||||
{
|
||||
//Add function to delete backups from SFTP
|
||||
}
|
||||
|
||||
private function deleteEmptyBackupFolder($folderPath, $server)
|
||||
{
|
||||
$checkEmpty = instant_remote_process(["[ -z \"$(ls -A '$folderPath')\" ] && echo 'empty' || echo 'not empty'"], $server);
|
||||
|
||||
if (trim($checkEmpty) === 'empty') {
|
||||
instant_remote_process(["rmdir '$folderPath'"], $server);
|
||||
|
||||
$parentFolder = dirname($folderPath);
|
||||
$checkParentEmpty = instant_remote_process(["[ -z \"$(ls -A '$parentFolder')\" ] && echo 'empty' || echo 'not empty'"], $server);
|
||||
|
||||
if (trim($checkParentEmpty) === 'empty') {
|
||||
instant_remote_process(["rmdir '$parentFolder'"], $server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.backup-edit', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from local storage.'],
|
||||
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
|
||||
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace App\Livewire\Project\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class BackupExecutions extends Component
|
||||
{
|
||||
@@ -12,9 +15,12 @@ class BackupExecutions extends Component
|
||||
public $executions = [];
|
||||
public $setDeletableBackup;
|
||||
|
||||
public $delete_backup_s3 = true;
|
||||
public $delete_backup_sftp = true;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
|
||||
@@ -31,19 +37,34 @@ class BackupExecutions extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteBackup($exeuctionId)
|
||||
#[On('deleteBackup')]
|
||||
public function deleteBackup($executionId, $password)
|
||||
{
|
||||
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||
if (is_null($execution)) {
|
||||
$this->dispatch('error', 'Backup execution not found.');
|
||||
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
$execution = $this->backup->executions()->where('id', $executionId)->first();
|
||||
if (is_null($execution)) {
|
||||
$this->dispatch('error', 'Backup execution not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
|
||||
} else {
|
||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
|
||||
}
|
||||
|
||||
if ($this->delete_backup_s3) {
|
||||
// Add logic to delete from S3
|
||||
}
|
||||
|
||||
if ($this->delete_backup_sftp) {
|
||||
// Add logic to delete from SFTP
|
||||
}
|
||||
|
||||
$execution->delete();
|
||||
$this->dispatch('success', 'Backup deleted.');
|
||||
$this->refreshBackupExecutions();
|
||||
@@ -106,4 +127,14 @@ class BackupExecutions extends Component
|
||||
}
|
||||
return $dateObj->format('Y-m-d H:i:s T');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.backup-executions', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
|
||||
['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ class Heading extends Component
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public $docker_cleanup = true;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
@@ -54,7 +56,7 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
StopDatabase::run($this->database);
|
||||
StopDatabase::run($this->database, false, $this->docker_cleanup);
|
||||
$this->database->status = 'exited';
|
||||
$this->database->save();
|
||||
$this->check_status();
|
||||
@@ -71,4 +73,13 @@ class Heading extends Component
|
||||
$activity = StartDatabase::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.heading', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => 'Cleanup docker build cache and unused images (next deployment could take longer).'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,12 @@ class DeleteEnvironment extends Component
|
||||
|
||||
public bool $disabled = false;
|
||||
|
||||
public string $environmentName = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
@@ -13,9 +13,12 @@ class DeleteProject extends Component
|
||||
|
||||
public bool $disabled = false;
|
||||
|
||||
public string $projectName = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->projectName = Project::findOrFail($this->project_id)->name;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
@@ -52,7 +52,7 @@ class Configuration extends Component
|
||||
$application = $this->service->applications->find($id);
|
||||
if ($application) {
|
||||
$application->restart();
|
||||
$this->dispatch('success', 'Application restarted successfully.');
|
||||
$this->dispatch('success', 'Service application restarted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -65,7 +65,7 @@ class Configuration extends Component
|
||||
$database = $this->service->databases->find($id);
|
||||
if ($database) {
|
||||
$database->restart();
|
||||
$this->dispatch('success', 'Database restarted successfully.');
|
||||
$this->dispatch('success', 'Service database restarted successfully.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -15,6 +15,8 @@ use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class FileStorage extends Component
|
||||
{
|
||||
@@ -83,8 +85,13 @@ class FileStorage extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$message = 'File deleted.';
|
||||
if ($this->fileStorage->is_directory) {
|
||||
@@ -127,8 +134,15 @@ class FileStorage extends Component
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function render()
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.file-storage');
|
||||
return view('livewire.project.service.file-storage', [
|
||||
'directoryDeletionCheckboxes' => [
|
||||
['id' => 'permanently_delete', 'label' => 'The selected directory and all its contents will be permantely deleted form the server.'],
|
||||
],
|
||||
'fileDeletionCheckboxes' => [
|
||||
['id' => 'permanently_delete', 'label' => 'The selected file will be permanently deleted form the server.'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ class Navbar extends Component
|
||||
|
||||
public $isDeploymentProgress = false;
|
||||
|
||||
public $docker_cleanup = true;
|
||||
|
||||
public $title = 'Configuration';
|
||||
|
||||
public function mount()
|
||||
@@ -42,7 +44,7 @@ class Navbar extends Component
|
||||
|
||||
public function serviceStarted()
|
||||
{
|
||||
$this->dispatch('success', 'Service status changed.');
|
||||
// $this->dispatch('success', 'Service status changed.');
|
||||
if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) {
|
||||
$this->service->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
@@ -62,11 +64,6 @@ class Navbar extends Component
|
||||
$this->dispatch('success', 'Service status updated.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
|
||||
public function checkDeployments()
|
||||
{
|
||||
try {
|
||||
@@ -97,14 +94,9 @@ class Navbar extends Component
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function stop(bool $forceCleanup = false)
|
||||
public function stop()
|
||||
{
|
||||
StopService::run($this->service);
|
||||
if ($forceCleanup) {
|
||||
$this->dispatch('success', 'Containers cleaned up.');
|
||||
} else {
|
||||
$this->dispatch('success', 'Service stopped.');
|
||||
}
|
||||
StopService::run($this->service, false, $this->docker_cleanup);
|
||||
ServiceStatusChanged::dispatch();
|
||||
}
|
||||
|
||||
@@ -123,4 +115,13 @@ class Navbar extends Component
|
||||
$activity = StartService::run($this->service);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar', [
|
||||
'checkboxes' => [
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Livewire\Project\Service;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Component;
|
||||
|
||||
class ServiceApplicationView extends Component
|
||||
@@ -11,6 +13,10 @@ class ServiceApplicationView extends Component
|
||||
|
||||
public $parameters;
|
||||
|
||||
public $docker_cleanup = true;
|
||||
|
||||
public $delete_volumes = true;
|
||||
|
||||
protected $rules = [
|
||||
'application.human_name' => 'nullable',
|
||||
'application.description' => 'nullable',
|
||||
@@ -23,11 +29,6 @@ class ServiceApplicationView extends Component
|
||||
'application.is_stripprefix_enabled' => 'nullable|boolean',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.service-application-view');
|
||||
}
|
||||
|
||||
public function updatedApplicationFqdn()
|
||||
{
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||
@@ -56,8 +57,14 @@ class ServiceApplicationView extends Component
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->application->delete();
|
||||
$this->dispatch('success', 'Application deleted.');
|
||||
@@ -91,4 +98,17 @@ class ServiceApplicationView extends Component
|
||||
$this->dispatch('generateDockerCompose');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.service-application-view', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
// ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
|
||||
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
|
||||
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Jobs\DeleteResourceJob;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -10,6 +15,8 @@ class Danger extends Component
|
||||
{
|
||||
public $resource;
|
||||
|
||||
public $resourceName;
|
||||
|
||||
public $projectUuid;
|
||||
|
||||
public $environmentName;
|
||||
@@ -18,22 +25,93 @@ class Danger extends Component
|
||||
|
||||
public bool $delete_volumes = true;
|
||||
|
||||
public bool $docker_cleanup = true;
|
||||
|
||||
public bool $delete_connected_networks = true;
|
||||
|
||||
public ?string $modalId = null;
|
||||
|
||||
public string $resourceDomain = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalId = new Cuid2;
|
||||
$parameters = get_route_parameters();
|
||||
$this->modalId = new Cuid2;
|
||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||
$this->environmentName = data_get($parameters, 'environment_name');
|
||||
|
||||
if ($this->resource === null) {
|
||||
if (isset($parameters['service_uuid'])) {
|
||||
$this->resource = Service::where('uuid', $parameters['service_uuid'])->first();
|
||||
} elseif (isset($parameters['stack_service_uuid'])) {
|
||||
$this->resource = ServiceApplication::where('uuid', $parameters['stack_service_uuid'])->first()
|
||||
?? ServiceDatabase::where('uuid', $parameters['stack_service_uuid'])->first();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->resource === null) {
|
||||
$this->resourceName = 'Unknown Resource';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! method_exists($this->resource, 'type')) {
|
||||
$this->resourceName = 'Unknown Resource';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
$this->resourceName = $this->resource->name ?? 'Application';
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
case 'standalone-redis':
|
||||
case 'standalone-mongodb':
|
||||
case 'standalone-mysql':
|
||||
case 'standalone-mariadb':
|
||||
case 'standalone-keydb':
|
||||
case 'standalone-dragonfly':
|
||||
case 'standalone-clickhouse':
|
||||
$this->resourceName = $this->resource->name ?? 'Database';
|
||||
break;
|
||||
case 'service':
|
||||
$this->resourceName = $this->resource->name ?? 'Service';
|
||||
break;
|
||||
case 'service-application':
|
||||
$this->resourceName = $this->resource->name ?? 'Service Application';
|
||||
break;
|
||||
case 'service-database':
|
||||
$this->resourceName = $this->resource->name ?? 'Service Database';
|
||||
break;
|
||||
default:
|
||||
$this->resourceName = 'Unknown Resource';
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->resource) {
|
||||
$this->addError('resource', 'Resource not found.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// $this->authorize('delete', $this->resource);
|
||||
$this->resource->delete();
|
||||
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
|
||||
DeleteResourceJob::dispatch(
|
||||
$this->resource,
|
||||
$this->delete_configurations,
|
||||
$this->delete_volumes,
|
||||
$this->docker_cleanup,
|
||||
$this->delete_connected_networks
|
||||
);
|
||||
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
@@ -43,4 +121,19 @@ class Danger extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.danger', [
|
||||
'checkboxes' => [
|
||||
['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
|
||||
['id' => 'delete_connected_networks', 'label' => __('resource.delete_connected_networks')],
|
||||
['id' => 'delete_configurations', 'label' => __('resource.delete_configurations')],
|
||||
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
|
||||
// ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
|
||||
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
|
||||
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Destination extends Component
|
||||
{
|
||||
@@ -115,8 +117,13 @@ class Destination extends Component
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
}
|
||||
|
||||
public function removeServer(int $network_id, int $server_id)
|
||||
public function removeServer(int $network_id, int $server_id, $password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
|
||||
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ class Show extends Component
|
||||
|
||||
public string $type;
|
||||
|
||||
public string $scheduledTaskName;
|
||||
|
||||
protected $rules = [
|
||||
'task.enabled' => 'required|boolean',
|
||||
'task.name' => 'required|string',
|
||||
@@ -49,6 +51,7 @@ class Show extends Component
|
||||
|
||||
$this->modalId = new Cuid2;
|
||||
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
|
||||
$this->scheduledTaskName = $this->task->name;
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
@@ -75,9 +78,9 @@ class Show extends Component
|
||||
$this->task->delete();
|
||||
|
||||
if ($this->type == 'application') {
|
||||
return redirect()->route('project.application.configuration', $this->parameters);
|
||||
return redirect()->route('project.application.configuration', $this->parameters, $this->scheduledTaskName);
|
||||
} else {
|
||||
return redirect()->route('project.service.configuration', $this->parameters);
|
||||
return redirect()->route('project.service.configuration', $this->parameters, $this->scheduledTaskName);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
@@ -36,8 +38,13 @@ class Show extends Component
|
||||
$this->dispatch('success', 'Storage updated successfully');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->storage->delete();
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Livewire\Server;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Delete extends Component
|
||||
{
|
||||
@@ -11,8 +13,12 @@ class Delete extends Component
|
||||
|
||||
public $server;
|
||||
|
||||
public function delete()
|
||||
public function delete($password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->authorize('delete', $this->server);
|
||||
if ($this->server->hasDefinedResources()) {
|
||||
|
||||
@@ -7,6 +7,8 @@ use App\Actions\Proxy\StartProxy;
|
||||
use App\Events\ProxyStatusChanged;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
|
||||
class Deploy extends Component
|
||||
{
|
||||
@@ -94,21 +96,43 @@ class Deploy extends Component
|
||||
public function stop(bool $forceStop = true)
|
||||
{
|
||||
try {
|
||||
if ($this->server->isSwarm()) {
|
||||
instant_remote_process([
|
||||
'docker service rm coolify-proxy_traefik',
|
||||
], $this->server);
|
||||
} else {
|
||||
instant_remote_process([
|
||||
'docker rm -f coolify-proxy',
|
||||
], $this->server);
|
||||
$containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||
$timeout = 30;
|
||||
|
||||
$process = $this->stopContainer($containerName, $timeout);
|
||||
|
||||
$startTime = time();
|
||||
while ($process->running()) {
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->proxy->force_stop = $forceStop;
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
|
||||
$this->removeContainer($containerName);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->server->proxy->force_stop = $forceStop;
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
private function stopContainer(string $containerName, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
private function forceStopContainer(string $containerName)
|
||||
{
|
||||
instant_remote_process(["docker kill $containerName"], $this->server, throwError: false);
|
||||
}
|
||||
|
||||
private function removeContainer(string $containerName)
|
||||
{
|
||||
instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Livewire\Team;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AdminView extends Component
|
||||
{
|
||||
@@ -73,8 +75,12 @@ class AdminView extends Component
|
||||
$team->delete();
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
public function delete($id, $password)
|
||||
{
|
||||
if (!Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
return;
|
||||
}
|
||||
if (! auth()->user()->isInstanceAdmin()) {
|
||||
return $this->dispatch('error', 'You are not authorized to delete users');
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
use OpenApi\Attributes as OA;
|
||||
use RuntimeException;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
@@ -149,13 +151,65 @@ class Application extends BaseModel
|
||||
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
|
||||
}
|
||||
|
||||
public function getContainersToStop(bool $previewDeployments = false): array
|
||||
{
|
||||
$containers = $previewDeployments
|
||||
? getCurrentApplicationContainerStatus($this->destination->server, $this->id, includePullrequests: true)
|
||||
: getCurrentApplicationContainerStatus($this->destination->server, $this->id, 0);
|
||||
|
||||
return $containers->pluck('Names')->toArray();
|
||||
}
|
||||
|
||||
public function stopContainers(array $containerNames, $server, int $timeout = 600)
|
||||
{
|
||||
$processes = [];
|
||||
foreach ($containerNames as $containerName) {
|
||||
$processes[$containerName] = $this->stopContainer($containerName, $server, $timeout);
|
||||
}
|
||||
|
||||
$startTime = time();
|
||||
while (count($processes) > 0) {
|
||||
$finishedProcesses = array_filter($processes, function ($process) {
|
||||
return !$process->running();
|
||||
});
|
||||
foreach ($finishedProcesses as $containerName => $process) {
|
||||
unset($processes[$containerName]);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopRemainingContainers(array_keys($processes), $server);
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
public function stopContainer(string $containerName, $server, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
public function removeContainer(string $containerName, $server)
|
||||
{
|
||||
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
public function forceStopRemainingContainers(array $containerNames, $server)
|
||||
{
|
||||
foreach ($containerNames as $containerName) {
|
||||
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
ray('Deleting workdir');
|
||||
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
||||
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +230,14 @@ class Application extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_connected_networks($uuid)
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
|
||||
|
||||
public function additional_servers()
|
||||
{
|
||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||
@@ -283,7 +345,7 @@ class Application extends BaseModel
|
||||
public function publishDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value ? '/'.ltrim($value, '/') : null,
|
||||
set: fn ($value) => $value ? '/' . ltrim($value, '/') : null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -291,7 +353,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||
if (str($this->git_repository)->contains('bitbucket')) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}";
|
||||
}
|
||||
@@ -318,7 +380,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
@@ -337,7 +399,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
||||
}
|
||||
// Convert the SSH URL to HTTPS URL
|
||||
@@ -354,7 +416,7 @@ class Application extends BaseModel
|
||||
|
||||
public function gitCommitLink($link): string
|
||||
{
|
||||
if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) {
|
||||
if (!is_null(data_get($this, 'source.html_url')) && !is_null(data_get($this, 'git_repository')) && !is_null(data_get($this, 'git_branch'))) {
|
||||
if (str($this->source->html_url)->contains('bitbucket')) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
|
||||
}
|
||||
@@ -365,7 +427,7 @@ class Application extends BaseModel
|
||||
$git_repository = str_replace('.git', '', $this->git_repository);
|
||||
$url = Url::fromString($git_repository);
|
||||
$url = $url->withUserInfo('');
|
||||
$url = $url->withPath($url->getPath().'/commits/'.$link);
|
||||
$url = $url->withPath($url->getPath() . '/commits/' . $link);
|
||||
|
||||
return $url->__toString();
|
||||
}
|
||||
@@ -418,7 +480,7 @@ class Application extends BaseModel
|
||||
public function baseDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => '/'.ltrim($value, '/'),
|
||||
set: fn ($value) => '/' . ltrim($value, '/'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -761,7 +823,7 @@ class Application extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return application_configuration_dir()."/{$this->uuid}";
|
||||
return application_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
|
||||
public function isLogDrainEnabled()
|
||||
@@ -771,7 +833,7 @@ class Application extends BaseModel
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect;
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build . $this->redirect;
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||
} else {
|
||||
@@ -825,7 +887,7 @@ class Application extends BaseModel
|
||||
|
||||
public function dirOnServer()
|
||||
{
|
||||
return application_configuration_dir()."/{$this->uuid}";
|
||||
return application_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
|
||||
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||
@@ -871,7 +933,7 @@ class Application extends BaseModel
|
||||
if ($this->source->is_public) {
|
||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
|
||||
if (! $only_checkout) {
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
@@ -888,7 +950,7 @@ class Application extends BaseModel
|
||||
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
|
||||
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||
}
|
||||
if (! $only_checkout) {
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
@@ -949,7 +1011,7 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
@@ -957,14 +1019,14 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,7 +1055,7 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
@@ -1001,14 +1063,14 @@ class Application extends BaseModel
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,20 +1122,20 @@ class Application extends BaseModel
|
||||
}
|
||||
if ($source->startsWith('.')) {
|
||||
$source = $source->after('.');
|
||||
$source = $workdir.$source;
|
||||
$source = $workdir . $source;
|
||||
}
|
||||
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
}
|
||||
$labels = collect(data_get($service, 'labels', []));
|
||||
if (! $labels->contains('coolify.managed')) {
|
||||
if (!$labels->contains('coolify.managed')) {
|
||||
$labels->push('coolify.managed=true');
|
||||
}
|
||||
if (! $labels->contains('coolify.applicationId')) {
|
||||
$labels->push('coolify.applicationId='.$this->id);
|
||||
if (!$labels->contains('coolify.applicationId')) {
|
||||
$labels->push('coolify.applicationId=' . $this->id);
|
||||
}
|
||||
if (! $labels->contains('coolify.type')) {
|
||||
if (!$labels->contains('coolify.type')) {
|
||||
$labels->push('coolify.type=application');
|
||||
}
|
||||
data_set($service, 'labels', $labels->toArray());
|
||||
@@ -1149,7 +1211,7 @@ class Application extends BaseModel
|
||||
$jsonNames = $json->keys()->toArray();
|
||||
$diff = array_diff($jsonNames, $names);
|
||||
$json = $json->filter(function ($value, $key) use ($diff) {
|
||||
return ! in_array($key, $diff);
|
||||
return !in_array($key, $diff);
|
||||
});
|
||||
if ($json) {
|
||||
$this->docker_compose_domains = json_encode($json);
|
||||
@@ -1166,13 +1228,12 @@ class Application extends BaseModel
|
||||
} else {
|
||||
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
||||
{
|
||||
$customLabels = data_get($this, 'custom_labels');
|
||||
if (! $customLabels) {
|
||||
if (!$customLabels) {
|
||||
return;
|
||||
}
|
||||
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
|
||||
@@ -1255,10 +1316,10 @@ class Application extends BaseModel
|
||||
continue;
|
||||
}
|
||||
if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) {
|
||||
$healthcheckCommand .= ' '.trim($trimmedLine, '\\ ');
|
||||
$healthcheckCommand .= ' ' . trim($trimmedLine, '\\ ');
|
||||
}
|
||||
if (isset($healthcheckCommand) && ! str_contains($trimmedLine, '\\') && ! empty($healthcheckCommand)) {
|
||||
$healthcheckCommand .= ' '.$trimmedLine;
|
||||
if (isset($healthcheckCommand) && !str_contains($trimmedLine, '\\') && !empty($healthcheckCommand)) {
|
||||
$healthcheckCommand .= ' ' . $trimmedLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Spatie\Url\Url;
|
||||
@@ -68,7 +70,7 @@ class Service extends BaseModel
|
||||
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
|
||||
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
|
||||
|
||||
$newConfigHash = $images.$domains.$images.$storages;
|
||||
$newConfigHash = $images . $domains . $images . $storages;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
@@ -131,15 +133,80 @@ class Service extends BaseModel
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
|
||||
public function getContainersToStop(): array
|
||||
{
|
||||
$containersToStop = [];
|
||||
$applications = $this->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
$containersToStop[] = "{$application->name}-{$this->uuid}";
|
||||
}
|
||||
$dbs = $this->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
$containersToStop[] = "{$db->name}-{$this->uuid}";
|
||||
}
|
||||
return $containersToStop;
|
||||
}
|
||||
|
||||
public function stopContainers(array $containerNames, $server, int $timeout = 300)
|
||||
{
|
||||
$processes = [];
|
||||
foreach ($containerNames as $containerName) {
|
||||
$processes[$containerName] = $this->stopContainer($containerName, $timeout);
|
||||
}
|
||||
|
||||
$startTime = time();
|
||||
while (count($processes) > 0) {
|
||||
$finishedProcesses = array_filter($processes, function ($process) {
|
||||
return !$process->running();
|
||||
});
|
||||
foreach (array_keys($finishedProcesses) as $containerName) {
|
||||
unset($processes[$containerName]);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
|
||||
if (time() - $startTime >= $timeout) {
|
||||
$this->forceStopRemainingContainers(array_keys($processes), $server);
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
public function stopContainer(string $containerName, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
public function removeContainer(string $containerName, $server)
|
||||
{
|
||||
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
|
||||
}
|
||||
|
||||
public function forceStopRemainingContainers(array $containerNames, $server)
|
||||
{
|
||||
foreach ($containerNames as $containerName) {
|
||||
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
|
||||
$this->removeContainer($containerName, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'server');
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
||||
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete_connected_networks($uuid)
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
|
||||
public function status()
|
||||
{
|
||||
$applications = $this->applications;
|
||||
@@ -994,7 +1061,7 @@ class Service extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir()."/{$this->uuid}";
|
||||
return service_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
|
||||
public function saveComposeConfigs()
|
||||
|
||||
@@ -23,7 +23,7 @@ class ServiceApplication extends BaseModel
|
||||
|
||||
public function restart()
|
||||
{
|
||||
$container_id = $this->name.'-'.$this->service->uuid;
|
||||
$container_id = $this->name . '-' . $this->service->uuid;
|
||||
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class ServiceApplication extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir()."/{$this->service->uuid}";
|
||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
||||
}
|
||||
|
||||
public function serviceType()
|
||||
|
||||
@@ -21,7 +21,7 @@ class ServiceDatabase extends BaseModel
|
||||
|
||||
public function restart()
|
||||
{
|
||||
$container_id = $this->name.'-'.$this->service->uuid;
|
||||
$container_id = $this->name . '-' . $this->service->uuid;
|
||||
remote_process(["docker restart {$container_id}"], $this->service->server);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ServiceDatabase extends BaseModel
|
||||
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir()."/{$this->service->uuid}";
|
||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
||||
}
|
||||
|
||||
public function service()
|
||||
|
||||
Reference in New Issue
Block a user