mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-28 09:34:36 +08:00
fix(github): use the owner/repo slug for GitHub API calls with SSH repository URLs
Closes #10175 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c3d110496c
commit
3c85187377
5 files changed
+97
-16
No files matched your search
@@ -3348,18 +3348,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
return 'GIT_SSH_COMMAND="'.$sshCommand.'" git ls-remote '.escapeshellarg($this->fullRepoUrl).' '.escapeshellarg($lsRemoteRef);
|
||||
}
|
||||
|
||||
private function update_repository_project_id(): void
|
||||
{
|
||||
$repositorySlug = gitRepositorySlug($this->customRepository);
|
||||
$repository = githubApi($this->source, "repos/{$repositorySlug}");
|
||||
$repository_project_id = data_get($repository, 'data.id');
|
||||
if (isset($repository_project_id) && $this->application->repository_project_id !== $repository_project_id) {
|
||||
$this->application->repository_project_id = $repository_project_id;
|
||||
$this->application->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function check_git_if_build_needed()
|
||||
{
|
||||
if (is_object($this->source) && $this->source->getMorphClass() === GithubApp::class && $this->source->is_public === false) {
|
||||
$repository = githubApi($this->source, "repos/{$this->customRepository}");
|
||||
$data = data_get($repository, 'data');
|
||||
$repository_project_id = data_get($data, 'id');
|
||||
if (isset($repository_project_id)) {
|
||||
if (blank($this->application->repository_project_id) || $this->application->repository_project_id !== $repository_project_id) {
|
||||
$this->application->repository_project_id = $repository_project_id;
|
||||
$this->application->save();
|
||||
}
|
||||
}
|
||||
$this->update_repository_project_id();
|
||||
}
|
||||
$this->resolve_bitbucket_pull_request_commit();
|
||||
$this->generate_git_import_commands();
|
||||
|
||||
@@ -70,7 +70,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function update_comment()
|
||||
{
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: '/repos/'.gitRepositorySlug($this->application->git_repository)."/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [
|
||||
'body' => $this->body,
|
||||
], throwError: false);
|
||||
if (data_get($data, 'message') === 'Not Found') {
|
||||
@@ -80,7 +80,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function create_comment()
|
||||
{
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: '/repos/'.gitRepositorySlug($this->application->git_repository)."/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
|
||||
'body' => $this->body,
|
||||
]);
|
||||
$this->preview->pull_request_issue_comment_id = $data['id'];
|
||||
@@ -89,7 +89,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function delete_comment()
|
||||
{
|
||||
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
|
||||
githubApi(source: $this->application->source, endpoint: '/repos/'.gitRepositorySlug($this->application->git_repository)."/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
|
||||
}
|
||||
|
||||
private function getPreviewLinks(): string
|
||||
|
||||
@@ -98,7 +98,7 @@ class Previews extends Component
|
||||
if (! $this->application->isGithubAppSource()) {
|
||||
throw new \Exception('Loading pull requests is only supported for GitHub App sources.');
|
||||
}
|
||||
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
|
||||
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: '/repos/'.gitRepositorySlug($this->application->git_repository).'/pulls');
|
||||
$this->rate_limit_remaining = $rate_limit_remaining;
|
||||
$this->pull_requests = $data->sortBy('number')->values();
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -163,20 +163,21 @@ class Source extends Component
|
||||
$this->dispatch('configurationChanged');
|
||||
|
||||
['repository' => $customRepository] = $this->application->customRepository();
|
||||
$repositorySlug = gitRepositorySlug($customRepository);
|
||||
$repository_project_id = null;
|
||||
|
||||
if ($sourceType === GithubApp::class) {
|
||||
$repository = githubApi($source, "repos/{$customRepository}");
|
||||
$repository = githubApi($source, "repos/{$repositorySlug}");
|
||||
$repository_project_id = data_get($repository, 'data.id');
|
||||
} elseif ($sourceType === GitlabApp::class) {
|
||||
if ($source->isConnected()) {
|
||||
$encoded = urlencode($customRepository);
|
||||
$encoded = urlencode($repositorySlug);
|
||||
$project = gitlabApi($source, "/projects/{$encoded}");
|
||||
$repository_project_id = data_get($project, 'data.id');
|
||||
}
|
||||
} elseif ($sourceType === GiteaApp::class) {
|
||||
if ($source->isConnected()) {
|
||||
$repositoryPath = collect(explode('/', $customRepository, 2))->map(fn ($part) => rawurlencode($part))->implode('/');
|
||||
$repositoryPath = collect(explode('/', $repositorySlug, 2))->map(fn ($part) => rawurlencode($part))->implode('/');
|
||||
$repository = giteaApi($source, "/repos/{$repositoryPath}");
|
||||
$repository_project_id = data_get($repository, 'data.id');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Livewire\Project\Application\Source;
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Client\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->team = Team::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
$project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||
|
||||
// A public GitHub App skips installation token generation, so only the repository lookup is faked.
|
||||
$this->githubApp = GithubApp::create([
|
||||
'name' => 'own-github-app',
|
||||
'team_id' => $this->team->id,
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://github.com',
|
||||
'is_public' => true,
|
||||
]);
|
||||
|
||||
$this->application = Application::factory()->create([
|
||||
'environment_id' => $environment->id,
|
||||
'git_repository' => 'git@github.com:acme/shop.git',
|
||||
'source_id' => null,
|
||||
'source_type' => null,
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/acme/shop' => Http::response(['id' => 4242]),
|
||||
'*' => Http::response(['message' => 'Not Found'], 404, ['X-RateLimit-Reset' => now()->timestamp]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('looks up the repository id with the owner/repo slug when the deployment uses an ssh url', function (string $gitRepository) {
|
||||
$job = (new ReflectionClass(ApplicationDeploymentJob::class))->newInstanceWithoutConstructor();
|
||||
$reflection = new ReflectionObject($job);
|
||||
$reflection->getProperty('application')->setValue($job, $this->application);
|
||||
$reflection->getProperty('source')->setValue($job, $this->githubApp);
|
||||
$reflection->getProperty('customRepository')->setValue($job, $gitRepository);
|
||||
|
||||
$reflection->getMethod('update_repository_project_id')->invoke($job);
|
||||
|
||||
Http::assertSent(fn (Request $request) => $request->url() === 'https://api.github.com/repos/acme/shop');
|
||||
expect($this->application->refresh()->repository_project_id)->toBe(4242);
|
||||
})->with([
|
||||
'scp-style ssh url' => ['git@github.com:acme/shop.git'],
|
||||
'ssh scheme url' => ['ssh://git@github.com:2222/acme/shop.git'],
|
||||
'owner/repo slug' => ['acme/shop'],
|
||||
]);
|
||||
|
||||
it('stores the repository id when an ssh url application switches to a github app source', function () {
|
||||
$this->actingAs($this->user);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
Livewire::test(Source::class, ['application' => $this->application])
|
||||
->call('changeSource', $this->githubApp->id, GithubApp::class)
|
||||
->assertDispatched('success');
|
||||
|
||||
Http::assertSent(fn (Request $request) => $request->url() === 'https://api.github.com/repos/acme/shop');
|
||||
expect($this->application->refresh()->repository_project_id)->toBe(4242);
|
||||
});
|
||||
Reference in new issue
Block a user