From c953482ba95c652ac076a9473633dfaff14cca7c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 31 May 2023 11:24:02 +0200 Subject: [PATCH] pull request webhooks --- .../Livewire/Project/Application/General.php | 36 +++---- .../Livewire/Project/Application/Previews.php | 5 +- app/Jobs/ApplicationDeploymentJob.php | 8 +- app/Models/Application.php | 9 +- app/Models/ApplicationDeploymentQueue.php | 1 + app/Models/ApplicationSetting.php | 18 +++- bootstrap/helpers/applications.php | 3 +- ...1717_create_application_settings_table.php | 12 +-- ...te_application_deployment_queues_table.php | 1 + .../seeders/ApplicationSettingsSeeder.php | 2 +- .../project/application/deployments.blade.php | 15 ++- .../project/application/general.blade.php | 14 +-- .../project/application/previews.blade.php | 3 +- routes/webhooks.php | 83 +++++++++++++--- thunder-tests/thunderEnvironment.json | 30 ++++-- thunder-tests/thunderclient.json | 96 +++++++++++++++++++ 16 files changed, 272 insertions(+), 64 deletions(-) diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index bf24024e6..2591482c8 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -24,12 +24,12 @@ class General extends Component public string|null $global_wildcard_domain = null; public bool $is_static; - public bool $is_git_submodules_allowed; - public bool $is_git_lfs_allowed; - public bool $is_debug; - public bool $is_previews; - public bool $is_auto_deploy; - public bool $is_force_https; + public bool $is_git_submodules_enabled; + public bool $is_git_lfs_enabled; + public bool $is_debug_enabled; + public bool $is_preview_deployments_enabled; + public bool $is_auto_deploy_enabled; + public bool $is_force_https_enabled; protected $rules = [ 'application.name' => 'required|min:6', @@ -51,12 +51,12 @@ public function instantSave() { // @TODO: find another way - if possible $this->application->settings->is_static = $this->is_static; - $this->application->settings->is_git_submodules_allowed = $this->is_git_submodules_allowed; - $this->application->settings->is_git_lfs_allowed = $this->is_git_lfs_allowed; - $this->application->settings->is_debug = $this->is_debug; - $this->application->settings->is_previews = $this->is_previews; - $this->application->settings->is_auto_deploy = $this->is_auto_deploy; - $this->application->settings->is_force_https = $this->is_force_https; + $this->application->settings->is_git_submodules_enabled = $this->is_git_submodules_enabled; + $this->application->settings->is_git_lfs_enabled = $this->is_git_lfs_enabled; + $this->application->settings->is_debug_enabled = $this->is_debug_enabled; + $this->application->settings->is_preview_deployments_enabled = $this->is_preview_deployments_enabled; + $this->application->settings->is_auto_deploy_enabled = $this->is_auto_deploy_enabled; + $this->application->settings->is_force_https_enabled = $this->is_force_https_enabled; $this->application->settings->save(); $this->application->refresh(); $this->emit('saved', 'Application settings updated!'); @@ -72,12 +72,12 @@ protected function checkWildCardDomain() public function mount() { $this->is_static = $this->application->settings->is_static; - $this->is_git_submodules_allowed = $this->application->settings->is_git_submodules_allowed; - $this->is_git_lfs_allowed = $this->application->settings->is_git_lfs_allowed; - $this->is_debug = $this->application->settings->is_debug; - $this->is_previews = $this->application->settings->is_previews; - $this->is_auto_deploy = $this->application->settings->is_auto_deploy; - $this->is_force_https = $this->application->settings->is_force_https; + $this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled; + $this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled; + $this->is_debug_enabled = $this->application->settings->is_debug_enabled; + $this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled; + $this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled; + $this->is_force_https_enabled = $this->application->settings->is_force_https_enabled; $this->checkWildCardDomain(); } public function generateGlobalRandomDomain() diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index 86024aec7..48b6f74fd 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -75,10 +75,7 @@ public function stop(int $pull_request_id) ray('Stopping container: ' . $container_name); instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); - $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); - if ($found) { - $found->delete(); - } + ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); $this->application->refresh(); } catch (\Throwable $th) { return general_error_handler($th, $this); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 1d4a077c2..934ec70e9 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -501,7 +501,7 @@ private function set_labels_for_applications() // Set labels for http (redirect to https) $labels[] = "traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)"; $labels[] = "traefik.http.routers.{$http_label}.entryPoints=http"; - if ($this->application->settings->is_force_https) { + if ($this->application->settings->is_force_https_enabled) { $labels[] = "traefik.http.routers.{$http_label}.middlewares=redirect-to-https"; } } else { @@ -539,7 +539,7 @@ private function execute_now( 'command' => $commandText, ]); $this->activity->save(); - if ($isDebuggable && !$this->application->settings->is_debug) { + if ($isDebuggable && !$this->application->settings->is_debug_enabled) { $hideFromOutput = true; } $remote_process = resolve(RunRemoteProcess::class, [ @@ -565,10 +565,10 @@ private function set_git_import_settings($git_clone_command) if ($this->application->git_commit_sha !== 'HEAD') { $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1"; } - if ($this->application->settings->is_git_submodules_allowed) { + if ($this->application->settings->is_git_submodules_enabled) { $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive"; } - if ($this->application->settings->is_git_lfs_allowed) { + if ($this->application->settings->is_git_lfs_enabled) { $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull"; } return $git_clone_command; diff --git a/app/Models/Application.php b/app/Models/Application.php index 4f19481c2..e3dbe0706 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -154,7 +154,14 @@ public function get_deployment(string $deployment_uuid) } public function isDeployable(): bool { - if ($this->settings->is_auto_deploy) { + if ($this->settings->is_auto_deploy_enabled) { + return true; + } + return false; + } + public function isPRDeployable(): bool + { + if ($this->settings->is_preview_deployments_enabled) { return true; } return false; diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index cee3293b0..474c38cc8 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -15,5 +15,6 @@ class ApplicationDeploymentQueue extends Model 'force_rebuild', 'commit', 'status', + 'is_webhook', ]; } diff --git a/app/Models/ApplicationSetting.php b/app/Models/ApplicationSetting.php index c0beabe7c..3bdd6a7cd 100644 --- a/app/Models/ApplicationSetting.php +++ b/app/Models/ApplicationSetting.php @@ -7,10 +7,24 @@ class ApplicationSetting extends Model { + protected $cast = [ + 'is_static' => 'boolean', + 'is_auto_deploy_enabled' => 'boolean', + 'is_force_https_enabled' => 'boolean', + 'is_debug_enabled' => 'boolean', + 'is_preview_deployments_enabled' => 'boolean', + 'is_git_submodules_enabled' => 'boolean', + 'is_git_lfs_enabled' => 'boolean', + ]; protected $fillable = [ 'application_id', - 'is_git_submodules_allowed', - 'is_git_lfs_allowed', + 'is_static', + 'is_auto_deploy_enabled', + 'is_force_https_enabled', + 'is_debug_enabled', + 'is_preview_deployments_enabled', + 'is_git_submodules_enabled', + 'is_git_lfs_enabled', ]; public function isStatic(): Attribute { diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index e72c7c980..9914bc9a9 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -3,7 +3,7 @@ use App\Jobs\ApplicationDeploymentJob; use App\Models\ApplicationDeploymentQueue; -function queue_application_deployment(int $application_id, string $deployment_uuid, int|null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false) +function queue_application_deployment(int $application_id, string $deployment_uuid, int|null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false) { ray('Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild); $deployment = ApplicationDeploymentQueue::create([ @@ -11,6 +11,7 @@ function queue_application_deployment(int $application_id, string $deployment_uu 'deployment_uuid' => $deployment_uuid, 'pull_request_id' => $pull_request_id, 'force_rebuild' => $force_rebuild, + 'is_webhook' => $is_webhook, 'commit' => $commit, ]); $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at'); diff --git a/database/migrations/2023_03_27_081717_create_application_settings_table.php b/database/migrations/2023_03_27_081717_create_application_settings_table.php index 0e7e386a2..e13f7f366 100644 --- a/database/migrations/2023_03_27_081717_create_application_settings_table.php +++ b/database/migrations/2023_03_27_081717_create_application_settings_table.php @@ -14,13 +14,13 @@ public function up(): void Schema::create('application_settings', function (Blueprint $table) { $table->id(); $table->boolean('is_static')->default(false); - $table->boolean('is_git_submodules_allowed')->default(true); - $table->boolean('is_git_lfs_allowed')->default(true); - $table->boolean('is_auto_deploy')->default(true); - $table->boolean('is_force_https')->default(true); + $table->boolean('is_git_submodules_enabled')->default(true); + $table->boolean('is_git_lfs_enabled')->default(true); + $table->boolean('is_auto_deploy_enabled')->default(true); + $table->boolean('is_force_https_enabled')->default(true); + $table->boolean('is_debug_enabled')->default(false); + $table->boolean('is_preview_deployments_enabled')->default(false); // $table->boolean('is_dual_cert')->default(false); - $table->boolean('is_debug')->default(false); - $table->boolean('is_previews')->default(false); // $table->boolean('is_custom_ssl')->default(false); // $table->boolean('is_http2')->default(false); $table->foreignId('application_id'); diff --git a/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php b/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php index bf8bfcc35..9573b9735 100644 --- a/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php +++ b/database/migrations/2023_05_24_083426_create_application_deployment_queues_table.php @@ -19,6 +19,7 @@ public function up(): void $table->boolean('force_rebuild')->default(false); $table->string('commit')->default('HEAD'); $table->string('status')->default('queued'); + $table->boolean('is_webhook')->default(false); $table->timestamps(); }); } diff --git a/database/seeders/ApplicationSettingsSeeder.php b/database/seeders/ApplicationSettingsSeeder.php index 1b6068ad5..3ab5bd967 100644 --- a/database/seeders/ApplicationSettingsSeeder.php +++ b/database/seeders/ApplicationSettingsSeeder.php @@ -18,7 +18,7 @@ class ApplicationSettingsSeeder extends Seeder public function run(): void { $application_1 = Application::find(1)->load(['settings']); - $application_1->settings->is_debug = false; + $application_1->settings->is_debug_enabled = false; $application_1->settings->save(); } } diff --git a/resources/views/livewire/project/application/deployments.blade.php b/resources/views/livewire/project/application/deployments.blade.php index e8fc089ca..bcb74b296 100644 --- a/resources/views/livewire/project/application/deployments.blade.php +++ b/resources/views/livewire/project/application/deployments.blade.php @@ -20,7 +20,20 @@ class="hover:no-underline">
@if (data_get($deployment, 'pull_request_id')) -
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
+
+ Pull Request #{{ data_get($deployment, 'pull_request_id') }} + @if (data_get($deployment, 'is_webhook')) + (Webhook) + @endif +
+ @elseif (data_get($deployment, 'is_webhook')) +
Webhook (commit + @if (data_get($deployment, 'commit')) + {{ data_get($deployment, 'commit') }}) + @else + HEAD) + @endif +
@else
Commit: @if (data_get($deployment, 'commit')) diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 7285434f1..a84bf1ac9 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -69,17 +69,19 @@

Advanced

- + instantSave id="is_force_https_enabled" label="Force Https" /> - {{-- --}} - + + - {{-- diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index b237e4fa0..285fb83b0 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -1,9 +1,8 @@
-

Pull Requests on Git

- Load Pull Requests + Load Opened Pull Requests @isset($rate_limit_remaining)
Requests remaning till rate limited by Git: {{ $rate_limit_remaining }}
diff --git a/routes/webhooks.php b/routes/webhooks.php index e698cb12b..a0ee3e720 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -2,6 +2,7 @@ use App\Jobs\ApplicationDeploymentJob; use App\Models\Application; +use App\Models\ApplicationPreview; use App\Models\PrivateKey; use App\Models\GithubApp; use App\Models\GithubEventsApplications; @@ -85,23 +86,83 @@ if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } + ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); } if ($x_github_event === 'pull_request') { - $id = data_get($payload, 'pull_request.base.repo.id'); - $branch = data_get($payload, 'pull_request.base.ref'); + $action = data_get($payload, 'action'); + $id = data_get($payload, 'repository.id'); + $pull_request_id = data_get($payload, 'number'); + $pull_request_html_url = data_get($payload, 'pull_request.html_url'); + $branch = data_get($payload, 'pull_request.head.ref'); + $base_branch = data_get($payload, 'pull_request.base.ref'); + ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); } if (!$id || !$branch) { - return response('not cool'); + return response('Nothing to do. No id or branch found.'); + } + $applications = Application::where('repository_project_id', $id); + if ($x_github_event === 'push') { + $applications = $applications->where('git_branch', $branch)->get(); + } + if ($x_github_event === 'pull_request') { + $applications = $applications->where('git_branch', $base_branch)->get(); + } + + if ($applications->isEmpty()) { + return response('Nothing to do. No applications found.'); } - $applications = Application::where('repository_project_id', $id)->where('git_branch', $branch)->get(); foreach ($applications as $application) { - if ($application->isDeployable()) { - $deployment_uuid = new Cuid2(7); - dispatch(new ApplicationDeploymentJob( - deployment_uuid: $deployment_uuid, - application_uuid: $application->uuid, - force_rebuild: false, - )); + if ($x_github_event === 'push') { + if ($application->isDeployable()) { + ray('Deploying ' . $application->name . ' with branch ' . $branch); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application_id: $application->id, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true + ); + } else { + ray('Deployments disabled for ' . $application->name); + } + } + if ($x_github_event === 'pull_request') { + if ($action === 'opened') { + if ($application->isPRDeployable()) { + $deployment_uuid = new Cuid2(7); + $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); + if (!$found) { + ApplicationPreview::create([ + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url + ]); + } + queue_application_deployment( + application_id: $application->id, + pull_request_id: $pull_request_id, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true + ); + ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); + return response('Preview Deployment queued.'); + } else { + ray('Preview deployments disabled for ' . $application->name); + return response('Nothing to do. Preview Deployments disabled.'); + } + } + if ($action === 'closed') { + $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); + if ($found) { + $found->delete(); + $container_name = generate_container_name($application->uuid, $pull_request_id); + ray('Stopping container: ' . $container_name); + remote_process(["docker rm -f $container_name"], $application->destination->server); + return response('Preview Deployment closed.'); + } + return response('Nothing to do. No Preview Deplyoment found'); + } } } } catch (\Exception $e) { diff --git a/thunder-tests/thunderEnvironment.json b/thunder-tests/thunderEnvironment.json index a6c69759e..5ae55d572 100644 --- a/thunder-tests/thunderEnvironment.json +++ b/thunder-tests/thunderEnvironment.json @@ -1,13 +1,29 @@ [ { - "_id": "bd3f3c2e-b161-40c9-9077-efcf40dfe1f4", - "name": "(Local Env)", + "_id": "e3fbfa6d-da5a-422c-95c5-904c27da8e5a", + "name": "(Global Env)", "default": false, "global": true, - "local": true, - "sortNum": -1, - "created": "2023-05-09T11:48:22.144Z", - "modified": "2023-05-09T11:48:22.144Z", - "data": [] + "sortNum": -2, + "created": "2023-05-31T08:28:50.859Z", + "modified": "2023-05-31T08:28:50.859Z", + "data": [ + { + "name": "repository_id", + "value": "603035348" + }, + { + "name": "repository_ref", + "value": "nodejs-fastify" + }, + { + "name": "repository_name", + "value": "coollabsio/coolify-examples" + }, + { + "name": "repository_ref_pr", + "value": "nodejs-fastify-pr" + } + ] } ] \ No newline at end of file diff --git a/thunder-tests/thunderclient.json b/thunder-tests/thunderclient.json index c8a4faa51..7c9423af7 100644 --- a/thunder-tests/thunderclient.json +++ b/thunder-tests/thunderclient.json @@ -46,5 +46,101 @@ "form": [] }, "tests": [] + }, + { + "_id": "b5386afc-ad91-428f-88ac-0f449c5c26fd", + "colId": "e6458286-eef1-401c-be84-860b111d66f0", + "containerId": "b8cfd093-5467-44a2-9221-ad0207717310", + "name": "PR - Opened", + "url": "http://localhost:8000/webhooks/source/github/events", + "method": "POST", + "sortNum": 20000, + "created": "2023-05-31T08:23:28.904Z", + "modified": "2023-05-31T09:07:17.450Z", + "headers": [ + { + "name": "X-GitHub-Delivery", + "value": "9b4bc300-ee63-11ed-9133-5f71dd83487d" + }, + { + "name": "X-GitHub-Event", + "value": "pull_request" + }, + { + "name": "X-GitHub-Hook-ID", + "value": "400873078" + }, + { + "name": "X-GitHub-Hook-Installation-Target-ID", + "value": "292941" + }, + { + "name": "X-GitHub-Hook-Installation-Target-Type", + "value": "integration" + }, + { + "name": "X-Hub-Signature-256", + "value": "sha256=d5c8d05cc6de14422ab3661d37ec4b98e71f4fdd63d1116f5dedfcb0213ee03d" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ], + "params": [], + "body": { + "type": "json", + "raw": "{\n \"action\": \"opened\",\n \"number\": 1,\n \"pull_request\": {\n \"html_url\": \"https://github.com/{{repository_name}}/pull/1\",\n \"head\": {\n \"ref\":\"{{repository_ref_pr}}\"\n },\n \"base\": {\n \"ref\":\"{{repository_ref}}\"\n }\n },\n \"repository\": {\n \"id\": \"{{repository_id}}\",\n \"full_name\": \"{{repository_name}}\"\n }\n}", + "form": [] + }, + "tests": [] + }, + { + "_id": "7e7a3abd-dc01-454f-aa80-eaeb2c18aa56", + "colId": "e6458286-eef1-401c-be84-860b111d66f0", + "containerId": "b8cfd093-5467-44a2-9221-ad0207717310", + "name": "PR - Closed", + "url": "http://localhost:8000/webhooks/source/github/events", + "method": "POST", + "sortNum": 30000, + "created": "2023-05-31T09:15:15.833Z", + "modified": "2023-05-31T09:15:29.822Z", + "headers": [ + { + "name": "X-GitHub-Delivery", + "value": "9b4bc300-ee63-11ed-9133-5f71dd83487d" + }, + { + "name": "X-GitHub-Event", + "value": "pull_request" + }, + { + "name": "X-GitHub-Hook-ID", + "value": "400873078" + }, + { + "name": "X-GitHub-Hook-Installation-Target-ID", + "value": "292941" + }, + { + "name": "X-GitHub-Hook-Installation-Target-Type", + "value": "integration" + }, + { + "name": "X-Hub-Signature-256", + "value": "sha256=d5c8d05cc6de14422ab3661d37ec4b98e71f4fdd63d1116f5dedfcb0213ee03d" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ], + "params": [], + "body": { + "type": "json", + "raw": "{\n \"action\": \"closed\",\n \"number\": 1,\n \"pull_request\": {\n \"html_url\": \"https://github.com/{{repository_name}}/pull/1\",\n \"head\": {\n \"ref\":\"{{repository_ref_pr}}\"\n },\n \"base\": {\n \"ref\":\"{{repository_ref}}\"\n }\n },\n \"repository\": {\n \"id\": \"{{repository_id}}\",\n \"full_name\": \"{{repository_name}}\"\n }\n}", + "form": [] + }, + "tests": [] } ] \ No newline at end of file