fix: check domain on new app via api

This commit is contained in:
Andras Bacsai 2024-07-01 11:39:10 +02:00
parent b86924bc0e
commit dbc235d84a
4 changed files with 166 additions and 5 deletions

View File

@ -37,7 +37,7 @@ public function create_application(Request $request)
{ {
ray()->clearAll(); ray()->clearAll();
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy']; $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile'];
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
if (is_null($teamId)) { if (is_null($teamId)) {
return invalid_token(); return invalid_token();
@ -94,9 +94,6 @@ public function create_application(Request $request)
if (! $environment) { if (! $environment) {
return response()->json(['error' => 'Environment not found.'], 404); return response()->json(['error' => 'Environment not found.'], 404);
} }
if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
}
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) { if (! $server) {
return response()->json(['error' => 'Server not found.'], 404); return response()->json(['error' => 'Server not found.'], 404);
@ -110,6 +107,9 @@ public function create_application(Request $request)
} }
$destination = $destinations->first(); $destination = $destinations->first();
if ($type === 'public') { if ($type === 'public') {
if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
}
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
'git_repository' => 'string|required', 'git_repository' => 'string|required',
@ -151,6 +151,9 @@ public function create_application(Request $request)
return response()->json(serialize_api_response($application)); return response()->json(serialize_api_response($application));
} elseif ($type === 'private-gh-app') { } elseif ($type === 'private-gh-app') {
if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
}
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
'git_repository' => 'string|required', 'git_repository' => 'string|required',
@ -204,6 +207,9 @@ public function create_application(Request $request)
return response()->json(serialize_api_response($application)); return response()->json(serialize_api_response($application));
} elseif ($type === 'private-deploy-key') { } elseif ($type === 'private-deploy-key') {
if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
}
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
'git_repository' => 'string|required', 'git_repository' => 'string|required',
@ -249,6 +255,75 @@ public function create_application(Request $request)
); );
} }
return response()->json(serialize_api_response($application));
} elseif ($type === 'dockerfile') {
if (! $request->has('name')) {
$request->offsetSet('name', 'dockerfile-'.new Cuid2(7));
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
'dockerfile' => 'string|required',
]);
if ($validator->fails()) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
}
$return = $this->validateDataApplications($request, $server);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
if (! isBase64Encoded($request->dockerfile)) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'dockerfile' => 'The dockerfile should be base64 encoded.',
],
], 422);
}
$dockerFile = base64_decode($request->dockerfile);
if (mb_detect_encoding($dockerFile, 'ASCII', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'dockerfile' => 'The dockerfile should be base64 encoded.',
],
], 422);
}
$dockerFile = base64_decode($request->dockerfile);
$this->removeUnnecessaryFieldsFromRequest($request);
$port = get_port_from_dockerfile($request->dockerfile);
if (! $port) {
$port = 80;
}
$application = new Application();
$application->fill($request->all());
$application->fqdn = $fqdn;
$application->ports_exposes = $port;
$application->build_pack = 'dockerfile';
$application->dockerfile = $dockerFile;
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
$application->save();
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
no_questions_asked: true,
is_api: true,
);
}
return response()->json(serialize_api_response($application)); return response()->json(serialize_api_response($application));
} }
@ -826,6 +901,8 @@ private function removeUnnecessaryFieldsFromRequest(Request $request)
private function validateDataApplications(Request $request, Server $server) private function validateDataApplications(Request $request, Server $server)
{ {
$teamId = get_team_id_from_token();
// Validate ports_mappings // Validate ports_mappings
if ($request->has('ports_mappings')) { if ($request->has('ports_mappings')) {
$ports = []; $ports = [];
@ -881,6 +958,14 @@ private function validateDataApplications(Request $request, Server $server)
'errors' => $errors, 'errors' => $errors,
], 422); ], 422);
} }
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId)) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'domains' => 'One of the domain is already used.',
],
], 422);
}
} }
} }
} }

View File

@ -502,7 +502,7 @@ public function checkSentinel()
$sentinel_found = json_decode($sentinel_found, true); $sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited'); $status = data_get($sentinel_found, '0.State.Status', 'exited');
if ($status !== 'running') { if ($status !== 'running') {
ray('Sentinel is not running, starting it...'); // ray('Sentinel is not running, starting it...');
PullSentinelImageJob::dispatch($this); PullSentinelImageJob::dispatch($this);
} else { } else {
// ray('Sentinel is running'); // ray('Sentinel is running');

View File

@ -27,6 +27,11 @@ public function restart()
instant_remote_process(["docker restart {$container_id}"], $this->service->server); instant_remote_process(["docker restart {$container_id}"], $this->service->server);
} }
public static function ownedByCurrentTeamAPI(int $teamId)
{
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
}
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {
return data_get($this, 'is_log_drain_enabled', false); return data_get($this, 'is_log_drain_enabled', false);

View File

@ -56,6 +56,8 @@
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use function PHPUnit\Framework\isEmpty;
function base_configuration_dir(): string function base_configuration_dir(): string
{ {
return '/data/coolify'; return '/data/coolify';
@ -2129,6 +2131,75 @@ function ip_match($ip, $cidrs, &$match = null)
return false; return false;
} }
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null)
{
if (is_null($teamId)) {
return response()->json(['error' => 'Team ID is required.'], 400);
}
if (is_array($domains)) {
$domains = collect($domains);
}
$domains = $domains->map(function ($domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
return str($domain);
});
$applications = Application::ownedByCurrentTeamAPI($teamId)->get('fqdn');
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get('fqdn');
$domainFound = false;
foreach ($applications as $app) {
if (is_null($app->fqdn)) {
continue;
}
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
$domainFound = true;
break;
}
}
}
if ($domainFound) {
return true;
}
foreach ($serviceApplications as $app) {
if (isEmpty($app->fqdn)) {
continue;
}
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
foreach ($list_of_domains as $domain) {
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
$domainFound = true;
break;
}
}
}
if ($domainFound) {
return true;
}
$settings = InstanceSettings::get();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
$domain = str($domain)->beforeLast('/');
}
$naked_domain = str($domain)->value();
if ($domains->contains($naked_domain)) {
return true;
}
}
}
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null) function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
{ {
if ($resource) { if ($resource) {