pull request webhooks

This commit is contained in:
Andras Bacsai 2023-05-31 11:24:02 +02:00
parent 232d2ccf79
commit c953482ba9
16 changed files with 272 additions and 64 deletions

View File

@ -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()

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -15,5 +15,6 @@ class ApplicationDeploymentQueue extends Model
'force_rebuild',
'commit',
'status',
'is_webhook',
];
}

View File

@ -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
{

View File

@ -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');

View File

@ -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');

View File

@ -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();
});
}

View File

@ -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();
}
}

View File

@ -20,7 +20,20 @@
class="hover:no-underline">
<div class="flex flex-col justify-start">
@if (data_get($deployment, 'pull_request_id'))
<div>Pull Request #{{ data_get($deployment, 'pull_request_id') }}</div>
<div>
Pull Request #{{ data_get($deployment, 'pull_request_id') }}
@if (data_get($deployment, 'is_webhook'))
(Webhook)
@endif
</div>
@elseif (data_get($deployment, 'is_webhook'))
<div>Webhook (commit
@if (data_get($deployment, 'commit'))
{{ data_get($deployment, 'commit') }})
@else
HEAD)
@endif
</div>
@else
<div>Commit:
@if (data_get($deployment, 'commit'))

View File

@ -69,17 +69,19 @@
</div>
<h3>Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="More logs will be visible during a deployment." instantSave id="is_debug"
<x-forms.checkbox helper="More logs will be visible during a deployment." instantSave id="is_debug_enabled"
label="Debug" />
<x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..."
instantSave id="is_force_https" label="Force Https" />
instantSave id="is_force_https_enabled" label="Force Https" />
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
id="is_auto_deploy" label="Auto Deploy" />
{{-- <x-forms.checkbox helper="Preview deployments" instantSave id="is_previews" label="Previews?" /> --}}
<x-forms.checkbox instantSave id="is_git_submodules_allowed" label="Git Submodules"
id="is_auto_deploy_enabled" label="Auto Deploy" />
<x-forms.checkbox
helper="Automatically deploy Preview Deployments for all opened PR's. Closed PRs deletes Preview Deployments."
instantSave id="is_preview_deployments_enabled" label="Auto Previews Deployments" />
<x-forms.checkbox instantSave id="is_git_submodules_enabled" label="Git Submodules"
helper="Allow Git Submodules during build process." />
<x-forms.checkbox instantSave id="is_git_lfs_allowed" label="Git LFS"
<x-forms.checkbox instantSave id="is_git_lfs_enabled" label="Git LFS"
helper="Allow Git LFS during build process." />
{{-- <x-forms.checkbox disabled instantSave id="is_dual_cert" label="Dual Certs?" />
<x-forms.checkbox disabled instantSave id="is_custom_ssl" label="Is Custom SSL?" />

View File

@ -1,9 +1,8 @@
<div>
<livewire:project.application.preview.form :application="$application" />
<h3>Pull Requests on Git</h3>
<div>
<x-forms.button wire:click="load_prs">Load Pull Requests
<x-forms.button wire:click="load_prs">Load Opened Pull Requests
</x-forms.button>
@isset($rate_limit_remaining)
<div class="pt-1 text-sm">Requests remaning till rate limited by Git: {{ $rate_limit_remaining }}</div>

View File

@ -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) {

View File

@ -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"
}
]
}
]

View File

@ -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": []
}
]