refactor(models): extract defaultStandaloneDockerAttributes method on Server

Extract duplicated inline StandaloneDocker attribute arrays in the
Server boot lifecycle into a dedicated method, eliminating repetition
between the root-server (id=0) and normal-server paths.

Also harden the shared_environment_variables migration by wrapping
DDL statements in DB::transaction() and using DROP CONSTRAINT IF EXISTS
to make the migration safely re-runnable.

Add unit test covering the extracted method to verify uuid is always
present in bootstrap attributes.
This commit is contained in:
Andras Bacsai 2026-03-31 14:44:45 +02:00
parent 4f6e1f7e42
commit 466eb8504e
3 changed files with 61 additions and 24 deletions

View file

@ -155,12 +155,7 @@ protected static function booted()
'server_id' => $server->id,
])->save();
} else {
(new StandaloneDocker)->forceFill([
'id' => 0,
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $server->id,
])->saveQuietly();
(new StandaloneDocker)->forceFill($server->defaultStandaloneDockerAttributes(id: 0))->saveQuietly();
}
} else {
if ($server->isSwarm()) {
@ -171,12 +166,7 @@ protected static function booted()
]);
} else {
$standaloneDocker = new StandaloneDocker;
$standaloneDocker->forceFill([
'name' => 'coolify',
'uuid' => (string) new Cuid2,
'network' => 'coolify',
'server_id' => $server->id,
]);
$standaloneDocker->forceFill($server->defaultStandaloneDockerAttributes());
$standaloneDocker->saveQuietly();
}
}
@ -1043,6 +1033,25 @@ public function team()
return $this->belongsTo(Team::class);
}
/**
* @return array{id?: int, name: string, uuid: string, network: string, server_id: int}
*/
public function defaultStandaloneDockerAttributes(?int $id = null): array
{
$attributes = [
'name' => 'coolify',
'uuid' => (string) new Cuid2,
'network' => 'coolify',
'server_id' => $this->id,
];
if (! is_null($id)) {
$attributes['id'] = $id;
}
return $attributes;
}
public function environment_variables()
{
return $this->hasMany(SharedEnvironmentVariable::class)->where('type', 'server');

View file

@ -5,17 +5,23 @@
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::statement("ALTER TABLE shared_environment_variables DROP CONSTRAINT shared_environment_variables_type_check");
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment', 'server'))");
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->foreignId('server_id')->nullable()->constrained()->onDelete('cascade');
$table->unique(['key', 'server_id', 'team_id']);
DB::transaction(function () {
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment', 'server'))");
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->foreignId('server_id')->nullable()->constrained()->onDelete('cascade');
// NULL != NULL in PostgreSQL unique indexes, so this only enforces uniqueness
// for server-scoped rows (where server_id is non-null). Other scopes are covered
// by existing unique constraints on ['key', 'project_id', 'team_id'] and ['key', 'environment_id', 'team_id'].
$table->unique(['key', 'server_id', 'team_id']);
});
});
}
@ -24,12 +30,14 @@ public function up(): void
*/
public function down(): void
{
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropUnique(['key', 'server_id', 'team_id']);
$table->dropForeign(['server_id']);
$table->dropColumn('server_id');
DB::transaction(function () {
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropUnique(['key', 'server_id', 'team_id']);
$table->dropForeign(['server_id']);
$table->dropColumn('server_id');
});
DB::statement('ALTER TABLE shared_environment_variables DROP CONSTRAINT IF EXISTS shared_environment_variables_type_check');
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))");
});
DB::statement("ALTER TABLE shared_environment_variables DROP CONSTRAINT shared_environment_variables_type_check");
DB::statement("ALTER TABLE shared_environment_variables ADD CONSTRAINT shared_environment_variables_type_check CHECK (type IN ('team', 'project', 'environment'))");
}
};

View file

@ -0,0 +1,20 @@
<?php
use App\Models\Server;
it('includes a uuid in standalone docker bootstrap attributes for the root server path', function () {
$server = new Server;
$server->id = 0;
$attributes = $server->defaultStandaloneDockerAttributes(id: 0);
expect($attributes)
->toMatchArray([
'id' => 0,
'name' => 'coolify',
'network' => 'coolify',
'server_id' => 0,
])
->and($attributes['uuid'])->toBeString()
->and($attributes['uuid'])->not->toBe('');
});