fix(servers): retain cloud instances awaiting IPs

Persist DigitalOcean, Hetzner, and Vultr servers before public IP
assignment, then backfill placeholder addresses from provider state.

Treat partial Sentinel snapshots as non-authoritative and document the
destinations API with OpenAPI schemas.
This commit is contained in:
Andras Bacsai
2026-07-11 21:35:10 +02:00
parent d3fbb32c52
commit e01b8a057e
29 changed files with 1275 additions and 59 deletions
@@ -126,3 +126,112 @@ function createPushUpdatePostgresql(Team $team, array $attributes = []): Standal
return $database;
}
test('partial sentinel snapshots do not mark missing databases as exited', function () {
$team = Team::factory()->create();
$database = createPushUpdatePostgresql($team, [
'status' => 'running:healthy',
'is_public' => true,
]);
$server = $database->destination->server;
Queue::fake();
$data = [
'snapshot' => [
'version' => 1,
'complete' => false,
],
'containers' => [
[
'name' => 'unrelated-container',
'state' => 'running',
'health_status' => 'healthy',
'labels' => [
'coolify.managed' => 'true',
],
],
],
];
$job = new PushServerUpdateJob($server, $data);
$job->handle();
$database->refresh();
expect($database->status)->toBe('running:healthy');
});
test('partial sentinel snapshots apply directly observed database status updates', function () {
$team = Team::factory()->create();
$database = createPushUpdatePostgresql($team, [
'status' => 'exited',
]);
$server = $database->destination->server;
Queue::fake();
$data = [
'snapshot' => [
'version' => 1,
'complete' => false,
],
'containers' => [
[
'name' => $database->uuid,
'state' => 'running',
'health_status' => 'healthy',
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'com.docker.compose.service' => $database->uuid,
],
],
],
];
$job = new PushServerUpdateJob($server, $data);
$job->handle();
$database->refresh();
expect($database->status)->toBe('running:healthy');
});
test('partial sentinel snapshots do not trigger missing tcp proxy recovery', function () {
$team = Team::factory()->create();
$database = createPushUpdatePostgresql($team, [
'status' => 'exited',
'is_public' => true,
]);
$server = $database->destination->server;
Queue::fake();
$data = [
'snapshot' => [
'version' => 1,
'complete' => false,
],
'containers' => [
[
'name' => $database->uuid,
'state' => 'running',
'health_status' => 'healthy',
'labels' => [
'coolify.managed' => 'true',
'coolify.type' => 'database',
'com.docker.compose.service' => $database->uuid,
],
],
],
];
$job = new PushServerUpdateJob($server, $data);
$job->handle();
$database->refresh();
expect($database->status)->toBe('running:healthy');
Queue::assertNothingPushed();
});