mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(rollback): find the current image of containers with any name
The rollback image list inspected a container named after the bare application UUID, so `current` was null for timestamped, prefixed, and custom-named containers. Find the running production containers by their owner labels instead. Closes #12296 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f97398c83c
commit
3d4df5e06f
@@ -4,6 +4,7 @@ namespace App\Actions\Application;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
/**
|
||||
@@ -27,10 +28,7 @@ class GetRollbackImages
|
||||
|
||||
$server = $application->destination?->server;
|
||||
if ($server?->isFunctional()) {
|
||||
$output = instant_remote_process([
|
||||
"docker inspect --format='{{.Config.Image}}' {$application->uuid}",
|
||||
], $server, throwError: false);
|
||||
$current = self::imageTag(str($output)->trim()->toString());
|
||||
$current = self::currentTag($server, $application);
|
||||
|
||||
$repository = $application->docker_registry_image_name ?: $application->uuid;
|
||||
$output = instant_remote_process([
|
||||
@@ -61,6 +59,21 @@ class GetRollbackImages
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag of the running production containers. Container names can have a timestamp or a
|
||||
* custom name, so find them by their owner labels. Null when no container runs, or when the
|
||||
* running containers use different tags (for example during a rolling update).
|
||||
*/
|
||||
private static function currentTag(Server $server, Application $application): ?string
|
||||
{
|
||||
$tags = getCurrentApplicationContainerStatus($server, $application, 0)
|
||||
->filter(fn ($container) => data_get($container, 'State') === 'running')
|
||||
->map(fn ($container) => self::imageTag((string) data_get($container, 'Image')))
|
||||
->unique();
|
||||
|
||||
return $tags->count() === 1 ? $tags->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each successful deployment pushes `<image name>:<commit>` to the registry, so the commits of
|
||||
* recent deployments are rollback targets. Docker image applications do not push, and
|
||||
|
||||
@@ -5,11 +5,14 @@ use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
@@ -80,3 +83,64 @@ it('lists no registry images when the application does not push commit images',
|
||||
'docker compose build pack' => [['build_pack' => 'dockercompose']],
|
||||
'dockerfile without git' => [['build_pack' => 'dockerfile', 'dockerfile' => 'FROM nginx']],
|
||||
]);
|
||||
|
||||
describe('on a reachable server', function () {
|
||||
beforeEach(function () {
|
||||
Server::flushIdentityMap();
|
||||
InstanceSettings::forceCreate(['id' => 0, 'fqdn' => 'https://coolify.test']);
|
||||
$server = $this->application->destination->server;
|
||||
$server->update(['private_key_id' => PrivateKey::factory()->create(['team_id' => $server->team_id])->id]);
|
||||
$server->settings()->update(['is_reachable' => true, 'is_usable' => true]);
|
||||
$this->application->update(['docker_registry_image_name' => null]);
|
||||
$this->application = $this->application->fresh();
|
||||
});
|
||||
|
||||
function fakeRollbackServer(Application $application, array $containers): void
|
||||
{
|
||||
$uuid = $application->uuid;
|
||||
$ps = collect($containers)->map(fn (array $container) => json_encode([
|
||||
'ID' => $container['name'],
|
||||
'Names' => $container['name'],
|
||||
'Image' => $container['image'],
|
||||
'State' => $container['state'] ?? 'running',
|
||||
'Labels' => "coolify.applicationUuid={$uuid},coolify.pullRequestId=".($container['pr'] ?? 0).',coolify.managed=true',
|
||||
]))->implode(PHP_EOL);
|
||||
$images = implode(PHP_EOL, ["{$uuid}#aaaaaaa#2026-10-01 10:00:00", "{$uuid}#bbbbbbb#2026-10-02 10:00:00"]);
|
||||
|
||||
Process::fake(fn ($process) => Process::result(output: str_contains($process->command, 'docker images') ? $images : $ps));
|
||||
}
|
||||
|
||||
it('finds the current image of containers that are not named after the application uuid', function (string $name) {
|
||||
fakeRollbackServer($this->application, [['name' => $name, 'image' => "{$this->application->uuid}:bbbbbbb"]]);
|
||||
|
||||
$result = GetRollbackImages::run($this->application);
|
||||
|
||||
expect($result['current'])->toBe('bbbbbbb')
|
||||
->and(collect($result['images'])->pluck('is_current', 'tag')->all())->toBe(['aaaaaaa' => false, 'bbbbbbb' => true]);
|
||||
})->with([
|
||||
'timestamped name' => ['app-20261001T100000'],
|
||||
'custom prefix' => ['example-app-20261001T100000'],
|
||||
'custom internal name' => ['example-app'],
|
||||
]);
|
||||
|
||||
it('ignores stopped and preview containers', function () {
|
||||
$uuid = $this->application->uuid;
|
||||
fakeRollbackServer($this->application, [
|
||||
['name' => 'example-app', 'image' => "{$uuid}:bbbbbbb"],
|
||||
['name' => 'example-app-old', 'image' => "{$uuid}:aaaaaaa", 'state' => 'exited'],
|
||||
['name' => "{$uuid}-pr-1", 'image' => "{$uuid}:pr-1", 'pr' => 1],
|
||||
]);
|
||||
|
||||
expect(GetRollbackImages::run($this->application)['current'])->toBe('bbbbbbb');
|
||||
});
|
||||
|
||||
it('reports no current image when running containers use different tags', function () {
|
||||
$uuid = $this->application->uuid;
|
||||
fakeRollbackServer($this->application, [
|
||||
['name' => 'example-app-20261001T100000', 'image' => "{$uuid}:aaaaaaa"],
|
||||
['name' => 'example-app-20261002T100000', 'image' => "{$uuid}:bbbbbbb"],
|
||||
]);
|
||||
|
||||
expect(GetRollbackImages::run($this->application)['current'])->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user