diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 5d94d7ab8..aa5f2ff14 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -880,12 +880,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } else { if ($application->build_pack === 'dockercompose') { LoadComposeFile::dispatch($application); @@ -1004,12 +1009,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } else { if ($application->build_pack === 'dockercompose') { LoadComposeFile::dispatch($application); @@ -1101,12 +1111,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } else { if ($application->build_pack === 'dockercompose') { LoadComposeFile::dispatch($application); @@ -1190,12 +1205,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } return response()->json(serializeApiResponse([ @@ -1254,12 +1274,17 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, no_questions_asked: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } return response()->json(serializeApiResponse([ @@ -1896,11 +1921,16 @@ class ApplicationsController extends Controller if ($instantDeploy) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } } return response()->json([ @@ -2717,13 +2747,21 @@ class ApplicationsController extends Controller $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, force_rebuild: $force, is_api: true, no_questions_asked: $instant_deploy ); + if ($result['status'] === 'skipped') { + return response()->json( + [ + 'message' => $result['message'], + ], + 200 + ); + } return response()->json( [ @@ -2878,12 +2916,17 @@ class ApplicationsController extends Controller $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, restart_only: true, is_api: true, ); + if ($result['status'] === 'skipped') { + return response()->json([ + 'message' => $result['message'], + ], 200); + } return response()->json( [ diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 6b9f40fca..00bb97f27 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -8,6 +8,7 @@ use App\Http\Controllers\Controller; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\Server; +use App\Models\Service; use App\Models\Tag; use Illuminate\Http\Request; use OpenApi\Attributes as OA; @@ -297,17 +298,21 @@ class DeployController extends Controller return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid]; } switch ($resource?->getMorphClass()) { - case \App\Models\Application::class: + case Application::class: $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $resource, deployment_uuid: $deployment_uuid, force_rebuild: $force, pull_request_id: $pr, ); - $message = "Application {$resource->name} deployment queued."; + if ($result['status'] === 'skipped') { + $message = $result['message']; + } else { + $message = "Application {$resource->name} deployment queued."; + } break; - case \App\Models\Service::class: + case Service::class: StartService::run($resource); $message = "Service {$resource->name} started. It could take a while, be patient."; break; diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index 33d8f8532..490b66e58 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -100,18 +100,26 @@ class Bitbucket extends Controller if ($x_bitbucket_event === 'repo:push') { if ($application->isDeployable()) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, commit: $commit, force_rebuild: false, is_webhook: true ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, @@ -143,7 +151,7 @@ class Bitbucket extends Controller ]); } } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -152,11 +160,19 @@ class Bitbucket extends Controller is_webhook: true, git_type: 'bitbucket' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php index 87fd2255f..3c3d6e0b6 100644 --- a/app/Http/Controllers/Webhook/Gitea.php +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -116,19 +116,27 @@ class Gitea extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, commit: data_get($payload, 'after', 'HEAD'), is_webhook: true, ); - $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', - 'application_uuid' => $application->uuid, - 'application_name' => $application->name, - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } } else { $paths = str($application->watch_paths)->explode("\n"); $return_payloads->push([ @@ -175,7 +183,7 @@ class Gitea extends Controller ]); } } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -184,11 +192,19 @@ class Gitea extends Controller is_webhook: true, git_type: 'gitea' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 882f2be8b..597ec023f 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -122,19 +122,29 @@ class Github extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, commit: data_get($payload, 'after', 'HEAD'), is_webhook: true, ); - $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', - 'application_uuid' => $application->uuid, - 'application_name' => $application->name, - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'deployment_uuid' => $result['deployment_uuid'], + ]); + } } else { $paths = str($application->watch_paths)->explode("\n"); $return_payloads->push([ @@ -181,7 +191,8 @@ class Github extends Controller ]); } } - queue_application_deployment( + + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -190,11 +201,19 @@ class Github extends Controller is_webhook: true, git_type: 'github' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, @@ -341,7 +360,7 @@ class Github extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, commit: data_get($payload, 'after', 'HEAD'), @@ -349,10 +368,11 @@ class Github extends Controller is_webhook: true, ); $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', + 'status' => $result['status'], + 'message' => $result['message'], 'application_uuid' => $application->uuid, 'application_name' => $application->name, + 'deployment_uuid' => $result['deployment_uuid'], ]); } else { $paths = str($application->watch_paths)->explode("\n"); @@ -389,7 +409,7 @@ class Github extends Controller 'pull_request_html_url' => $pull_request_html_url, ]); } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -398,11 +418,19 @@ class Github extends Controller is_webhook: true, git_type: 'github' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview deployment queued.', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index cf6874b8c..d6d12a05f 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -142,19 +142,28 @@ class Gitlab extends Controller $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { $deployment_uuid = new Cuid2; - queue_application_deployment( + $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, commit: data_get($payload, 'after', 'HEAD'), force_rebuild: false, is_webhook: true, ); - $return_payloads->push([ - 'status' => 'success', - 'message' => 'Deployment queued.', - 'application_uuid' => $application->uuid, - 'application_name' => $application->name, - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'status' => $result['status'], + 'message' => $result['message'], + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } } else { $paths = str($application->watch_paths)->explode("\n"); $return_payloads->push([ @@ -201,7 +210,7 @@ class Gitlab extends Controller ]); } } - queue_application_deployment( + $result = queue_application_deployment( application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, @@ -210,11 +219,19 @@ class Gitlab extends Controller is_webhook: true, git_type: 'gitlab' ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Preview Deployment queued', - ]); + if ($result['status'] === 'skipped') { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'skipped', + 'message' => $result['message'], + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview Deployment queued', + ]); + } } else { $return_payloads->push([ 'application' => $application->name, diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 6cf642f27..7bea73aa2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1392,7 +1392,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue return; } foreach ($destination_ids as $destination_id) { - $destination = StandaloneDocker::find($destination_id); + $destination = StandaloneDocker::find($destination_id)->first(); $server = $destination->server; if ($server->team_id !== $this->mainServer->team_id) { $this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!"); diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 0d7d7755f..475d2dfa8 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -84,11 +84,16 @@ class Heading extends Component return; } $this->setDeploymentUuid(); - queue_application_deployment( + $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deploymentUuid, force_rebuild: $force_rebuild, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return $this->redirectRoute('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], @@ -126,11 +131,16 @@ class Heading extends Component return; } $this->setDeploymentUuid(); - queue_application_deployment( + $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deploymentUuid, restart_only: true, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return $this->redirectRoute('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index bdf62706c..88ce65c53 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -159,13 +159,18 @@ class Previews extends Component 'pull_request_html_url' => $pull_request_html_url, ]); } - queue_application_deployment( + $result = queue_application_deployment( application: $this->application, deployment_uuid: $this->deployment_uuid, force_rebuild: false, pull_request_id: $pull_request_id, git_type: $found->git_type ?? null, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return redirect()->route('project.application.deployment.show', [ 'project_uuid' => $this->parameters['project_uuid'], diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index 1759fe08a..71a913add 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -79,7 +79,7 @@ class Destination extends Component $deployment_uuid = new Cuid2; $server = Server::ownedByCurrentTeam()->findOrFail($server_id); $destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail(); - queue_application_deployment( + $result = queue_application_deployment( deployment_uuid: $deployment_uuid, application: $this->resource, server: $server, @@ -87,6 +87,11 @@ class Destination extends Component only_this_server: true, no_questions_asked: true, ); + if ($result['status'] === 'skipped') { + $this->dispatch('success', 'Deployment skipped', $result['message']); + + return; + } return redirect()->route('project.application.deployment.show', [ 'project_uuid' => data_get($this->resource, 'environment.project.uuid'), diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index d5283898e..3f1e8513c 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -24,6 +24,26 @@ function queue_application_deployment(Application $application, string $deployme if ($destination) { $destination_id = $destination->id; } + + // Check if there's already a deployment in progress or queued for this application and commit + $existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id) + ->where('commit', $commit) + ->whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value]) + ->first(); + + if ($existing_deployment) { + // If force_rebuild is true or rollback is true or no_questions_asked is true, we'll still create a new deployment + if (! $force_rebuild && ! $rollback && ! $no_questions_asked) { + // Return the existing deployment's details + return [ + 'status' => 'skipped', + 'message' => 'Deployment already queued for this commit.', + 'deployment_uuid' => $existing_deployment->deployment_uuid, + 'existing_deployment' => $existing_deployment, + ]; + } + } + $deployment = ApplicationDeploymentQueue::create([ 'application_id' => $application_id, 'application_name' => $application->name, @@ -47,11 +67,17 @@ function queue_application_deployment(Application $application, string $deployme ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, ); - } elseif (next_queuable($server_id, $application_id)) { + } elseif (next_queuable($server_id, $application_id, $commit)) { ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, ); } + + return [ + 'status' => 'queued', + 'message' => 'Deployment queued.', + 'deployment_uuid' => $deployment_uuid, + ]; } function force_start_deployment(ApplicationDeploymentQueue $deployment) { @@ -78,20 +104,35 @@ function queue_next_deployment(Application $application) } } -function next_queuable(string $server_id, string $application_id): bool +function next_queuable(string $server_id, string $application_id, string $commit = 'HEAD'): bool { - $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', ApplicationDeploymentStatus::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) { + // Check if there's already a deployment in progress for this application and commit + $existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id) + ->where('commit', $commit) + ->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value) + ->first(); + + if ($existing_deployment) { return false; } + + // Check if there's any deployment in progress for this application + $in_progress = ApplicationDeploymentQueue::where('application_id', $application_id) + ->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value) + ->exists(); + + if ($in_progress) { + return false; + } + + // Check server's concurrent build limit $server = Server::find($server_id); $concurrent_builds = $server->settings->concurrent_builds; + $active_deployments = ApplicationDeploymentQueue::where('server_id', $server_id) + ->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value) + ->count(); - if ($deployments->count() > $concurrent_builds) { + if ($active_deployments >= $concurrent_builds) { return false; }