mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
Fix: Concurrent builds ignored & add deployment queue limit (#7488)
This commit is contained in:
@@ -388,7 +388,11 @@ class DeployController extends Controller
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force, $pr);
|
$result = $this->deploy_resource($resource, $force, $pr);
|
||||||
|
if (isset($result['status']) && $result['status'] === 429) {
|
||||||
|
return response()->json(['message' => $result['message']], 429)->header('Retry-After', 60);
|
||||||
|
}
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $result;
|
||||||
if ($deployment_uuid) {
|
if ($deployment_uuid) {
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
} else {
|
} else {
|
||||||
@@ -430,7 +434,11 @@ class DeployController extends Controller
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($applications as $resource) {
|
foreach ($applications as $resource) {
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
$result = $this->deploy_resource($resource, $force);
|
||||||
|
if (isset($result['status']) && $result['status'] === 429) {
|
||||||
|
return response()->json(['message' => $result['message']], 429)->header('Retry-After', 60);
|
||||||
|
}
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $result;
|
||||||
if ($deployment_uuid) {
|
if ($deployment_uuid) {
|
||||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
}
|
}
|
||||||
@@ -474,8 +482,11 @@ class DeployController extends Controller
|
|||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: $force,
|
force_rebuild: $force,
|
||||||
pull_request_id: $pr,
|
pull_request_id: $pr,
|
||||||
|
is_api: true,
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return ['message' => $result['message'], 'deployment_uuid' => null, 'status' => 429];
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$message = $result['message'];
|
$message = $result['message'];
|
||||||
} else {
|
} else {
|
||||||
$message = "Application {$resource->name} deployment queued.";
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ class Bitbucket extends Controller
|
|||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true
|
is_webhook: true
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
@@ -144,7 +146,9 @@ class Bitbucket extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'bitbucket'
|
git_type: 'bitbucket'
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ class Gitea extends Controller
|
|||||||
commit: data_get($payload, 'after', 'HEAD'),
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
@@ -169,7 +171,9 @@ class Gitea extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'gitea'
|
git_type: 'gitea'
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ class Github extends Controller
|
|||||||
commit: data_get($payload, 'after', 'HEAD'),
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
@@ -197,7 +199,9 @@ class Github extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'github'
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
@@ -347,12 +351,15 @@ class Github extends Controller
|
|||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
}
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'status' => $result['status'],
|
'status' => $result['status'],
|
||||||
'message' => $result['message'],
|
'message' => $result['message'],
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'application_name' => $application->name,
|
'application_name' => $application->name,
|
||||||
'deployment_uuid' => $result['deployment_uuid'],
|
'deployment_uuid' => $result['deployment_uuid'] ?? null,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$paths = str($application->watch_paths)->explode("\n");
|
$paths = str($application->watch_paths)->explode("\n");
|
||||||
@@ -411,7 +418,9 @@ class Github extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'github'
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
|
|||||||
@@ -131,7 +131,9 @@ class Gitlab extends Controller
|
|||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'status' => $result['status'],
|
'status' => $result['status'],
|
||||||
'message' => $result['message'],
|
'message' => $result['message'],
|
||||||
@@ -202,7 +204,9 @@ class Gitlab extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'gitlab'
|
git_type: 'gitlab'
|
||||||
);
|
);
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'queue_full') {
|
||||||
|
return response($result['message'], 429)->header('Retry-After', 60);
|
||||||
|
} elseif ($result['status'] === 'skipped') {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
'status' => 'skipped',
|
'status' => 'skipped',
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ class Heading extends Component
|
|||||||
deployment_uuid: $this->deploymentUuid,
|
deployment_uuid: $this->deploymentUuid,
|
||||||
force_rebuild: $force_rebuild,
|
force_rebuild: $force_rebuild,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'queue_full') {
|
||||||
|
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'skipped') {
|
||||||
$this->dispatch('error', 'Deployment skipped', $result['message']);
|
$this->dispatch('error', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
@@ -144,6 +149,11 @@ class Heading extends Component
|
|||||||
deployment_uuid: $this->deploymentUuid,
|
deployment_uuid: $this->deploymentUuid,
|
||||||
restart_only: true,
|
restart_only: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'queue_full') {
|
||||||
|
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'skipped') {
|
||||||
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,11 @@ class Previews extends Component
|
|||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
git_type: $found->git_type ?? null,
|
git_type: $found->git_type ?? null,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'queue_full') {
|
||||||
|
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'skipped') {
|
||||||
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Rollback extends Component
|
|||||||
|
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
commit: $commit,
|
commit: $commit,
|
||||||
@@ -60,6 +60,12 @@ class Rollback extends Component
|
|||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($result['status'] === 'queue_full') {
|
||||||
|
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
return redirect()->route('project.application.deployment.show', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
'application_uuid' => $this->parameters['application_uuid'],
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ class Destination extends Component
|
|||||||
only_this_server: true,
|
only_this_server: true,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'queue_full') {
|
||||||
|
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($result['status'] === 'skipped') {
|
if ($result['status'] === 'skipped') {
|
||||||
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ class Advanced extends Component
|
|||||||
#[Validate(['integer', 'min:1'])]
|
#[Validate(['integer', 'min:1'])]
|
||||||
public int $dynamicTimeout = 1;
|
public int $dynamicTimeout = 1;
|
||||||
|
|
||||||
|
#[Validate(['integer', 'min:1'])]
|
||||||
|
public int $deploymentQueueLimit = 25;
|
||||||
|
|
||||||
public function mount(string $server_uuid)
|
public function mount(string $server_uuid)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -43,12 +46,14 @@ class Advanced extends Component
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
$this->server->settings->concurrent_builds = $this->concurrentBuilds;
|
$this->server->settings->concurrent_builds = $this->concurrentBuilds;
|
||||||
$this->server->settings->dynamic_timeout = $this->dynamicTimeout;
|
$this->server->settings->dynamic_timeout = $this->dynamicTimeout;
|
||||||
|
$this->server->settings->deployment_queue_limit = $this->deploymentQueueLimit;
|
||||||
$this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold;
|
$this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold;
|
||||||
$this->server->settings->server_disk_usage_check_frequency = $this->serverDiskUsageCheckFrequency;
|
$this->server->settings->server_disk_usage_check_frequency = $this->serverDiskUsageCheckFrequency;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->concurrentBuilds = $this->server->settings->concurrent_builds;
|
$this->concurrentBuilds = $this->server->settings->concurrent_builds;
|
||||||
$this->dynamicTimeout = $this->server->settings->dynamic_timeout;
|
$this->dynamicTimeout = $this->server->settings->dynamic_timeout;
|
||||||
|
$this->deploymentQueueLimit = $this->server->settings->deployment_queue_limit;
|
||||||
$this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold;
|
$this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold;
|
||||||
$this->serverDiskUsageCheckFrequency = $this->server->settings->server_disk_usage_check_frequency;
|
$this->serverDiskUsageCheckFrequency = $this->server->settings->server_disk_usage_check_frequency;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use OpenApi\Attributes as OA;
|
|||||||
properties: [
|
properties: [
|
||||||
'id' => ['type' => 'integer'],
|
'id' => ['type' => 'integer'],
|
||||||
'concurrent_builds' => ['type' => 'integer'],
|
'concurrent_builds' => ['type' => 'integer'],
|
||||||
|
'deployment_queue_limit' => ['type' => 'integer'],
|
||||||
'dynamic_timeout' => ['type' => 'integer'],
|
'dynamic_timeout' => ['type' => 'integer'],
|
||||||
'force_disabled' => ['type' => 'boolean'],
|
'force_disabled' => ['type' => 'boolean'],
|
||||||
'force_server_cleanup' => ['type' => 'boolean'],
|
'force_server_cleanup' => ['type' => 'boolean'],
|
||||||
|
|||||||
@@ -28,6 +28,20 @@ function queue_application_deployment(Application $application, string $deployme
|
|||||||
$destination_id = $destination->id;
|
$destination_id = $destination->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the deployment queue is full for this server
|
||||||
|
$serverForQueueCheck = $server ?? Server::find($server_id);
|
||||||
|
$queue_limit = $serverForQueueCheck->settings->deployment_queue_limit ?? 25;
|
||||||
|
$queued_count = ApplicationDeploymentQueue::where('server_id', $server_id)
|
||||||
|
->where('status', ApplicationDeploymentStatus::QUEUED->value)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($queued_count >= $queue_limit) {
|
||||||
|
return [
|
||||||
|
'status' => 'queue_full',
|
||||||
|
'message' => 'Deployment queue is full. Please wait for existing deployments to complete.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there's already a deployment in progress or queued for this application and commit
|
// Check if there's already a deployment in progress or queued for this application and commit
|
||||||
$existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id)
|
$existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id)
|
||||||
->where('commit', $commit)
|
->where('commit', $commit)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->integer('deployment_queue_limit')->default(25)->after('concurrent_builds');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('deployment_queue_limit');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -10618,6 +10618,9 @@
|
|||||||
"concurrent_builds": {
|
"concurrent_builds": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"deployment_queue_limit": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"dynamic_timeout": {
|
"dynamic_timeout": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6759,6 +6759,8 @@ components:
|
|||||||
type: integer
|
type: integer
|
||||||
concurrent_builds:
|
concurrent_builds:
|
||||||
type: integer
|
type: integer
|
||||||
|
deployment_queue_limit:
|
||||||
|
type: integer
|
||||||
dynamic_timeout:
|
dynamic_timeout:
|
||||||
type: integer
|
type: integer
|
||||||
force_disabled:
|
force_disabled:
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<x-forms.input canGate="update" :canResource="$server" id="dynamicTimeout"
|
<x-forms.input canGate="update" :canResource="$server" id="dynamicTimeout"
|
||||||
label="Deployment timeout (seconds)" required
|
label="Deployment timeout (seconds)" required
|
||||||
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" id="deploymentQueueLimit"
|
||||||
|
label="Deployment queue limit" required
|
||||||
|
helper="Maximum number of queued deployments allowed. New deployments will be rejected with a 429 status when the limit is reached." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user