diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index d32248042..2c74f09dc 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -11,6 +11,9 @@ class StopApplication public function handle(Application $application) { $server = $application->destination->server; + if (!$server->isFunctional()) { + return 'Server is not functional'; + } if ($server->isSwarm()) { instant_remote_process(["docker stack rm {$application->uuid}" ], $server); } else { diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index e6316ecc9..019001070 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -17,6 +17,9 @@ class StopDatabase public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { $server = $database->destination->server; + if (!$server->isFunctional()) { + return 'Server is not functional'; + } instant_remote_process( ["docker rm -f {$database->uuid}"], $server diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php index 743158c2d..f7464a697 100644 --- a/app/Actions/Service/DeleteService.php +++ b/app/Actions/Service/DeleteService.php @@ -10,36 +10,41 @@ class DeleteService use AsAction; public function handle(Service $service) { - $server = data_get($service, 'server'); - if ($server->isFunctional()) { - StopService::run($service); - } - $storagesToDelete = collect([]); + try { + $server = data_get($service, 'server'); + if ($server->isFunctional()) { + $storagesToDelete = collect([]); - $service->environment_variables()->delete(); - $commands = []; - foreach ($service->applications()->get() as $application) { - $storages = $application->persistentStorages()->get(); - foreach ($storages as $storage) { - $storagesToDelete->push($storage); - } - $application->forceDelete(); - } - foreach ($service->databases()->get() as $database) { - $storages = $database->persistentStorages()->get(); - foreach ($storages as $storage) { - $storagesToDelete->push($storage); - } - $database->forceDelete(); - } - if ($server->isFunctional()) { - foreach ($storagesToDelete as $storage) { - $commands[] = "docker volume rm -f $storage->name"; - } - $commands[] = "docker rm -f $service->uuid"; + $service->environment_variables()->delete(); + $commands = []; + foreach ($service->applications()->get() as $application) { + $storages = $application->persistentStorages()->get(); + foreach ($storages as $storage) { + $storagesToDelete->push($storage); + } + } + foreach ($service->databases()->get() as $database) { + $storages = $database->persistentStorages()->get(); + foreach ($storages as $storage) { + $storagesToDelete->push($storage); + } + } + foreach ($storagesToDelete as $storage) { + $commands[] = "docker volume rm -f $storage->name"; + } + $commands[] = "docker rm -f $service->uuid"; - instant_remote_process($commands, $server, false); + instant_remote_process($commands, $server, false); + } + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } finally { + foreach ($service->applications()->get() as $application) { + $application->forceDelete(); + } + foreach ($service->databases()->get() as $database) { + $database->forceDelete(); + } } - $service->forceDelete(); } } diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index d9ac6376e..e1ce9eba1 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -10,6 +10,10 @@ class StopService use AsAction; public function handle(Service $service) { + $server = $service->destination->server; + if (!$server->isFunctional()) { + return 'Server is not functional'; + } ray('Stopping service: ' . $service->name); $applications = $service->applications()->get(); foreach ($applications as $application) { diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php new file mode 100644 index 000000000..1674c01d8 --- /dev/null +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -0,0 +1,295 @@ +cleanup_stucked_resources(); + } + private function cleanup_stucked_resources() + { + + try { + $applications = Application::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($applications as $application) { + echo "Deleting stuck application: {$application->name}\n"; + $application->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck application: {$e->getMessage()}\n"; + } + try { + $postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($postgresqls as $postgresql) { + echo "Deleting stuck postgresql: {$postgresql->name}\n"; + $postgresql->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n"; + } + try { + $redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($redis as $redis) { + echo "Deleting stuck redis: {$redis->name}\n"; + $redis->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck redis: {$e->getMessage()}\n"; + } + try { + $mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($mongodbs as $mongodb) { + echo "Deleting stuck mongodb: {$mongodb->name}\n"; + $mongodb->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n"; + } + try { + $mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($mysqls as $mysql) { + echo "Deleting stuck mysql: {$mysql->name}\n"; + $mysql->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck mysql: {$e->getMessage()}\n"; + } + try { + $mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($mariadbs as $mariadb) { + echo "Deleting stuck mariadb: {$mariadb->name}\n"; + $mariadb->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n"; + } + try { + $services = Service::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($services as $service) { + echo "Deleting stuck service: {$service->name}\n"; + $service->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck service: {$e->getMessage()}\n"; + } + try { + $serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($serviceApps as $serviceApp) { + echo "Deleting stuck serviceapp: {$serviceApp->name}\n"; + $serviceApp->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; + } + try { + $serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get(); + foreach ($serviceDbs as $serviceDb) { + echo "Deleting stuck serviceapp: {$serviceDb->name}\n"; + $serviceDb->forceDelete(); + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; + } + + // Cleanup any resources that are not attached to any environment or destination or server + try { + $applications = Application::all(); + foreach ($applications as $application) { + if (!data_get($application, 'environment')) { + echo 'Application without environment: ' . $application->name . ' soft deleting\n'; + $application->delete(); + continue; + } + if (!$application->destination()) { + echo 'Application without destination: ' . $application->name . ' soft deleting\n'; + $application->delete(); + continue; + } + if (!data_get($application, 'destination.server')) { + echo 'Application without server: ' . $application->name . ' soft deleting\n'; + $application->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in application: {$e->getMessage()}\n"; + } + try { + $postgresqls = StandalonePostgresql::all()->where('id', '!=', 0); + foreach ($postgresqls as $postgresql) { + if (!data_get($postgresql, 'environment')) { + echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n'; + $postgresql->delete(); + continue; + } + if (!$postgresql->destination()) { + echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n'; + $postgresql->delete(); + continue; + } + if (!data_get($postgresql, 'destination.server')) { + echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n'; + $postgresql->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in postgresql: {$e->getMessage()}\n"; + } + try { + $redis = StandaloneRedis::all(); + foreach ($redis as $redis) { + if (!data_get($redis, 'environment')) { + echo 'Redis without environment: ' . $redis->name . ' soft deleting\n'; + $redis->delete(); + continue; + } + if (!$redis->destination()) { + echo 'Redis without destination: ' . $redis->name . ' soft deleting\n'; + $redis->delete(); + continue; + } + if (!data_get($redis, 'destination.server')) { + echo 'Redis without server: ' . $redis->name . ' soft deleting\n'; + $redis->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in redis: {$e->getMessage()}\n"; + } + + try { + $mongodbs = StandaloneMongodb::all(); + foreach ($mongodbs as $mongodb) { + if (!data_get($mongodb, 'environment')) { + echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n'; + $mongodb->delete(); + continue; + } + if (!$mongodb->destination()) { + echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n'; + $mongodb->delete(); + continue; + } + if (!data_get($mongodb, 'destination.server')) { + echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n'; + $mongodb->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in mongodb: {$e->getMessage()}\n"; + } + + try { + $mysqls = StandaloneMysql::all(); + foreach ($mysqls as $mysql) { + if (!data_get($mysql, 'environment')) { + echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n'; + $mysql->delete(); + continue; + } + if (!$mysql->destination()) { + echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n'; + $mysql->delete(); + continue; + } + if (!data_get($mysql, 'destination.server')) { + echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n'; + $mysql->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in mysql: {$e->getMessage()}\n"; + } + + try { + $mariadbs = StandaloneMariadb::all(); + foreach ($mariadbs as $mariadb) { + if (!data_get($mariadb, 'environment')) { + echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n'; + $mariadb->delete(); + continue; + } + if (!$mariadb->destination()) { + echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n'; + $mariadb->delete(); + continue; + } + if (!data_get($mariadb, 'destination.server')) { + echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n'; + $mariadb->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in mariadb: {$e->getMessage()}\n"; + } + + try { + $services = Service::all(); + foreach ($services as $service) { + if (!data_get($service, 'environment')) { + echo 'Service without environment: ' . $service->name . ' soft deleting\n'; + $service->delete(); + continue; + } + if (!$service->destination()) { + echo 'Service without destination: ' . $service->name . ' soft deleting\n'; + $service->delete(); + continue; + } + if (!data_get($service, 'server')) { + echo 'Service without server: ' . $service->name . ' soft deleting\n'; + $service->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in service: {$e->getMessage()}\n"; + } + try { + $serviceApplications = ServiceApplication::all(); + foreach ($serviceApplications as $service) { + if (!data_get($service, 'service')) { + echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n'; + $service->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in serviceApplications: {$e->getMessage()}\n"; + } + try { + $serviceDatabases = ServiceDatabase::all(); + foreach ($serviceDatabases as $service) { + if (!data_get($service, 'service')) { + echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n'; + $service->delete(); + continue; + } + } + } catch (\Throwable $e) { + echo "Error in ServiceDatabases: {$e->getMessage()}\n"; + } + } +} diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php new file mode 100644 index 000000000..8bbd27d64 --- /dev/null +++ b/app/Console/Commands/CleanupUnreachableServers.php @@ -0,0 +1,26 @@ +where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get(); + if ($servers->count() > 0) { + foreach ($servers as $server) { + echo "Cleanup unreachable server ($server->id) with name $server->name"; + $server->update([ + 'ip' => '1.2.3.4' + ]); + } + } + } +} diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 798dcf275..c4dc20988 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -4,19 +4,11 @@ namespace App\Console\Commands; use App\Enums\ApplicationDeploymentStatus; use App\Jobs\CleanupHelperContainersJob; -use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\InstanceSettings; use App\Models\ScheduledDatabaseBackup; use App\Models\Server; -use App\Models\Service; -use App\Models\ServiceApplication; -use App\Models\ServiceDatabase; -use App\Models\StandaloneMariadb; -use App\Models\StandaloneMongodb; -use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; -use App\Models\StandaloneRedis; use Illuminate\Console\Command; use Illuminate\Support\Facades\Http; @@ -31,7 +23,7 @@ class Init extends Command $cleanup = $this->option('cleanup'); if ($cleanup) { echo "Running cleanups...\n"; - $this->cleanup_stucked_resources(); + $this->call('cleanup:stucked-resources'); // Required for falsely deleted coolify db $this->restore_coolify_db_backup(); @@ -137,273 +129,5 @@ class Init extends Command echo "Error: {$e->getMessage()}\n"; } } - private function cleanup_stucked_resources() - { - try { - $applications = Application::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($applications as $application) { - echo "Deleting stuck application: {$application->name}\n"; - $application->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck application: {$e->getMessage()}\n"; - } - try { - $postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($postgresqls as $postgresql) { - echo "Deleting stuck postgresql: {$postgresql->name}\n"; - $postgresql->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n"; - } - try { - $redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($redis as $redis) { - echo "Deleting stuck redis: {$redis->name}\n"; - $redis->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck redis: {$e->getMessage()}\n"; - } - try { - $mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($mongodbs as $mongodb) { - echo "Deleting stuck mongodb: {$mongodb->name}\n"; - $mongodb->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n"; - } - try { - $mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($mysqls as $mysql) { - echo "Deleting stuck mysql: {$mysql->name}\n"; - $mysql->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck mysql: {$e->getMessage()}\n"; - } - try { - $mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($mariadbs as $mariadb) { - echo "Deleting stuck mariadb: {$mariadb->name}\n"; - $mariadb->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n"; - } - try { - $services = Service::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($services as $service) { - echo "Deleting stuck service: {$service->name}\n"; - $service->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck service: {$e->getMessage()}\n"; - } - try { - $serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($serviceApps as $serviceApp) { - echo "Deleting stuck serviceapp: {$serviceApp->name}\n"; - $serviceApp->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; - } - try { - $serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get(); - foreach ($serviceDbs as $serviceDb) { - echo "Deleting stuck serviceapp: {$serviceDb->name}\n"; - $serviceDb->forceDelete(); - } - } catch (\Throwable $e) { - echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; - } - - // Cleanup any resources that are not attached to any environment or destination or server - try { - $applications = Application::all(); - foreach ($applications as $application) { - if (!data_get($application, 'environment')) { - echo 'Application without environment: ' . $application->name . ' soft deleting\n'; - $application->delete(); - continue; - } - if (!$application->destination()) { - echo 'Application without destination: ' . $application->name . ' soft deleting\n'; - $application->delete(); - continue; - } - if (!data_get($application, 'destination.server')) { - echo 'Application without server: ' . $application->name . ' soft deleting\n'; - $application->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in application: {$e->getMessage()}\n"; - } - try { - $postgresqls = StandalonePostgresql::all()->where('id', '!=', 0); - foreach ($postgresqls as $postgresql) { - if (!data_get($postgresql, 'environment')) { - echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n'; - $postgresql->delete(); - continue; - } - if (!$postgresql->destination()) { - echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n'; - $postgresql->delete(); - continue; - } - if (!data_get($postgresql, 'destination.server')) { - echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n'; - $postgresql->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in postgresql: {$e->getMessage()}\n"; - } - try { - $redis = StandaloneRedis::all(); - foreach ($redis as $redis) { - if (!data_get($redis, 'environment')) { - echo 'Redis without environment: ' . $redis->name . ' soft deleting\n'; - $redis->delete(); - continue; - } - if (!$redis->destination()) { - echo 'Redis without destination: ' . $redis->name . ' soft deleting\n'; - $redis->delete(); - continue; - } - if (!data_get($redis, 'destination.server')) { - echo 'Redis without server: ' . $redis->name . ' soft deleting\n'; - $redis->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in redis: {$e->getMessage()}\n"; - } - - try { - $mongodbs = StandaloneMongodb::all(); - foreach ($mongodbs as $mongodb) { - if (!data_get($mongodb, 'environment')) { - echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n'; - $mongodb->delete(); - continue; - } - if (!$mongodb->destination()) { - echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n'; - $mongodb->delete(); - continue; - } - if (!data_get($mongodb, 'destination.server')) { - echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n'; - $mongodb->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in mongodb: {$e->getMessage()}\n"; - } - - try { - $mysqls = StandaloneMysql::all(); - foreach ($mysqls as $mysql) { - if (!data_get($mysql, 'environment')) { - echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n'; - $mysql->delete(); - continue; - } - if (!$mysql->destination()) { - echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n'; - $mysql->delete(); - continue; - } - if (!data_get($mysql, 'destination.server')) { - echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n'; - $mysql->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in mysql: {$e->getMessage()}\n"; - } - - try { - $mariadbs = StandaloneMariadb::all(); - foreach ($mariadbs as $mariadb) { - if (!data_get($mariadb, 'environment')) { - echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n'; - $mariadb->delete(); - continue; - } - if (!$mariadb->destination()) { - echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n'; - $mariadb->delete(); - continue; - } - if (!data_get($mariadb, 'destination.server')) { - echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n'; - $mariadb->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in mariadb: {$e->getMessage()}\n"; - } - - try { - $services = Service::all(); - foreach ($services as $service) { - if (!data_get($service, 'environment')) { - echo 'Service without environment: ' . $service->name . ' soft deleting\n'; - $service->delete(); - continue; - } - if (!$service->destination()) { - echo 'Service without destination: ' . $service->name . ' soft deleting\n'; - $service->delete(); - continue; - } - if (!data_get($service, 'server')) { - echo 'Service without server: ' . $service->name . ' soft deleting\n'; - $service->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in service: {$e->getMessage()}\n"; - } - try { - $serviceApplications = ServiceApplication::all(); - foreach ($serviceApplications as $service) { - if (!data_get($service, 'service')) { - echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n'; - $service->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in serviceApplications: {$e->getMessage()}\n"; - } - try { - $serviceDatabases = ServiceDatabase::all(); - foreach ($serviceDatabases as $service) { - if (!data_get($service, 'service')) { - echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n'; - $service->delete(); - continue; - } - } - } catch (\Throwable $e) { - echo "Error in ServiceDatabases: {$e->getMessage()}\n"; - } - } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 50db6d681..c56feb902 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -36,6 +36,8 @@ class Kernel extends ConsoleKernel } else { // Instance Jobs $schedule->command('horizon:snapshot')->everyFiveMinutes(); + $schedule->command('cleanup:unreachable-servers')->daily(); + $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 34f358367..457cc04ad 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -348,9 +348,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ], ); } - $this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'"); + $this->application_deployment_queue->addLogEntry("Image pushed to docker registry."); } catch (Exception $e) { - $this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'"); + $this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information."); if ($forceFail) { throw $e; } @@ -488,10 +488,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); } - $this->server->executeRemoteCommand( - commands: $this->application->prepareHelperImage($this->deployment_uuid), - loggingModel: $this->application_deployment_queue - ); + ray('asddf'); + $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->clone_repository(); $this->generate_image_names(); diff --git a/app/Jobs/ApplicationDeploymentNewJob.php b/app/Jobs/ApplicationDeploymentNewJob.php deleted file mode 100644 index 94b9a4e27..000000000 --- a/app/Jobs/ApplicationDeploymentNewJob.php +++ /dev/null @@ -1,180 +0,0 @@ -mainServer = data_get($this->application, 'destination.server'); - $this->deploymentUuid = data_get($this->deployment, 'deployment_uuid'); - $this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0); - $this->gitType = data_get($this->deployment, 'git_type'); - - $this->basedir = $this->application->generateBaseDir($this->deploymentUuid); - $this->workdir = $this->basedir . rtrim($this->application->base_directory, '/'); - } - public function handle() - { - try { - ray()->clearAll(); - $this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value); - - $hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network); - if ($this->application->dockerfile_target_build) { - $buildTarget = " --target {$this->application->dockerfile_target_build} "; - } - - // Get the git repository and port (custom port or default port) - [ - 'repository' => $this->gitRepository, - 'port' => $this->gitPort - ] = $this->application->customRepository(); - - // Get the git branch and git import commands - [ - 'commands' => $this->gitImportCommands, - 'branch' => $this->gitBranch, - 'fullRepoUrl' => $this->gitFullRepoUrl - ] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType); - - $this->servers = $this->application->servers(); - - if ($this->deployment->restart_only) { - if ($this->application->build_pack === 'dockerimage') { - throw new \Exception('Restart only is not supported for docker image based deployments'); - } - $this->deployment->addLogEntry("Starting deployment of {$this->application->name}."); - $this->servers->each(function ($server) { - $this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}."); - $this->restartOnly($server); - }); - } - $this->next(ApplicationDeploymentStatus::FINISHED->value); - } catch (Throwable $exception) { - $this->fail($exception); - } finally { - $this->servers->each(function ($server) { - $this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}."); - $server->executeRemoteCommand( - commands: collect([])->push([ - "command" => "docker rm -f {$this->deploymentUuid}", - "hidden" => true, - "ignoreErrors" => true, - ]), - loggingModel: $this->deployment - ); - }); - } - } - public function restartOnly(Server $server) - { - $server->executeRemoteCommand( - commands: $this->application->prepareHelperImage($this->deploymentUuid), - loggingModel: $this->deployment - ); - - $privateKey = data_get($this->application, 'private_key.private_key', null); - $gitLsRemoteCommand = collect([]); - if ($privateKey) { - $privateKey = base64_decode($privateKey); - $gitLsRemoteCommand - ->push([ - "command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh") - ]) - ->push([ - "command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa") - ]) - ->push([ - "command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa") - ]) - ->push([ - "name" => "git_commit_sha", - "command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"), - "hidden" => true, - ]); - } else { - $gitLsRemoteCommand->push([ - "name" => "git_commit_sha", - "command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"), - "hidden" => true, - ]); - } - $this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch."); - - $server->executeRemoteCommand( - commands: $gitLsRemoteCommand, - loggingModel: $this->deployment - ); - $commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t"); - - [ - 'productionImageName' => $productionImageName - ] = $this->application->generateImageNames($commit, $this->pullRequestId); - - $this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists."); - $server->checkIfDockerImageExists($productionImageName, $this->deployment); - - if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) { - $this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build."); - - $server->createWorkDirForDeployment($this->workdir, $this->deployment); - - $this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir); - $this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir); - return; - } - throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.'); - } - public function failed(Throwable $exception): void - { - ray($exception); - $this->next(ApplicationDeploymentStatus::FAILED->value); - } - private function next(string $status) - { - // If the deployment is cancelled by the user, don't update the status - if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) { - $this->deployment->update([ - 'status' => $status, - ]); - } - queue_next_deployment($this->application, isNew: true); - } -} diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index a29c87c09..35d98412f 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -5,6 +5,7 @@ namespace App\Jobs; use App\Actions\Application\StopApplication; use App\Actions\Database\StopDatabase; use App\Actions\Service\DeleteService; +use App\Actions\Service\StopService; use App\Models\Application; use App\Models\Service; use App\Models\StandaloneMariadb; @@ -30,41 +31,22 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted public function handle() { try { - $server = $this->resource->destination->server; - $this->resource->delete(); - if (!$server->isFunctional()) { - if ($this->resource->type() === 'service') { - ray('dispatching delete service'); - DeleteService::dispatch($this->resource); - } else { - $this->resource->forceDelete(); - } - return 'Server is not functional'; - } + $this->resource->forceDelete(); switch ($this->resource->type()) { case 'application': StopApplication::run($this->resource); break; case 'standalone-postgresql': - StopDatabase::run($this->resource); - break; case 'standalone-redis': - StopDatabase::run($this->resource); - break; case 'standalone-mongodb': - StopDatabase::run($this->resource); - break; case 'standalone-mysql': - StopDatabase::run($this->resource); - break; case 'standalone-mariadb': StopDatabase::run($this->resource); break; - } - if ($this->resource->type() === 'service') { - DeleteService::dispatch($this->resource); - } else { - $this->resource->forceDelete(); + case 'service': + StopService::run($this->resource); + DeleteService::run($this->resource); + break; } } catch (\Throwable $e) { send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage()); diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 23bd94fb2..0f6d61957 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -245,9 +245,10 @@ class General extends Component $domains = $domains->unique(); foreach ($domains as $domain) { if (!validate_dns_entry($domain, $this->application->destination->server)) { - $showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.Make sure you have added the DNS records correctly.Check this documentation for further help."); + $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.","Make sure you have added the DNS records correctly.Check this documentation for further help."); } } + check_fqdn_usage($this->application); $this->application->fqdn = $domains->implode(','); } diff --git a/app/Livewire/Project/Service/Application.php b/app/Livewire/Project/Service/Application.php index 3ff711400..c3157921b 100644 --- a/app/Livewire/Project/Service/Application.php +++ b/app/Livewire/Project/Service/Application.php @@ -53,6 +53,7 @@ class Application extends Component public function submit() { try { + check_fqdn_usage($this->application); $this->validate(); $this->application->save(); updateCompose($this->application); diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index 37664c870..b0f1c97c7 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -24,7 +24,8 @@ class Danger extends Component public function delete() { try { - DeleteResourceJob::dispatchSync($this->resource); + $this->resource->delete(); + DeleteResourceJob::dispatch($this->resource); return redirect()->route('project.resource.index', [ 'project_uuid' => $this->projectUuid, 'environment_name' => $this->environmentName diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index cd7e2be92..125bf2fb4 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -45,6 +45,11 @@ class ResourceOperations extends Component 'destination_id' => $new_destination->id, ]); $new_resource->save(); + if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') { + $customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n"); + $new_resource->custom_labels = base64_encode($customLabels); + $new_resource->save(); + } $environmentVaribles = $this->resource->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $newEnvironmentVariable = $environmentVarible->replicate()->fill([ diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index 13ebc97d4..31abe8910 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -11,6 +11,7 @@ class Show extends Component use AuthorizesRequests; public ?Server $server = null; public $parameters = []; + protected $listeners = ['proxyStatusUpdated' => '$refresh']; public function mount() { $this->parameters = get_route_parameters(); @@ -19,14 +20,13 @@ class Show extends Component if (is_null($this->server)) { return redirect()->route('server.index'); } - } catch (\Throwable $e) { return handleError($e, $this); } } public function submit() { - $this->dispatch('serverRefresh',false); + $this->dispatch('serverRefresh', false); } public function render() { diff --git a/app/Models/Application.php b/app/Models/Application.php index 9e9626ec2..1f9614baa 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -6,11 +6,9 @@ use App\Enums\ApplicationDeploymentStatus; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Collection; use Spatie\Activitylog\Models\Activity; use Illuminate\Support\Str; use RuntimeException; -use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2; class Application extends BaseModel @@ -39,6 +37,7 @@ class Application extends BaseModel ]); }); static::deleting(function ($application) { + $application->update(['fqdn' => null]); $application->settings()->delete(); $storages = $application->persistentStorages()->get(); $server = data_get($application, 'destination.server'); @@ -52,64 +51,6 @@ class Application extends BaseModel $application->environment_variables_preview()->delete(); }); } - // Build packs / deployment types - - - public function servers(): Collection - { - $mainServer = data_get($this, 'destination.server'); - $additionalDestinations = data_get($this, 'additional_destinations', null); - $additionalServers = collect([]); - if ($this->isMultipleServerDeployment()) { - ray('asd'); - if (str($additionalDestinations)->isNotEmpty()) { - $additionalDestinations = str($additionalDestinations)->explode(','); - foreach ($additionalDestinations as $destinationId) { - $destination = StandaloneDocker::find($destinationId)->whereNot('id', $mainServer->id)->first(); - $server = data_get($destination, 'server'); - $additionalServers->push($server); - } - } - } - return collect([$mainServer])->merge($additionalServers); - } - - public function generateImageNames(string $commit, int $pullRequestId) - { - if ($this->dockerfile) { - if ($this->docker_registry_image_name) { - $buildImageName = Str::lower("{$this->docker_registry_image_name}:build"); - $productionImageName = Str::lower("{$this->docker_registry_image_name}:latest"); - } else { - $buildImageName = Str::lower("{$this->uuid}:build"); - $productionImageName = Str::lower("{$this->uuid}:latest"); - } - } else if ($this->build_pack === 'dockerimage') { - $productionImageName = Str::lower("{$this->docker_registry_image_name}:{$this->docker_registry_image_tag}"); - } else if ($pullRequestId === 0) { - $dockerImageTag = str($commit)->substr(0, 128); - if ($this->docker_registry_image_name) { - $buildImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}-build"); - $productionImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}"); - } else { - $buildImageName = Str::lower("{$this->uuid}:{$dockerImageTag}-build"); - $productionImageName = Str::lower("{$this->uuid}:{$dockerImageTag}"); - } - } else if ($pullRequestId !== 0) { - if ($this->docker_registry_image_name) { - $buildImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}-build"); - $productionImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}"); - } else { - $buildImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}-build"); - $productionImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}"); - } - } - return [ - 'buildImageName' => $buildImageName, - 'productionImageName' => $productionImageName, - ]; - } - // End of build packs / deployment types public function is_github_based(): bool { @@ -465,31 +406,6 @@ class Application extends BaseModel return true; } } - public function isMultipleServerDeployment() - { - return false; - if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) { - return true; - } - return false; - } - public function healthCheckUrl() - { - if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') { - return null; - } - if (!$this->health_check_port) { - $health_check_port = $this->ports_exposes_array[0]; - } else { - $health_check_port = $this->health_check_port; - } - if ($this->health_check_path) { - $full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}"; - } else { - $full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"; - } - return $full_healthcheck_url; - } function customRepository() { preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches); @@ -511,296 +427,7 @@ class Application extends BaseModel { return "/artifacts/{$uuid}"; } - function generateHealthCheckCommands() - { - if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') { - // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl. - return 'exit 0'; - } - if (!$this->health_check_port) { - $health_check_port = $this->ports_exposes_array[0]; - } else { - $health_check_port = $this->health_check_port; - } - if ($this->health_check_path) { - $this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}"; - $generated_healthchecks_commands = [ - "curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path} > /dev/null" - ]; - } else { - $this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"; - $generated_healthchecks_commands = [ - "curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/" - ]; - } - return implode(' ', $generated_healthchecks_commands); - } - function generateLocalPersistentVolumes(int $pullRequestId) - { - $persistentStorages = []; - $volumeNames = []; - foreach ($this->persistentStorages as $persistentStorage) { - $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; - if ($pullRequestId !== 0) { - $volume_name = $volume_name . '-pr-' . $pullRequestId; - } - $persistentStorages[] = $volume_name . ':' . $persistentStorage->mount_path; - - if ($persistentStorage->host_path) { - continue; - } - - $name = $persistentStorage->name; - - if ($pullRequestId !== 0) { - $name = $name . '-pr-' . $pullRequestId; - } - - $volumeNames[$name] = [ - 'name' => $name, - 'external' => false, - ]; - } - - return [ - 'persistentStorages' => $persistentStorages, - 'volumeNames' => $volumeNames, - ]; - } - public function generateEnvironmentVariables($ports) - { - $environmentVariables = collect(); - // ray('Generate Environment Variables')->green(); - if ($this->pull_request_id === 0) { - // ray($this->runtime_environment_variables)->green(); - foreach ($this->runtime_environment_variables as $env) { - $environmentVariables->push("$env->key=$env->value"); - } - foreach ($this->nixpacks_environment_variables as $env) { - $environmentVariables->push("$env->key=$env->value"); - } - } else { - // ray($this->runtime_environment_variables_preview)->green(); - foreach ($this->runtime_environment_variables_preview as $env) { - $environmentVariables->push("$env->key=$env->value"); - } - foreach ($this->nixpacks_environment_variables_preview as $env) { - $environmentVariables->push("$env->key=$env->value"); - } - } - // Add PORT if not exists, use the first port as default - if ($environmentVariables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) { - $environmentVariables->push("PORT={$ports[0]}"); - } - return $environmentVariables->all(); - } - function generateDockerComposeFile(Server $server, ApplicationDeploymentQueue $deployment, string $workdir) - { - $pullRequestId = $deployment->pull_request_id; - $ports = $this->settings->is_static ? [80] : $this->ports_exposes_array; - $container_name = generateApplicationContainerName($this, $this->pull_request_id); - $commit = str($deployment->getOutput('git_commit_sha'))->before("\t"); - - [ - 'productionImageName' => $productionImageName - ] = $this->generateImageNames($commit, $pullRequestId); - - [ - 'persistentStorages' => $persistentStorages, - 'volumeNames' => $volumeNames - ] = $this->generateLocalPersistentVolumes($pullRequestId); - - $environmentVariables = $this->generateEnvironmentVariables($ports); - - if (data_get($this, 'custom_labels')) { - $labels = collect(str($this->custom_labels)->explode(',')); - $labels = $labels->filter(function ($value, $key) { - return !Str::startsWith($value, 'coolify.'); - }); - $this->custom_labels = $labels->implode(','); - $this->save(); - } else { - $labels = collect(generateLabelsApplication($this, $this->preview)); - } - if ($this->pull_request_id !== 0) { - $labels = collect(generateLabelsApplication($this, $this->preview)); - } - $labels = $labels->merge(defaultLabels($this->id, $this->uuid, $this->pull_request_id))->toArray(); - $docker_compose = [ - 'version' => '3.8', - 'services' => [ - $container_name => [ - 'image' => $productionImageName, - 'container_name' => $container_name, - 'restart' => RESTART_MODE, - 'environment' => $environmentVariables, - 'expose' => $ports, - 'networks' => [ - $this->destination->network, - ], - 'healthcheck' => [ - 'test' => [ - 'CMD-SHELL', - $this->generateHealthCheckCommands() - ], - 'interval' => $this->health_check_interval . 's', - 'timeout' => $this->health_check_timeout . 's', - 'retries' => $this->health_check_retries, - 'start_period' => $this->health_check_start_period . 's' - ], - 'mem_limit' => $this->limits_memory, - 'memswap_limit' => $this->limits_memory_swap, - 'mem_swappiness' => $this->limits_memory_swappiness, - 'mem_reservation' => $this->limits_memory_reservation, - 'cpus' => (float) $this->limits_cpus, - 'cpu_shares' => $this->limits_cpu_shares, - ] - ], - 'networks' => [ - $this->destination->network => [ - 'external' => true, - 'name' => $this->destination->network, - 'attachable' => true - ] - ] - ]; - if (!is_null($this->limits_cpuset)) { - data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset); - } - if ($server->isSwarm()) { - data_forget($docker_compose, 'services.' . $container_name . '.container_name'); - data_forget($docker_compose, 'services.' . $container_name . '.expose'); - data_forget($docker_compose, 'services.' . $container_name . '.restart'); - - data_forget($docker_compose, 'services.' . $container_name . '.mem_limit'); - data_forget($docker_compose, 'services.' . $container_name . '.memswap_limit'); - data_forget($docker_compose, 'services.' . $container_name . '.mem_swappiness'); - data_forget($docker_compose, 'services.' . $container_name . '.mem_reservation'); - data_forget($docker_compose, 'services.' . $container_name . '.cpus'); - data_forget($docker_compose, 'services.' . $container_name . '.cpuset'); - data_forget($docker_compose, 'services.' . $container_name . '.cpu_shares'); - - $docker_compose['services'][$container_name]['deploy'] = [ - 'placement' => [ - 'constraints' => [ - 'node.role == worker' - ] - ], - 'mode' => 'replicated', - 'replicas' => 1, - 'update_config' => [ - 'order' => 'start-first' - ], - 'rollback_config' => [ - 'order' => 'start-first' - ], - 'labels' => $labels, - 'resources' => [ - 'limits' => [ - 'cpus' => $this->limits_cpus, - 'memory' => $this->limits_memory, - ], - 'reservations' => [ - 'cpus' => $this->limits_cpus, - 'memory' => $this->limits_memory, - ] - ] - ]; - } else { - $docker_compose['services'][$container_name]['labels'] = $labels; - } - if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => "tcp://127.0.0.1:24224", - 'fluentd-async' => "true", - 'fluentd-sub-second-precision' => "true", - ] - ]; - } - if ($this->settings->is_gpu_enabled) { - $docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'] = [ - [ - 'driver' => data_get($this, 'settings.gpu_driver', 'nvidia'), - 'capabilities' => ['gpu'], - 'options' => data_get($this, 'settings.gpu_options', []) - ] - ]; - if (data_get($this, 'settings.gpu_count')) { - $count = data_get($this, 'settings.gpu_count'); - if ($count === 'all') { - $docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count; - } else { - $docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count; - } - } else if (data_get($this, 'settings.gpu_device_ids')) { - $docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this, 'settings.gpu_device_ids'); - } - } - if ($this->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.' . $container_name . '.healthcheck'); - } - if (count($this->ports_mappings_array) > 0 && $this->pull_request_id === 0) { - $docker_compose['services'][$container_name]['ports'] = $this->ports_mappings_array; - } - if (count($persistentStorages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistentStorages; - } - if (count($volumeNames) > 0) { - $docker_compose['volumes'] = $volumeNames; - } - - $docker_compose['services'][$this->uuid] = $docker_compose['services'][$container_name]; - - data_forget($docker_compose, 'services.' . $container_name); - - $docker_compose = Yaml::dump($docker_compose, 10); - $docker_compose_base64 = base64_encode($docker_compose); - $server->executeRemoteCommand( - commands: collect([])->push([ - 'command' => executeInDocker($deployment->deployment_uuid, "echo '{$docker_compose_base64}' | base64 -d > {$workdir}/docker-compose.yml"), - 'hidden' => true, - 'ignoreErrors' => true - ]), - loggingModel: $deployment - ); - } - function rollingUpdateApplication(Server $server, ApplicationDeploymentQueue $deployment, string $workdir) - { - $pullRequestId = $deployment->pull_request_id; - $containerName = generateApplicationContainerName($this, $pullRequestId); - // if (count($this->ports_mappings_array) > 0) { - // $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.'); - $containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId); - // if ($pullRequestId === 0) { - // $containers = $containers->filter(function ($container) use ($containerName) { - // return data_get($container, 'Names') !== $containerName; - // }); - // } - $containers->each(function ($container) use ($server, $deployment) { - $removingContainerName = data_get($container, 'Names'); - $server->executeRemoteCommand( - commands: collect([])->push([ - 'command' => "docker rm -f $removingContainerName", - 'hidden' => true, - 'ignoreErrors' => true - ]), - loggingModel: $deployment - ); - }); - // } - $server->executeRemoteCommand( - commands: collect([])->push([ - 'command' => executeInDocker($deployment->deployment_uuid, "docker compose --project-directory {$workdir} up --build -d"), - 'hidden' => true, - 'ignoreErrors' => true - ]), - loggingModel: $deployment - ); - $deployment->addLogEntry("New container started."); - } - 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') { @@ -978,34 +605,6 @@ class Application extends BaseModel ]; } } - public function prepareHelperImage(string $deploymentUuid) - { - $basedir = $this->generateBaseDir($deploymentUuid); - $helperImage = config('coolify.helper_image'); - $server = data_get($this, 'destination.server'); - $network = data_get($this, 'destination.network'); - - $serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server); - $dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server); - - $commands = collect([]); - if ($dockerConfigFileExists === 'OK') { - $commands->push([ - "command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage", - "hidden" => true, - ]); - } else { - $commands->push([ - "command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}", - "hidden" => true, - ]); - } - $commands->push([ - "command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"), - "hidden" => true, - ]); - return $commands; - } function parseCompose(int $pull_request_id = 0) { if ($this->docker_compose_raw) { @@ -1116,4 +715,12 @@ class Application extends BaseModel $this->save(); return $customLabels; } + public function fqdns(): Attribute + { + return Attribute::make( + get: fn () => is_null($this->fqdn) + ? [] + : explode(',', $this->fqdn), + ); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 432a296e9..972751083 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,16 +2,12 @@ namespace App\Models; -use App\Enums\ApplicationDeploymentStatus; use App\Enums\ProxyStatus; use App\Enums\ProxyTypes; use App\Notifications\Server\Revived; use App\Notifications\Server\Unreachable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Support\Carbon; -use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Process; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\SchemalessAttributesTrait; use Illuminate\Support\Str; @@ -335,20 +331,6 @@ class Server extends BaseModel if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) { return false; } - // foreach ($this->applications() as $application) { - // if (data_get($application, 'fqdn')) { - // $shouldRun = true; - // break; - // } - // } - // ray($this->services()->get()); - - // if ($this->id === 0) { - // $settings = InstanceSettings::get(); - // if (data_get($settings, 'fqdn')) { - // $shouldRun = true; - // } - // } return true; } public function isFunctional() @@ -483,153 +465,4 @@ class Server extends BaseModel return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); } } - public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null) - { - static::$batch_counter++; - foreach ($commands as $command) { - $realCommand = data_get($command, 'command'); - if (is_null($realCommand)) { - throw new \RuntimeException('Command is not set'); - } - $hidden = data_get($command, 'hidden', false); - $ignoreErrors = data_get($command, 'ignoreErrors', false); - $customOutputType = data_get($command, 'customOutputType'); - $name = data_get($command, 'name'); - $remoteCommand = generateSshCommand($this, $realCommand); - - $process = Process::timeout(3600)->idleTimeout(3600)->start($remoteCommand, function (string $type, string $output) use ($realCommand, $hidden, $customOutputType, $loggingModel, $name) { - $output = str($output)->trim(); - if ($output->startsWith('╔')) { - $output = "\n" . $output; - } - $newLogEntry = [ - 'command' => remove_iip($realCommand), - 'output' => remove_iip($output), - 'type' => $customOutputType ?? $type === 'err' ? 'stderr' : 'stdout', - 'timestamp' => Carbon::now('UTC'), - 'hidden' => $hidden, - 'batch' => static::$batch_counter, - ]; - if ($loggingModel) { - if (!$loggingModel->logs) { - $newLogEntry['order'] = 1; - } else { - $previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR); - $newLogEntry['order'] = count($previousLogs) + 1; - } - if ($name) { - $newLogEntry['name'] = $name; - } - - $previousLogs[] = $newLogEntry; - $loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR); - $loggingModel->save(); - } - }); - if ($loggingModel) { - $loggingModel->update([ - 'current_process_id' => $process->id(), - ]); - } - $processResult = $process->wait(); - if ($processResult->exitCode() !== 0) { - if (!$ignoreErrors) { - if ($loggingModel) { - $status = ApplicationDeploymentStatus::FAILED->value; - $loggingModel->status = $status; - $loggingModel->save(); - } - throw new \RuntimeException($processResult->errorOutput()); - } - } - } - } - public function stopApplicationRelatedRunningContainers(string $applicationId, string $containerName) - { - $containers = getCurrentApplicationContainerStatus($this, $applicationId, 0); - $containers = $containers->filter(function ($container) use ($containerName) { - return data_get($container, 'Names') !== $containerName; - }); - $containers->each(function ($container) { - $removableContainer = data_get($container, 'Names'); - $this->server->executeRemoteCommand( - commands: collect([ - 'command' => "docker rm -f $removableContainer >/dev/null 2>&1", - 'hidden' => true, - 'ignoreErrors' => true - ]), - loggingModel: $this->deploymentQueueEntry - ); - }); - } - public function getHostIPMappings($network) - { - $addHosts = null; - $allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $this); - if (!is_null($allContainers)) { - $allContainers = format_docker_command_output_to_json($allContainers); - $ips = collect([]); - if (count($allContainers) > 0) { - $allContainers = $allContainers[0]; - foreach ($allContainers as $container) { - $containerName = data_get($container, 'Name'); - if ($containerName === 'coolify-proxy') { - continue; - } - $containerIp = data_get($container, 'IPv4Address'); - if ($containerName && $containerIp) { - $containerIp = str($containerIp)->before('/'); - $ips->put($containerName, $containerIp->value()); - } - } - } - $addHosts = $ips->map(function ($ip, $name) { - return "--add-host $name:$ip"; - })->implode(' '); - } - return $addHosts; - } - public function checkIfDockerImageExists(string $imageName, ApplicationDeploymentQueue $deployment) - { - $this->executeRemoteCommand( - commands: collect([ - [ - "name" => "local_image_found", - "command" => "docker images -q {$imageName} 2>/dev/null", - "hidden" => true, - ] - ]), - loggingModel: $deployment - ); - if (str($deployment->getOutput('local_image_found'))->isEmpty()) { - $this->executeRemoteCommand( - commands: collect([ - [ - "command" => "docker pull {$imageName} 2>/dev/null", - "ignoreErrors" => true, - "hidden" => true - ], - [ - "name" => "local_image_found", - "command" => "docker images -q {$imageName} 2>/dev/null", - "hidden" => true, - ] - ]), - loggingModel: $deployment - ); - } - } - public function createWorkDirForDeployment(string $workdir, ApplicationDeploymentQueue $deployment) - { - $this->executeRemoteCommand( - commands: collect([ - [ - "command" => executeInDocker($deployment->deployment_uuid, "mkdir -p {$workdir}"), - "ignoreErrors" => true, - "hidden" => true - ], - ]), - loggingModel: $deployment - ); - } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 42cb01108..adbf6c500 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,13 +2,11 @@ namespace App\Models; -use App\Actions\Service\DeleteService; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Illuminate\Support\Str; -use Symfony\Component\Yaml\Yaml; class Service extends BaseModel { diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 510395266..be1eb0f6d 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Facades\Cache; class ServiceApplication extends BaseModel { @@ -15,6 +14,7 @@ class ServiceApplication extends BaseModel protected static function booted() { static::deleting(function ($service) { + $service->update(['fqdn' => null]); $service->persistentStorages()->delete(); $service->fileStorages()->delete(); }); @@ -55,7 +55,6 @@ class ServiceApplication extends BaseModel get: fn () => is_null($this->fqdn) ? [] : explode(',', $this->fqdn), - ); } public function getFilesFromServer(bool $isInit = false) diff --git a/app/Traits/ExecuteRemoteCommandNew.php b/app/Traits/ExecuteRemoteCommandNew.php deleted file mode 100644 index ca56d9e50..000000000 --- a/app/Traits/ExecuteRemoteCommandNew.php +++ /dev/null @@ -1,77 +0,0 @@ -each(function ($singleCommand) use ($server, $logModel) { - $command = data_get($singleCommand, 'command') ?? $singleCommand[0] ?? null; - if ($command === null) { - throw new \RuntimeException('Command is not set'); - } - $hidden = data_get($singleCommand, 'hidden', false); - $customType = data_get($singleCommand, 'type'); - $ignoreErrors = data_get($singleCommand, 'ignore_errors', false); - $save = data_get($singleCommand, 'save'); - - $remote_command = generateSshCommand($server, $command); - $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $logModel, $save) { - $output = str($output)->trim(); - if ($output->startsWith('╔')) { - $output = "\n" . $output; - } - $newLogEntry = [ - 'command' => remove_iip($command), - 'output' => remove_iip($output), - 'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout', - 'timestamp' => Carbon::now('UTC'), - 'hidden' => $hidden, - 'batch' => static::$batch_counter, - ]; - - if (!$logModel->logs) { - $newLogEntry['order'] = 1; - } else { - $previousLogs = json_decode($logModel->logs, associative: true, flags: JSON_THROW_ON_ERROR); - $newLogEntry['order'] = count($previousLogs) + 1; - } - - $previousLogs[] = $newLogEntry; - $logModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR); - $logModel->save(); - - if ($save) { - $this->remoteCommandOutputs[$save] = str($output)->trim(); - } - }); - $logModel->update([ - 'current_process_id' => $process->id(), - ]); - - $processResult = $process->wait(); - if ($processResult->exitCode() !== 0) { - if (!$ignoreErrors) { - $status = ApplicationDeploymentStatus::FAILED->value; - $logModel->status = $status; - $logModel->save(); - throw new \RuntimeException($processResult->errorOutput()); - } - } - }); - } -} diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 079c1add8..de381212f 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -1,15 +1,12 @@ id; $deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}"); @@ -31,13 +28,33 @@ function queue_application_deployment(Application $application, string $deployme 'git_type' => $git_type ]); + if (next_queuable($server_id, $application_id)) { + dispatch(new ApplicationDeploymentJob( + application_deployment_queue_id: $deployment->id, + )); + } +} + +function queue_next_deployment(Application $application) +{ + $server_id = $application->destination->server_id; + $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first(); + if ($next_found) { + dispatch(new ApplicationDeploymentJob( + application_deployment_queue_id: $next_found->id, + )); + } +} + +function next_queuable(string $server_id, string $application_id): bool +{ $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at'); $same_application_deployments = $deployments->where('application_id', $application_id); $in_progress = $same_application_deployments->filter(function ($value, $key) { return $value->status === 'in_progress'; }); if ($in_progress->count() > 0) { - return; + return false; } $server = Server::find($server_id); $concurrent_builds = $server->settings->concurrent_builds; @@ -45,296 +62,7 @@ function queue_application_deployment(Application $application, string $deployme ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}"); if ($deployments->count() > $concurrent_builds) { - return; - } - if ($is_new_deployment) { - dispatch(new ApplicationDeploymentNewJob( - deployment: $deployment, - application: Application::find($application_id) - )); - } else { - dispatch(new ApplicationDeploymentJob( - application_deployment_queue_id: $deployment->id, - )); + return false; } -} - -function queue_next_deployment(Application $application, bool $isNew = false) -{ - $server_id = $application->destination->server_id; - $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();; - // ray($next_found, $server_id); - if ($next_found) { - if ($isNew) { - dispatch(new ApplicationDeploymentNewJob( - deployment: $next_found, - application: $application - )); - } else { - dispatch(new ApplicationDeploymentJob( - application_deployment_queue_id: $next_found->id, - )); - } - } -} -// Deployment things -function generateHostIpMapping(Server $server, string $network) -{ - // Generate custom host<->ip hostnames - $allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $server); - $allContainers = format_docker_command_output_to_json($allContainers); - $ips = collect([]); - if (count($allContainers) > 0) { - $allContainers = $allContainers[0]; - foreach ($allContainers as $container) { - $containerName = data_get($container, 'Name'); - if ($containerName === 'coolify-proxy') { - continue; - } - $containerIp = data_get($container, 'IPv4Address'); - if ($containerName && $containerIp) { - $containerIp = str($containerIp)->before('/'); - $ips->put($containerName, $containerIp->value()); - } - } - } - return $ips->map(function ($ip, $name) { - return "--add-host $name:$ip"; - })->implode(' '); -} - -function generateBaseDir(string $deplyomentUuid) -{ - return "/artifacts/$deplyomentUuid"; -} -function generateWorkdir(string $deplyomentUuid, Application $application) -{ - return generateBaseDir($deplyomentUuid) . rtrim($application->base_directory, '/'); -} - -function prepareHelperContainer(Server $server, string $network, string $deploymentUuid) -{ - $basedir = generateBaseDir($deploymentUuid); - $helperImage = config('coolify.helper_image'); - - $serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server); - $dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server); - - $commands = collect([]); - if ($dockerConfigFileExists === 'OK') { - $commands->push([ - "command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage", - "hidden" => true, - ]); - } else { - $commands->push([ - "command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}", - "hidden" => true, - ]); - } - $commands->push([ - "command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"), - "hidden" => true, - ]); - return $commands; -} - -function generateComposeFile(string $deploymentUuid, Server $server, string $network, Application $application, string $containerName, string $imageName, ?ApplicationPreview $preview = null, int $pullRequestId = 0) -{ - $ports = $application->settings->is_static ? [80] : $application->ports_exposes_array; - $workDir = generateWorkdir($deploymentUuid, $application); - $persistent_storages = generateLocalPersistentVolumes($application, $pullRequestId); - $volume_names = generateLocalPersistentVolumesOnlyVolumeNames($application, $pullRequestId); - $environment_variables = generateEnvironmentVariables($application, $ports, $pullRequestId); - - if (data_get($application, 'custom_labels')) { - $labels = collect(str($application->custom_labels)->explode(',')); - $labels = $labels->filter(function ($value, $key) { - return !str($value)->startsWith('coolify.'); - }); - $application->custom_labels = $labels->implode(','); - $application->save(); - } else { - $labels = collect(generateLabelsApplication($application, $preview)); - } - if ($pullRequestId !== 0) { - $labels = collect(generateLabelsApplication($application, $preview)); - } - $labels = $labels->merge(defaultLabels($application->id, $application->uuid, 0))->toArray(); - $docker_compose = [ - 'version' => '3.8', - 'services' => [ - $containerName => [ - 'image' => $imageName, - 'container_name' => $containerName, - 'restart' => RESTART_MODE, - 'environment' => $environment_variables, - 'labels' => $labels, - 'expose' => $ports, - 'networks' => [ - $network, - ], - 'mem_limit' => $application->limits_memory, - 'memswap_limit' => $application->limits_memory_swap, - 'mem_swappiness' => $application->limits_memory_swappiness, - 'mem_reservation' => $application->limits_memory_reservation, - 'cpus' => (int) $application->limits_cpus, - 'cpu_shares' => $application->limits_cpu_shares, - ] - ], - 'networks' => [ - $network => [ - 'external' => true, - 'name' => $network, - 'attachable' => true - ] - ] - ]; - if (!is_null($application->limits_cpuset)) { - data_set($docker_compose, "services.{$containerName}.cpuset", $application->limits_cpuset); - } - if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) { - $docker_compose['services'][$containerName]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => "tcp://127.0.0.1:24224", - 'fluentd-async' => "true", - 'fluentd-sub-second-precision' => "true", - ] - ]; - } - if ($application->settings->is_gpu_enabled) { - $docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'] = [ - [ - 'driver' => data_get($application, 'settings.gpu_driver', 'nvidia'), - 'capabilities' => ['gpu'], - 'options' => data_get($application, 'settings.gpu_options', []) - ] - ]; - if (data_get($application, 'settings.gpu_count')) { - $count = data_get($application, 'settings.gpu_count'); - if ($count === 'all') { - $docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = $count; - } else { - $docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count; - } - } else if (data_get($application, 'settings.gpu_device_ids')) { - $docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($application, 'settings.gpu_device_ids'); - } - } - if ($application->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.' . $containerName . '.healthcheck'); - } - if (count($application->ports_mappings_array) > 0 && $pullRequestId === 0) { - $docker_compose['services'][$containerName]['ports'] = $application->ports_mappings_array; - } - if (count($persistent_storages) > 0) { - $docker_compose['services'][$containerName]['volumes'] = $persistent_storages; - } - if (count($volume_names) > 0) { - $docker_compose['volumes'] = $volume_names; - } - $docker_compose = Yaml::dump($docker_compose, 10); - $docker_compose_base64 = base64_encode($docker_compose); - $commands = collect([]); - $commands->push([ - "command" => executeInDocker($deploymentUuid, "echo '{$docker_compose_base64}' | base64 -d > {$workDir}/docker-compose.yml"), - "hidden" => true, - ]); - return $commands; -} -function generateLocalPersistentVolumes(Application $application, int $pullRequestId = 0) -{ - $local_persistent_volumes = []; - foreach ($application->persistentStorages as $persistentStorage) { - $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; - if ($pullRequestId !== 0) { - $volume_name = $volume_name . '-pr-' . $pullRequestId; - } - $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; - } - return $local_persistent_volumes; -} - -function generateLocalPersistentVolumesOnlyVolumeNames(Application $application, int $pullRequestId = 0) -{ - $local_persistent_volumes_names = []; - foreach ($application->persistentStorages as $persistentStorage) { - if ($persistentStorage->host_path) { - continue; - } - $name = $persistentStorage->name; - - if ($pullRequestId !== 0) { - $name = $name . '-pr-' . $pullRequestId; - } - - $local_persistent_volumes_names[$name] = [ - 'name' => $name, - 'external' => false, - ]; - } - return $local_persistent_volumes_names; -} -function generateEnvironmentVariables(Application $application, $ports, int $pullRequestId = 0) -{ - $environment_variables = collect(); - // ray('Generate Environment Variables')->green(); - if ($pullRequestId === 0) { - // ray($this->application->runtime_environment_variables)->green(); - foreach ($application->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); - } - foreach ($application->nixpacks_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); - } - } else { - // ray($this->application->runtime_environment_variables_preview)->green(); - foreach ($application->runtime_environment_variables_preview as $env) { - $environment_variables->push("$env->key=$env->value"); - } - foreach ($application->nixpacks_environment_variables_preview as $env) { - $environment_variables->push("$env->key=$env->value"); - } - } - // Add PORT if not exists, use the first port as default - if ($environment_variables->filter(fn ($env) => str($env)->contains('PORT'))->isEmpty()) { - $environment_variables->push("PORT={$ports[0]}"); - } - return $environment_variables->all(); -} - -function startNewApplication(Application $application, string $deploymentUuid, ApplicationDeploymentQueue $loggingModel) -{ - $commands = collect([]); - $workDir = generateWorkdir($deploymentUuid, $application); - if ($application->build_pack === 'dockerimage') { - $loggingModel->addLogEntry('Pulling latest images from the registry.'); - $commands->push( - [ - "command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} pull"), - "hidden" => true - ], - [ - "command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"), - "hidden" => true - ], - ); - } else { - $commands->push( - [ - "command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"), - "hidden" => true - ], - ); - } - return $commands; -} -function removeOldDeployment(string $containerName) -{ - $commands = collect([]); - $commands->push( - ["docker rm -f $containerName >/dev/null 2>&1"], - ); - return $commands; + return true; } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 3cff9ba24..c3b6f9f01 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -22,6 +22,7 @@ use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Internal\GeneralNotification; use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; +use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Cache; @@ -107,6 +108,12 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n } return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds."; } + if ($error instanceof UniqueConstraintViolationException) { + if (isset($livewire)) { + return $livewire->dispatch('error', "A resource with the same name already exists."); + } + return "A resource with the same name already exists."; + } if ($error instanceof Throwable) { $message = $error->getMessage(); @@ -1660,3 +1667,33 @@ function ip_match($ip, $cidrs, &$match = null) } return false; } +function check_fqdn_usage(ServiceApplication|Application $own_resource) +{ + $domains = collect($own_resource->fqdns)->map(function ($domain) { + return Url::fromString($domain)->getHost(); + }); + $apps = Application::all(); + foreach ($apps as $app) { + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + $naked_domain = Url::fromString($domain)->getHost(); + if ($domains->contains($naked_domain)) { + if ($app->uuid !== $own_resource->uuid ) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource."); + } + } + } + } + $apps = ServiceApplication::all(); + foreach ($apps as $app) { + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + $naked_domain = Url::fromString($domain)->getHost(); + if ($domains->contains($naked_domain)) { + if ($app->uuid !== $own_resource->uuid) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource."); + } + } + } + } +} diff --git a/config/sentry.php b/config/sentry.php index 4e79f8e6a..050405d5e 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.202', + 'release' => '4.0.0-beta.203', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index e8285e73e..c1ca7dc2b 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ - - + class="mt-1.5 text-xs leading-2 opacity-90" x-html="toast.description"> diff --git a/versions.json b/versions.json index fa4d00248..762be10fd 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.202" + "version": "4.0.0-beta.203" } } }