github webhooks

This commit is contained in:
Andras Bacsai 2023-05-09 14:42:10 +02:00
parent 19ad184cd6
commit 76bf601e1b
18 changed files with 217 additions and 20 deletions

View File

@ -72,7 +72,7 @@ public function loadRepositories($github_app_id)
$this->repositories = collect(); $this->repositories = collect();
$this->page = 1; $this->page = 1;
$this->github_app = GithubApp::where('id', $github_app_id)->first(); $this->github_app = GithubApp::where('id', $github_app_id)->first();
$this->token = generate_github_token($this->github_app); $this->token = generate_github_installation_token($this->github_app);
$this->loadRepositoryByPage(); $this->loadRepositoryByPage();
if ($this->repositories->count() < $this->total_repositories_count) { if ($this->repositories->count() < $this->total_repositories_count) {
while ($this->repositories->count() < $this->total_repositories_count) { while ($this->repositories->count() < $this->total_repositories_count) {
@ -123,6 +123,7 @@ public function submit()
} }
$application = Application::create([ $application = Application::create([
'name' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}:{$this->selected_branch_name}", 'name' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}:{$this->selected_branch_name}",
'project_id' => $this->selected_repository_id,
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}", 'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
'git_branch' => $this->selected_branch_name, 'git_branch' => $this->selected_branch_name,
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',

View File

@ -469,7 +469,7 @@ private function gitImport()
$this->execute_in_builder($git_clone_command) $this->execute_in_builder($git_clone_command)
]; ];
} else { } else {
$github_access_token = generate_github_token($this->source); $github_access_token = generate_github_installation_token($this->source);
return [ return [
$this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}") $this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}")
]; ];

View File

@ -24,6 +24,7 @@ protected static function booted()
protected $fillable = [ protected $fillable = [
'name', 'name',
'project_id',
'description', 'description',
'git_repository', 'git_repository',
'git_branch', 'git_branch',

View File

@ -12,7 +12,10 @@ protected static function boot()
parent::boot(); parent::boot();
static::creating(function (Model $model) { static::creating(function (Model $model) {
// Generate a UUID if one isn't set
if (!$model->uuid) {
$model->uuid = (string) new Cuid2(7); $model->uuid = (string) new Cuid2(7);
}
}); });
} }
} }

View File

@ -4,7 +4,7 @@
class GithubApp extends BaseModel class GithubApp extends BaseModel
{ {
protected $fillable = ['name', 'organization', 'api_url', 'html_url', 'custom_user', 'custom_port', 'team_id']; protected $fillable = ['name', 'uuid', 'organization', 'api_url', 'html_url', 'custom_user', 'custom_port', 'team_id'];
protected $casts = [ protected $casts = [
'is_public' => 'boolean', 'is_public' => 'boolean',
]; ];

View File

@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class GithubEventsApplications extends Model
{
protected $fillable = [
'delivery_guid',
'application_id',
];
}

View File

@ -189,8 +189,8 @@ function generateRandomName()
use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token\Builder; use Lcobucci\JWT\Token\Builder;
if (!function_exists('generate_github_token')) { if (!function_exists('generate_github_installation_token')) {
function generate_github_token(GithubApp $source) function generate_github_installation_token(GithubApp $source)
{ {
$signingKey = InMemory::plainText($source->privateKey->private_key); $signingKey = InMemory::plainText($source->privateKey->private_key);
$algorithm = new Sha256(); $algorithm = new Sha256();
@ -213,6 +213,23 @@ function generate_github_token(GithubApp $source)
return $token->json()['token']; return $token->json()['token'];
} }
} }
if (!function_exists('generate_github_jwt_token')) {
function generate_github_jwt_token(GithubApp $source)
{
$signingKey = InMemory::plainText($source->privateKey->private_key);
$algorithm = new Sha256();
$tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default()));
$now = new DateTimeImmutable();
$now = $now->setTime($now->format('H'), $now->format('i'));
$issuedToken = $tokenBuilder
->issuedBy($source->app_id)
->issuedAt($now->modify('-1 minute'))
->expiresAt($now->modify('+10 minutes'))
->getToken($algorithm, $signingKey)
->toString();
return $issuedToken;
}
}
if (!function_exists('getParameters')) { if (!function_exists('getParameters')) {
function getParameters() function getParameters()
{ {

View File

@ -13,6 +13,7 @@ public function up(): void
{ {
Schema::create('applications', function (Blueprint $table) { Schema::create('applications', function (Blueprint $table) {
$table->id(); $table->id();
$table->integer('project_id')->nullable();
$table->string('uuid')->unique(); $table->string('uuid')->unique();
$table->string('name'); $table->string('name');

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('github_events_applications', function (Blueprint $table) {
$table->id();
$table->string('delivery_guid');
$table->foreignId('application_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('github_events_applications');
}
};

View File

@ -25,6 +25,7 @@ public function run(): void
Application::create([ Application::create([
'name' => 'coollabsio/coolify-examples:nodejs-fastify', 'name' => 'coollabsio/coolify-examples:nodejs-fastify',
'project_id' => 603035348,
'git_repository' => 'coollabsio/coolify-examples', 'git_repository' => 'coollabsio/coolify-examples',
'git_branch' => 'nodejs-fastify', 'git_branch' => 'nodejs-fastify',
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',

View File

@ -26,7 +26,8 @@ public function run(): void
'team_id' => $root_team->id, 'team_id' => $root_team->id,
]); ]);
GithubApp::create([ GithubApp::create([
'name' => 'coolify-laravel-development-private-github', 'name' => 'coolify-laravel-development-public',
'uuid' => '69420',
'api_url' => 'https://api.github.com', 'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com', 'html_url' => 'https://github.com',
'is_public' => false, 'is_public' => false,

View File

@ -1,5 +1,5 @@
<x-layout> <x-layout>
<div>v{{ config('coolify.version') }}</div> <div>v{{ config('version') }}</div>
<a href="/login">Login</a> <a href="/login">Login</a>
@if ($is_registration_enabled) @if ($is_registration_enabled)
<a href="/register">Register</a> <a href="/register">Register</a>

View File

@ -18,13 +18,6 @@
Route::get('/health', function () { Route::get('/health', function () {
return 'OK'; return 'OK';
}); });
Route::get('/webhooks/source/github/redirect', function () {
$code = request()->get('code');
$state = request()->get('state');
$github_app = GithubApp::where('uuid', $state)->firstOrFail();
return 'OK';
});
// Route::middleware('auth:sanctum')->get('/user', function (Request $request) { // Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
// return $request->user(); // return $request->user();
// }); // });

View File

@ -38,7 +38,6 @@
})->flatten(); })->flatten();
$private_keys = PrivateKey::where('team_id', $id)->get(); $private_keys = PrivateKey::where('team_id', $id)->get();
$github_apps = GithubApp::private(); $github_apps = GithubApp::private();
return view('dashboard', [ return view('dashboard', [
'servers' => $servers->sortBy('name'), 'servers' => $servers->sortBy('name'),
'projects' => $projects->sortBy('name'), 'projects' => $projects->sortBy('name'),
@ -86,7 +85,7 @@
Route::get('/source/new', fn () => view('source.new'))->name('source.new'); Route::get('/source/new', fn () => view('source.new'))->name('source.new');
Route::get('/source/github/{github_app_uuid}', function (Request $request) { Route::get('/source/github/{github_app_uuid}', function (Request $request) {
$github_app = GithubApp::where('uuid', request()->github_app_uuid)->first(); $github_app = GithubApp::where('uuid', request()->github_app_uuid)->first();
$name = Str::kebab('coolify' . $github_app->name); $name = Str::of(Str::kebab($github_app->name))->start('coolify-');
$settings = InstanceSettings::first(); $settings = InstanceSettings::first();
$host = $request->schemeAndHttpHost(); $host = $request->schemeAndHttpHost();
if ($settings->fqdn) { if ($settings->fqdn) {

View File

@ -1,9 +1,14 @@
<?php <?php
use App\Jobs\DeployApplicationJob;
use App\Models\Application;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use App\Models\GithubApp; use App\Models\GithubApp;
use App\Models\GithubEventsApplications;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
Route::get('/source/github/redirect', function () { Route::get('/source/github/redirect', function () {
try { try {
@ -50,3 +55,58 @@
return generalErrorHandler($e); return generalErrorHandler($e);
} }
}); });
Route::post('/source/github/events', function () {
try {
$x_github_delivery = request()->header('X-GitHub-Delivery');
$x_github_event = Str::lower(request()->header('X-GitHub-Event'));
$x_github_hook_installation_target_id = request()->header('X-GitHub-Hook-Installation-Target-Id');
$x_hub_signature_256 = request()->header('X-Hub-Signature-256');
$payload = request()->collect();
if ($x_github_event === 'ping') {
// Just pong
return response('pong');
}
if ($x_github_event === 'installation') {
// Installation handled by setup redirect url. Repositories queried on-demand.
return response('cool');
}
$github_app = GithubApp::where('app_id', $x_github_hook_installation_target_id)->firstOrFail();
// TODO: Verify signature
// $webhook_secret = data_get($github_app, 'webhook_secret');
// $key = hash('sha256', $webhook_secret, true);
// $hmac = hash_hmac('sha256', request()->getContent(), $key);
// if (!hash_equals($hmac, $x_hub_signature_256)) {
// return response('not cool');
// }
if ($x_github_event === 'push') {
$id = data_get($payload, 'repository.id');
$branch = data_get($payload, 'ref');
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
}
if ($x_github_event === 'pull_request') {
$id = data_get($payload, 'pull_request.base.repo.id');
$branch = data_get($payload, 'pull_request.base.ref');
}
if (!$id || !$branch) {
return response('not cool');
}
$applications = Application::where('project_id', $id)->where('git_branch', $branch)->get();
foreach ($applications as $application) {
GithubEventsApplications::create([
"delivery_guid" => $x_github_delivery,
"application_id" => $application->id
]);
$deployment_uuid = new Cuid2(7);
dispatch(new DeployApplicationJob(
deployment_uuid: $deployment_uuid,
application_uuid: $application->uuid,
force_rebuild: false,
));
}
} catch (\Exception $e) {
return generalErrorHandler($e);
}
});

View File

@ -1 +1,17 @@
[] [
{
"_id": "e6458286-eef1-401c-be84-860b111d66f0",
"colName": "Webhooks",
"created": "2023-05-09T11:45:36.504Z",
"sortNum": 10000,
"folders": [
{
"_id": "b8cfd093-5467-44a2-9221-ad0207717310",
"name": "GitHub",
"containerId": "",
"created": "2023-05-09T11:45:40.630Z",
"sortNum": 10000
}
]
}
]

View File

@ -1 +1,13 @@
[] [
{
"_id": "bd3f3c2e-b161-40c9-9077-efcf40dfe1f4",
"name": "(Local Env)",
"default": false,
"global": true,
"local": true,
"sortNum": -1,
"created": "2023-05-09T11:48:22.144Z",
"modified": "2023-05-09T11:48:22.144Z",
"data": []
}
]

View File

@ -1 +1,50 @@
[] [
{
"_id": "b3d379ab-e5e4-4ba4-991d-b6c8c6bbcb98",
"colId": "e6458286-eef1-401c-be84-860b111d66f0",
"containerId": "b8cfd093-5467-44a2-9221-ad0207717310",
"name": "Push",
"url": "http://localhost:8000/webhooks/source/github/events",
"method": "POST",
"sortNum": 10000,
"created": "2023-05-09T11:45:50.227Z",
"modified": "2023-05-09T12:22:27.192Z",
"headers": [
{
"name": "X-GitHub-Delivery",
"value": "9b4bc300-ee63-11ed-9133-5f71dd83487d"
},
{
"name": "X-GitHub-Event",
"value": "push"
},
{
"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 \"ref\": \"{{repository_ref}}\",\n \"repository\": {\n \"id\": \"{{repository_id}}\",\n \"full_name\": \"{{repository_name}}\"\n }\n}",
"form": []
},
"tests": []
}
]