mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-28 02:06:37 -04:00
Merge remote-tracking branch 'origin/next' into v5-parallel-inertia-react
This commit is contained in:
@@ -147,6 +147,125 @@ describe('GitHub Source Change Component', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
test('ghe.com installation path uses github cloud owner scoped route', function () {
|
||||
$githubApp = new GithubApp;
|
||||
$githubApp->forceFill([
|
||||
'id' => 123,
|
||||
'name' => 'provided-github-app',
|
||||
'organization' => 'acme-enterprise',
|
||||
'html_url' => 'https://octocorp.ghe.com',
|
||||
'team_id' => 456,
|
||||
]);
|
||||
|
||||
$installationUrl = getInstallationPath($githubApp);
|
||||
parse_str(parse_url($installationUrl, PHP_URL_QUERY), $query);
|
||||
$installState = $query['state'] ?? null;
|
||||
|
||||
expect($installationUrl)->toStartWith('https://octocorp.ghe.com/apps/acme-enterprise/provided-github-app/installations/new?')
|
||||
->and($installState)->not->toBeEmpty()
|
||||
->and(Cache::get('github-app-setup-state:'.hash('sha256', $installState)))
|
||||
->toMatchArray([
|
||||
'action' => 'install',
|
||||
'github_app_id' => 123,
|
||||
'team_id' => 456,
|
||||
]);
|
||||
});
|
||||
|
||||
test('installation path is pure and never calls github or mutates the app', function () {
|
||||
Http::fake();
|
||||
|
||||
$privateKey = PrivateKey::create([
|
||||
'name' => 'github-app-local-name',
|
||||
'private_key' => validPrivateKey(),
|
||||
'team_id' => $this->team->id,
|
||||
'is_git_related' => true,
|
||||
]);
|
||||
|
||||
$githubApp = GithubApp::create([
|
||||
'name' => 'Local Display Name',
|
||||
'organization' => 'acme-enterprise',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://octocorp.ghe.com',
|
||||
'custom_user' => 'git',
|
||||
'custom_port' => 22,
|
||||
'app_id' => 12345,
|
||||
'private_key_id' => $privateKey->id,
|
||||
'team_id' => $this->team->id,
|
||||
'is_system_wide' => false,
|
||||
]);
|
||||
|
||||
$installationUrl = getInstallationPath($githubApp);
|
||||
|
||||
Http::assertNothingSent();
|
||||
|
||||
expect($installationUrl)->toStartWith('https://octocorp.ghe.com/apps/acme-enterprise/local-display-name/installations/new?')
|
||||
->and($githubApp->refresh()->name)->toBe('Local Display Name')
|
||||
->and($privateKey->refresh()->name)->toBe('github-app-local-name');
|
||||
});
|
||||
|
||||
test('syncGithubAppName persists the github slug and renames the private key', function () {
|
||||
Http::fake([
|
||||
'*/app' => Http::response(['slug' => 'actual-github-slug']),
|
||||
'*/zen' => Http::response('Keep it logically awesome.'),
|
||||
]);
|
||||
|
||||
$privateKey = PrivateKey::create([
|
||||
'name' => 'github-app-local-name',
|
||||
'private_key' => validPrivateKey(),
|
||||
'team_id' => $this->team->id,
|
||||
'is_git_related' => true,
|
||||
]);
|
||||
|
||||
$githubApp = GithubApp::create([
|
||||
'name' => 'Local Display Name',
|
||||
'organization' => 'acme-enterprise',
|
||||
'api_url' => 'https://api.github.com',
|
||||
'html_url' => 'https://octocorp.ghe.com',
|
||||
'custom_user' => 'git',
|
||||
'custom_port' => 22,
|
||||
'app_id' => 12345,
|
||||
'private_key_id' => $privateKey->id,
|
||||
'team_id' => $this->team->id,
|
||||
'is_system_wide' => false,
|
||||
]);
|
||||
|
||||
$appSlug = syncGithubAppName($githubApp, true);
|
||||
|
||||
expect($appSlug)->toBe('actual-github-slug')
|
||||
->and($githubApp->refresh()->name)->toBe('actual-github-slug')
|
||||
->and($privateKey->refresh()->name)->toBe('github-app-actual-github-slug');
|
||||
});
|
||||
|
||||
test('ghe.com installation path encodes the organization segment', function () {
|
||||
$githubApp = new GithubApp;
|
||||
$githubApp->forceFill([
|
||||
'id' => 123,
|
||||
'name' => 'provided-github-app',
|
||||
'organization' => '/acme enterprise/',
|
||||
'html_url' => 'https://octocorp.ghe.com',
|
||||
'team_id' => 456,
|
||||
]);
|
||||
|
||||
$installationUrl = getInstallationPath($githubApp);
|
||||
|
||||
expect($installationUrl)->toStartWith('https://octocorp.ghe.com/apps/acme%20enterprise/provided-github-app/installations/new?');
|
||||
});
|
||||
|
||||
test('ghe.com installation path keeps app scoped fallback when organization is blank', function () {
|
||||
$githubApp = new GithubApp;
|
||||
$githubApp->forceFill([
|
||||
'id' => 123,
|
||||
'name' => 'provided-github-app',
|
||||
'organization' => null,
|
||||
'html_url' => 'https://octocorp.ghe.com',
|
||||
'team_id' => 456,
|
||||
]);
|
||||
|
||||
$installationUrl = getInstallationPath($githubApp);
|
||||
|
||||
expect($installationUrl)->toStartWith('https://octocorp.ghe.com/apps/provided-github-app/installations/new?');
|
||||
});
|
||||
|
||||
test('defaults webhook endpoint to app url when it is the first available endpoint', function () {
|
||||
config(['app.url' => 'http://localhost:8000']);
|
||||
|
||||
@@ -207,6 +326,10 @@ describe('GitHub Source Change Component', function () {
|
||||
});
|
||||
|
||||
test('can mount with fully configured github app', function () {
|
||||
Http::fake([
|
||||
'https://api.github.com/app' => Http::response(['slug' => 'test-github-app']),
|
||||
]);
|
||||
|
||||
$privateKey = PrivateKey::create([
|
||||
'name' => 'Test Key',
|
||||
'private_key' => validPrivateKey(),
|
||||
@@ -241,6 +364,10 @@ describe('GitHub Source Change Component', function () {
|
||||
});
|
||||
|
||||
test('can update github app from null to valid values', function () {
|
||||
Http::fake([
|
||||
'https://api.github.com/app' => Http::response(['slug' => 'test-github-app']),
|
||||
]);
|
||||
|
||||
$privateKey = PrivateKey::create([
|
||||
'name' => 'Test Key',
|
||||
'private_key' => validPrivateKey(),
|
||||
|
||||
@@ -17,6 +17,7 @@ use Database\Seeders\StandaloneDockerSeeder;
|
||||
use Database\Seeders\TeamSeeder;
|
||||
use Database\Seeders\UserSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
@@ -33,6 +34,11 @@ function seedRailpackExamplePrerequisites(): void
|
||||
]);
|
||||
}
|
||||
|
||||
function limaServers(): Collection
|
||||
{
|
||||
return collect(DevelopmentRailpackExamplesSeeder::LIMA_SERVERS);
|
||||
}
|
||||
|
||||
it('can seed the railpack examples directly on a clean development database', function () {
|
||||
config()->set('app.env', 'local');
|
||||
|
||||
@@ -44,14 +50,51 @@ it('can seed the railpack examples directly on a clean development database', fu
|
||||
expect(StandaloneDocker::query()->find(0))->not->toBeNull();
|
||||
expect(GithubApp::query()->find(0))->not->toBeNull();
|
||||
expect(GitlabApp::query()->find(1))->not->toBeNull();
|
||||
expect(Project::query()->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)->exists())->toBeTrue();
|
||||
expect(Application::query()->count())->toBe(count(DevelopmentRailpackExamplesSeeder::examples()));
|
||||
expect(Application::query()->count())->toBe(count(DevelopmentRailpackExamplesSeeder::examples()) * limaServers()->count());
|
||||
|
||||
$project = Project::query()
|
||||
->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)
|
||||
->first();
|
||||
|
||||
expect($project)
|
||||
->not->toBeNull()
|
||||
->and($project->environments)->toHaveCount(limaServers()->count());
|
||||
|
||||
foreach (limaServers() as $limaServer) {
|
||||
$server = Server::query()->where('uuid', $limaServer['server_uuid'])->first();
|
||||
|
||||
expect($server)->not->toBeNull();
|
||||
expect($server->settings->sentinel_custom_url)->toBe('http://host.lima.internal:8000');
|
||||
expect(StandaloneDocker::query()->whereRelation('server', 'uuid', $limaServer['server_uuid'])->exists())->toBeTrue();
|
||||
expect($project->environments()->where('uuid', $limaServer['environment_uuid'])->exists())->toBeTrue();
|
||||
}
|
||||
});
|
||||
|
||||
it('seeds the railpack examples in development mode', function () {
|
||||
config()->set('app.env', 'local');
|
||||
|
||||
seedRailpackExamplePrerequisites();
|
||||
$legacyProject = Project::query()->create([
|
||||
'uuid' => 'railpack-examples-lima-ubuntu-2404',
|
||||
'name' => 'Railpack Examples - lima-ubuntu-2404',
|
||||
'description' => 'Legacy generated Railpack examples project',
|
||||
'team_id' => 0,
|
||||
]);
|
||||
Application::query()->create([
|
||||
'uuid' => 'lima-ubuntu-2404-railpack-nextjs-ssr',
|
||||
'name' => 'Legacy Railpack Next.js SSR Example',
|
||||
'repository_project_id' => DevelopmentRailpackExamplesSeeder::REPOSITORY_PROJECT_ID,
|
||||
'git_repository' => DevelopmentRailpackExamplesSeeder::GIT_REPOSITORY,
|
||||
'git_branch' => DevelopmentRailpackExamplesSeeder::GIT_BRANCH,
|
||||
'build_pack' => 'railpack',
|
||||
'ports_exposes' => '3000',
|
||||
'environment_id' => $legacyProject->environments()->first()->id,
|
||||
'destination_id' => 0,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'source_id' => 0,
|
||||
'source_type' => GithubApp::class,
|
||||
]);
|
||||
|
||||
$this->seed(DevelopmentRailpackExamplesSeeder::class);
|
||||
|
||||
$project = Project::query()
|
||||
@@ -61,30 +104,58 @@ it('seeds the railpack examples in development mode', function () {
|
||||
expect($project)
|
||||
->not->toBeNull()
|
||||
->and($project->name)->toBe('Railpack Examples')
|
||||
->and($project->environments)->toHaveCount(1)
|
||||
->and($project->environments->first()->uuid)->toBe(DevelopmentRailpackExamplesSeeder::ENVIRONMENT_UUID);
|
||||
->and($project->environments)->toHaveCount(limaServers()->count());
|
||||
expect(Project::query()->pluck('uuid')->sort()->values()->all())->toBe([
|
||||
'project',
|
||||
DevelopmentRailpackExamplesSeeder::PROJECT_UUID,
|
||||
]);
|
||||
expect(Application::query()->where('uuid', 'lima-ubuntu-2404-railpack-nextjs-ssr')->exists())->toBeFalse();
|
||||
|
||||
$applications = $project->applications()->with('settings')->orderBy('uuid')->get();
|
||||
|
||||
expect($applications)->toHaveCount(count(DevelopmentRailpackExamplesSeeder::examples()));
|
||||
expect($applications)->toHaveCount(count(DevelopmentRailpackExamplesSeeder::examples()) * limaServers()->count());
|
||||
expect($applications->every(fn (Application $application) => $application->build_pack === 'railpack'))->toBeTrue();
|
||||
|
||||
$examples = collect(DevelopmentRailpackExamplesSeeder::examples())->keyBy('uuid');
|
||||
expect($applications->every(
|
||||
fn (Application $application) => $application->git_repository === ($examples->get($application->uuid)['git_repository'] ?? DevelopmentRailpackExamplesSeeder::GIT_REPOSITORY)
|
||||
fn (Application $application) => $application->git_repository === ($examples->get(str($application->uuid)->after('-')->value())['git_repository'] ?? DevelopmentRailpackExamplesSeeder::GIT_REPOSITORY)
|
||||
))->toBeTrue();
|
||||
expect($applications->every(
|
||||
fn (Application $application) => $application->git_branch === ($examples->get($application->uuid)['git_branch'] ?? DevelopmentRailpackExamplesSeeder::GIT_BRANCH)
|
||||
fn (Application $application) => $application->git_branch === ($examples->get(str($application->uuid)->after('-')->value())['git_branch'] ?? DevelopmentRailpackExamplesSeeder::GIT_BRANCH)
|
||||
))->toBeTrue();
|
||||
|
||||
$nestjs = $applications->firstWhere('uuid', 'railpack-nestjs');
|
||||
$angularStatic = $applications->firstWhere('uuid', 'railpack-angular-static');
|
||||
$eleventyStatic = $applications->firstWhere('uuid', 'railpack-eleventy-static');
|
||||
$pythonFlask = $applications->firstWhere('uuid', 'railpack-python-flask');
|
||||
$goGin = $applications->firstWhere('uuid', 'railpack-go-gin');
|
||||
$rust = $applications->firstWhere('uuid', 'railpack-rust');
|
||||
$githubDeployKey = $applications->firstWhere('uuid', 'railpack-github-deploy-key');
|
||||
$gitlabDeployKey = $applications->firstWhere('uuid', 'railpack-gitlab-deploy-key');
|
||||
$gitlabPublic = $applications->firstWhere('uuid', 'railpack-gitlab-public-example');
|
||||
foreach (limaServers() as $limaServer) {
|
||||
$limaEnvironment = $project->environments()
|
||||
->where('uuid', $limaServer['environment_uuid'])
|
||||
->first();
|
||||
|
||||
expect($limaEnvironment)
|
||||
->not->toBeNull()
|
||||
->and($limaEnvironment->name)->toBe($limaServer['environment_name']);
|
||||
|
||||
$limaApplications = $limaEnvironment->applications()->with('settings', 'destination.server')->orderBy('uuid')->get();
|
||||
|
||||
expect($limaApplications)->toHaveCount(count(DevelopmentRailpackExamplesSeeder::examples()));
|
||||
expect($limaApplications->every(fn (Application $application) => $application->build_pack === 'railpack'))->toBeTrue();
|
||||
expect($limaApplications->every(fn (Application $application) => str($application->uuid)->startsWith($limaServer['uuid_prefix'])))->toBeTrue();
|
||||
expect($limaApplications->every(fn (Application $application) => $application->destination->server->uuid === $limaServer['server_uuid']))->toBeTrue();
|
||||
expect($limaApplications->every(
|
||||
fn (Application $application) => $application->git_repository === ($examples->get(str($application->uuid)->after($limaServer['uuid_prefix'])->value())['git_repository'] ?? DevelopmentRailpackExamplesSeeder::GIT_REPOSITORY)
|
||||
))->toBeTrue();
|
||||
expect($limaApplications->every(
|
||||
fn (Application $application) => $application->git_branch === ($examples->get(str($application->uuid)->after($limaServer['uuid_prefix'])->value())['git_branch'] ?? DevelopmentRailpackExamplesSeeder::GIT_BRANCH)
|
||||
))->toBeTrue();
|
||||
}
|
||||
|
||||
$nestjs = $applications->firstWhere('uuid', 'ubuntu24-railpack-nestjs');
|
||||
$angularStatic = $applications->firstWhere('uuid', 'ubuntu24-railpack-angular-static');
|
||||
$eleventyStatic = $applications->firstWhere('uuid', 'ubuntu24-railpack-eleventy-static');
|
||||
$pythonFlask = $applications->firstWhere('uuid', 'ubuntu24-railpack-python-flask');
|
||||
$goGin = $applications->firstWhere('uuid', 'ubuntu24-railpack-go-gin');
|
||||
$rust = $applications->firstWhere('uuid', 'ubuntu24-railpack-rust');
|
||||
$githubDeployKey = $applications->firstWhere('uuid', 'ubuntu24-railpack-github-deploy-key');
|
||||
$gitlabDeployKey = $applications->firstWhere('uuid', 'ubuntu24-railpack-gitlab-deploy-key');
|
||||
$gitlabPublic = $applications->firstWhere('uuid', 'ubuntu24-railpack-gitlab-public-example');
|
||||
|
||||
expect($nestjs)
|
||||
->not->toBeNull()
|
||||
@@ -156,6 +227,12 @@ it('skips the railpack examples outside development mode', function () {
|
||||
|
||||
expect(Project::query()->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)->exists())->toBeFalse();
|
||||
expect(Application::query()->where('uuid', 'railpack-nextjs-ssr')->exists())->toBeFalse();
|
||||
|
||||
foreach (limaServers() as $limaServer) {
|
||||
expect(Project::query()->where('uuid', 'railpack-examples-lima-ubuntu-2404')->exists())->toBeFalse();
|
||||
expect(Project::query()->where('uuid', 'railpack-examples-lima-ubuntu-2604')->exists())->toBeFalse();
|
||||
expect(Application::query()->where('uuid', $limaServer['uuid_prefix'].'railpack-nextjs-ssr')->exists())->toBeFalse();
|
||||
}
|
||||
});
|
||||
|
||||
it('is idempotent when run multiple times', function () {
|
||||
@@ -170,5 +247,14 @@ it('is idempotent when run multiple times', function () {
|
||||
->first();
|
||||
|
||||
expect($project)->not->toBeNull();
|
||||
expect($project->applications()->count())->toBe(count(DevelopmentRailpackExamplesSeeder::examples()));
|
||||
expect($project->applications()->count())->toBe(count(DevelopmentRailpackExamplesSeeder::examples()) * limaServers()->count());
|
||||
|
||||
foreach (limaServers() as $limaServer) {
|
||||
$limaEnvironment = $project->environments()
|
||||
->where('uuid', $limaServer['environment_uuid'])
|
||||
->first();
|
||||
|
||||
expect($limaEnvironment)->not->toBeNull();
|
||||
expect($limaEnvironment->applications()->count())->toBe(count(DevelopmentRailpackExamplesSeeder::examples()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
const TWO_FILE_COMPOSE = <<<'YAML'
|
||||
services:
|
||||
app:
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./wg_config.conf:/app/ps/wg0.conf
|
||||
- ./override_trays.json:/app/ps/override_tray.json
|
||||
YAML;
|
||||
|
||||
const DATA_DIR_COMPOSE = <<<'YAML'
|
||||
services:
|
||||
app:
|
||||
image: nginx:latest
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
YAML;
|
||||
|
||||
beforeEach(function () {
|
||||
Bus::fake();
|
||||
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create(['team_id' => $team->id]);
|
||||
$destination = StandaloneDocker::where('server_id', $server->id)->first();
|
||||
$project = Project::factory()->create(['team_id' => $team->id]);
|
||||
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||
|
||||
$this->destination = $destination;
|
||||
$this->environment = $environment;
|
||||
});
|
||||
|
||||
function makeComposeApplication(string $dockerComposeRaw): Application
|
||||
{
|
||||
return Application::factory()->create([
|
||||
'environment_id' => test()->environment->id,
|
||||
'destination_id' => test()->destination->id,
|
||||
'destination_type' => test()->destination->getMorphClass(),
|
||||
'build_pack' => 'dockercompose',
|
||||
'docker_compose_raw' => $dockerComposeRaw,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: Service, 1: ServiceApplication}
|
||||
*/
|
||||
function makeComposeService(string $dockerComposeRaw): array
|
||||
{
|
||||
$service = Service::factory()->create([
|
||||
'environment_id' => test()->environment->id,
|
||||
'server_id' => test()->destination->server_id,
|
||||
'destination_id' => test()->destination->id,
|
||||
'destination_type' => test()->destination->getMorphClass(),
|
||||
'docker_compose_raw' => $dockerComposeRaw,
|
||||
]);
|
||||
|
||||
$serviceApplication = ServiceApplication::create([
|
||||
'name' => 'app',
|
||||
'service_id' => $service->id,
|
||||
]);
|
||||
|
||||
return [$service, $serviceApplication];
|
||||
}
|
||||
|
||||
function seedFileVolume($resource, string $baseDir, string $fileName, string $mountPath, string $content): void
|
||||
{
|
||||
LocalFileVolume::create([
|
||||
'fs_path' => "{$baseDir}/{$fileName}",
|
||||
'mount_path' => $mountPath,
|
||||
'content' => $content,
|
||||
'is_directory' => false,
|
||||
'resource_id' => $resource->id,
|
||||
'resource_type' => $resource->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
|
||||
it('preserves existing application file volume content when reparsing compose bind mounts', function () {
|
||||
$application = makeComposeApplication(TWO_FILE_COMPOSE);
|
||||
$baseDir = application_configuration_dir()."/{$application->uuid}";
|
||||
|
||||
seedFileVolume($application, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
|
||||
seedFileVolume($application, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '0');
|
||||
|
||||
applicationParser($application);
|
||||
|
||||
$fileVolume = $application->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
|
||||
|
||||
expect($fileVolume->content)->toBe('0')
|
||||
->and($fileVolume->is_directory)->toBeFalse();
|
||||
});
|
||||
|
||||
it('keeps existing application file volumes as files when content is empty', function () {
|
||||
$application = makeComposeApplication(TWO_FILE_COMPOSE);
|
||||
$baseDir = application_configuration_dir()."/{$application->uuid}";
|
||||
|
||||
seedFileVolume($application, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
|
||||
seedFileVolume($application, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '');
|
||||
|
||||
applicationParser($application);
|
||||
|
||||
$fileVolume = $application->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
|
||||
|
||||
expect($fileVolume->content)->toBe('')
|
||||
->and($fileVolume->is_directory)->toBeFalse();
|
||||
});
|
||||
|
||||
it('defaults new application bind mounts to directories', function () {
|
||||
$application = makeComposeApplication(DATA_DIR_COMPOSE);
|
||||
|
||||
applicationParser($application);
|
||||
|
||||
$fileVolume = $application->fileStorages()->where('mount_path', '/app/data')->first();
|
||||
|
||||
expect($fileVolume->content)->toBeNull()
|
||||
->and($fileVolume->is_directory)->toBeTrue();
|
||||
});
|
||||
|
||||
it('preserves existing service file volume content when reparsing compose bind mounts', function () {
|
||||
[$service, $serviceApplication] = makeComposeService(TWO_FILE_COMPOSE);
|
||||
$baseDir = service_configuration_dir()."/{$service->uuid}";
|
||||
|
||||
seedFileVolume($serviceApplication, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
|
||||
seedFileVolume($serviceApplication, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '0');
|
||||
|
||||
serviceParser($service);
|
||||
|
||||
$fileVolume = $serviceApplication->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
|
||||
|
||||
expect($fileVolume->content)->toBe('0')
|
||||
->and($fileVolume->is_directory)->toBeFalse();
|
||||
});
|
||||
|
||||
it('keeps existing service file volumes as files when content is empty', function () {
|
||||
[$service, $serviceApplication] = makeComposeService(TWO_FILE_COMPOSE);
|
||||
$baseDir = service_configuration_dir()."/{$service->uuid}";
|
||||
|
||||
seedFileVolume($serviceApplication, $baseDir, 'wg_config.conf', '/app/ps/wg0.conf', 'test-conf');
|
||||
seedFileVolume($serviceApplication, $baseDir, 'override_trays.json', '/app/ps/override_tray.json', '');
|
||||
|
||||
serviceParser($service);
|
||||
|
||||
$fileVolume = $serviceApplication->fileStorages()->where('mount_path', '/app/ps/override_tray.json')->first();
|
||||
|
||||
expect($fileVolume->content)->toBe('')
|
||||
->and($fileVolume->is_directory)->toBeFalse();
|
||||
});
|
||||
|
||||
it('defaults new service bind mounts to directories', function () {
|
||||
[$service, $serviceApplication] = makeComposeService(DATA_DIR_COMPOSE);
|
||||
|
||||
serviceParser($service);
|
||||
|
||||
$fileVolume = $serviceApplication->fileStorages()->where('mount_path', '/app/data')->first();
|
||||
|
||||
expect($fileVolume->content)->toBeNull()
|
||||
->and($fileVolume->is_directory)->toBeTrue();
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Project;
|
||||
use Database\Seeders\PrivateKeySeeder;
|
||||
use Database\Seeders\ProjectSeeder;
|
||||
use Database\Seeders\TeamSeeder;
|
||||
use Database\Seeders\UserSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('seeds the first project with lima environments', function () {
|
||||
$this->seed([
|
||||
UserSeeder::class,
|
||||
TeamSeeder::class,
|
||||
PrivateKeySeeder::class,
|
||||
ProjectSeeder::class,
|
||||
]);
|
||||
|
||||
$project = Project::query()
|
||||
->where('uuid', 'project')
|
||||
->first();
|
||||
|
||||
expect($project)
|
||||
->not->toBeNull()
|
||||
->and($project->name)->toBe('My first project')
|
||||
->and($project->environments()->pluck('uuid', 'name')->all())->toBe([
|
||||
'ubuntu24' => 'ubuntu24',
|
||||
'ubuntu26' => 'ubuntu26',
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::unguarded(function () {
|
||||
InstanceSettings::query()->create([
|
||||
'id' => 0,
|
||||
'is_registration_enabled' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->team = Team::factory()->create(['show_boarding' => false]);
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
session(['currentTeam' => $this->team]);
|
||||
|
||||
$this->privateKey = PrivateKey::create([
|
||||
'team_id' => $this->team->id,
|
||||
'name' => 'Test Key',
|
||||
'private_key' => '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----',
|
||||
]);
|
||||
});
|
||||
|
||||
it('shows the lima start command on lima server general pages in development', function () {
|
||||
config()->set('app.env', 'local');
|
||||
|
||||
foreach (limaStartCommandDefinitions() as $definition) {
|
||||
$server = createServerForLimaStartCommandTest($definition['uuid'], $definition['port']);
|
||||
|
||||
$this->get(route('server.show', ['server_uuid' => $server->uuid]))
|
||||
->assertSuccessful()
|
||||
->assertSee('Start this Lima VM locally')
|
||||
->assertSee($definition['command']);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not show the lima start command outside development', function () {
|
||||
config()->set('app.env', 'testing');
|
||||
|
||||
$server = createServerForLimaStartCommandTest('lima-ubuntu-2404', 2222);
|
||||
|
||||
$this->get(route('server.show', ['server_uuid' => $server->uuid]))
|
||||
->assertSuccessful()
|
||||
->assertDontSee('Start this Lima VM locally')
|
||||
->assertDontSee('limactl start --yes --name=coolify-lima-ubuntu-2404 docker/lima/ubuntu-2404.yaml');
|
||||
});
|
||||
|
||||
function createServerForLimaStartCommandTest(string $uuid, int $port): Server
|
||||
{
|
||||
return Server::factory()->create([
|
||||
'uuid' => $uuid,
|
||||
'name' => $uuid,
|
||||
'ip' => 'host.docker.internal',
|
||||
'port' => $port,
|
||||
'team_id' => test()->team->id,
|
||||
'private_key_id' => test()->privateKey->id,
|
||||
'proxy' => [
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
function limaStartCommandDefinitions(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'uuid' => 'lima-ubuntu-2404',
|
||||
'port' => 2222,
|
||||
'command' => 'limactl start --yes --name=coolify-lima-ubuntu-2404 docker/lima/ubuntu-2404.yaml',
|
||||
],
|
||||
[
|
||||
'uuid' => 'lima-ubuntu-2604',
|
||||
'port' => 2223,
|
||||
'command' => 'limactl start --yes --name=coolify-lima-ubuntu-2604 docker/lima/ubuntu-2604.yaml',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::create(['id' => 0]);
|
||||
});
|
||||
|
||||
function makeNavbarServer(bool $isFunctional): array
|
||||
{
|
||||
$team = Team::factory()->create();
|
||||
$user = User::factory()->create();
|
||||
$user->teams()->attach($team, ['role' => 'admin']);
|
||||
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now(),
|
||||
]);
|
||||
|
||||
$server->settings()->update([
|
||||
'is_reachable' => $isFunctional,
|
||||
'is_usable' => $isFunctional,
|
||||
'is_sentinel_enabled' => true,
|
||||
]);
|
||||
|
||||
test()->actingAs($user);
|
||||
session(['currentTeam' => $team]);
|
||||
|
||||
return [$server->fresh(), $user, $team];
|
||||
}
|
||||
|
||||
it('does not show sentinel sync status before the server is validated', function () {
|
||||
[$server] = makeNavbarServer(isFunctional: false);
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $server])
|
||||
->assertDontSee('Sentinel')
|
||||
->assertDontSee('In sync')
|
||||
->assertDontSee('Out of sync');
|
||||
});
|
||||
|
||||
it('shows sentinel sync status after the server is validated', function () {
|
||||
[$server] = makeNavbarServer(isFunctional: true);
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $server])
|
||||
->assertSee('Sentinel')
|
||||
->assertSee('In sync');
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Server;
|
||||
use Database\Seeders\PrivateKeySeeder;
|
||||
use Database\Seeders\ServerSeeder;
|
||||
use Database\Seeders\TeamSeeder;
|
||||
use Database\Seeders\UserSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
function limaServerDefinitions(): array
|
||||
{
|
||||
return [
|
||||
['uuid' => 'lima-ubuntu-2404', 'port' => 2222, 'template' => 'ubuntu-2404.yaml', 'memory' => '2GiB', 'disk' => '20GiB'],
|
||||
['uuid' => 'lima-ubuntu-2604', 'port' => 2223, 'template' => 'ubuntu-2604.yaml', 'memory' => '2GiB', 'disk' => '20GiB'],
|
||||
];
|
||||
}
|
||||
|
||||
it('seeds the development testing host and lima servers', function () {
|
||||
$this->seed([
|
||||
UserSeeder::class,
|
||||
TeamSeeder::class,
|
||||
PrivateKeySeeder::class,
|
||||
ServerSeeder::class,
|
||||
]);
|
||||
|
||||
$testingHost = Server::query()->where('uuid', 'localhost')->first();
|
||||
|
||||
expect($testingHost)
|
||||
->not->toBeNull()
|
||||
->and($testingHost->ip)->toBe('coolify-testing-host');
|
||||
|
||||
foreach (limaServerDefinitions() as $definition) {
|
||||
$limaServer = Server::query()->where('uuid', $definition['uuid'])->first();
|
||||
|
||||
expect($limaServer)
|
||||
->not->toBeNull()
|
||||
->and($limaServer->name)->toBe($definition['uuid'])
|
||||
->and($limaServer->ip)->toBe('host.docker.internal')
|
||||
->and($limaServer->port)->toBe($definition['port'])
|
||||
->and($limaServer->user)->toBe('root')
|
||||
->and($limaServer->team_id)->toBe(0)
|
||||
->and($limaServer->private_key_id)->toBe(1)
|
||||
->and($limaServer->settings)->not->toBeNull()
|
||||
->and($limaServer->settings->sentinel_custom_url)->toBe('http://host.lima.internal:8000')
|
||||
->and($limaServer->destinations())->toHaveCount(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the lima templates aligned with the seeded servers', function () {
|
||||
foreach (limaServerDefinitions() as $definition) {
|
||||
$template = Yaml::parseFile(base_path("docker/lima/{$definition['template']}"));
|
||||
$script = data_get($template, 'provision.0.script');
|
||||
|
||||
expect(data_get($template, 'ssh.localPort'))
|
||||
->toBe($definition['port'])
|
||||
->and(data_get($template, 'memory'))->toBe($definition['memory'])
|
||||
->and(data_get($template, 'disk'))->toBe($definition['disk'])
|
||||
->and(data_get($template, 'containerd.system'))->toBeFalse()
|
||||
->and(data_get($template, 'containerd.user'))->toBeFalse()
|
||||
->and(data_get($template, 'networks'))->toBeNull()
|
||||
->and($script)->toContain('ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user