From 987409bae41d0354106908a64ced2aba4cbc46fb Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 10:43:18 +0100 Subject: [PATCH] fix: bitbucket manual deployments --- app/Jobs/ApplicationDeploymentJob.php | 32 +++-- app/Models/Application.php | 38 +++++- .../application/configuration.blade.php | 2 +- .../project/application/previews.blade.php | 12 +- routes/webhooks.php | 129 ++++++++++++++---- 5 files changed, 166 insertions(+), 47 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index cca9745c1..9e046fd42 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -158,7 +158,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->preview->fqdn = $preview_fqdn; $this->preview->save(); } - ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); + if ($this->application->is_github_based()) { + ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); + } } } @@ -230,6 +232,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->next(ApplicationDeploymentStatus::FINISHED->value); $this->application->isConfigurationChanged(false); return; + } else if ($this->pull_request_id !== 0) { + $this->deploy_pull_request(); } else if ($this->application->dockerfile) { $this->deploy_simple_dockerfile(); } else if ($this->application->build_pack === 'dockercompose') { @@ -241,11 +245,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else if ($this->application->build_pack === 'static') { $this->deploy_static_buildpack(); } else { - if ($this->pull_request_id !== 0) { - $this->deploy_pull_request(); - } else { - $this->deploy_nixpacks_buildpack(); - } + $this->deploy_nixpacks_buildpack(); } if ($this->server->isProxyShouldRun()) { dispatch(new ContainerStatusJob($this->server)); @@ -256,13 +256,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } $this->next(ApplicationDeploymentStatus::FINISHED->value); if ($this->pull_request_id !== 0) { - ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED); + if ($this->application->is_github_based()) { + ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED); + } } $this->application->isConfigurationChanged(true); } catch (Exception $e) { if ($this->pull_request_id !== 0) { - ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR); + if ($this->application->is_github_based()) { + ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR); + } } + ray($e); $this->fail($e); throw $e; } finally { @@ -729,10 +734,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->generate_nixpacks_confs(); } $this->generate_compose_file(); - - // Needs separate preview variables $this->generate_build_env_variables(); - if ($this->application->build_pack !== 'nixpacks') { + if ($this->application->build_pack === 'dockerfile') { $this->add_build_env_variables_to_dockerfile(); } $this->build_image(); @@ -868,7 +871,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private function generate_git_import_commands() { - ['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands($this->deployment_uuid, $this->pull_request_id, $this->git_type); + ['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands( + deployment_uuid: $this->deployment_uuid, + pull_request_id: $this->pull_request_id, + git_type: $this->git_type, + commit: $this->commit + ); return $commands; } diff --git a/app/Models/Application.php b/app/Models/Application.php index 116cfdd76..ab45e1b1e 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -111,6 +111,13 @@ class Application extends BaseModel } // End of build packs / deployment types + public function is_github_based(): bool + { + if (data_get($this, 'source')) { + return true; + } + return false; + } public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -807,7 +814,7 @@ class Application extends BaseModel } return $git_clone_command; } - function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null) + function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null) { $branch = $this->git_branch; ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); @@ -820,7 +827,6 @@ class Application extends BaseModel if ($pull_request_id !== 0) { $pr_branch_name = "pr-{$pull_request_id}-coolify"; } - if ($this->deploymentType() === 'source') { $source_html_url = data_get($this, 'source.html_url'); $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)); @@ -926,6 +932,34 @@ class Application extends BaseModel $fullRepoUrl = $customRepository; $git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}"; $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command); + + if ($pull_request_id !== 0) { + if ($git_type === 'gitlab') { + $branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name"; + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); + } else { + $commands->push("echo 'Checking out $branch'"); + } + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + } else if ($git_type === 'github') { + $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); + } else { + $commands->push("echo 'Checking out $branch'"); + } + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + } else if ($git_type === 'bitbucket') { + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); + } else { + $commands->push("echo 'Checking out $branch'"); + } + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit"; + } + } + if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, $git_clone_command)); } else { diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index 265927fb6..b6d6cad5c 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -39,7 +39,7 @@ Webhooks - @if ($application->git_based() && $application->build_pack !== 'static') + @if ($application->git_based()) Preview Deployments diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 0342a1027..a9364d94c 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -1,11 +1,13 @@
-
-

Pull Requests on Git

- Load Pull Requests - -
+ @if ($application->is_github_based()) +
+

Pull Requests on Git

+ Load Pull Requests + +
+ @endif @isset($rate_limit_remaining)
Requests remaining till rate limited by Git: {{ $rate_limit_remaining }}
@endisset diff --git a/routes/webhooks.php b/routes/webhooks.php index cb2c0d704..3eab89a3a 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -239,43 +239,58 @@ Route::post('/source/bitbucket/events/manual', function () { $return_payloads = collect([]); $payload = request()->collect(); $headers = request()->headers->all(); - $x_bitbucket_token = data_get($headers, 'x-hub-signature', [""])[0]; - $x_bitbucket_event = data_get($headers, 'x-event-key', [""])[0]; + $x_bitbucket_token = data_get($headers, 'x-hub-signature.0', ""); + $x_bitbucket_event = data_get($headers, 'x-event-key.0', ""); + $handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']); + if (!$handled_events->contains($x_bitbucket_event)) { + return response([ + 'status' => 'failed', + 'message' => 'Nothing to do. Event not handled.', + ]); + } if ($x_bitbucket_event === 'repo:push') { $branch = data_get($payload, 'push.changes.0.new.name'); - $name = data_get($payload, 'repository.name'); + $full_name = data_get($payload, 'repository.full_name'); if (!$branch) { - $return_payloads->push([ + return response([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', ]); - return response($return_payloads); } - ray('Manual Webhook bitbucket Push Event with branch: ' . $branch); + ray('Manual webhook bitbucket push event with branch: ' . $branch); } - $applications = Application::where('git_repository', 'like', "%$name%"); - if ($x_bitbucket_event === 'repo:push') { - $applications = $applications->where('git_branch', $branch)->get(); - if ($applications->isEmpty()) { - $return_payloads->push([ - 'status' => 'failed', - 'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $name.", - ]); - return response($return_payloads); - } + if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') { + $branch = data_get($payload, 'pullrequest.destination.branch.name'); + $base_branch = data_get($payload, 'pullrequest.source.branch.name'); + $full_name = data_get($payload, 'repository.full_name'); + $pull_request_id = data_get($payload, 'pullrequest.id'); + $pull_request_html_url = data_get($payload, 'pullrequest.links.html.href'); + $commit = data_get($payload, 'pullrequest.source.commit.hash'); + } + $applications = Application::where('git_repository', 'like', "%$full_name%"); + $applications = $applications->where('git_branch', $branch)->get(); + if ($applications->isEmpty()) { + return response([ + 'status' => 'failed', + 'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.", + ]); } foreach ($applications as $application) { + if (!$application->isPRDeployable()) { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Preview deployments disabled.', + ]); + continue; + } $webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket'); $payload = request()->getContent(); - fwrite(STDOUT, $payload); - list($algo, $hash) = explode('=', $x_bitbucket_token, 2); - $payloadHash = hash_hmac($algo, $payload, $webhook_secret); - - if (!hash_equals($hash, $payloadHash)) { + if (!hash_equals($hash, $payloadHash) && !isDev()) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', @@ -289,34 +304,94 @@ Route::post('/source/bitbucket/events/manual', function () { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', - 'message' => 'Server is not functional', + 'message' => 'Server is not functional.', ]); ray('Server is not functional: ' . $application->destination->server->name); continue; } if ($x_bitbucket_event === 'repo:push') { - if ($application->isDeployable()) { + if ($application->isPRDeployable()) { ray('Deploying ' . $application->name . ' with branch ' . $branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( - application_id: $application->id, + application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, is_webhook: true ); + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); } else { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', - 'message' => 'Deployments disabled', + 'message' => 'Preview deployments disabled.', + ]); + } + } + if ($x_bitbucket_event === 'pullrequest:created') { + if ($application->isPRDeployable()) { + ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); + $deployment_uuid = new Cuid2(7); + $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); + if (!$found) { + ApplicationPreview::create([ + 'git_type' => 'bitbucket', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } + queue_application_deployment( + application: $application, + pull_request_id: $pull_request_id, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + commit: $commit, + is_webhook: true, + git_type: 'bitbucket' + ); + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Preview deployments disabled.', + ]); + } + } + if ($x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') { + ray('Pull request rejected'); + $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); + if ($found) { + $found->delete(); + $container_name = generateApplicationContainerName($application, $pull_request_id); + instant_remote_process(["docker rm -f $container_name"], $application->destination->server); + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment closed.', + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'No preview deployment found.', ]); - ray('Deployments disabled for ' . $application->name); } } } + ray($return_payloads); return response($return_payloads); } catch (Exception $e) { - ray($e->getMessage()); + ray($e); return handleError($e); } });