mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 00:17:16 +00:00
Merge remote-tracking branch 'origin/main' into 11244-deprecated-docker-flags
This commit is contained in:
commit
4a7e77f809
15 files changed
+357
-10
No files matched your search
@@ -1,14 +1,17 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\ScheduledJobManager;
|
||||
use App\Jobs\ScheduledTaskJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -91,6 +94,63 @@ it('skips expensive dispatch for non-due schedules while seeding dedup cache', f
|
||||
expect(Cache::get("scheduled-task:{$task->id}"))->not->toBeNull();
|
||||
});
|
||||
|
||||
it('dispatches the instance coolify-db backup even when its id is zero', function () {
|
||||
config(['constants.coolify.self_hosted' => true]);
|
||||
Carbon::setTestNow(Carbon::create(2026, 5, 27, 0, 1, 0, 'UTC'));
|
||||
Queue::fake();
|
||||
|
||||
$database = createScheduledBackupDatabase();
|
||||
$backup = createScheduledDatabaseBackup($database, [
|
||||
'id' => 0,
|
||||
'frequency' => '* * * * *',
|
||||
]);
|
||||
|
||||
expect($backup->id)->toBe(0);
|
||||
|
||||
(new ScheduledJobManager)->handle();
|
||||
|
||||
Queue::assertPushed(DatabaseBackupJob::class, 1);
|
||||
Queue::assertPushed(DatabaseBackupJob::class, fn (DatabaseBackupJob $job) => $job->backup->id === 0);
|
||||
});
|
||||
|
||||
it('dispatches zero-id schedules and continues with positive ids', function () {
|
||||
config(['constants.coolify.self_hosted' => true]);
|
||||
Carbon::setTestNow(Carbon::create(2026, 5, 27, 0, 1, 0, 'UTC'));
|
||||
Queue::fake();
|
||||
|
||||
$application = createScheduledTaskApplication();
|
||||
$database = StandalonePostgresql::create([
|
||||
'name' => 'coolify-db',
|
||||
'image' => 'postgres:16-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'postgres',
|
||||
'status' => 'running',
|
||||
'environment_id' => $application->environment_id,
|
||||
'destination_id' => $application->destination_id,
|
||||
'destination_type' => $application->destination_type,
|
||||
]);
|
||||
|
||||
$zeroIdBackup = createScheduledDatabaseBackup($database, ['id' => 0]);
|
||||
$positiveIdBackup = createScheduledDatabaseBackup($database);
|
||||
$zeroIdTask = createScheduledApplicationTask($application, ['id' => 0]);
|
||||
$positiveIdTask = createScheduledApplicationTask($application);
|
||||
|
||||
expect($zeroIdBackup->id)->toBe(0)
|
||||
->and($positiveIdBackup->id)->toBeGreaterThan(0)
|
||||
->and($zeroIdTask->id)->toBe(0)
|
||||
->and($positiveIdTask->id)->toBeGreaterThan(0);
|
||||
|
||||
(new ScheduledJobManager)->handle();
|
||||
|
||||
Queue::assertPushed(DatabaseBackupJob::class, 2);
|
||||
Queue::assertPushed(DatabaseBackupJob::class, fn (DatabaseBackupJob $job) => $job->backup->id === 0);
|
||||
Queue::assertPushed(DatabaseBackupJob::class, fn (DatabaseBackupJob $job) => $job->backup->id === $positiveIdBackup->id);
|
||||
Queue::assertPushed(ScheduledTaskJob::class, 2);
|
||||
Queue::assertPushed(ScheduledTaskJob::class, fn (ScheduledTaskJob $job) => $job->task->id === 0);
|
||||
Queue::assertPushed(ScheduledTaskJob::class, fn (ScheduledTaskJob $job) => $job->task->id === $positiveIdTask->id);
|
||||
});
|
||||
|
||||
it('does not query relationships when constructing scheduled task jobs', function () {
|
||||
$application = createScheduledTaskApplication();
|
||||
|
||||
@@ -148,3 +208,53 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
'status' => 'running',
|
||||
]);
|
||||
}
|
||||
|
||||
function createScheduledBackupDatabase(): StandalonePostgresql
|
||||
{
|
||||
$application = createScheduledTaskApplication();
|
||||
|
||||
return StandalonePostgresql::create([
|
||||
'name' => 'coolify-db',
|
||||
'image' => 'postgres:16-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'postgres',
|
||||
'status' => 'running',
|
||||
'environment_id' => $application->environment_id,
|
||||
'destination_id' => $application->destination_id,
|
||||
'destination_type' => $application->destination_type,
|
||||
]);
|
||||
}
|
||||
|
||||
function createScheduledDatabaseBackup(StandalonePostgresql $database, array $overrides = []): ScheduledDatabaseBackup
|
||||
{
|
||||
$backup = new ScheduledDatabaseBackup;
|
||||
$backup->forceFill(array_merge([
|
||||
'enabled' => true,
|
||||
'save_s3' => false,
|
||||
'frequency' => '* * * * *',
|
||||
'database_id' => $database->id,
|
||||
'database_type' => $database->getMorphClass(),
|
||||
'team_id' => $database->environment->project->team_id,
|
||||
], $overrides));
|
||||
$backup->save();
|
||||
|
||||
return $backup->fresh();
|
||||
}
|
||||
|
||||
function createScheduledApplicationTask(Application $application, array $overrides = []): ScheduledTask
|
||||
{
|
||||
$task = new ScheduledTask;
|
||||
$task->forceFill(array_merge([
|
||||
'name' => 'scheduled-task',
|
||||
'command' => 'echo hello',
|
||||
'frequency' => '* * * * *',
|
||||
'timeout' => 300,
|
||||
'enabled' => true,
|
||||
'team_id' => $application->environment->project->team_id,
|
||||
'application_id' => $application->id,
|
||||
], $overrides));
|
||||
$task->save();
|
||||
|
||||
return $task->fresh();
|
||||
}
|
||||
@@ -45,3 +45,30 @@ it('only adds Grafana extra fields for Grafana server images', function (string
|
||||
'promtail' => ['grafana/promtail:latest', false],
|
||||
'tempo' => ['grafana/tempo:latest', false],
|
||||
]);
|
||||
|
||||
it('exposes Jean Server authentication and access settings', function () {
|
||||
$service = serviceExtraFieldsTestServiceWithApplicationImage('ghcr.io/coollabsio/jean-server:latest');
|
||||
|
||||
$service->environment_variables()->createMany([
|
||||
['key' => 'SERVICE_PASSWORD_64_JEAN', 'value' => 'secret-token', 'is_preview' => false],
|
||||
['key' => 'JEAN_ALLOWED_ORIGINS', 'value' => 'https://jean.example.com', 'is_preview' => false],
|
||||
]);
|
||||
|
||||
$fields = $service->extraFields();
|
||||
|
||||
expect($fields)->toHaveKey('')
|
||||
->and($fields[''])->toMatchArray([
|
||||
'Token' => [
|
||||
'key' => 'SERVICE_PASSWORD_64_JEAN',
|
||||
'value' => 'secret-token',
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
'sortOrder' => 1,
|
||||
],
|
||||
'Allowed Origins' => [
|
||||
'key' => 'JEAN_ALLOWED_ORIGINS',
|
||||
'value' => 'https://jean.example.com',
|
||||
'sortOrder' => 2,
|
||||
],
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Sentinel reports container memory `used` in bytes. The application metrics
|
||||
* chart labels those values as megabytes, so the series must be converted.
|
||||
*
|
||||
* @see https://github.com/coollabsio/coolify/issues/11246
|
||||
*/
|
||||
it('converts sentinel container memory samples from bytes to megabytes', function () {
|
||||
$metrics = [
|
||||
[1_700_000_000_000, 84_996_096.0],
|
||||
[1_700_000_005_000, 104_857_600.0],
|
||||
];
|
||||
|
||||
$converted = convertContainerMemoryBytesToMegabytes($metrics);
|
||||
|
||||
expect($converted)->toBe([
|
||||
[1_700_000_000_000, 81.06],
|
||||
[1_700_000_005_000, 100.0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves timestamps and converts a zero byte sample to zero megabytes', function () {
|
||||
expect(convertContainerMemoryBytesToMegabytes([
|
||||
[1_700_000_000_000, 0.0],
|
||||
]))->toBe([
|
||||
[1_700_000_000_000, 0.0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('leaves an empty series unchanged', function () {
|
||||
expect(convertContainerMemoryBytesToMegabytes([]))->toBe([]);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
it('includes a Jean Server one-click service template with all deployment environment variables', function () {
|
||||
$compose = file_get_contents(__DIR__.'/../../templates/compose/jean.yaml');
|
||||
|
||||
expect($compose)
|
||||
->toContain('ghcr.io/coollabsio/jean-server:${JEAN_VERSION:-latest}')
|
||||
->toContain('SERVICE_URL_JEAN_3456')
|
||||
->toContain('JEAN_HEADLESS=${JEAN_HEADLESS:-1}')
|
||||
->toContain('JEAN_HOST=${JEAN_HOST:-0.0.0.0}')
|
||||
->toContain('JEAN_PORT=${JEAN_PORT:-3456}')
|
||||
->toContain('JEAN_TOKEN=${SERVICE_PASSWORD_64_JEAN}')
|
||||
->toContain('JEAN_NO_TOKEN=${JEAN_NO_TOKEN:-0}')
|
||||
->toContain('JEAN_ALLOW_UNSAFE_NO_TOKEN=${JEAN_ALLOW_UNSAFE_NO_TOKEN:-0}')
|
||||
->toContain('JEAN_ALLOW_NATIVE_OPEN=${JEAN_ALLOW_NATIVE_OPEN:-0}')
|
||||
->toContain('JEAN_ALLOWED_ORIGINS=${JEAN_ALLOWED_ORIGINS:-}')
|
||||
->toContain('JEAN_DATA_DIR=/home/jean/.local/share/com.jean.desktop')
|
||||
->toContain('jean-data:/home/jean/.local/share/com.jean.desktop')
|
||||
->toContain('http://127.0.0.1:3456/readyz');
|
||||
|
||||
foreach (['service-templates.json', 'service-templates-latest.json'] as $templateFile) {
|
||||
$templates = json_decode(
|
||||
file_get_contents(__DIR__."/../../templates/{$templateFile}"),
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR,
|
||||
);
|
||||
|
||||
expect($templates)->toHaveKey('jean');
|
||||
expect($templates['jean']['port'] ?? null)->toBe('3456');
|
||||
expect($templates['jean']['logo'] ?? null)->toBe('svgs/jean.png');
|
||||
expect($templates['jean']['category'] ?? null)->toBe('development');
|
||||
|
||||
$generatedCompose = base64_decode($templates['jean']['compose'], strict: true);
|
||||
|
||||
expect($generatedCompose)
|
||||
->toContain('ghcr.io/coollabsio/jean-server:${JEAN_VERSION:-latest}')
|
||||
->toContain('JEAN_TOKEN=${SERVICE_PASSWORD_64_JEAN}');
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user