mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(tasks): run scheduled tasks only in running app containers (#12226)
This commit is contained in:
@@ -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');
|
||||
});
|
||||
Reference in New Issue
Block a user