From 776b1cb68dade983c2b1d5c8f47a8626553ae935 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 13 Nov 2023 21:16:48 +0100 Subject: [PATCH 01/10] Add unauthenticated method to handle authentication exceptions --- app/Exceptions/Handler.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 24f4eff9e..1ac36da85 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,6 +4,7 @@ use App\Models\InstanceSettings; use App\Models\User; +use Illuminate\Auth\AuthenticationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Sentry\Laravel\Integration; use Sentry\State\Scope; @@ -40,6 +41,13 @@ class Handler extends ExceptionHandler ]; private InstanceSettings $settings; + protected function unauthenticated($request, AuthenticationException $exception) + { + if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) { + return response()->json(['message' => $exception->getMessage()], 401); + } + return redirect()->guest($exception->redirectTo() ?? route('login')); + } /** * Register the exception handling callbacks for the application. */ From c4d9deabef9698276e6b21510776068ca926e69f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 13 Nov 2023 21:17:17 +0100 Subject: [PATCH 02/10] Add debugging statement to report exceptions in development environment --- app/Exceptions/Handler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 1ac36da85..b4f63661c 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -55,6 +55,7 @@ public function register(): void { $this->reportable(function (Throwable $e) { if (isDev()) { + ray($e); return; } $this->settings = InstanceSettings::get(); From 423cf62d92afa099e89887c662de299be2629f29 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 08:52:17 +0100 Subject: [PATCH 03/10] Add support for dynamic docker-compose file name in ApplicationDeploymentJob.php --- app/Jobs/ApplicationDeploymentJob.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index fbf6ef005..ca01b8d50 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -218,12 +218,16 @@ public function handle(): void } finally { if (isset($this->docker_compose_base64)) { $readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at); + $composeFileName = "$this->configuration_dir/docker-compose.yml"; + if ($this->pull_request_id !== 0) { + $composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml"; + } $this->execute_remote_command( [ "mkdir -p $this->configuration_dir" ], [ - "echo '{$this->docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml", + "echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName", ], [ "echo '{$readme}' > $this->configuration_dir/README.md", From 84b74f0b57254be8874878f06e11e00960e7e6e6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 10:59:02 +0100 Subject: [PATCH 04/10] Update version numbers to 4.0.0-beta.132 --- config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/sentry.php b/config/sentry.php index 980c71a6c..01c00e557 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.131', + 'release' => '4.0.0-beta.132', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 4a865bbd4..57d619e4d 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Tue, 14 Nov 2023 11:04:45 +0100 Subject: [PATCH 05/10] Add error handling for missing email settings in EmailChannel.php --- app/Notifications/Channels/EmailChannel.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index 82fc9a65c..99afcf5a7 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -29,6 +29,10 @@ public function send(SendsEmail $notifiable, Notification $notification): void ->html((string)$mailMessage->render()) ); } catch (Exception $e) { + $error = $e->getMessage(); + if ($error === 'No email settings found.') { + throw $e; + } ray($e->getMessage()); $message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:"; if (isset($recepients)) { From 8db66952e84b473658d95785fbcb34018f11913f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 13:26:14 +0100 Subject: [PATCH 06/10] Add manual Git webhooks and migration files --- app/Http/Livewire/Project/Shared/Webhooks.php | 18 ++ app/Jobs/ApplicationDeploymentJob.php | 25 +- bootstrap/helpers/applications.php | 3 +- bootstrap/helpers/github.php | 5 +- bootstrap/helpers/shared.php | 8 + ...11_14_103450_add_manual_webhook_secret.php | 30 +++ .../2023_11_14_121416_add_git_type.php | 34 +++ .../project/shared/webhooks.blade.php | 25 +- routes/webhooks.php | 245 +++++++++++++++++- 9 files changed, 383 insertions(+), 10 deletions(-) create mode 100644 database/migrations/2023_11_14_103450_add_manual_webhook_secret.php create mode 100644 database/migrations/2023_11_14_121416_add_git_type.php diff --git a/app/Http/Livewire/Project/Shared/Webhooks.php b/app/Http/Livewire/Project/Shared/Webhooks.php index a943347b1..d425b51df 100644 --- a/app/Http/Livewire/Project/Shared/Webhooks.php +++ b/app/Http/Livewire/Project/Shared/Webhooks.php @@ -8,9 +8,27 @@ class Webhooks extends Component { public $resource; public ?string $deploywebhook = null; + public ?string $githubManualWebhook = null; + public ?string $gitlabManualWebhook = null; + protected $rules = [ + 'resource.manual_webhook_secret_github' => 'nullable|string', + 'resource.manual_webhook_secret_gitlab' => 'nullable|string', + ]; + public function saveSecret() + { + try { + $this->validate(); + $this->resource->save(); + $this->emit('success','Secret Saved.'); + } catch (\Exception $e) { + return handleError($e, $this); + } + } public function mount() { $this->deploywebhook = generateDeployWebhook($this->resource); + $this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github'); + $this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab'); } public function render() { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ca01b8d50..86566c534 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -53,6 +53,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private StandaloneDocker|SwarmDocker $destination; private Server $server; private ?ApplicationPreview $preview = null; + private ?string $git_type = null; private string $container_name; private ?string $currently_running_container_name = null; @@ -99,6 +100,8 @@ public function __construct(int $application_deployment_queue_id) $this->force_rebuild = $this->application_deployment_queue->force_rebuild; $this->restart_only = $this->application_deployment_queue->restart_only; + $this->git_type = data_get($this->application_deployment_queue, 'git_type'); + $source = data_get($this->application, 'source'); if ($source) { $this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first(); @@ -656,7 +659,7 @@ private function generate_git_import_commands() } if ($this->pull_request_id !== 0) { $this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name"; - $commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name")); + $commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin $this->branch && git checkout $pr_branch_name")); } return $commands->implode(' && '); } @@ -668,14 +671,28 @@ private function generate_git_import_commands() throw new Exception('Private key not found. Please add a private key to the application and try again.'); } $private_key = base64_encode($private_key); - $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}"; - $git_clone_command = $this->set_git_import_settings($git_clone_command); + $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}"; + $git_clone_command = $this->set_git_import_settings($git_clone_command_base); $commands = collect([ executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"), executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"), - executeInDocker($this->deployment_uuid, $git_clone_command) ]); + if ($this->pull_request_id !== 0) { + ray($this->git_type); + if ($this->git_type === 'gitlab') { + $this->branch = "merge-requests/{$this->pull_request_id}/head:$pr_branch_name"; + $commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'")); + $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name"; + } + if ($this->git_type === 'github') { + $this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name"; + $commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'")); + $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name"; + } + } + + $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); return $commands->implode(' && '); } if ($this->application->deploymentType() === 'other') { diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index d78d19992..fce225dc5 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -4,7 +4,7 @@ use App\Models\Application; 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, bool $is_webhook = false, bool $restart_only = 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, bool $restart_only = false, ?string $git_type = null) { $deployment = ApplicationDeploymentQueue::create([ 'application_id' => $application_id, @@ -14,6 +14,7 @@ function queue_application_deployment(int $application_id, string $deployment_uu 'is_webhook' => $is_webhook, 'restart_only' => $restart_only, 'commit' => $commit, + 'git_type' => $git_type ]); $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at'); $running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at'); diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 16ac3671a..60ff6d9c9 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -50,8 +50,11 @@ function generate_github_jwt_token(GithubApp $source) return $issuedToken; } -function githubApi(GithubApp|GitlabApp $source, string $endpoint, string $method = 'get', array|null $data = null, bool $throwError = true) +function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', array|null $data = null, bool $throwError = true) { + if (is_null($source)) { + throw new \Exception('Not implemented yet.'); + } if ($source->getMorphClass() == 'App\Models\GithubApp') { if ($source->is_public) { $response = Http::github($source->api_url)->$method($endpoint); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index d6766a8d0..1abb90ce5 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -510,6 +510,14 @@ function generateDeployWebhook($resource) $url = $api . $endpoint . "?uuid=$uuid&force=false"; return $url; } +function generateGitManualWebhook($resource, $type) { + if ($resource->getMorphClass() === 'App\Models\Application') { + $baseUrl = base_url(); + $api = Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual"; + return $api; + } + return null; +} function removeAnsiColors($text) { return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); diff --git a/database/migrations/2023_11_14_103450_add_manual_webhook_secret.php b/database/migrations/2023_11_14_103450_add_manual_webhook_secret.php new file mode 100644 index 000000000..3d1f37d38 --- /dev/null +++ b/database/migrations/2023_11_14_103450_add_manual_webhook_secret.php @@ -0,0 +1,30 @@ +string('manual_webhook_secret_github')->nullable(); + $table->string('manual_webhook_secret_gitlab')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('manual_webhook_secret_github'); + $table->dropColumn('manual_webhook_secret_gitlab'); + }); + } +}; diff --git a/database/migrations/2023_11_14_121416_add_git_type.php b/database/migrations/2023_11_14_121416_add_git_type.php new file mode 100644 index 000000000..1e2a307fe --- /dev/null +++ b/database/migrations/2023_11_14_121416_add_git_type.php @@ -0,0 +1,34 @@ +string('git_type')->nullable(); + }); + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->string('git_type')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_previews', function (Blueprint $table) { + $table->dropColumn('git_type'); + }); + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('git_type'); + }); + } +}; diff --git a/resources/views/livewire/project/shared/webhooks.blade.php b/resources/views/livewire/project/shared/webhooks.blade.php index ba0461bdb..b47aba097 100644 --- a/resources/views/livewire/project/shared/webhooks.blade.php +++ b/resources/views/livewire/project/shared/webhooks.blade.php @@ -1,10 +1,31 @@ -
+

Webhooks

- + +
+
+

Manual Git Webhooks

+
+
+ + +
+
+ + +
+ Save +
diff --git a/routes/webhooks.php b/routes/webhooks.php index bb2c55a3d..8ca16dd8c 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -63,6 +63,245 @@ return handleError($e); } }); +Route::post('/source/gitlab/events/manual', function () { + try { + $payload = request()->collect(); + $headers = request()->headers->all(); + ray($payload, $headers); + $x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); + $x_gitlab_event = data_get($payload, 'object_kind'); + if ($x_gitlab_event === 'push') { + $branch = data_get($payload, 'ref'); + $full_name = data_get($payload, 'project.path_with_namespace'); + if (Str::isMatch('/refs\/heads\/*/', $branch)) { + $branch = Str::after($branch, 'refs/heads/'); + } + if (!$branch) { + return response('Nothing to do. No branch found in the request.'); + } + ray('Manual Webhook GitLab Push Event with branch: ' . $branch); + } + if ($x_gitlab_event === 'merge_request') { + $action = data_get($payload, 'object_attributes.action'); + ray($action); + $branch = data_get($payload, 'object_attributes.source_branch'); + $base_branch = data_get($payload, 'object_attributes.target_branch'); + $full_name = data_get($payload, 'project.path_with_namespace'); + $pull_request_id = data_get($payload, 'object_attributes.iid'); + $pull_request_html_url = data_get($payload, 'object_attributes.url'); + if (!$branch) { + return response('Nothing to do. No branch found in the request.'); + } + ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); + } + $applications = Application::whereNotNull('private_key_id')->where('git_repository', 'like', "%$full_name%"); + if ($x_gitlab_event === 'push') { + $applications = $applications->where('git_branch', $branch)->get(); + if ($applications->isEmpty()) { + return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name."); + } + } + if ($x_gitlab_event === 'merge_request') { + $applications = $applications->where('git_branch', $base_branch)->get(); + if ($applications->isEmpty()) { + return response("Nothing to do. No applications found with branch '$base_branch'."); + } + } + foreach ($applications as $application) { + $webhook_secret = data_get($application, 'manual_webhook_secret_gitlab'); + if ($webhook_secret !== $x_gitlab_token) { + ray('Invalid signature'); + continue; + } + $isFunctional = $application->destination->server->isFunctional(); + if (!$isFunctional) { + ray('Server is not functional: ' . $application->destination->server->name); + continue; + } + if ($x_gitlab_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_gitlab_event === 'merge_request') { + if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') { + 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([ + 'git_type' => 'gitlab', + '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, + git_type: 'gitlab' + ); + 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 = generateApplicationContainerName($application, $pull_request_id); + // ray('Stopping container: ' . $container_name); + instant_remote_process(["docker rm -f $container_name"], $application->destination->server); + return response('Preview Deployment closed.'); + } + return response('Nothing to do. No Preview Deployment found'); + } + } + } + } catch (Exception $e) { + ray($e->getMessage()); + return handleError($e); + } +}); +Route::post('/source/github/events/manual', function () { + try { + $x_github_event = Str::lower(request()->header('X-GitHub-Event')); + $x_hub_signature_256 = Str::after(request()->header('X-Hub-Signature-256'), 'sha256='); + $content_type = request()->header('Content-Type'); + $payload = request()->collect(); + if ($x_github_event === 'ping') { + // Just pong + return response('pong'); + } + + if ($content_type !== 'application/json') { + $payload = json_decode(data_get($payload, 'payload'), true); + } + ray($payload); + if ($x_github_event === 'push') { + $branch = data_get($payload, 'ref'); + $full_name = data_get($payload, 'repository.full_name'); + if (Str::isMatch('/refs\/heads\/*/', $branch)) { + $branch = Str::after($branch, 'refs/heads/'); + } + ray('Manual Webhook GitHub Push Event with branch: ' . $branch); + } + if ($x_github_event === 'pull_request') { + $action = data_get($payload, 'action'); + $full_name = data_get($payload, 'repository.full_name'); + $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 with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); + } + if (!$branch) { + return response('Nothing to do. No branch found in the request.'); + } + $applications = Application::whereNotNull('private_key_id')->where('git_repository', 'like', "%$full_name%"); + + if ($x_github_event === 'push') { + $applications = $applications->where('git_branch', $branch)->get(); + if ($applications->isEmpty()) { + return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name."); + } + } + 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 with branch '$base_branch'."); + } + } + foreach ($applications as $application) { + ray($application); + $webhook_secret = data_get($application, 'manual_webhook_secret_github'); + ray($webhook_secret); + $hmac = hash_hmac('sha256', request()->getContent(), $webhook_secret); + ray($hmac, $x_hub_signature_256); + if (!hash_equals($x_hub_signature_256, $hmac)) { + ray('Invalid signature'); + continue; + } + $isFunctional = $application->destination->server->isFunctional(); + if (!$isFunctional) { + ray('Server is not functional: ' . $application->destination->server->name); + continue; + } + 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' || $action === 'synchronize' || $action === 'reopened') { + 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([ + 'git_type' => 'github', + '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, + git_type: 'github' + ); + 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 = generateApplicationContainerName($application, $pull_request_id); + // ray('Stopping container: ' . $container_name); + instant_remote_process(["docker rm -f $container_name"], $application->destination->server); + return response('Preview Deployment closed.'); + } + return response('Nothing to do. No Preview Deployment found'); + } + } + } + } catch (Exception $e) { + ray($e->getMessage()); + return handleError($e); + } +}); Route::post('/source/github/events', function () { try { $id = null; @@ -150,6 +389,7 @@ $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); if (!$found) { ApplicationPreview::create([ + 'git_type' => 'github', 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, 'pull_request_html_url' => $pull_request_html_url, @@ -160,7 +400,8 @@ pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, force_rebuild: false, - is_webhook: true + is_webhook: true, + git_type: 'github' ); 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.'); @@ -169,7 +410,7 @@ return response('Nothing to do. Preview Deployments disabled.'); } } - if ($action === 'closed') { + if ($action === 'closed' || $action === 'close') { $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); if ($found) { $found->delete(); From 36d65ad5a85c12fbfa6491b6639f7c84f5840668 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 14:07:33 +0100 Subject: [PATCH 07/10] Fix Dockerfile location in deployment job --- app/Jobs/ApplicationDeploymentJob.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 86566c534..f9f6ec90c 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -356,7 +356,7 @@ private function deploy_simple_dockerfile() $this->prepare_builder_image(); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile") + executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir$this->dockerfile_location") ], ); $this->generate_image_names(); @@ -1013,7 +1013,7 @@ private function build_image() }"); } else { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true + executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true ]); $dockerfile = base64_encode("FROM {$this->application->static_image} @@ -1125,7 +1125,7 @@ private function generate_build_env_variables() private function add_build_env_variables_to_dockerfile() { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile' + executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile' ]); $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); @@ -1134,7 +1134,7 @@ private function add_build_env_variables_to_dockerfile() } $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/{$this->dockerfile_location}"), + executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"), "hidden" => true ]); } From 3a3c9448a4e1e731c966c4c75c61681588ce3247 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 14:07:42 +0100 Subject: [PATCH 08/10] Add gitWebhook method to Application model and fix Dockerfile input display --- app/Models/Application.php | 12 ++++++++++++ .../livewire/project/application/general.blade.php | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 0e72f94ce..27f34df34 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -85,6 +85,18 @@ public function gitBranchLocation(): Attribute ); } + public function gitWebhook(): Attribute + { + return Attribute::make( + get: function () { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { + return "{$this->source->html_url}/{$this->git_repository}/settings/hooks"; + } + return $this->git_repository; + } + ); + } + public function gitCommits(): Attribute { return Attribute::make( diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 173aa52e8..2753b4bf7 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -69,10 +69,12 @@
- @if ($application->build_pack === 'dockerfile') + @if ($application->build_pack === 'dockerfile' && !$application->dockerfile) + @endif + @if ($application->build_pack === 'dockerfile') @endif From 0590ed7b2e1b4e05cef7579249eb8628cb9e3c44 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 14:07:48 +0100 Subject: [PATCH 09/10] Update webhooks configuration and application search. --- .../views/livewire/project/shared/webhooks.blade.php | 8 +++++++- routes/webhooks.php | 7 +++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/resources/views/livewire/project/shared/webhooks.blade.php b/resources/views/livewire/project/shared/webhooks.blade.php index b47aba097..243b5dea3 100644 --- a/resources/views/livewire/project/shared/webhooks.blade.php +++ b/resources/views/livewire/project/shared/webhooks.blade.php @@ -12,13 +12,19 @@

Manual Git Webhooks

-
+
+
+ + Webhook Configuration on GitHub + + +
where('git_repository', 'like', "%$full_name%"); + $applications = Application::where('git_repository', 'like', "%$full_name%"); if ($x_gitlab_event === 'push') { $applications = $applications->where('git_branch', $branch)->get(); if ($applications->isEmpty()) { @@ -192,7 +192,6 @@ if ($content_type !== 'application/json') { $payload = json_decode(data_get($payload, 'payload'), true); } - ray($payload); if ($x_github_event === 'push') { $branch = data_get($payload, 'ref'); $full_name = data_get($payload, 'repository.full_name'); @@ -213,8 +212,7 @@ if (!$branch) { return response('Nothing to do. No branch found in the request.'); } - $applications = Application::whereNotNull('private_key_id')->where('git_repository', 'like', "%$full_name%"); - + $applications = Application::where('git_repository', 'like', "%$full_name%"); if ($x_github_event === 'push') { $applications = $applications->where('git_branch', $branch)->get(); if ($applications->isEmpty()) { @@ -227,6 +225,7 @@ return response("Nothing to do. No applications found with branch '$base_branch'."); } } + ray($applications); foreach ($applications as $application) { ray($application); $webhook_secret = data_get($application, 'manual_webhook_secret_github'); From e4b21959325f6fd607a07e699ad7e3d9b5e4cce6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Nov 2023 14:14:21 +0100 Subject: [PATCH 10/10] Fix manual Git webhook generation --- bootstrap/helpers/shared.php | 3 ++ .../project/shared/webhooks.blade.php | 46 ++++++++++--------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 1abb90ce5..a11e857ce 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -511,6 +511,9 @@ function generateDeployWebhook($resource) return $url; } function generateGitManualWebhook($resource, $type) { + if ($resource->source_id !== 0) { + return null; + } if ($resource->getMorphClass() === 'App\Models\Application') { $baseUrl = base_url(); $api = Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual"; diff --git a/resources/views/livewire/project/shared/webhooks.blade.php b/resources/views/livewire/project/shared/webhooks.blade.php index 243b5dea3..c77ae3315 100644 --- a/resources/views/livewire/project/shared/webhooks.blade.php +++ b/resources/views/livewire/project/shared/webhooks.blade.php @@ -11,27 +11,31 @@

Manual Git Webhooks

- -
- - + @if ($githubManualWebhook && $gitlabManualWebhook) + +
+ + -
- - Webhook Configuration on GitHub - - - -
- - -
- Save - +
+ + Webhook Configuration on GitHub + + + +
+ + +
+ Save + + @else + You are using an official Git App. You do not need manual webhooks. + @endif