mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 12:16:45 +00:00
fix(resources): clarify build server hosting restrictions (#10961)
This commit is contained in:
1 parent
7d699818e8
commit
913d033c75
25 files changed
+621
-70
No files matched your search
@@ -1237,6 +1237,12 @@ class ApplicationsController extends Controller
|
||||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
if (! $server->canHostResources()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['server_uuid' => ['The specified server is configured as a build server and cannot host resources.']],
|
||||
], 422);
|
||||
}
|
||||
$destinations = $server->destinations();
|
||||
if ($destinations->count() == 0) {
|
||||
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||
|
||||
@@ -1813,6 +1813,12 @@ class DatabasesController extends Controller
|
||||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
if (! $server->canHostResources()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['server_uuid' => ['The specified server is configured as a build server and cannot host resources.']],
|
||||
], 422);
|
||||
}
|
||||
$destinations = $server->destinations();
|
||||
if ($destinations->count() == 0) {
|
||||
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||
|
||||
@@ -736,6 +736,13 @@ class ServersController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($request->boolean('is_build_server') && ! $server->isBuildServer() && ! $server->isEmpty()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['is_build_server' => ['A server with existing resources cannot be configured as a build server.']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$server->update($updateFields);
|
||||
if ($request->has('is_build_server')) {
|
||||
$server->settings()->update([
|
||||
|
||||
@@ -444,6 +444,12 @@ class ServicesController extends Controller
|
||||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
if (! $server->canHostResources()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['server_uuid' => ['The specified server is configured as a build server and cannot host resources.']],
|
||||
], 422);
|
||||
}
|
||||
$destinations = $server->destinations();
|
||||
if ($destinations->count() == 0) {
|
||||
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||
@@ -501,7 +507,8 @@ class ServicesController extends Controller
|
||||
if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
|
||||
data_set($servicePayload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($servicePayload);
|
||||
$service = new Service($servicePayload);
|
||||
$service->save();
|
||||
$service->name = $request->name ?? "$oneClickServiceName-".$service->uuid;
|
||||
$service->description = $request->description;
|
||||
if ($request->has('is_container_label_escape_enabled')) {
|
||||
@@ -639,6 +646,12 @@ class ServicesController extends Controller
|
||||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
if (! $server->canHostResources()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['server_uuid' => ['The specified server is configured as a build server and cannot host resources.']],
|
||||
], 422);
|
||||
}
|
||||
$destinations = $server->destinations();
|
||||
if ($destinations->count() == 0) {
|
||||
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||
|
||||
@@ -34,7 +34,7 @@ class CloneMe extends Component
|
||||
|
||||
public ?int $selectedServer = null;
|
||||
|
||||
public ?int $selectedDestination = null;
|
||||
public ?string $selectedDestination = null;
|
||||
|
||||
public ?Server $server = null;
|
||||
|
||||
@@ -76,9 +76,9 @@ class CloneMe extends Component
|
||||
return view('livewire.project.clone-me');
|
||||
}
|
||||
|
||||
public function selectServer($server_id, $destination_id)
|
||||
public function selectServer($server_id, $destination_uuid)
|
||||
{
|
||||
if ($server_id == $this->selectedServer && $destination_id == $this->selectedDestination) {
|
||||
if ($server_id == $this->selectedServer && $destination_uuid === $this->selectedDestination) {
|
||||
$this->selectedServer = null;
|
||||
$this->selectedDestination = null;
|
||||
$this->server = null;
|
||||
@@ -86,7 +86,7 @@ class CloneMe extends Component
|
||||
return;
|
||||
}
|
||||
$this->selectedServer = $server_id;
|
||||
$this->selectedDestination = $destination_id;
|
||||
$this->selectedDestination = $destination_uuid;
|
||||
$this->server = $this->servers->where('id', $server_id)->first();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,10 @@ class CloneMe extends Component
|
||||
'selectedDestination' => 'required',
|
||||
'newName' => ValidationPatterns::nameRules(),
|
||||
]);
|
||||
$selectedDestination = find_resource_destination_for_current_team($this->selectedDestination);
|
||||
if (! $selectedDestination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
if ($type === 'project') {
|
||||
$foundProject = Project::where('name', $this->newName)->first();
|
||||
if ($foundProject) {
|
||||
@@ -130,7 +134,6 @@ class CloneMe extends Component
|
||||
$databases = $this->environment->databases();
|
||||
$services = $this->environment->services;
|
||||
foreach ($applications as $application) {
|
||||
$selectedDestination = $this->servers->flatMap(fn ($server) => $server->destinations())->where('id', $this->selectedDestination)->first();
|
||||
clone_application($application, $selectedDestination, [
|
||||
'environment_id' => $environment->id,
|
||||
], $this->cloneVolumeData);
|
||||
@@ -147,7 +150,8 @@ class CloneMe extends Component
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
'destination_id' => $selectedDestination->id,
|
||||
'destination_type' => $selectedDestination->getMorphClass(),
|
||||
]);
|
||||
$newDatabase->save();
|
||||
|
||||
@@ -265,7 +269,9 @@ class CloneMe extends Component
|
||||
])->fill([
|
||||
'uuid' => $uuid,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
'destination_id' => $selectedDestination->id,
|
||||
'destination_type' => $selectedDestination->getMorphClass(),
|
||||
'server_id' => $selectedDestination->server_id,
|
||||
]);
|
||||
$newService->save();
|
||||
|
||||
|
||||
@@ -47,19 +47,20 @@ class DockerCompose extends Component
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
|
||||
$destination_uuid = $this->query['destination'] ?? null;
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$service = Service::create([
|
||||
$service = new Service([
|
||||
'docker_compose_raw' => $this->dockerComposeRaw,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => $destination->server_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination_class,
|
||||
]);
|
||||
$service->save();
|
||||
|
||||
$variables = parseEnvFormatToArray($this->envFile);
|
||||
foreach ($variables as $key => $data) {
|
||||
|
||||
@@ -115,7 +115,7 @@ class DockerImage extends Component
|
||||
$parser->parse($dockerImage);
|
||||
|
||||
$destination_uuid = $this->query['destination'] ?? null;
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
@@ -133,7 +133,7 @@ class DockerImage extends Component
|
||||
// Determine the image tag based on whether it's a hash or regular tag
|
||||
$imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag();
|
||||
|
||||
$application = Application::create([
|
||||
$application = new Application([
|
||||
'name' => 'docker-image-'.new_public_id(),
|
||||
'repository_project_id' => 0,
|
||||
'git_repository' => 'coollabsio/coolify',
|
||||
@@ -147,6 +147,7 @@ class DockerImage extends Component
|
||||
'destination_type' => $destination_class,
|
||||
'health_check_enabled' => false,
|
||||
]);
|
||||
$application->save();
|
||||
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->update([
|
||||
|
||||
@@ -192,7 +192,7 @@ class GithubPrivateRepository extends Component
|
||||
}
|
||||
|
||||
$destination_uuid = $this->query['destination'] ?? null;
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
@@ -201,7 +201,7 @@ class GithubPrivateRepository extends Component
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
|
||||
$application = Application::create([
|
||||
$application = new Application([
|
||||
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
|
||||
'repository_project_id' => $this->selected_repository_id,
|
||||
'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(),
|
||||
@@ -216,6 +216,7 @@ class GithubPrivateRepository extends Component
|
||||
'source_id' => $this->github_app->id,
|
||||
'source_type' => $this->github_app->getMorphClass(),
|
||||
]);
|
||||
$application->save();
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$this->validate();
|
||||
try {
|
||||
$destination_uuid = $this->query['destination'] ?? null;
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
@@ -185,7 +185,8 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application_init['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
$application = new Application($application_init);
|
||||
$application->save();
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ class PublicGitRepository extends Component
|
||||
$project_uuid = $this->parameters['project_uuid'];
|
||||
$environment_uuid = $this->parameters['environment_uuid'];
|
||||
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
@@ -336,7 +336,8 @@ class PublicGitRepository extends Component
|
||||
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application_init['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
$application = new Application($application_init);
|
||||
$application->save();
|
||||
|
||||
$application->settings->is_static = $this->isStatic;
|
||||
$application->settings->save();
|
||||
|
||||
@@ -24,6 +24,8 @@ class Select extends Component
|
||||
|
||||
public Collection|null|Server $servers;
|
||||
|
||||
public ?Collection $buildServers = null;
|
||||
|
||||
public bool $onlyBuildServerAvailable = false;
|
||||
|
||||
public ?Collection $standaloneDockers;
|
||||
@@ -380,7 +382,7 @@ class Select extends Component
|
||||
|
||||
return;
|
||||
}
|
||||
if (count($this->servers) === 1) {
|
||||
if (count($this->servers) === 1 && $this->buildServers?->isEmpty()) {
|
||||
$server = $this->servers->first();
|
||||
if ($server instanceof Server) {
|
||||
$this->setServer($server);
|
||||
@@ -452,12 +454,8 @@ class Select extends Component
|
||||
public function loadServers()
|
||||
{
|
||||
$this->servers = Server::isUsable()->get()->sortBy('name');
|
||||
$this->allServers = $this->servers;
|
||||
|
||||
if ($this->allServers && $this->allServers->isNotEmpty()) {
|
||||
$this->onlyBuildServerAvailable = $this->allServers->every(function ($server) {
|
||||
return $server->isBuildServer();
|
||||
});
|
||||
}
|
||||
$this->buildServers = Server::isUsableBuildServer()->get()->sortBy('name');
|
||||
$this->allServers = $this->servers->concat($this->buildServers);
|
||||
$this->onlyBuildServerAvailable = $this->servers->isEmpty() && $this->buildServers->isNotEmpty();
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'dockerfile' => 'required',
|
||||
]);
|
||||
$destination_uuid = $this->query['destination'] ?? null;
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
throw new \Exception('Destination not found.');
|
||||
}
|
||||
@@ -51,7 +51,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
if (! $port) {
|
||||
$port = 80;
|
||||
}
|
||||
$application = Application::create([
|
||||
$application = new Application([
|
||||
'name' => 'dockerfile-'.new_public_id(),
|
||||
'repository_project_id' => 0,
|
||||
'git_repository' => 'coollabsio/coolify',
|
||||
@@ -66,6 +66,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'source_id' => 0,
|
||||
'source_type' => GithubApp::class,
|
||||
]);
|
||||
$application->save();
|
||||
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->update([
|
||||
|
||||
@@ -33,7 +33,7 @@ class Create extends Component
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
if (isset($type) && isset($destination_uuid)) {
|
||||
$destination = find_destination_for_current_team($destination_uuid);
|
||||
$destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $destination) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@@ -96,7 +96,8 @@ class Create extends Component
|
||||
if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
|
||||
data_set($service_payload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($service_payload);
|
||||
$service = new Service($service_payload);
|
||||
$service->save();
|
||||
$service->name = "$oneClickServiceName-".$service->uuid;
|
||||
$service->save();
|
||||
if ($oneClickDotEnvs?->count() > 0) {
|
||||
|
||||
@@ -118,9 +118,8 @@ class Destination extends Component
|
||||
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
||||
$network = StandaloneDocker::ownedByCurrentTeam()->where('server_id', $server->id)->findOrFail($network_id);
|
||||
$this->authorize('update', $this->resource);
|
||||
|
||||
$this->resource->getConnection()->transaction(function () use ($network, $server) {
|
||||
$main_destination = $this->resource->destination;
|
||||
$mainDestination = $this->resource->destination;
|
||||
$this->resource->update([
|
||||
'destination_id' => $network->id,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
@@ -128,7 +127,7 @@ class Destination extends Component
|
||||
$this->resource->additional_networks()
|
||||
->wherePivot('server_id', $server->id)
|
||||
->detach($network->id);
|
||||
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
|
||||
$this->resource->additional_networks()->attach($mainDestination->id, ['server_id' => $mainDestination->server->id]);
|
||||
});
|
||||
$this->resource->refresh();
|
||||
$this->refreshServers();
|
||||
|
||||
@@ -11,7 +11,6 @@ use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
@@ -19,7 +18,6 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -37,6 +35,8 @@ class ResourceOperations extends Component
|
||||
|
||||
public $servers;
|
||||
|
||||
public $buildServers;
|
||||
|
||||
public bool $cloneVolumeData = false;
|
||||
|
||||
public function mount()
|
||||
@@ -45,7 +45,9 @@ class ResourceOperations extends Component
|
||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||
$this->environmentUuid = data_get($parameters, 'environment_uuid');
|
||||
$this->projects = Project::ownedByCurrentTeamCached();
|
||||
$this->servers = currentTeam()->servers->filter(fn ($server) => ! $server->isBuildServer());
|
||||
$servers = currentTeam()->servers()->get();
|
||||
$this->servers = $servers->reject(fn ($server) => $server->isBuildServer());
|
||||
$this->buildServers = $servers->filter(fn ($server) => $server->isBuildServer());
|
||||
}
|
||||
|
||||
public function toggleVolumeCloning(bool $value)
|
||||
@@ -53,20 +55,20 @@ class ResourceOperations extends Component
|
||||
$this->cloneVolumeData = $value;
|
||||
}
|
||||
|
||||
public function cloneTo($destination_id)
|
||||
public function cloneTo($destination_uuid)
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->resource);
|
||||
|
||||
$new_destination = StandaloneDocker::ownedByCurrentTeam()->find($destination_id);
|
||||
if (! $new_destination) {
|
||||
$new_destination = SwarmDocker::ownedByCurrentTeam()->find($destination_id);
|
||||
}
|
||||
$new_destination = find_resource_destination_for_current_team($destination_uuid);
|
||||
if (! $new_destination) {
|
||||
return $this->addError('destination_id', 'Destination not found.');
|
||||
}
|
||||
$uuid = new_public_id();
|
||||
$server = $new_destination->server;
|
||||
if (! $server->canHostResources()) {
|
||||
return $this->addError('destination_id', 'The selected server cannot host resources.');
|
||||
}
|
||||
|
||||
if ($this->resource->getMorphClass() === Application::class) {
|
||||
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
|
||||
@@ -99,6 +101,7 @@ class ResourceOperations extends Component
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
'destination_id' => $new_destination->id,
|
||||
'destination_type' => $new_destination->getMorphClass(),
|
||||
]);
|
||||
$new_resource->save();
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ class Show extends Component
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
||||
$this->syncData();
|
||||
if (! $this->server->isEmpty()) {
|
||||
if (! $this->server->isBuildServer() && ! $this->server->isEmpty()) {
|
||||
$this->isBuildServerLocked = true;
|
||||
}
|
||||
// Load saved Hetzner status and validation state
|
||||
@@ -409,6 +409,12 @@ class Show extends Component
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->server);
|
||||
if ($value === true && ! $this->server->isEmpty()) {
|
||||
$this->isBuildServer = false;
|
||||
$this->dispatch('error', 'A server with existing resources cannot be configured as a build server.');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($value === true && $this->isSentinelEnabled) {
|
||||
$this->isSentinelEnabled = false;
|
||||
$this->isMetricsEnabled = false;
|
||||
|
||||
+22
-2
@@ -509,9 +509,29 @@ class Server extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public static function isUsable()
|
||||
public static function isUsable(): Builder
|
||||
{
|
||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false)->whereRelation('settings', 'force_disabled', false);
|
||||
return self::usableByBuildServerStatus(false);
|
||||
}
|
||||
|
||||
public static function isUsableBuildServer(): Builder
|
||||
{
|
||||
return self::usableByBuildServerStatus(true);
|
||||
}
|
||||
|
||||
private static function usableByBuildServerStatus(bool $isBuildServer): Builder
|
||||
{
|
||||
return Server::ownedByCurrentTeam()
|
||||
->whereRelation('settings', 'is_reachable', true)
|
||||
->whereRelation('settings', 'is_usable', true)
|
||||
->whereRelation('settings', 'is_swarm_worker', false)
|
||||
->whereRelation('settings', 'is_build_server', $isBuildServer)
|
||||
->whereRelation('settings', 'force_disabled', false);
|
||||
}
|
||||
|
||||
public function canHostResources(): bool
|
||||
{
|
||||
return ! $this->isBuildServer();
|
||||
}
|
||||
|
||||
public function settings()
|
||||
|
||||
@@ -109,6 +109,7 @@ class ServerSetting extends Model
|
||||
'sentinel_token' => 'encrypted',
|
||||
'is_reachable' => 'boolean',
|
||||
'is_usable' => 'boolean',
|
||||
'is_build_server' => 'boolean',
|
||||
'is_terminal_enabled' => 'boolean',
|
||||
'disable_application_image_retention' => 'boolean',
|
||||
'connection_timeout' => 'integer',
|
||||
|
||||
Reference in new issue
Block a user