feat: installation/update github apps

This commit is contained in:
Andras Bacsai 2023-05-09 11:33:50 +02:00
parent db92dc3636
commit 19ad184cd6
20 changed files with 179 additions and 35 deletions

View File

@ -35,7 +35,7 @@ public function changePrivateKey()
$this->private_key->save();
session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get();
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
}

View File

@ -26,7 +26,7 @@ public function submit($data)
$this->application->refresh();
$this->emit('clearAddEnv');
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
}

View File

@ -28,7 +28,7 @@ public function submit($data)
$this->application->refresh();
$this->emit('clearAddStorage');
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Livewire\Project;
use App\Models\Project;
use Livewire\Component;
class Delete extends Component
{
public int $project_id;
public int $resource_count = 0;
public function delete()
{
$this->validate([
'project_id' => 'required|int',
]);
$project = Project::findOrFail($this->project_id);
if ($project->applications->count() > 0) {
return $this->emit('error', 'Project has applications, please delete them first.');
}
$project->delete();
return redirect()->route('dashboard');
}
}

View File

@ -16,6 +16,7 @@ class GithubPrivateRepository extends Component
public $github_apps;
public GithubApp $github_app;
public $parameters;
public $type;
public int $selected_repository_id;
public string $selected_repository_owner;
@ -110,8 +111,16 @@ public function loadDestinations()
public function submit()
{
try {
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
if ($this->type === 'project') {
$project = Project::create([
'name' => generateRandomName(),
'team_id' => session('currentTeam')->id
]);
$environment = $project->load(['environments'])->environments->first();
} else {
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
}
$application = Application::create([
'name' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}:{$this->selected_branch_name}",
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
@ -130,7 +139,7 @@ public function submit()
'environment_name' => $environment->name
]);
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
public function mount()

View File

@ -54,7 +54,7 @@ public function checkServer()
$this->dockerComposeVersion = 'Not installed.';
}
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
public function delete()

View File

@ -31,7 +31,7 @@ public function createGitHubApp()
"custom_port" => 'required|int',
"is_system_wide" => 'required|bool',
]);
GithubApp::create([
$github_app = GithubApp::create([
'name' => $this->name,
'organization' => $this->organization,
'api_url' => $this->api_url,
@ -41,8 +41,9 @@ public function createGitHubApp()
'is_system_wide' => $this->is_system_wide,
'team_id' => session('currentTeam')->id,
]);
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
}

View File

@ -36,7 +36,7 @@ public function submit()
$this->validate();
$this->github_app->save();
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
public function instantSave()
@ -46,7 +46,7 @@ public function instantSave()
$this->github_app->save();
$this->emit('saved', 'GitHub settings updated!');
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
public function mount()
@ -64,7 +64,7 @@ public function delete()
$this->github_app->delete();
redirect()->route('dashboard');
} catch (\Exception $e) {
return generalErrorHandlerLivewire($e, $this);
return generalErrorHandler($e, $this);
}
}
}

View File

@ -31,6 +31,9 @@ public function boot(): void
->prefix('api')
->group(base_path('routes/api.php'));
Route::prefix('webhooks')
->group(base_path('routes/webhooks.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});

View File

@ -14,19 +14,23 @@
use Illuminate\Support\Facades\Storage;
use Spatie\Activitylog\Contracts\Activity;
if (!function_exists('generalErrorHandlerLivewire')) {
function generalErrorHandlerLivewire(\Throwable $e, $that)
if (!function_exists('generalErrorHandler')) {
function generalErrorHandler(\Throwable $e, $that = null)
{
if ($e instanceof QueryException) {
if ($e->errorInfo[0] === '23505') {
$that->emit('error', 'Duplicate entry found.');
} else if (count($e->errorInfo) === 4) {
$that->emit('error', $e->errorInfo[3]);
if ($that) {
if ($e instanceof QueryException) {
if ($e->errorInfo[0] === '23505') {
$that->emit('error', 'Duplicate entry found.');
} else if (count($e->errorInfo) === 4) {
$that->emit('error', $e->errorInfo[3]);
} else {
$that->emit('error', $e->errorInfo[2]);
}
} else {
$that->emit('error', $e->errorInfo[2]);
$that->emit('error', $e->getMessage());
}
} else {
$that->emit('error', $e);
dump($e);
}
}
}

View File

@ -1,19 +1,25 @@
@props([
'isWarning' => null,
'disabled' => null,
'defaultClass' => 'text-white bg-neutral-800 hover:bg-violet-600 h-8',
'defaultWarningClass' => 'text-white bg-red-500 hover:bg-red-600 h-8',
'disabledClass' => 'text-neutral-400 bg-neutral-900 h-8',
'loadingClass' => 'text-black bg-green-500 h-8',
'confirm' => null,
'confirmAction' => null,
])
<button {{ $attributes }} @class([
$defaultClass => !$confirm && !$isWarning,
$defaultWarningClass => $confirm || $isWarning,
]) @if ($attributes->whereStartsWith('wire:click'))
$defaultClass => !$confirm && !$isWarning && !$disabled,
$defaultWarningClass => ($confirm || $isWarning) && !$disabled,
$disabledClass => $disabled,
]) @if ($attributes->whereStartsWith('wire:click') && !$disabled)
wire:target="{{ explode('(', $attributes->whereStartsWith('wire:click')->first())[0] }}"
wire:loading.delay.class="{{ $loadingClass }}" wire:loading.delay.attr="disabled"
wire:loading.delay.class.remove="{{ $defaultClass }} {{ $attributes->whereStartsWith('class')->first() }}"
@endif
@if ($disabled !== null)
disabled title="{{ $disabled }}"
@endif
@isset($confirm)
x-on:click="toggleConfirmModal('{{ $confirm }}', '{{ explode('(', $confirmAction)[0] }}')"
@endisset

View File

@ -0,0 +1,13 @@
<div x-data="{ deleteProject: false }">
<x-naked-modal show="deleteProject" message='Are you sure you would like to delete this project?' />
@if ($resource_count > 0)
<x-inputs.button isWarning disabled="First delete all resources.">
Delete
</x-inputs.button>
@else
<x-inputs.button isWarning x-on:click.prevent="deleteProject = true">
Delete
</x-inputs.button>
@endif
</div>

View File

@ -2,7 +2,9 @@
@if ($github_apps->count() > 0)
<h1>Choose a GitHub App</h1>
@foreach ($github_apps as $ghapp)
<x-inputs.button wire:click="loadRepositories({{ $ghapp->id }})">{{ $ghapp->name }}</x-inputs.button>
<x-inputs.button wire:key="{{ $ghapp->id }}" wire:click="loadRepositories({{ $ghapp->id }})">
{{ $ghapp->name }}
</x-inputs.button>
@endforeach
<div>
@if ($repositories->count() > 0)

View File

@ -21,6 +21,9 @@
<x-inputs.input id="github_app.client_secret" label="Client Secret" type="password" disabled />
<x-inputs.input id="github_app.webhook_secret" label="Webhook Secret" type="password" disabled />
<x-inputs.button type="submit">Save</x-inputs.button>
<x-inputs.button isWarning x-on:click.prevent="deleteSource = true">
Delete
</x-inputs.button>
@else
<div class="py-2">
<x-inputs.button type="submit">Save</x-inputs.button>
@ -30,6 +33,4 @@
</div>
@endif
</form>
</div>

View File

@ -18,7 +18,7 @@
<livewire:project.new.public-git-repository :type="$type" />
</div>
<div x-cloak x-show="activeTab === 'github-private-repo'">
<livewire:project.new.github-private-repository />
<livewire:project.new.github-private-repository :type="$type" />
</div>
</div>
</x-layout>

View File

@ -1,8 +1,14 @@
<x-layout>
<h1>Resources <a href="{{ route('project.resources.new', Route::current()->parameters()) }}">
<div class="flex items-center gap-2">
<h1>Resources</h1>
<a href="{{ route('project.resources.new', Route::current()->parameters()) }}">
<x-inputs.button>New</x-inputs.button>
</a>
</h1>
<livewire:project.delete :project_id="$project->id" :resource_count="$project->applications->count()" />
</div>
@if ($environment->applications->count() === 0)
<p>No resources yet.</p>
@endif
<div>
@foreach ($environment->applications as $application)
<p>

View File

@ -25,13 +25,14 @@ function createGithubApp() {
name,
url: baseUrl,
hook_attributes: {
url: `${webhookBaseUrl}/github/events`
url: `${webhookBaseUrl}/source/github/events`,
active: true,
},
redirect_url: `${webhookBaseUrl}/github`,
redirect_url: `${webhookBaseUrl}/source/github/redirect`,
callback_urls: [`${baseUrl}/login/github/app`],
public: false,
request_oauth_on_install: false,
setup_url: `${webhookBaseUrl}/github/install?source=${uuid}`,
setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`,
setup_on_update: true,
default_permissions: {
contents: 'read',
@ -54,5 +55,14 @@ function createGithubApp() {
form.submit();
}
</script>
@elseif($github_app->app_id && !$github_app->installation_id)
<a href="{{ $installation_url }}">
<x-inputs.button>Install Repositories</x-inputs.button>
</a>
@elseif($github_app->app_id && $github_app->installation_id)
<a href="{{ $installation_url }}">
<x-inputs.button>Update Repositories</x-inputs.button>
</a>
@endif
</x-layout>

View File

@ -1,5 +1,6 @@
<?php
use App\Models\GithubApp;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
@ -17,6 +18,13 @@
Route::get('/health', function () {
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) {
// return $request->user();
// });

View File

@ -85,16 +85,20 @@
Route::middleware(['auth'])->group(function () {
Route::get('/source/new', fn () => view('source.new'))->name('source.new');
Route::get('/source/github/{github_app_uuid}', function (Request $request) {
$github_app = GithubApp::where('uuid', request()->github_app_uuid)->first();
$name = Str::kebab('coolify' . $github_app->name);
$settings = InstanceSettings::first();
$host = $request->schemeAndHttpHost();
if ($settings->fqdn) {
$host = $settings->fqdn;
}
$github_app = GithubApp::where('uuid', request()->github_app_uuid)->first();
$installation_path = $github_app->html_url === 'https://github.com' ? 'apps' : 'github-apps';
$installation_url = "$github_app->html_url/$installation_path/$name/installations/new";
return view('source.github.show', [
'github_app' => $github_app,
'host' => $host,
'name' => Str::kebab('coolify' . $github_app->name)
'name' => $name,
'installation_url' => $installation_url,
]);
})->name('source.github.show');
});

52
routes/webhooks.php Normal file
View File

@ -0,0 +1,52 @@
<?php
use App\Models\PrivateKey;
use App\Models\GithubApp;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
Route::get('/source/github/redirect', function () {
try {
$code = request()->get('code');
$state = request()->get('state');
$github_app = GithubApp::where('uuid', $state)->firstOrFail();
$api_url = data_get($github_app, 'api_url');
$data = Http::withBody(null)->accept('application/vnd.github+json')->post("$api_url/app-manifests/$code/conversions")->throw()->json();
$id = data_get($data, 'id');
$slug = data_get($data, 'slug');
$client_id = data_get($data, 'client_id');
$client_secret = data_get($data, 'client_secret');
$private_key = data_get($data, 'pem');
$webhook_secret = data_get($data, 'webhook_secret');
$private_key = PrivateKey::create([
'name' => $slug,
'private_key' => $private_key,
'team_id' => $github_app->team_id
]);
$github_app->app_id = $id;
$github_app->client_id = $client_id;
$github_app->client_secret = $client_secret;
$github_app->webhook_secret = $webhook_secret;
$github_app->private_key_id = $private_key->id;
$github_app->save();
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) {
return generalErrorHandler($e);
}
});
Route::get('/source/github/install', function () {
try {
$installation_id = request()->get('installation_id');
$source = request()->get('source');
$setup_action = request()->get('setup_action');
$github_app = GithubApp::where('uuid', $source)->firstOrFail();
if ($setup_action === 'install') {
$github_app->installation_id = $installation_id;
$github_app->save();
}
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) {
return generalErrorHandler($e);
}
});