diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index de2f56b2b..6fc30f13f 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2459,35 +2459,35 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); private function handleRegistryAuth() { - if (!$this->dockerRegistry) { - throw new Exception('Registry not found.'); + $registries = $this->application->registries; + + if ($registries->isEmpty()) { + throw new Exception('No registries found.'); } - $token = escapeshellarg($this->dockerRegistry->token); - $username = escapeshellarg($this->dockerRegistry->username); + foreach ($registries as $registry) { + $token = escapeshellarg($registry->token); + $username = escapeshellarg($registry->username); - // Handle different registry types - $url = match ($this->dockerRegistry->type) { - 'docker_hub' => '', // Docker Hub doesn't need URL specified - 'custom' => escapeshellarg($this->dockerRegistry->url), - default => escapeshellarg($this->dockerRegistry->url) - }; + $url = match ($registry->type) { + 'docker_hub' => '', + 'custom' => escapeshellarg($registry->url), + default => escapeshellarg($registry->url) + }; - $this->application_deployment_queue->addLogEntry('Attempting to log into registry...'); + $this->application_deployment_queue->addLogEntry("Attempting to log into registry {$registry->name}..."); - // Build login command based on registry type - $command = $this->dockerRegistry->type === 'docker_hub' - ? "echo {{secrets.token}} | docker login -u {$username} --password-stdin" - : "echo {{secrets.token}} | docker login {$url} -u {$username} --password-stdin"; + $command = $registry->type === 'docker_hub' + ? "echo {{secrets.token}} | docker login -u {$username} --password-stdin" + : "echo {{secrets.token}} | docker login {$url} -u {$username} --password-stdin"; - $this->execute_remote_command( - [ + $this->execute_remote_command([ 'command' => $command, 'secrets' => [ 'token' => $token, ], 'hidden' => true, - ] - ); + ]); + } } } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 08758ba14..061fd51c4 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -73,7 +73,8 @@ class General extends Component 'application.docker_registry_image_name' => 'nullable', 'application.docker_registry_image_tag' => 'nullable', 'application.docker_use_custom_registry' => 'boolean', - 'application.docker_registry_id' => 'nullable|required_if:application.docker_use_custom_registry,true', + 'application.selectedRegistries' => 'required_if:application.docker_use_custom_registry,true|array', + 'application.selectedRegistries.*' => 'exists:docker_registries,id', 'application.dockerfile_location' => 'nullable', 'application.docker_compose_location' => 'nullable', 'application.docker_compose' => 'nullable', @@ -117,7 +118,7 @@ class General extends Component 'application.docker_registry_image_name' => 'Docker registry image name', 'application.docker_registry_image_tag' => 'Docker registry image tag', 'application.docker_use_custom_registry' => 'Use private registry', - 'application.docker_registry_id' => 'Registry', + 'application.selectedRegistries' => 'Registries', 'application.dockerfile_location' => 'Dockerfile location', 'application.docker_compose_location' => 'Docker compose location', 'application.docker_compose' => 'Docker compose', @@ -173,6 +174,10 @@ class General extends Component if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) { $this->dispatch('configurationChanged'); } + + if ($this->application->docker_use_custom_registry) { + $this->application->selectedRegistries = $this->application->registries->pluck('id')->toArray(); + } } public function instantSave() @@ -433,6 +438,13 @@ class General extends Component $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); $showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!'); + if ($this->application->docker_use_custom_registry) { + $this->application->registries()->sync($this->application->selectedRegistries); + } else { + $this->application->registries()->detach(); + } + + $this->application->save(); } catch (\Throwable $e) { $originalFqdn = $this->application->getOriginal('fqdn'); if ($originalFqdn !== $this->application->fqdn) { diff --git a/app/Livewire/Project/New/DockerImage.php b/app/Livewire/Project/New/DockerImage.php index 7833a11b9..660c60b5c 100644 --- a/app/Livewire/Project/New/DockerImage.php +++ b/app/Livewire/Project/New/DockerImage.php @@ -14,13 +14,14 @@ class DockerImage extends Component { public string $dockerImage = ''; public bool $useCustomRegistry = false; - public ?int $selectedRegistry = null; + public array $selectedRegistries = []; public array $parameters; public array $query; protected $rules = [ 'dockerImage' => 'required|string', - 'selectedRegistry' => 'required_if:useCustomRegistry,true|nullable|exists:docker_registries,id' + 'selectedRegistries' => 'required_if:useCustomRegistry,true|array', + 'selectedRegistries.*' => 'exists:docker_registries,id' ]; public function mount() @@ -31,54 +32,68 @@ class DockerImage extends Component public function submit() { - $this->validate(['dockerImage' => 'required',]); + $this->validate([ + 'dockerImage' => 'required', + 'selectedRegistries' => 'required_if:useCustomRegistry,true|array', + 'selectedRegistries.*' => 'exists:docker_registries,id' + ]); - $image = str($this->dockerImage)->before(':'); - $tag = str($this->dockerImage)->contains(':') ? - str($this->dockerImage)->after(':') : - 'latest'; + try { + $image = str($this->dockerImage)->before(':'); + $tag = str($this->dockerImage)->contains(':') ? + str($this->dockerImage)->after(':') : + 'latest'; - $destination_uuid = $this->query['destination']; - $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); - if (! $destination) { - $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); + $destination_uuid = $this->query['destination']; + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (! $destination) { + $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); + } + if (! $destination) { + throw new \Exception('Destination not found. What?!'); + } + $destination_class = $destination->getMorphClass(); + + $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); + $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + + $name = 'docker-image-' . new Cuid2; + error_log($name); + $application = Application::create([ + 'name' => $name, + 'repository_project_id' => 0, + 'git_repository' => 'coollabsio/coolify', + 'git_branch' => 'main', + 'build_pack' => 'dockerimage', + 'ports_exposes' => 80, + 'docker_registry_image_name' => $image, + 'docker_registry_image_tag' => $tag, + 'docker_use_custom_registry' => $this->useCustomRegistry, + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination_class, + 'health_check_enabled' => false, + ]); + + // if ($this->useCustomRegistry && !empty($this->selectedRegistries)) { + // $application->registries()->attach($this->selectedRegistries); + // } + + error_log($application->uuid); + $fqdn = generateFqdn($destination->server, $application->uuid); + $application->update([ + 'name' => 'docker-image-' . $application->uuid, + 'fqdn' => $fqdn, + ]); + + return redirect()->route('project.application.configuration', [ + 'application_uuid' => $application->uuid, + 'environment_name' => $environment->name, + 'project_uuid' => $project->uuid, + ]); + } catch (\Exception $e) { + $this->dispatch('error', $e->getMessage()); } - if (! $destination) { - throw new \Exception('Destination not found. What?!'); - } - $destination_class = $destination->getMorphClass(); - - $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); - $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); - - $application = Application::create([ - 'name' => 'docker-image-' . new Cuid2, - 'repository_project_id' => 0, - 'git_repository' => 'coollabsio/coolify', - 'git_branch' => 'main', - 'build_pack' => 'dockerimage', - 'ports_exposes' => 80, - 'docker_registry_image_name' => $image, - 'docker_registry_image_tag' => $tag, - 'docker_use_custom_registry' => $this->useCustomRegistry, - 'docker_registry_id' => $this->selectedRegistry ?? null, - 'environment_id' => $environment->id, - 'destination_id' => $destination->id, - 'destination_type' => $destination_class, - 'health_check_enabled' => false, - ]); - - $fqdn = generateFqdn($destination->server, $application->uuid); - $application->update([ - 'name' => 'docker-image-' . $application->uuid, - 'fqdn' => $fqdn, - ]); - - return redirect()->route('project.application.configuration', [ - 'application_uuid' => $application->uuid, - 'environment_name' => $environment->name, - 'project_uuid' => $project->uuid, - ]); } public function render() diff --git a/app/Models/Application.php b/app/Models/Application.php index d45e0892e..8ece2d6e5 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -110,6 +110,15 @@ class Application extends BaseModel private static $parserVersion = '4'; + protected $fillable = [ + 'docker_use_custom_registry', + ]; + + protected $casts = [ + 'docker_use_custom_registry' => 'boolean', + 'selectedRegistries' => 'array', + ]; + protected $guarded = []; protected $appends = ['server_status']; @@ -1653,8 +1662,8 @@ class Application extends BaseModel } } - public function registry() + public function registries() { - return $this->belongsTo(DockerRegistry::class); + return $this->belongsToMany(DockerRegistry::class, 'application_docker_registry'); } } diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php index dd5ba66b7..e5a3ab19a 100644 --- a/app/View/Components/Forms/Select.php +++ b/app/View/Components/Forms/Select.php @@ -19,6 +19,7 @@ class Select extends Component public ?string $label = null, public ?string $helper = null, public bool $required = false, + public bool $multiple = false, public string $defaultClass = 'select' ) { // @@ -33,11 +34,13 @@ class Select extends Component $this->id = new Cuid2; } if (is_null($this->name)) { - $this->name = $this->id; + $this->name = $this->multiple ? "{$this->id}[]" : $this->id; } $this->label = Str::title($this->label); - return view('components.forms.select'); + return view('components.forms.select', [ + 'multiple' => $this->multiple, + ]); } } diff --git a/database/migrations/2024_11_08_084443_add_registry_to_applications_table.php b/database/migrations/2024_11_08_084443_add_registry_to_applications_table.php index c01657efe..89575c943 100644 --- a/database/migrations/2024_11_08_084443_add_registry_to_applications_table.php +++ b/database/migrations/2024_11_08_084443_add_registry_to_applications_table.php @@ -8,16 +8,7 @@ return new class extends Migration { public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->foreignId('docker_registry_id')->nullable(); $table->boolean('docker_use_custom_registry')->default(false); }); } - - public function down(): void - { - Schema::table('applications', function (Blueprint $table) { - $table->dropColumn('docker_registry_id'); - $table->dropColumn('docker_use_custom_registry'); - }); - } }; diff --git a/database/migrations/2024_11_10_203115_create_registries_table.php b/database/migrations/2024_11_7_203115_create_registries_table.php similarity index 63% rename from database/migrations/2024_11_10_203115_create_registries_table.php rename to database/migrations/2024_11_7_203115_create_registries_table.php index cc71abc06..336598a49 100644 --- a/database/migrations/2024_11_10_203115_create_registries_table.php +++ b/database/migrations/2024_11_7_203115_create_registries_table.php @@ -16,22 +16,10 @@ return new class extends Migration { $table->text('token')->nullable(); $table->timestamps(); }); - - // Add foreign key constraint - Schema::table('applications', function (Blueprint $table) { - $table->foreign('docker_registry_id') - ->references('id') - ->on('docker_registries') - ->nullOnDelete(); - }); } public function down(): void { - Schema::table('applications', function (Blueprint $table) { - $table->dropForeign(['docker_registry_id']); - }); - Schema::dropIfExists('docker_registries'); } }; diff --git a/database/migrations/2024_12_03_184606_create_application_docker_registry_table.php b/database/migrations/2024_12_03_184606_create_application_docker_registry_table.php new file mode 100644 index 000000000..dedae7067 --- /dev/null +++ b/database/migrations/2024_12_03_184606_create_application_docker_registry_table.php @@ -0,0 +1,24 @@ +id(); + $table->unsignedBigInteger('application_id'); + $table->unsignedBigInteger('docker_registry_id'); + $table->foreign('application_id')->references('id')->on('applications')->cascadeOnDelete(); + $table->foreign('docker_registry_id')->references('id')->on('docker_registries')->cascadeOnDelete(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('application_docker_registry'); + } +}; diff --git a/resources/views/components/forms/select.blade.php b/resources/views/components/forms/select.blade.php index 4da9eca1b..f57280295 100644 --- a/resources/views/components/forms/select.blade.php +++ b/resources/views/components/forms/select.blade.php @@ -1,6 +1,7 @@
@if ($label) -
@@ -187,8 +189,7 @@
Nixpacks will detect the required configuration automatically. - Framework + Framework Specific Docs
@endif diff --git a/resources/views/livewire/project/new/docker-image.blade.php b/resources/views/livewire/project/new/docker-image.blade.php index e9cbf6dfe..890139523 100644 --- a/resources/views/livewire/project/new/docker-image.blade.php +++ b/resources/views/livewire/project/new/docker-image.blade.php @@ -15,8 +15,8 @@ @if ($useCustomRegistry)
- - + @foreach ($registries as $registry) @endforeach