fix(tasks): run scheduled tasks only in running app containers (#12226)

This commit is contained in:
Andras Bacsai
2026-10-19 21:28:54 +02:00
committed by GitHub
parent 6d36605176
commit 5ae125ad39
2 changed files with 89 additions and 5 deletions
+5 -5
View File
@@ -155,12 +155,12 @@ class ScheduledTaskJob implements ShouldBeEncrypted, ShouldQueue
]);
if ($this->resource->type() === 'application') {
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
// `docker ps -a` also lists stopped containers, for example the old container of a rolling update.
getCurrentApplicationContainerStatus($this->server, $this->resource, 0)
->filter(fn ($container) => data_get($container, 'State') === 'running')
->each(function ($container) {
$this->containers[] = str_replace('/', '', $container['Names']);
});
}
} elseif ($this->resource->type() === 'service') {
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
@@ -182,7 +182,7 @@ class ScheduledTaskJob implements ShouldBeEncrypted, ShouldQueue
}
foreach ($this->containers as $containerName) {
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
if (count($this->containers) == 1 || $containerName === $this->task->container || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) {
$cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'";
$dockerCommand = $this->server->isNonRoot() ? 'sudo docker' : 'docker';
$execCommand = "{$dockerCommand} exec ".escapeshellarg($containerName)." {$cmd}";
@@ -0,0 +1,84 @@
<?php
use App\Events\ScheduledTaskDone;
use App\Jobs\ScheduledTaskJob;
use App\Models\Application;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\ScheduledTask;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Process;
uses(RefreshDatabase::class);
beforeEach(function () {
Server::flushIdentityMap();
Event::fake([ScheduledTaskDone::class]);
Notification::fake();
InstanceSettings::forceCreate(['id' => 0]);
config(['app.maintenance.store' => 'array', 'constants.ssh.mux_enabled' => false]);
$team = Team::factory()->create();
$server = Server::factory()->create([
'team_id' => $team->id,
'private_key_id' => PrivateKey::factory()->create(['team_id' => $team->id])->id,
]);
$server->settings->update(['is_reachable' => true, 'is_usable' => true, 'force_disabled' => false, 'docker_version' => '28.0.0']);
$destination = StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail();
$project = Project::factory()->create(['team_id' => $team->id]);
$this->application = Application::factory()->create([
'environment_id' => Environment::factory()->create(['project_id' => $project->id])->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
$this->task = ScheduledTask::factory()->create([
'team_id' => $team->id,
'application_id' => $this->application->id,
'container' => null,
'command' => 'echo hello',
]);
});
/**
* @param array<string, string> $containers container name => docker state
*/
function fakeApplicationContainers(array $containers): void
{
$output = collect($containers)
->map(fn (string $state, string $name) => json_encode(['ID' => $name, 'Names' => $name, 'Labels' => 'coolify.applicationId=1', 'State' => $state]))
->implode("\n");
Process::fake(fn ($process) => str_contains($process->command, 'docker ps -a --filter')
? Process::result(output: $output)
: Process::result(output: 'done'));
}
test('a scheduled task ignores stopped application containers', function () {
$uuid = $this->application->uuid;
fakeApplicationContainers(["{$uuid}-111111111111" => 'exited', "{$uuid}-222222222222" => 'running']);
(new ScheduledTaskJob($this->task))->handle();
Process::assertRan(fn ($process) => str_contains($process->command, "docker exec '{$uuid}-222222222222' sh -c"));
Process::assertDidntRun(fn ($process) => str_contains($process->command, "docker exec '{$uuid}-111111111111'"));
expect($this->task->executions()->sole()->status)->toBe('success');
});
test('a scheduled task runs in the application container with the exact name', function () {
$uuid = $this->application->uuid;
fakeApplicationContainers(["{$uuid}-111111111111" => 'running', "{$uuid}-222222222222" => 'running']);
$this->task->update(['container' => "{$uuid}-222222222222"]);
(new ScheduledTaskJob($this->task->refresh()))->handle();
Process::assertRan(fn ($process) => str_contains($process->command, "docker exec '{$uuid}-222222222222' sh -c"));
expect($this->task->executions()->sole()->status)->toBe('success');
});