From a37f7486395c6fb1002bd9e1ad7d0eb36c040335 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 8 May 2023 11:51:03 +0200 Subject: [PATCH] feat: add private gh repos --- .../Livewire/Project/Application/Deploy.php | 2 +- .../Application/EnvironmentVariable/Add.php | 2 +- .../Application/EnvironmentVariable/Show.php | 2 +- .../Project/Application/Storages/Add.php | 2 +- .../Project/New/GithubPrivateRepository.php | 142 ++++++++++++++++++ .../Project/New/PublicGitRepository.php | 2 +- app/Http/Livewire/Server/PrivateKey.php | 2 +- app/Jobs/DeployApplicationJob.php | 33 +--- app/Models/GithubApp.php | 8 + bootstrap/helpers.php | 39 +++++ database/seeders/ApplicationSeeder.php | 2 +- database/seeders/GithubAppSeeder.php | 2 +- .../seeders/LocalPersistentVolumeSeeder.php | 3 +- .../project/application/general.blade.php | 18 ++- .../new/github-private-repository.blade.php | 79 ++++++++++ resources/views/project/new.blade.php | 2 +- 16 files changed, 292 insertions(+), 48 deletions(-) create mode 100644 app/Http/Livewire/Project/New/GithubPrivateRepository.php create mode 100644 resources/views/livewire/project/new/github-private-repository.blade.php diff --git a/app/Http/Livewire/Project/Application/Deploy.php b/app/Http/Livewire/Project/Application/Deploy.php index 23d6bdbee..fc64dc3ce 100644 --- a/app/Http/Livewire/Project/Application/Deploy.php +++ b/app/Http/Livewire/Project/Application/Deploy.php @@ -23,7 +23,7 @@ class Deploy extends Component public function mount() { - $this->parameters = Route::current()->parameters(); + $this->parameters = saveParameters(); $this->application = Application::where('id', $this->applicationId)->first(); $this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); } diff --git a/app/Http/Livewire/Project/Application/EnvironmentVariable/Add.php b/app/Http/Livewire/Project/Application/EnvironmentVariable/Add.php index cde5876fd..1947da9dc 100644 --- a/app/Http/Livewire/Project/Application/EnvironmentVariable/Add.php +++ b/app/Http/Livewire/Project/Application/EnvironmentVariable/Add.php @@ -23,7 +23,7 @@ class Add extends Component ]; public function mount() { - $this->parameters = Route::current()->parameters(); + $this->parameters = saveParameters(); } public function submit() { diff --git a/app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php b/app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php index 8c855e8f4..e01caf139 100644 --- a/app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php +++ b/app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php @@ -20,7 +20,7 @@ class Show extends Component ]; public function mount() { - $this->parameters = Route::current()->parameters(); + $this->parameters = saveParameters(); } public function submit() { diff --git a/app/Http/Livewire/Project/Application/Storages/Add.php b/app/Http/Livewire/Project/Application/Storages/Add.php index 8e2c9a384..4c03551b1 100644 --- a/app/Http/Livewire/Project/Application/Storages/Add.php +++ b/app/Http/Livewire/Project/Application/Storages/Add.php @@ -23,7 +23,7 @@ class Add extends Component ]; public function mount() { - $this->parameters = Route::current()->parameters(); + $this->parameters = saveParameters(); } public function submit() { diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepository.php b/app/Http/Livewire/Project/New/GithubPrivateRepository.php new file mode 100644 index 000000000..e7418e2c9 --- /dev/null +++ b/app/Http/Livewire/Project/New/GithubPrivateRepository.php @@ -0,0 +1,142 @@ +page); + $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}"); + $json = $response->json(); + if ($response->status() !== 200) { + return $this->emit('error', $json['message']); + } + + if ($json['total_count'] === 0) { + return; + } + $this->total_repositories_count = $json['total_count']; + $this->repositories = $this->repositories->concat(collect($json['repositories'])); + } + protected function loadBranchByPage() + { + Log::info('Loading page ' . $this->page); + $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}"); + $json = $response->json(); + if ($response->status() !== 200) { + return $this->emit('error', $json['message']); + } + + $this->total_branches_count = count($json); + $this->branches = $this->branches->concat(collect($json)); + } + public function loadRepositories($github_app_id) + { + $this->repositories = collect(); + $this->page = 1; + $this->github_app = GithubApp::where('id', $github_app_id)->first(); + $this->token = generate_github_token($this->github_app); + $this->loadRepositoryByPage(); + if ($this->repositories->count() < $this->total_repositories_count) { + while ($this->repositories->count() < $this->total_repositories_count) { + $this->page++; + $this->loadRepositoryByPage(); + } + } + $this->selected_repository_id = $this->repositories[0]['id']; + } + public function loadBranches() + { + $this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login']; + $this->selected_repository_repo = $this->repositories->where('id', $this->selected_repository_id)->first()['name']; + $this->branches = collect(); + $this->page = 1; + $this->loadBranchByPage(); + if ($this->total_branches_count === 100) { + while ($this->total_branches_count === 100) { + $this->page++; + $this->loadBranchByPage(); + } + } + } + public function loadServers() + { + $this->servers = Server::validated(); + $this->selected_server_id = $this->servers[0]['id']; + } + public function loadDestinations() + { + $server = $this->servers->where('id', $this->selected_server_id)->first(); + $this->destinations = $server->standaloneDockers->merge($server->swarmDockers); + $this->selected_destination_id = $this->destinations[0]['id']; + $this->selected_destination_class = $this->destinations[0]->getMorphClass(); + } + 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(); + $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}", + 'git_branch' => $this->selected_branch_name, + 'build_pack' => 'nixpacks', + 'ports_exposes' => '3000', + 'environment_id' => $environment->id, + 'destination_id' => $this->selected_destination_id, + 'destination_type' => $this->selected_destination_class, + 'source_id' => $this->github_app->id, + 'source_type' => GithubApp::class, + ]); + redirect()->route('project.application.configuration', [ + 'application_uuid' => $application->uuid, + 'project_uuid' => $project->uuid, + 'environment_name' => $environment->name + ]); + } catch (\Exception $e) { + $this->emit('error', $e->getMessage()); + } + } + public function mount() + { + $this->parameters = saveParameters(); + $this->repositories = $this->branches = $this->servers = $this->destinations = collect(); + $this->github_apps = GithubApp::private(); + } +} diff --git a/app/Http/Livewire/Project/New/PublicGitRepository.php b/app/Http/Livewire/Project/New/PublicGitRepository.php index bd7f18a41..da49c195d 100644 --- a/app/Http/Livewire/Project/New/PublicGitRepository.php +++ b/app/Http/Livewire/Project/New/PublicGitRepository.php @@ -42,7 +42,7 @@ class PublicGitRepository extends Component $this->public_repository_url = 'https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify'; $this->port = 3000; } - $this->parameters = Route::current()->parameters(); + $this->parameters = saveParameters(); $this->servers = session('currentTeam')->load(['servers'])->servers; } public function chooseServer($server) diff --git a/app/Http/Livewire/Server/PrivateKey.php b/app/Http/Livewire/Server/PrivateKey.php index 6e57f4ad2..f70c3d502 100644 --- a/app/Http/Livewire/Server/PrivateKey.php +++ b/app/Http/Livewire/Server/PrivateKey.php @@ -20,7 +20,7 @@ class PrivateKey extends Component } public function mount() { - $this->parameters = Route::current()->parameters(); + $this->parameters = saveParameters(); $this->private_keys = ModelsPrivateKey::where('team_id', session('currentTeam')->id)->get(); } } diff --git a/app/Jobs/DeployApplicationJob.php b/app/Jobs/DeployApplicationJob.php index e6cfbe37d..752485fcf 100644 --- a/app/Jobs/DeployApplicationJob.php +++ b/app/Jobs/DeployApplicationJob.php @@ -372,29 +372,6 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap return implode(' ', $generated_healthchecks_commands); } - private function generate_jwt_token_for_github() - { - $signingKey = InMemory::plainText($this->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($this->source->app_id) - ->issuedAt($now) - ->expiresAt($now->modify('+10 minutes')) - ->getToken($algorithm, $signingKey) - ->toString(); - $token = Http::withHeaders([ - 'Authorization' => "Bearer $issuedToken", - 'Accept' => 'application/vnd.github.machine-man-preview+json' - ])->post("{$this->source->api_url}/app/installations/{$this->source->installation_id}/access_tokens"); - if ($token->failed()) { - throw new \Exception("Failed to get access token for $this->application->name from " . $this->source->name . " with error: " . $token->json()['message']); - } - return $token->json()['token']; - } - private function set_labels_for_applications() { $labels = []; @@ -472,8 +449,7 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $source_html_url_scheme = $url['scheme']; $git_clone_command = "git clone -q -b {$this->application->git_branch}"; - - if ($this->application->source->getMorphClass() == 'App\Models\GithubApp') { + if ($this->source->getMorphClass() == 'App\Models\GithubApp') { if ($this->source->is_public) { $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}"; $git_clone_command = $this->setGitImportSettings($git_clone_command); @@ -481,12 +457,11 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $this->execute_in_builder($git_clone_command) ]; } else { - if (!$this->application->source->app_id) { - $private_key = base64_encode($this->application->source->privateKey->private_key); + if (!$this->source->app_id) { + $private_key = base64_encode($this->source->privateKey->private_key); $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} git@$source_html_url_host:{$this->application->git_repository}.git {$this->workdir}"; $git_clone_command = $this->setGitImportSettings($git_clone_command); - return [ $this->execute_in_builder("mkdir -p /root/.ssh"), $this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), @@ -494,7 +469,7 @@ COPY --from={$this->application->uuid}:{$this->git_commit}-build /app/{$this->ap $this->execute_in_builder($git_clone_command) ]; } else { - $github_access_token = $this->generate_jwt_token_for_github(); + $github_access_token = generate_github_token($this->source); 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}") ]; diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php index 5e16fa574..b7a624b5b 100644 --- a/app/Models/GithubApp.php +++ b/app/Models/GithubApp.php @@ -15,4 +15,12 @@ class GithubApp extends BaseModel { return $this->belongsTo(PrivateKey::class); } + static public function public() + { + return GithubApp::where('team_id', session('currentTeam')->id)->where('is_public', true)->get(); + } + static public function private() + { + return GithubApp::where('team_id', session('currentTeam')->id)->where('is_public', false)->get(); + } } diff --git a/bootstrap/helpers.php b/bootstrap/helpers.php index d913fb94f..71505312a 100644 --- a/bootstrap/helpers.php +++ b/bootstrap/helpers.php @@ -2,6 +2,7 @@ use App\Actions\CoolifyTask\PrepareCoolifyTask; use App\Data\CoolifyTaskArgs; +use App\Models\GithubApp; use App\Models\Server; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\QueryException; @@ -9,6 +10,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Process; +use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Storage; use Spatie\Activitylog\Contracts\Activity; @@ -174,3 +176,40 @@ if (!function_exists('generateRandomName')) { return $generator->getName(); } } + +use Lcobucci\JWT\Encoding\ChainedFormatter; +use Lcobucci\JWT\Encoding\JoseEncoder; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Rsa\Sha256; +use Lcobucci\JWT\Token\Builder; + +if (!function_exists('generate_github_token')) { + function generate_github_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) + ->expiresAt($now->modify('+10 minutes')) + ->getToken($algorithm, $signingKey) + ->toString(); + $token = Http::withHeaders([ + 'Authorization' => "Bearer $issuedToken", + 'Accept' => 'application/vnd.github.machine-man-preview+json' + ])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens"); + if ($token->failed()) { + throw new \Exception("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']); + } + return $token->json()['token']; + } +} +if (!function_exists('saveParameters')) { + function saveParameters() + { + return Route::current()->parameters(); + } +} diff --git a/database/seeders/ApplicationSeeder.php b/database/seeders/ApplicationSeeder.php index 2a03a7911..2c6fb736e 100644 --- a/database/seeders/ApplicationSeeder.php +++ b/database/seeders/ApplicationSeeder.php @@ -24,7 +24,7 @@ class ApplicationSeeder extends Seeder $github_public_source = GithubApp::where('name', 'Public GitHub')->first(); Application::create([ - 'name' => 'Public application (from GitHub)', + 'name' => 'coollabsio/coolify-examples:nodejs-fastify', 'git_repository' => 'coollabsio/coolify-examples', 'git_branch' => 'nodejs-fastify', 'build_pack' => 'nixpacks', diff --git a/database/seeders/GithubAppSeeder.php b/database/seeders/GithubAppSeeder.php index 9e468f913..8a980b91c 100644 --- a/database/seeders/GithubAppSeeder.php +++ b/database/seeders/GithubAppSeeder.php @@ -31,7 +31,7 @@ class GithubAppSeeder extends Seeder 'html_url' => 'https://github.com', 'is_public' => false, 'app_id' => 292941, - 'installation_id' => 34157139, + 'installation_id' => 37267016, 'client_id' => 'Iv1.220e564d2b0abd8c', 'client_secret' => '96b1b31f36ce0a34386d11798ff35b9b6d8aba3a', 'webhook_secret' => '326a47b49054f03288f800d81247ec9414d0abf3', diff --git a/database/seeders/LocalPersistentVolumeSeeder.php b/database/seeders/LocalPersistentVolumeSeeder.php index 61f0f7ae5..79c105ed3 100644 --- a/database/seeders/LocalPersistentVolumeSeeder.php +++ b/database/seeders/LocalPersistentVolumeSeeder.php @@ -13,11 +13,10 @@ class LocalPersistentVolumeSeeder extends Seeder */ public function run(): void { - $application = Application::where('name', 'Public application (from GitHub)')->first(); LocalPersistentVolume::create([ 'name' => 'test-pv', 'mount_path' => '/data', - 'resource_id' => $application->id, + 'resource_id' => 1, 'resource_type' => Application::class, ]); } diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index a0d25e4bb..6ed6b5343 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -11,12 +11,13 @@ - + + @if ($application->settings->is_static) - + @endif @@ -42,15 +43,16 @@ Submit -
+
- - - - - + + + + + +
diff --git a/resources/views/livewire/project/new/github-private-repository.blade.php b/resources/views/livewire/project/new/github-private-repository.blade.php new file mode 100644 index 000000000..cfb6dc268 --- /dev/null +++ b/resources/views/livewire/project/new/github-private-repository.blade.php @@ -0,0 +1,79 @@ +
+ @if ($github_apps->count() > 0) +

Choose a GitHub App

+ @foreach ($github_apps as $ghapp) + {{ $ghapp->name }} + @endforeach +
+ @if ($repositories->count() > 0) +

Choose a Repository

+ + Select Repository + @endif +
+
+ @if ($branches->count() > 0) +

Choose a Branch

+ + Select Branch + @endif +
+ +
+ @if ($servers->count() > 0) +

Choose a Server

+ + Select Server + @endif +
+
+ @if ($destinations->count() > 0) +

Choose a Destination

+ + Select Destination + @endif +
+ @else + Add new github app + @endif + +
diff --git a/resources/views/project/new.blade.php b/resources/views/project/new.blade.php index c490cab06..2c14aa1e9 100644 --- a/resources/views/project/new.blade.php +++ b/resources/views/project/new.blade.php @@ -18,7 +18,7 @@
- github-private-repo +