mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 05:56:16 +00:00
Gitlab Self-managed Support (#56)
* Update UI to ask for URL for Gitlab as provider. * Create state for each source control in factory. * Save url for SourceControl when given. * Make Gitlab dynamically use correct URL (custom or default) * Update SourceControlsTest to check for custom url when given. * Style fixes. --------- Co-authored-by: Saeed Vaziry <61919774+saeedvaziry@users.noreply.github.com>
This commit is contained in:
@ -3,6 +3,7 @@
|
|||||||
namespace App\Actions\SourceControl;
|
namespace App\Actions\SourceControl;
|
||||||
|
|
||||||
use App\Models\SourceControl;
|
use App\Models\SourceControl;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
@ -16,6 +17,7 @@ public function connect(array $input): void
|
|||||||
'provider' => $input['provider'],
|
'provider' => $input['provider'],
|
||||||
'profile' => $input['name'],
|
'profile' => $input['name'],
|
||||||
'access_token' => $input['token'],
|
'access_token' => $input['token'],
|
||||||
|
'url' => Arr::has($input, 'url') ? $input['url'] : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! $sourceControl->provider()->connect()) {
|
if (! $sourceControl->provider()->connect()) {
|
||||||
@ -44,6 +46,11 @@ private function validate(array $input): void
|
|||||||
'token' => [
|
'token' => [
|
||||||
'required',
|
'required',
|
||||||
],
|
],
|
||||||
|
'url' => [
|
||||||
|
'nullable',
|
||||||
|
'url:http,https',
|
||||||
|
'ends_with:/',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
Validator::make($input, $rules)->validate();
|
Validator::make($input, $rules)->validate();
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,14 @@
|
|||||||
|
|
||||||
class Gitlab extends AbstractSourceControlProvider
|
class Gitlab extends AbstractSourceControlProvider
|
||||||
{
|
{
|
||||||
protected string $apiUrl = 'https://gitlab.com/api/v4';
|
protected string $defaultApiHost = 'https://gitlab.com/';
|
||||||
|
|
||||||
|
protected string $apiVersion = 'api/v4';
|
||||||
|
|
||||||
public function connect(): bool
|
public function connect(): bool
|
||||||
{
|
{
|
||||||
$res = Http::withToken($this->sourceControl->access_token)
|
$res = Http::withToken($this->sourceControl->access_token)
|
||||||
->get($this->apiUrl.'/projects');
|
->get($this->getApiUrl().'/projects');
|
||||||
|
|
||||||
return $res->successful();
|
return $res->successful();
|
||||||
}
|
}
|
||||||
@ -27,7 +29,7 @@ public function getRepo(string $repo = null): mixed
|
|||||||
{
|
{
|
||||||
$repository = $repo ? urlencode($repo) : null;
|
$repository = $repo ? urlencode($repo) : null;
|
||||||
$res = Http::withToken($this->sourceControl->access_token)
|
$res = Http::withToken($this->sourceControl->access_token)
|
||||||
->get($this->apiUrl.'/projects/'.$repository.'/repository/commits');
|
->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits');
|
||||||
|
|
||||||
$this->handleResponseErrors($res, $repo);
|
$this->handleResponseErrors($res, $repo);
|
||||||
|
|
||||||
@ -36,7 +38,9 @@ public function getRepo(string $repo = null): mixed
|
|||||||
|
|
||||||
public function fullRepoUrl(string $repo, string $key): string
|
public function fullRepoUrl(string $repo, string $key): string
|
||||||
{
|
{
|
||||||
return sprintf('git@gitlab.com-%s:%s.git', $key, $repo);
|
$host = parse_url($this->getApiUrl())['host'];
|
||||||
|
|
||||||
|
return sprintf('git@%s-%s:%s.git', $host, $key, $repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +50,7 @@ public function deployHook(string $repo, array $events, string $secret): array
|
|||||||
{
|
{
|
||||||
$repository = urlencode($repo);
|
$repository = urlencode($repo);
|
||||||
$response = Http::withToken($this->sourceControl->access_token)->post(
|
$response = Http::withToken($this->sourceControl->access_token)->post(
|
||||||
$this->apiUrl.'/projects/'.$repository.'/hooks',
|
$this->getApiUrl().'/projects/'.$repository.'/hooks',
|
||||||
[
|
[
|
||||||
'description' => 'deploy',
|
'description' => 'deploy',
|
||||||
'url' => url('/git-hooks?secret='.$secret),
|
'url' => url('/git-hooks?secret='.$secret),
|
||||||
@ -81,7 +85,7 @@ public function destroyHook(string $repo, string $hookId): void
|
|||||||
{
|
{
|
||||||
$repository = urlencode($repo);
|
$repository = urlencode($repo);
|
||||||
$response = Http::withToken($this->sourceControl->access_token)->delete(
|
$response = Http::withToken($this->sourceControl->access_token)->delete(
|
||||||
$this->apiUrl.'/projects/'.$repository.'/hooks/'.$hookId
|
$this->getApiUrl().'/projects/'.$repository.'/hooks/'.$hookId
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($response->status() != 204) {
|
if ($response->status() != 204) {
|
||||||
@ -96,7 +100,7 @@ public function getLastCommit(string $repo, string $branch): ?array
|
|||||||
{
|
{
|
||||||
$repository = urlencode($repo);
|
$repository = urlencode($repo);
|
||||||
$res = Http::withToken($this->sourceControl->access_token)
|
$res = Http::withToken($this->sourceControl->access_token)
|
||||||
->get($this->apiUrl.'/projects/'.$repository.'/repository/commits?ref_name='.$branch);
|
->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits?ref_name='.$branch);
|
||||||
|
|
||||||
$this->handleResponseErrors($res, $repo);
|
$this->handleResponseErrors($res, $repo);
|
||||||
|
|
||||||
@ -123,7 +127,7 @@ public function deployKey(string $title, string $repo, string $key): void
|
|||||||
{
|
{
|
||||||
$repository = urlencode($repo);
|
$repository = urlencode($repo);
|
||||||
$response = Http::withToken($this->sourceControl->access_token)->post(
|
$response = Http::withToken($this->sourceControl->access_token)->post(
|
||||||
$this->apiUrl.'/projects/'.$repository.'/deploy_keys',
|
$this->getApiUrl().'/projects/'.$repository.'/deploy_keys',
|
||||||
[
|
[
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'key' => $key,
|
'key' => $key,
|
||||||
@ -135,4 +139,13 @@ public function deployKey(string $title, string $repo, string $key): void
|
|||||||
throw new FailedToDeployGitKey(json_decode($response->body())->message);
|
throw new FailedToDeployGitKey(json_decode($response->body())->message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getApiUrl(): string
|
||||||
|
{
|
||||||
|
$host = $this->sourceControl->url === null
|
||||||
|
? $this->defaultApiHost
|
||||||
|
: $this->sourceControl->url;
|
||||||
|
|
||||||
|
return $host.$this->apiVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,4 +17,40 @@ public function definition(): array
|
|||||||
'access_token' => Str::random(10),
|
'access_token' => Str::random(10),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function gitlab(): Factory
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
return [
|
||||||
|
'provider' => \App\Enums\SourceControl::GITLAB,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function github(): Factory
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
return [
|
||||||
|
'provider' => \App\Enums\SourceControl::GITHUB,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bitbucket(): Factory
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
return [
|
||||||
|
'provider' => \App\Enums\SourceControl::BITBUCKET,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function custom(): Factory
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
return [
|
||||||
|
'provider' => \App\Enums\SourceControl::CUSTOM,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
resources/views/components/input-help.blade.php
Normal file
1
resources/views/components/input-help.blade.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p {{ $attributes->merge(['class' => 'mt-2 text-sm text-gray-500 dark:text-gray-300']) }}>{{ $slot }}</p>
|
@ -32,6 +32,17 @@
|
|||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if($provider === App\Enums\SourceControl::GITLAB)
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="url" value="Url (optional)" />
|
||||||
|
<x-text-input wire:model.defer="url" id="url" name="url" type="text" class="mt-1 w-full" placeholder="e.g. https://gitlab.example.com/" />
|
||||||
|
<x-input-help>If you run a self-managed gitlab enter the url here, leave empty to use gitlab.com</x-input-help>
|
||||||
|
@error('url')
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<x-input-label for="token" value="API Key" />
|
<x-input-label for="token" value="API Key" />
|
||||||
<x-text-input wire:model.defer="token" id="token" name="token" type="text" class="mt-1 w-full" />
|
<x-text-input wire:model.defer="token" id="token" name="token" type="text" class="mt-1 w-full" />
|
||||||
|
@ -17,21 +17,28 @@ class SourceControlsTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @dataProvider data
|
* @dataProvider data
|
||||||
*/
|
*/
|
||||||
public function test_connect_provider(string $provider): void
|
public function test_connect_provider(string $provider, ?string $customUrl): void
|
||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
Http::fake();
|
Http::fake();
|
||||||
|
|
||||||
Livewire::test(Connect::class)
|
$livewire = Livewire::test(Connect::class)
|
||||||
->set('token', 'token')
|
->set('token', 'token')
|
||||||
->set('name', 'profile')
|
->set('name', 'profile')
|
||||||
->set('provider', $provider)
|
->set('provider', $provider);
|
||||||
|
|
||||||
|
if ($customUrl !== null) {
|
||||||
|
$livewire->set('url', $customUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
$livewire
|
||||||
->call('connect')
|
->call('connect')
|
||||||
->assertSuccessful();
|
->assertSuccessful();
|
||||||
|
|
||||||
$this->assertDatabaseHas('source_controls', [
|
$this->assertDatabaseHas('source_controls', [
|
||||||
'provider' => $provider,
|
'provider' => $provider,
|
||||||
|
'url' => $customUrl,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +68,10 @@ public function test_delete_provider(string $provider): void
|
|||||||
public static function data(): array
|
public static function data(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
['github'],
|
['github', null],
|
||||||
['gitlab'],
|
['gitlab', null],
|
||||||
['bitbucket'],
|
['gitlab', 'https://git.example.com/'],
|
||||||
|
['bitbucket', null],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
86
tests/Unit/SourceControlProviders/GitlabTest.php
Normal file
86
tests/Unit/SourceControlProviders/GitlabTest.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit\SourceControlProviders;
|
||||||
|
|
||||||
|
use App\Models\SourceControl;
|
||||||
|
use App\SourceControlProviders\Gitlab;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class GitlabTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_default_gitlab_url(): void
|
||||||
|
{
|
||||||
|
$sourceControlModel = SourceControl::factory()
|
||||||
|
->gitlab()
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$gitlab = new Gitlab($sourceControlModel);
|
||||||
|
|
||||||
|
$this->assertSame('https://gitlab.com/api/v4', $gitlab->getApiUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_default_gitlab_repo_url(): void
|
||||||
|
{
|
||||||
|
$repo = 'test/repo';
|
||||||
|
$key = 'TEST_KEY';
|
||||||
|
|
||||||
|
$sourceControlModel = SourceControl::factory()
|
||||||
|
->gitlab()
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$gitlab = new Gitlab($sourceControlModel);
|
||||||
|
|
||||||
|
$this->assertSame('git@gitlab.com-TEST_KEY:test/repo.git', $gitlab->fullRepoUrl($repo, $key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider customUrlData
|
||||||
|
*/
|
||||||
|
public function test_custom_url(string $url, string $expected): void
|
||||||
|
{
|
||||||
|
$sourceControlModel = SourceControl::factory()
|
||||||
|
->gitlab()
|
||||||
|
->create(['url' => $url]);
|
||||||
|
|
||||||
|
$gitlab = new Gitlab($sourceControlModel);
|
||||||
|
|
||||||
|
$this->assertSame($expected, $gitlab->getApiUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider customRepoUrlData
|
||||||
|
*/
|
||||||
|
public function test_custom_full_repository_url(string $url, string $expected): void
|
||||||
|
{
|
||||||
|
$repo = 'test/repo';
|
||||||
|
$key = 'TEST_KEY';
|
||||||
|
|
||||||
|
$sourceControlModel = SourceControl::factory()
|
||||||
|
->gitlab()
|
||||||
|
->create(['url' => $url]);
|
||||||
|
|
||||||
|
$gitlab = new Gitlab($sourceControlModel);
|
||||||
|
|
||||||
|
$this->assertSame($expected, $gitlab->fullRepoUrl($repo, $key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function customRepoUrlData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['https://git.example.com/', 'git@git.example.com-TEST_KEY:test/repo.git'],
|
||||||
|
['https://git.test.example.com/', 'git@git.test.example.com-TEST_KEY:test/repo.git'],
|
||||||
|
['https://git.example.co.uk/', 'git@git.example.co.uk-TEST_KEY:test/repo.git'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function customUrlData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['https://git.example.com/', 'https://git.example.com/api/v4'],
|
||||||
|
['https://git.test.example.com/', 'https://git.test.example.com/api/v4'],
|
||||||
|
['https://git.example.co.uk/', 'https://git.example.co.uk/api/v4'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user