From d6882446649284c66d1c44fee15cfe51cf226b91 Mon Sep 17 00:00:00 2001 From: samirimtiaz1996 Date: Wed, 19 Jun 2024 00:59:39 +0600 Subject: [PATCH] add endpoints for filtering applications by domain and managing container labels - Add /api/v1/domains?uuid={application_uuid} endpoint (GET) to filter applications by domains for a given project UUID - Add /api/v1/domains endpoint (PUT) to update domains and regenerate container labels - Add /api/v1/domains endpoint (DELETE) to delete domains and regenerate container labels --- app/Http/Controllers/Api/Domains.php | 101 ++++++++++++++++++++++++++- app/Models/Application.php | 69 ++++++++++-------- routes/api.php | 3 +- 3 files changed, 141 insertions(+), 32 deletions(-) diff --git a/app/Http/Controllers/Api/Domains.php b/app/Http/Controllers/Api/Domains.php index c27ddf620..8e4e045a2 100644 --- a/app/Http/Controllers/Api/Domains.php +++ b/app/Http/Controllers/Api/Domains.php @@ -3,9 +3,11 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Models\Application; use App\Models\InstanceSettings; use App\Models\Project as ModelsProject; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Validator; class Domains extends Controller { @@ -15,6 +17,15 @@ public function domains(Request $request) if (is_null($teamId)) { return invalid_token(); } + $uuid = $request->query->get('uuid'); + if ($uuid) { + $domains = Application::getDomainsByUuid($uuid); + + return response()->json([ + 'uuid' => $uuid, + 'domains' => $domains, + ]); + } $projects = ModelsProject::where('team_id', $teamId)->get(); $domains = collect(); $applications = $projects->pluck('applications')->flatten(); @@ -38,7 +49,7 @@ public function domains(Request $request) 'ip' => $settings->public_ipv6, ]); } - if (! $settings->public_ipv4 && ! $settings->public_ipv6) { + if (!$settings->public_ipv4 && !$settings->public_ipv6) { $domains->push([ 'domain' => $fqdn, 'ip' => $ip, @@ -74,7 +85,7 @@ public function domains(Request $request) 'ip' => $settings->public_ipv6, ]); } - if (! $settings->public_ipv4 && ! $settings->public_ipv6) { + if (!$settings->public_ipv4 && !$settings->public_ipv6) { $domains->push([ 'domain' => $fqdn, 'ip' => $ip, @@ -101,4 +112,90 @@ public function domains(Request $request) return response()->json($domains); } + + public function updateDomains(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $validator = Validator::make($request->all(), [ + 'uuid' => 'required|string|exists:applications,uuid', + 'domains' => 'required|array', + 'domains.*' => 'required|string|distinct', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation failed', + 'errors' => $validator->errors() + ], 422); + } + + $application = Application::where('uuid', $request->uuid)->first(); + + if (!$application) { + return response()->json([ + 'success' => false, + 'message' => 'Application not found' + ], 404); + } + + $existingDomains = explode(',', $application->fqdn); + $newDomains = $request->domains; + $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) { + return !in_array($domain, $existingDomains); + }); + $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains)); + $application->fqdn = implode(',', $mergedDomains); + $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application))); + $application->save(); + return response()->json([ + 'success' => true, + 'message' => 'Domains updated successfully', + 'application' => $application + ]); + } + + public function deleteDomains(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $validator = Validator::make($request->all(), [ + 'uuid' => 'required|string|exists:applications,uuid', + 'domains' => 'required|array', + 'domains.*' => 'required|string|distinct', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validation failed', + 'errors' => $validator->errors() + ], 422); + } + + $application = Application::where('uuid', $request->uuid)->first(); + + if (!$application) { + return response()->json([ + 'success' => false, + 'message' => 'Application not found' + ], 404); + } + + $existingDomains = explode(',', $application->fqdn); + $domainsToDelete = $request->domains; + $updatedDomains = array_diff($existingDomains, $domainsToDelete); + $application->fqdn = implode(',', $updatedDomains); + $application->save(); + return response()->json([ + 'success' => true, + 'message' => 'Domains updated successfully', + 'application' => $application + ]); + } } diff --git a/app/Models/Application.php b/app/Models/Application.php index 6e55f6626..13925fb36 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -66,7 +66,7 @@ public function delete_configurations() $workdir = $this->workdir(); if (str($workdir)->endsWith($this->uuid)) { ray('Deleting workdir'); - instant_remote_process(['rm -rf '.$this->workdir()], $server, false); + instant_remote_process(['rm -rf ' . $this->workdir()], $server, false); } } @@ -165,7 +165,7 @@ public function type() public function publishDirectory(): Attribute { return Attribute::make( - set: fn ($value) => $value ? '/'.ltrim($value, '/') : null, + set: fn ($value) => $value ? '/' . ltrim($value, '/') : null, ); } @@ -173,7 +173,7 @@ public function gitBranchLocation(): Attribute { return Attribute::make( get: function () { - if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}"; } // Convert the SSH URL to HTTPS URL @@ -192,7 +192,7 @@ public function gitWebhook(): Attribute { return Attribute::make( get: function () { - if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/settings/hooks"; } // Convert the SSH URL to HTTPS URL @@ -211,7 +211,7 @@ public function gitCommits(): Attribute { return Attribute::make( get: function () { - if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}"; } // Convert the SSH URL to HTTPS URL @@ -228,7 +228,7 @@ public function gitCommits(): Attribute public function gitCommitLink($link): string { - if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { if (str($this->source->html_url)->contains('bitbucket')) { return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}"; } @@ -244,7 +244,7 @@ public function gitCommitLink($link): string $git_repository = str_replace('.git', '', $this->git_repository); $url = Url::fromString($git_repository); $url = $url->withUserInfo(''); - $url = $url->withPath($url->getPath().'/commits/'.$link); + $url = $url->withPath($url->getPath() . '/commits/' . $link); return $url->__toString(); } @@ -306,7 +306,7 @@ public function dockerComposePrLocation(): Attribute public function baseDirectory(): Attribute { return Attribute::make( - set: fn ($value) => '/'.ltrim($value, '/'), + set: fn ($value) => '/' . ltrim($value, '/'), ); } @@ -622,7 +622,7 @@ public function isHealthcheckDisabled(): bool public function workdir() { - return application_configuration_dir()."/{$this->uuid}"; + return application_configuration_dir() . "/{$this->uuid}"; } public function isLogDrainEnabled() @@ -632,7 +632,7 @@ public function isLogDrainEnabled() public function isConfigurationChanged(bool $save = false) { - $newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect; + $newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build . $this->redirect; if ($this->pull_request_id === 0 || $this->pull_request_id === null) { $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); } else { @@ -727,7 +727,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req if ($this->source->is_public) { $fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}"; - if (! $only_checkout) { + if (!$only_checkout) { $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true); } if ($exec_in_docker) { @@ -744,7 +744,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}"; $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; } - if (! $only_checkout) { + if (!$only_checkout) { $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false); } if ($exec_in_docker) { @@ -805,7 +805,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'github') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -813,14 +813,14 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit); + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit); } } @@ -849,7 +849,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'github') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -857,14 +857,14 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name); } elseif ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); } else { $commands->push("echo 'Checking out $branch'"); } - $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit); + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit); } } @@ -916,20 +916,20 @@ public function parseRawCompose() } if ($source->startsWith('.')) { $source = $source->after('.'); - $source = $workdir.$source; + $source = $workdir . $source; } $commands->push("mkdir -p $source > /dev/null 2>&1 || true"); } } } $labels = collect(data_get($service, 'labels', [])); - if (! $labels->contains('coolify.managed')) { + if (!$labels->contains('coolify.managed')) { $labels->push('coolify.managed=true'); } - if (! $labels->contains('coolify.applicationId')) { - $labels->push('coolify.applicationId='.$this->id); + if (!$labels->contains('coolify.applicationId')) { + $labels->push('coolify.applicationId=' . $this->id); } - if (! $labels->contains('coolify.type')) { + if (!$labels->contains('coolify.type')) { $labels->push('coolify.type=application'); } data_set($service, 'labels', $labels->toArray()); @@ -977,7 +977,7 @@ public function loadComposeFile($isInit = false) "cat .$workdir$composeFile", ]); $composeFileContent = instant_remote_process($commands, $this->destination->server, false); - if (! $composeFileContent) { + if (!$composeFileContent) { $this->docker_compose_location = $initialDockerComposeLocation; $this->save(); $commands = collect([ @@ -1001,7 +1001,7 @@ public function loadComposeFile($isInit = false) $jsonNames = $json->keys()->toArray(); $diff = array_diff($jsonNames, $names); $json = $json->filter(function ($value, $key) use ($diff) { - return ! in_array($key, $diff); + return !in_array($key, $diff); }); if ($json) { $this->docker_compose_domains = json_encode($json); @@ -1021,7 +1021,7 @@ public function loadComposeFile($isInit = false) public function parseContainerLabels(?ApplicationPreview $preview = null) { $customLabels = data_get($this, 'custom_labels'); - if (! $customLabels) { + if (!$customLabels) { return; } if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) { @@ -1104,10 +1104,10 @@ public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false continue; } if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) { - $healthcheckCommand .= ' '.trim($trimmedLine, '\\ '); + $healthcheckCommand .= ' ' . trim($trimmedLine, '\\ '); } - if (isset($healthcheckCommand) && ! str_contains($trimmedLine, '\\') && ! empty($healthcheckCommand)) { - $healthcheckCommand .= ' '.$trimmedLine; + if (isset($healthcheckCommand) && !str_contains($trimmedLine, '\\') && !empty($healthcheckCommand)) { + $healthcheckCommand .= ' ' . $trimmedLine; break; } } @@ -1167,4 +1167,15 @@ public function generate_preview_fqdn(int $pull_request_id) return $preview; } + + public static function getDomainsByUuid(string $uuid): array + { + $application = self::where('uuid', $uuid)->first(); + + if ($application) { + return $application->fqdns; + } + + return []; + } } diff --git a/routes/api.php b/routes/api.php index e5abaf86d..a2f8109cb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -39,7 +39,8 @@ Route::get('/resources', [Resources::class, 'resources']); Route::get('/domains', [Domains::class, 'domains']); - + Route::put('/domains', [Domains::class, 'updateDomains']); + Route::delete('/domains', [Domains::class, 'deleteDomains']); Route::get('/teams', [Team::class, 'teams']); Route::get('/team/current', [Team::class, 'current_team']); Route::get('/team/current/members', [Team::class, 'current_team_members']);