mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-28 18:14:35 -05:00
Catch and report failures while updating closed pull request status so preview deployment cleanup still runs for closed GitHub pull request webhooks. Add coverage for cleanup continuing when GitHub comment cleanup fails.
170 lines
5.9 KiB
PHP
170 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Actions\Application\CleanupPreviewDeployment;
|
|
use App\Enums\ProcessStatus;
|
|
use App\Http\Controllers\Webhook\Concerns\DetectsSkipDeployCommits;
|
|
use App\Models\Application;
|
|
use App\Models\ApplicationPreview;
|
|
use App\Models\GithubApp;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Throwable;
|
|
use Visus\Cuid2\Cuid2;
|
|
|
|
class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
|
|
{
|
|
use DetectsSkipDeployCommits;
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $tries = 3;
|
|
|
|
public int $timeout = 60;
|
|
|
|
public array $backoff = [30, 60, 120];
|
|
|
|
public function __construct(
|
|
public int $applicationId,
|
|
public ?int $githubAppId,
|
|
public string $action,
|
|
public int $pullRequestId,
|
|
public string $pullRequestHtmlUrl,
|
|
public ?string $pullRequestTitle,
|
|
public ?string $beforeSha,
|
|
public ?string $afterSha,
|
|
public string $commitSha,
|
|
public ?string $authorAssociation,
|
|
public string $fullName,
|
|
) {
|
|
$this->onQueue('high');
|
|
}
|
|
|
|
public function handle(): void
|
|
{
|
|
$application = Application::find($this->applicationId);
|
|
if (! $application) {
|
|
return;
|
|
}
|
|
|
|
$githubApp = $this->githubAppId ? GithubApp::find($this->githubAppId) : null;
|
|
|
|
if ($this->action === 'closed' || $this->action === 'close') {
|
|
$this->handleClosedAction($application);
|
|
|
|
return;
|
|
}
|
|
|
|
if ($this->action === 'opened' || $this->action === 'synchronize' || $this->action === 'reopened') {
|
|
$this->handleOpenAction($application, $githubApp);
|
|
}
|
|
}
|
|
|
|
private function handleClosedAction(Application $application): void
|
|
{
|
|
$found = ApplicationPreview::where('application_id', $application->id)
|
|
->where('pull_request_id', $this->pullRequestId)
|
|
->first();
|
|
|
|
if ($found) {
|
|
try {
|
|
$this->dispatchPullRequestClosedUpdate($application, $found);
|
|
} catch (Throwable $e) {
|
|
report($e);
|
|
} finally {
|
|
CleanupPreviewDeployment::run($application, $this->pullRequestId, $found);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function dispatchPullRequestClosedUpdate(Application $application, ApplicationPreview $preview): void
|
|
{
|
|
ApplicationPullRequestUpdateJob::dispatchSync(
|
|
application: $application,
|
|
preview: $preview,
|
|
status: ProcessStatus::CLOSED
|
|
);
|
|
}
|
|
|
|
private function handleOpenAction(Application $application, ?GithubApp $githubApp): void
|
|
{
|
|
if (! $application->isPRDeployable()) {
|
|
return;
|
|
}
|
|
|
|
if (self::shouldSkipDeployAny([$this->pullRequestTitle])) {
|
|
return;
|
|
}
|
|
|
|
// Check if PR deployments from public contributors are restricted
|
|
if (! $application->settings->is_pr_deployments_public_enabled) {
|
|
$trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
|
|
if (! in_array($this->authorAssociation, $trustedAssociations)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get changed files for watch path filtering
|
|
$changed_files = collect();
|
|
$repository_parts = explode('/', $this->fullName);
|
|
$owner = $repository_parts[0] ?? '';
|
|
$repo = $repository_parts[1] ?? '';
|
|
|
|
if ($this->action === 'synchronize' && $this->beforeSha && $this->afterSha) {
|
|
// For synchronize events, get files changed between before and after commits
|
|
$changed_files = collect(getGithubCommitRangeFiles($githubApp, $owner, $repo, $this->beforeSha, $this->afterSha));
|
|
} elseif ($this->action === 'opened' || $this->action === 'reopened') {
|
|
// For opened/reopened events, get all files in the PR
|
|
$changed_files = collect(getGithubPullRequestFiles($githubApp, $owner, $repo, $this->pullRequestId));
|
|
}
|
|
|
|
// Apply watch path filtering
|
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
|
if (! $is_watch_path_triggered && ! blank($application->watch_paths)) {
|
|
return;
|
|
}
|
|
|
|
// Create ApplicationPreview if not exists
|
|
$found = ApplicationPreview::where('application_id', $application->id)
|
|
->where('pull_request_id', $this->pullRequestId)
|
|
->first();
|
|
|
|
if (! $found) {
|
|
if ($application->build_pack === 'dockercompose') {
|
|
$preview = ApplicationPreview::create([
|
|
'git_type' => 'github',
|
|
'application_id' => $application->id,
|
|
'pull_request_id' => $this->pullRequestId,
|
|
'pull_request_html_url' => $this->pullRequestHtmlUrl,
|
|
'docker_compose_domains' => $application->docker_compose_domains,
|
|
]);
|
|
$preview->generate_preview_fqdn_compose();
|
|
} else {
|
|
$preview = ApplicationPreview::create([
|
|
'git_type' => 'github',
|
|
'application_id' => $application->id,
|
|
'pull_request_id' => $this->pullRequestId,
|
|
'pull_request_html_url' => $this->pullRequestHtmlUrl,
|
|
]);
|
|
$preview->generate_preview_fqdn();
|
|
}
|
|
}
|
|
|
|
// Queue the deployment
|
|
$deployment_uuid = new Cuid2;
|
|
queue_application_deployment(
|
|
application: $application,
|
|
pull_request_id: $this->pullRequestId,
|
|
deployment_uuid: $deployment_uuid,
|
|
force_rebuild: false,
|
|
commit: $this->commitSha,
|
|
is_webhook: true,
|
|
git_type: 'github'
|
|
);
|
|
}
|
|
}
|