mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 12:23:48 +00:00
Move V5 source, migrations, UI, scripts, and tests into documentation, then remove V5 routes, models, jobs, configuration, dependencies, and application hooks.
252 lines
8.5 KiB
Plaintext
252 lines
8.5 KiB
Plaintext
<?php
|
|
|
|
use App\Events\V5CanvasResourceUpdated;
|
|
use App\Events\V5ClusterUpdated;
|
|
use App\Jobs\V5BootstrapServerJob;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use App\Models\V5\Cluster;
|
|
use App\Models\V5\Server as V5Server;
|
|
use App\Services\Flux\AgentTokenIssuer;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Tests\Support\V5TestSchema;
|
|
|
|
beforeEach(function () {
|
|
Config::set('broadcasting.default', 'log');
|
|
Config::set('cache.default', 'array');
|
|
|
|
Schema::dropIfExists('v5_servers');
|
|
Schema::dropIfExists('v5_clusters');
|
|
Schema::dropIfExists('private_keys');
|
|
Schema::dropIfExists('team_user');
|
|
Schema::dropIfExists('teams');
|
|
Schema::dropIfExists('users');
|
|
});
|
|
|
|
function createBootstrapPreflightTables(): void
|
|
{
|
|
Schema::create('users', function ($table) {
|
|
$table->id();
|
|
$table->string('name')->default('Anonymous');
|
|
$table->string('email');
|
|
$table->timestamp('email_verified_at')->nullable();
|
|
$table->string('password')->nullable();
|
|
$table->rememberToken();
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('teams', function ($table) {
|
|
$table->id();
|
|
$table->string('name');
|
|
$table->string('description')->nullable();
|
|
$table->boolean('personal_team')->default(false);
|
|
$table->boolean('show_boarding')->default(false);
|
|
$table->timestamps();
|
|
});
|
|
|
|
Schema::create('team_user', function ($table) {
|
|
$table->id();
|
|
$table->foreignId('team_id');
|
|
$table->foreignId('user_id');
|
|
$table->string('role')->default('member');
|
|
$table->timestamps();
|
|
|
|
$table->unique(['team_id', 'user_id']);
|
|
});
|
|
|
|
Schema::create('private_keys', function ($table) {
|
|
$table->id();
|
|
$table->string('uuid')->unique();
|
|
$table->string('name');
|
|
$table->string('description')->nullable();
|
|
$table->longText('private_key');
|
|
$table->string('fingerprint')->nullable();
|
|
$table->boolean('is_git_related')->default(false);
|
|
$table->foreignId('team_id');
|
|
$table->timestamps();
|
|
});
|
|
|
|
V5TestSchema::createClustersTable();
|
|
V5TestSchema::createServersTable();
|
|
}
|
|
|
|
/**
|
|
* @return array{0: User, 1: Team}
|
|
*/
|
|
function createBootstrapPreflightUserWithTeam(): array
|
|
{
|
|
$user = User::withoutEvents(fn () => User::query()->create([
|
|
'name' => 'Margaret Hamilton',
|
|
'email' => 'margaret@example.com',
|
|
'email_verified_at' => now(),
|
|
'password' => 'password',
|
|
]));
|
|
$team = Team::withoutEvents(fn () => Team::query()->create([
|
|
'name' => 'V5 Tooling Team',
|
|
'description' => null,
|
|
'personal_team' => false,
|
|
'show_boarding' => false,
|
|
]));
|
|
$user->teams()->attach($team, ['role' => 'owner']);
|
|
|
|
return [$user, $team];
|
|
}
|
|
|
|
function createBootstrapPreflightPrivateKey(Team $team): PrivateKey
|
|
{
|
|
return PrivateKey::withoutEvents(fn () => PrivateKey::query()->forceCreate([
|
|
'uuid' => 'preflight-key-uuid',
|
|
'name' => 'Preflight SSH Key',
|
|
'description' => null,
|
|
'private_key' => "-----BEGIN OPENSSH PRIVATE KEY-----\ntest-key\n-----END OPENSSH PRIVATE KEY-----\n",
|
|
'fingerprint' => 'preflight-key',
|
|
'is_git_related' => false,
|
|
'team_id' => $team->id,
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* @return array{0: User, 1: Team, 2: Cluster, 3: V5Server}
|
|
*/
|
|
function createBootstrapPreflightClusterWithServer(array $clusterAttributes = [], array $serverAttributes = []): array
|
|
{
|
|
[$user, $team] = createBootstrapPreflightUserWithTeam();
|
|
$privateKey = createBootstrapPreflightPrivateKey($team);
|
|
$cluster = Cluster::query()->create([
|
|
'team_id' => $team->id,
|
|
'created_by_user_id' => $user->id,
|
|
'name' => 'Production Mesh',
|
|
'description' => null,
|
|
...$clusterAttributes,
|
|
]);
|
|
$server = V5Server::query()->create([
|
|
'team_id' => $team->id,
|
|
'cluster_id' => $cluster->id,
|
|
'created_by_user_id' => $user->id,
|
|
'private_key_id' => $privateKey->id,
|
|
'name' => 'prod-01',
|
|
'host' => '203.0.113.10',
|
|
'ssh_user' => 'root',
|
|
'ssh_port' => 22,
|
|
'status' => 'added',
|
|
...$serverAttributes,
|
|
]);
|
|
|
|
return [$user, $team, $cluster, $server];
|
|
}
|
|
|
|
it('rejects bootstrap when flux url is not configured', function () {
|
|
createBootstrapPreflightTables();
|
|
Config::set('coold.flux_url', '');
|
|
|
|
[$user, $team, $cluster, $server] = createBootstrapPreflightClusterWithServer();
|
|
|
|
Queue::fake();
|
|
|
|
$this
|
|
->actingAs($user)
|
|
->withSession(['currentTeam' => $team])
|
|
->postJson("/v5/clusters/{$cluster->uuid}/servers/{$server->uuid}/bootstrap")
|
|
->assertUnprocessable()
|
|
->assertJsonPath('message', 'COOLIFY_COOLD_FLUX_URL is not configured, so bootstrapped servers cannot be enrolled into Flux. Set it and retry the bootstrap.');
|
|
|
|
Queue::assertNothingPushed();
|
|
|
|
// The claim must not have been written either.
|
|
expect($server->refresh()->last_bootstrap_status)->toBeNull();
|
|
});
|
|
|
|
it('queues the bootstrap when the flux url is configured', function () {
|
|
createBootstrapPreflightTables();
|
|
Config::set('coold.flux_url', 'http://coolify.example.com:6443');
|
|
|
|
[$user, $team, $cluster, $server] = createBootstrapPreflightClusterWithServer();
|
|
|
|
Queue::fake();
|
|
|
|
$this
|
|
->actingAs($user)
|
|
->withSession(['currentTeam' => $team])
|
|
->postJson("/v5/clusters/{$cluster->uuid}/servers/{$server->uuid}/bootstrap")
|
|
->assertAccepted()
|
|
->assertJsonPath('message', 'Bootstrap queued.');
|
|
|
|
Queue::assertPushed(V5BootstrapServerJob::class);
|
|
});
|
|
|
|
it('writes the marker with the same coold version that is persisted on the server row', function () {
|
|
createBootstrapPreflightTables();
|
|
Config::set('coold.coolify_cli_bin', '/tmp/coolify');
|
|
Config::set('coold.flux_url', 'http://coolify.example.com:6443');
|
|
|
|
[, , $cluster, $server] = createBootstrapPreflightClusterWithServer([
|
|
'namespaces' => ['default'],
|
|
'coold_version' => 'v0.9.9',
|
|
], [
|
|
'last_bootstrap_status' => 'queued',
|
|
'last_bootstrap_ran_at' => now(),
|
|
]);
|
|
|
|
$issuer = Mockery::mock(AgentTokenIssuer::class);
|
|
$issuer->shouldReceive('issueForServer')->andReturn('signed-host-jwt');
|
|
app()->instance(AgentTokenIssuer::class, $issuer);
|
|
|
|
// The CLI reports a newer coold version than the cluster asked for; the
|
|
// marker and the database row must both use the reported version.
|
|
Process::fake([
|
|
'*' => Process::sequence()
|
|
->push(Process::result(output: ''))
|
|
->push(Process::result(output: json_encode([
|
|
'results' => [],
|
|
'coold_version' => 'v1.2.3',
|
|
'verified' => [[
|
|
'host' => "v5-server-{$server->uuid}",
|
|
'wireguard_ip' => '100.64.0.1',
|
|
'peer_count' => 0,
|
|
'status' => 'ok',
|
|
]],
|
|
], JSON_THROW_ON_ERROR)."\n"))
|
|
->push(Process::result(output: "public-key\n"))
|
|
->push(Process::result(output: "default=10.210.0.0/24\n"))
|
|
->push(Process::result(output: "marker written\n"))
|
|
->push(Process::result(output: "coold restarted\n")),
|
|
]);
|
|
|
|
Event::fake([V5ClusterUpdated::class, V5CanvasResourceUpdated::class]);
|
|
|
|
(new V5BootstrapServerJob($cluster->id, $server->id))->handle();
|
|
|
|
$server->refresh();
|
|
|
|
expect($server->last_bootstrap_status)->toBe('succeeded')
|
|
->and($server->status)->toBe('installed')
|
|
->and($server->coold_version)->toBe('v1.2.3');
|
|
|
|
$markerPayload = null;
|
|
|
|
Process::assertRan(function ($process) use (&$markerPayload): bool {
|
|
$command = implode(' ', array_map(fn ($part) => is_string($part) ? $part : json_encode($part), $process->command));
|
|
|
|
if (! str_contains($command, 'v5-node.json') || ! str_contains($command, 'base64 -d')) {
|
|
return false;
|
|
}
|
|
|
|
if (preg_match("/payload='([^']+)'/", $command, $matches) === 1) {
|
|
$markerPayload = json_decode(base64_decode($matches[1], true), true);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
expect($markerPayload)->toBeArray()
|
|
->and($markerPayload['coold_version'])->toBe('v1.2.3')
|
|
->and($markerPayload['coold_version'])->toBe($server->coold_version)
|
|
->and($markerPayload['server_uuid'])->toBe($server->uuid)
|
|
->and($markerPayload['wireguard_management_ip'])->toBe('100.64.0.1');
|
|
});
|