mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
@@ -433,7 +433,7 @@ class ApplicationsController extends Controller
|
||||
'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Ignored for the dockercompose build pack, which uses docker_compose_domains. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
'tags' => ['type' => 'array', 'items' => new OA\Items(type: 'string'), 'description' => 'Tags to assign to the application.'],
|
||||
'is_preserve_repository_enabled' => ['type' => 'boolean', 'default' => false, 'description' => 'Preserve repository during deployment.'],
|
||||
@@ -632,7 +632,7 @@ class ApplicationsController extends Controller
|
||||
'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Ignored for the dockercompose build pack, which uses docker_compose_domains. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
'tags' => ['type' => 'array', 'items' => new OA\Items(type: 'string'), 'description' => 'Tags to assign to the application.'],
|
||||
'is_preserve_repository_enabled' => ['type' => 'boolean', 'default' => false, 'description' => 'Preserve repository during deployment.'],
|
||||
@@ -831,7 +831,7 @@ class ApplicationsController extends Controller
|
||||
'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Ignored for the dockercompose build pack, which uses docker_compose_domains. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
'tags' => ['type' => 'array', 'items' => new OA\Items(type: 'string'), 'description' => 'Tags to assign to the application.'],
|
||||
'is_preserve_repository_enabled' => ['type' => 'boolean', 'default' => false, 'description' => 'Preserve repository during deployment.'],
|
||||
@@ -1001,7 +1001,7 @@ class ApplicationsController extends Controller
|
||||
'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Ignored for the dockercompose build pack, which uses docker_compose_domains. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
'tags' => ['type' => 'array', 'items' => new OA\Items(type: 'string'), 'description' => 'Tags to assign to the application.'],
|
||||
],
|
||||
@@ -1167,7 +1167,7 @@ class ApplicationsController extends Controller
|
||||
'http_basic_auth_argon2id_time_cost' => ['type' => 'integer', 'minimum' => 1, 'maximum' => 12, 'default' => 4, 'description' => 'Argon2id time cost (iterations) for the HTTP Basic Authentication password.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Ignored for the dockercompose build pack, which uses docker_compose_domains. Default: true.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
'tags' => ['type' => 'array', 'items' => new OA\Items(type: 'string'), 'description' => 'Tags to assign to the application.'],
|
||||
],
|
||||
@@ -1319,7 +1319,8 @@ class ApplicationsController extends Controller
|
||||
$fqdn = ValidationPatterns::normalizeApplicationDomains($request->domains);
|
||||
$request->offsetSet('domains', $fqdn);
|
||||
}
|
||||
$autogenerateDomain = $request->boolean('autogenerate_domain', true);
|
||||
// A Docker Compose application uses per-service domains; a top-level domain is never routed.
|
||||
$autogenerateDomain = $request->boolean('autogenerate_domain', true) && $request->build_pack !== 'dockercompose';
|
||||
$instantDeploy = $request->instant_deploy;
|
||||
$githubAppUuid = $request->github_app_uuid;
|
||||
$useBuildServer = $request->use_build_server;
|
||||
|
||||
@@ -3364,6 +3364,10 @@ class DatabasesController extends Controller
|
||||
response: 409,
|
||||
description: 'Another start, restart or import of this database is already in progress.',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 422,
|
||||
description: 'The database cannot start yet, for example because a CA certificate is missing.',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function action_deploy(Request $request)
|
||||
@@ -3386,6 +3390,10 @@ class DatabasesController extends Controller
|
||||
if (str($database->status)->contains('running')) {
|
||||
return response()->json(['message' => 'Database is already running.'], 400);
|
||||
}
|
||||
$prerequisiteError = StartDatabase::prerequisiteError($database);
|
||||
if ($prerequisiteError !== null) {
|
||||
return response()->json(['message' => $prerequisiteError], 422);
|
||||
}
|
||||
$reservation = StartDatabase::reserveOperation($database);
|
||||
if ($reservation === null) {
|
||||
return response()->json(['message' => ResourceStartActivity::DATABASE_OPERATION_IN_PROGRESS_MESSAGE], 409);
|
||||
@@ -3558,6 +3566,10 @@ class DatabasesController extends Controller
|
||||
response: 409,
|
||||
description: 'Another start, restart or import of this database is already in progress.',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 422,
|
||||
description: 'The database cannot start yet, for example because a CA certificate is missing.',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function action_restart(Request $request)
|
||||
@@ -3577,6 +3589,10 @@ class DatabasesController extends Controller
|
||||
|
||||
$this->authorize('manage', $database);
|
||||
|
||||
$prerequisiteError = StartDatabase::prerequisiteError($database);
|
||||
if ($prerequisiteError !== null) {
|
||||
return response()->json(['message' => $prerequisiteError], 422);
|
||||
}
|
||||
$reservation = StartDatabase::reserveOperation($database);
|
||||
if ($reservation === null) {
|
||||
return response()->json(['message' => ResourceStartActivity::DATABASE_OPERATION_IN_PROGRESS_MESSAGE], 409);
|
||||
|
||||
@@ -559,6 +559,11 @@ class DeployController extends Controller
|
||||
} catch (AuthorizationException $e) {
|
||||
return ['message' => 'Unauthorized to start this database.', 'deployment_uuid' => null];
|
||||
}
|
||||
$prerequisiteError = StartDatabase::prerequisiteError($resource);
|
||||
if ($prerequisiteError !== null) {
|
||||
$message = $prerequisiteError;
|
||||
break;
|
||||
}
|
||||
$reservation = StartDatabase::reserveOperation($resource);
|
||||
if ($reservation === null) {
|
||||
$message = ResourceStartActivity::DATABASE_OPERATION_IN_PROGRESS_MESSAGE;
|
||||
|
||||
@@ -158,6 +158,7 @@ class Heading extends Component
|
||||
return;
|
||||
}
|
||||
$this->auditDatabaseAction('ui.database.restarted');
|
||||
$this->dispatch('info', 'Restarting database.');
|
||||
$this->markDeploymentRunning($activity);
|
||||
$this->js("window.dispatchEvent(new CustomEvent('startdatabase'))");
|
||||
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
|
||||
|
||||
@@ -141,6 +141,11 @@ class Control extends Tool
|
||||
return ['message' => 'Database is already running.'];
|
||||
}
|
||||
|
||||
$prerequisiteError = StartDatabase::prerequisiteError($database);
|
||||
if ($prerequisiteError !== null) {
|
||||
throw new \RuntimeException($prerequisiteError);
|
||||
}
|
||||
|
||||
$reservation = StartDatabase::reserveOperation($database);
|
||||
if ($reservation === null) {
|
||||
throw new \RuntimeException(ResourceStartActivity::DATABASE_OPERATION_IN_PROGRESS_MESSAGE);
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
window.dispatchEvent(new CustomEvent('database-action-finished'));
|
||||
return;
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||
// start() opens the log dialog only when the start was queued.
|
||||
try {
|
||||
await $wire.$call('start');
|
||||
} finally {
|
||||
@@ -197,8 +197,7 @@
|
||||
return;
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('database-busy'));
|
||||
$wire.$dispatch('info', 'Restarting database.');
|
||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||
// restart() opens the log dialog only when the restart was queued.
|
||||
try {
|
||||
await $wire.$call('restart');
|
||||
} finally {
|
||||
|
||||
@@ -266,6 +266,26 @@ test('API create accepts a safe docker_compose_raw', function () {
|
||||
->assertCreated();
|
||||
});
|
||||
|
||||
test('API create does not generate a domain for a docker compose application', function () {
|
||||
Queue::fake();
|
||||
|
||||
$response = $this->withHeaders(['Authorization' => 'Bearer '.$this->token])
|
||||
->postJson('/api/v1/applications/public', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_uuid' => $this->environment->uuid,
|
||||
'server_uuid' => $this->server->uuid,
|
||||
'git_repository' => 'https://gitlab.com/coolify/compose-app',
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'dockercompose',
|
||||
'ports_exposes' => '80',
|
||||
'autogenerate_domain' => true,
|
||||
'docker_compose_raw' => SAFE_APPLICATION_COMPOSE,
|
||||
])
|
||||
->assertCreated();
|
||||
|
||||
expect(Application::query()->where('uuid', $response->json('uuid'))->value('fqdn'))->toBeNull();
|
||||
});
|
||||
|
||||
test('API update does not accept docker_compose_raw', function () {
|
||||
$this->withHeaders(['Authorization' => 'Bearer '.$this->token])
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
|
||||
@@ -86,3 +86,9 @@ test('public github repositories are stored as owner and repository with the pub
|
||||
->and($application->source_type)->toBe(GithubApp::class)
|
||||
->and($application->source_id)->toBe(0);
|
||||
});
|
||||
|
||||
test('a public application gets a generated domain by default', function () {
|
||||
$application = createPublicGitApplication(['autogenerate_domain' => true]);
|
||||
|
||||
expect($application->fqdn)->not->toBeEmpty();
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@@ -174,3 +175,43 @@ it('reads the data mount with sudo on a non-root server', function () {
|
||||
expect($database->fresh()->anonymousDataVolume())->toBe('3f1c0ffee')
|
||||
->and(ranRemoteCommandContaining("sudo docker inspect --format '{{range .Mounts}}{{if eq .Destination \"/var/lib/clickhouse\"}}{{.Type}} {{.Name}}{{end}}{{end}}' '{$database->uuid}' 2>/dev/null || sudo true"))->toBeTrue();
|
||||
});
|
||||
|
||||
it('rejects an API start or restart of a blocked database at once', function (string $method, string $path) {
|
||||
$database = create_standalone_clickhouse($this->environment->id, $this->destination);
|
||||
fakeClickhouseDataMount('3f1c0ffee');
|
||||
$plainToken = Str::random(40);
|
||||
$token = $this->owner->tokens()->create([
|
||||
'name' => 'clickhouse-test',
|
||||
'token' => hash('sha256', $plainToken),
|
||||
'abilities' => ['*'],
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$response = $this->withHeader('Authorization', 'Bearer '.$token->getKey().'|'.$plainToken)
|
||||
->json($method, str_replace('{uuid}', $database->uuid, $path));
|
||||
|
||||
$response->assertStatus(422);
|
||||
expect($response->json('message'))->toContain('unnamed Docker volume 3f1c0ffee');
|
||||
Queue::assertNothingPushed();
|
||||
})->with([
|
||||
'start' => ['POST', '/api/v1/databases/{uuid}/start'],
|
||||
'restart' => ['POST', '/api/v1/databases/{uuid}/restart'],
|
||||
]);
|
||||
|
||||
it('reports a blocked database in the deploy API', function () {
|
||||
$database = create_standalone_clickhouse($this->environment->id, $this->destination);
|
||||
fakeClickhouseDataMount('3f1c0ffee');
|
||||
$plainToken = Str::random(40);
|
||||
$token = $this->owner->tokens()->create([
|
||||
'name' => 'clickhouse-test',
|
||||
'token' => hash('sha256', $plainToken),
|
||||
'abilities' => ['*'],
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
$response = $this->withHeader('Authorization', 'Bearer '.$token->getKey().'|'.$plainToken)
|
||||
->postJson('/api/v1/deploy', ['uuid' => $database->uuid]);
|
||||
|
||||
expect($response->json('deployments.0.message'))->toContain('unnamed Docker volume 3f1c0ffee');
|
||||
Queue::assertNothingPushed();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user