fix(api): application endpoint issues part 2 (#7948)
This commit is contained in:
commit
fbacf7076e
5 changed files with 537 additions and 149 deletions
|
|
@ -153,11 +153,14 @@ public function applications(Request $request)
|
|||
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application domains.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
|
||||
'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
|
||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
|
|
@ -198,6 +201,7 @@ public function applications(Request $request)
|
|||
// 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
|
||||
'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository.'],
|
||||
'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
|
||||
'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
|
||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||
|
|
@ -315,11 +319,14 @@ public function create_public_application(Request $request)
|
|||
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application domains.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
|
||||
'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
|
||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
|
|
@ -359,6 +366,7 @@ public function create_public_application(Request $request)
|
|||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
|
||||
'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository'],
|
||||
'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
|
||||
'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
|
||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||
|
|
@ -476,11 +484,14 @@ public function create_private_gh_app_application(Request $request)
|
|||
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application domains.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
|
||||
'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
|
||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
|
|
@ -520,6 +531,7 @@ public function create_private_gh_app_application(Request $request)
|
|||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
|
||||
'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository.'],
|
||||
'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
|
||||
'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
|
||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||
|
|
@ -635,7 +647,7 @@ public function create_private_deploy_key_application(Request $request)
|
|||
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application domains.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
|
||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||
'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
|
||||
|
|
@ -671,6 +683,7 @@ public function create_private_deploy_key_application(Request $request)
|
|||
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
|
||||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
|
|
@ -771,7 +784,7 @@ public function create_dockerfile_application(Request $request)
|
|||
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application domains.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
|
||||
'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
|
||||
'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
|
||||
'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
|
||||
|
|
@ -804,6 +817,7 @@ public function create_dockerfile_application(Request $request)
|
|||
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
|
||||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
|
|
@ -987,7 +1001,7 @@ private function create_application(Request $request, $type)
|
|||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', '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', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled'];
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', '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', 'dockerfile_location', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'autogenerate_domain', 'is_container_label_escape_enabled'];
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
|
|
@ -1030,6 +1044,9 @@ private function create_application(Request $request, $type)
|
|||
$githubAppUuid = $request->github_app_uuid;
|
||||
$useBuildServer = $request->use_build_server;
|
||||
$isStatic = $request->is_static;
|
||||
$isSpa = $request->is_spa;
|
||||
$isAutoDeployEnabled = $request->is_auto_deploy_enabled;
|
||||
$isForceHttpsEnabled = $request->is_force_https_enabled;
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$customNginxConfiguration = $request->custom_nginx_configuration;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true);
|
||||
|
|
@ -1130,39 +1147,63 @@ private function create_application(Request $request, $type)
|
|||
$dockerComposeDomainsJson = collect();
|
||||
if ($request->has('docker_compose_domains')) {
|
||||
$dockerComposeDomains = collect($request->docker_compose_domains);
|
||||
$domainErrors = [];
|
||||
|
||||
foreach ($dockerComposeDomains as $index => $item) {
|
||||
// Collect all URLs from all docker_compose_domains items
|
||||
$urls = $dockerComposeDomains->flatMap(function ($item) {
|
||||
$domainValue = data_get($item, 'domain');
|
||||
if (filled($domainValue)) {
|
||||
$urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
str($urls)->explode(',')->each(function ($url) use (&$domainErrors) {
|
||||
$url = trim($url);
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$domainErrors[] = "Invalid URL: {$url}";
|
||||
|
||||
return;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
});
|
||||
if (blank($domainValue)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter();
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return $url;
|
||||
});
|
||||
|
||||
$duplicates = $urls->duplicates()->unique()->values();
|
||||
if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) {
|
||||
$errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed.';
|
||||
}
|
||||
|
||||
if (! empty($domainErrors)) {
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'docker_compose_domains' => $domainErrors,
|
||||
],
|
||||
'errors' => ['docker_compose_domains' => $errors],
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Check for domain conflicts
|
||||
if ($urls->isNotEmpty()) {
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['docker_compose_domains' => $result['error']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) {
|
||||
return response()->json([
|
||||
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
|
||||
'conflicts' => $result['conflicts'],
|
||||
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
|
||||
$dockerComposeDomains->each(function ($domain) use ($dockerComposeDomainsJson) {
|
||||
$dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]);
|
||||
});
|
||||
|
|
@ -1187,6 +1228,18 @@ private function create_application(Request $request, $type)
|
|||
$application->settings->is_static = $isStatic;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isSpa)) {
|
||||
$application->settings->is_spa = $isSpa;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isAutoDeployEnabled)) {
|
||||
$application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isForceHttpsEnabled)) {
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
|
|
@ -1319,39 +1372,63 @@ private function create_application(Request $request, $type)
|
|||
$dockerComposeDomainsJson = collect();
|
||||
if ($request->has('docker_compose_domains')) {
|
||||
$dockerComposeDomains = collect($request->docker_compose_domains);
|
||||
$domainErrors = [];
|
||||
|
||||
foreach ($dockerComposeDomains as $index => $item) {
|
||||
// Collect all URLs from all docker_compose_domains items
|
||||
$urls = $dockerComposeDomains->flatMap(function ($item) {
|
||||
$domainValue = data_get($item, 'domain');
|
||||
if (filled($domainValue)) {
|
||||
$urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
str($urls)->explode(',')->each(function ($url) use (&$domainErrors) {
|
||||
$url = trim($url);
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$domainErrors[] = "Invalid URL: {$url}";
|
||||
|
||||
return;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
});
|
||||
if (blank($domainValue)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter();
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return $url;
|
||||
});
|
||||
|
||||
$duplicates = $urls->duplicates()->unique()->values();
|
||||
if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) {
|
||||
$errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed. ';
|
||||
}
|
||||
|
||||
if (! empty($domainErrors)) {
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'docker_compose_domains' => $domainErrors,
|
||||
],
|
||||
'errors' => ['docker_compose_domains' => $errors],
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Check for domain conflicts
|
||||
if ($urls->isNotEmpty()) {
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['docker_compose_domains' => $result['error']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) {
|
||||
return response()->json([
|
||||
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
|
||||
'conflicts' => $result['conflicts'],
|
||||
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
|
||||
$dockerComposeDomains->each(function ($domain) use ($dockerComposeDomainsJson) {
|
||||
$dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]);
|
||||
});
|
||||
|
|
@ -1376,6 +1453,26 @@ private function create_application(Request $request, $type)
|
|||
$application->fqdn = generateUrl(server: $server, random: $application->uuid);
|
||||
$application->save();
|
||||
}
|
||||
if (isset($isStatic)) {
|
||||
$application->settings->is_static = $isStatic;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isSpa)) {
|
||||
$application->settings->is_spa = $isSpa;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isAutoDeployEnabled)) {
|
||||
$application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isForceHttpsEnabled)) {
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
|
|
@ -1476,39 +1573,63 @@ private function create_application(Request $request, $type)
|
|||
$dockerComposeDomainsJson = collect();
|
||||
if ($request->has('docker_compose_domains')) {
|
||||
$dockerComposeDomains = collect($request->docker_compose_domains);
|
||||
$domainErrors = [];
|
||||
|
||||
foreach ($dockerComposeDomains as $index => $item) {
|
||||
// Collect all URLs from all docker_compose_domains items
|
||||
$urls = $dockerComposeDomains->flatMap(function ($item) {
|
||||
$domainValue = data_get($item, 'domain');
|
||||
if (filled($domainValue)) {
|
||||
$urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
str($urls)->explode(',')->each(function ($url) use (&$domainErrors) {
|
||||
$url = trim($url);
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$domainErrors[] = "Invalid URL: {$url}";
|
||||
|
||||
return;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
});
|
||||
if (blank($domainValue)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter();
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return $url;
|
||||
});
|
||||
|
||||
$duplicates = $urls->duplicates()->unique()->values();
|
||||
if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) {
|
||||
$errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed.';
|
||||
}
|
||||
|
||||
if (! empty($domainErrors)) {
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'docker_compose_domains' => $domainErrors,
|
||||
],
|
||||
'errors' => ['docker_compose_domains' => $errors],
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Check for domain conflicts
|
||||
if ($urls->isNotEmpty()) {
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['docker_compose_domains' => $result['error']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) {
|
||||
return response()->json([
|
||||
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
|
||||
'conflicts' => $result['conflicts'],
|
||||
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
|
||||
$dockerComposeDomains->each(function ($domain) use ($dockerComposeDomainsJson) {
|
||||
$dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]);
|
||||
});
|
||||
|
|
@ -1529,6 +1650,26 @@ private function create_application(Request $request, $type)
|
|||
$application->fqdn = generateUrl(server: $server, random: $application->uuid);
|
||||
$application->save();
|
||||
}
|
||||
if (isset($isStatic)) {
|
||||
$application->settings->is_static = $isStatic;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isSpa)) {
|
||||
$application->settings->is_spa = $isSpa;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isAutoDeployEnabled)) {
|
||||
$application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isForceHttpsEnabled)) {
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
|
|
@ -1632,6 +1773,14 @@ private function create_application(Request $request, $type)
|
|||
$application->fqdn = generateUrl(server: $server, random: $application->uuid);
|
||||
$application->save();
|
||||
}
|
||||
if (isset($isForceHttpsEnabled)) {
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
|
|
@ -1734,6 +1883,14 @@ private function create_application(Request $request, $type)
|
|||
$application->fqdn = generateUrl(server: $server, random: $application->uuid);
|
||||
$application->save();
|
||||
}
|
||||
if (isset($isForceHttpsEnabled)) {
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
|
|
@ -2150,11 +2307,14 @@ public function delete_by_uuid(Request $request)
|
|||
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
|
||||
'name' => ['type' => 'string', 'description' => 'The application name.'],
|
||||
'description' => ['type' => 'string', 'description' => 'The application description.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application domains.'],
|
||||
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
|
||||
'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
|
||||
'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
|
||||
'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
|
||||
'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
||||
|
|
@ -2193,6 +2353,7 @@ public function delete_by_uuid(Request $request)
|
|||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
|
||||
'dockerfile_location' => ['type' => 'string', 'description' => 'The Dockerfile location in the repository.'],
|
||||
'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
|
||||
'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
|
||||
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
|
||||
|
|
@ -2297,7 +2458,7 @@ public function update_by_uuid(Request $request)
|
|||
$this->authorize('update', $application);
|
||||
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings','custom_network_aliases', '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', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled'];
|
||||
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'custom_network_aliases', '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', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'dockerfile_location', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override', 'is_container_label_escape_enabled'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
|
|
@ -2411,18 +2572,30 @@ public function update_by_uuid(Request $request)
|
|||
$requestHasDomains = $request->has('domains');
|
||||
if ($requestHasDomains && $server->isProxyShouldRun()) {
|
||||
$uuid = $request->uuid;
|
||||
$fqdn = $request->domains;
|
||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||
$urls = $request->domains;
|
||||
$urls = str($urls)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
$errors = [];
|
||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||
$domain = trim($domain);
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL) === false || ! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) {
|
||||
$errors[] = 'Invalid domain: '.$domain;
|
||||
$urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) {
|
||||
$url = trim($url);
|
||||
|
||||
// If "domains" is empty clear all URLs from the fqdn column
|
||||
if (blank($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $domain;
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = 'Invalid URL: '.$url;
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return str($url)->lower();
|
||||
});
|
||||
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
|
|
@ -2430,7 +2603,7 @@ public function update_by_uuid(Request $request)
|
|||
], 422);
|
||||
}
|
||||
// Check for domain conflicts
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($fqdn, $teamId, $uuid);
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $uuid);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
|
|
@ -2460,39 +2633,63 @@ public function update_by_uuid(Request $request)
|
|||
}
|
||||
|
||||
$dockerComposeDomains = collect($request->docker_compose_domains);
|
||||
$domainErrors = [];
|
||||
|
||||
foreach ($dockerComposeDomains as $item) {
|
||||
// Collect all URLs from all docker_compose_domains items
|
||||
$urls = $dockerComposeDomains->flatMap(function ($item) {
|
||||
$domainValue = data_get($item, 'domain');
|
||||
if (filled($domainValue)) {
|
||||
$urls = str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
str($urls)->explode(',')->each(function ($url) use (&$domainErrors) {
|
||||
$url = trim($url);
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$domainErrors[] = "Invalid URL: {$url}";
|
||||
|
||||
return;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$domainErrors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
});
|
||||
if (blank($domainValue)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return str($domainValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter();
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return $url;
|
||||
});
|
||||
|
||||
$duplicates = $urls->duplicates()->unique()->values();
|
||||
if ($duplicates->isNotEmpty() && ! $request->boolean('force_domain_override')) {
|
||||
$errors[] = 'The current request contains conflicting URLs: '.implode(', ', $duplicates->toArray()).' Use force_domain_override=true to proceed.';
|
||||
}
|
||||
|
||||
if (! empty($domainErrors)) {
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'docker_compose_domains' => $domainErrors,
|
||||
],
|
||||
'errors' => ['docker_compose_domains' => $errors],
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Check for domain conflicts
|
||||
if ($urls->isNotEmpty()) {
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $request->uuid);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['docker_compose_domains' => $result['error']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) {
|
||||
return response()->json([
|
||||
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
|
||||
'conflicts' => $result['conflicts'],
|
||||
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
|
||||
$yaml = Yaml::parse($application->docker_compose_raw);
|
||||
$services = data_get($yaml, 'services', []);
|
||||
$dockerComposeDomains->each(function ($domain) use ($services, $dockerComposeDomainsJson) {
|
||||
|
|
@ -2505,6 +2702,9 @@ public function update_by_uuid(Request $request)
|
|||
}
|
||||
$instantDeploy = $request->instant_deploy;
|
||||
$isStatic = $request->is_static;
|
||||
$isSpa = $request->is_spa;
|
||||
$isAutoDeployEnabled = $request->is_auto_deploy_enabled;
|
||||
$isForceHttpsEnabled = $request->is_force_https_enabled;
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$useBuildServer = $request->use_build_server;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled');
|
||||
|
|
@ -2519,6 +2719,21 @@ public function update_by_uuid(Request $request)
|
|||
$application->settings->save();
|
||||
}
|
||||
|
||||
if (isset($isSpa)) {
|
||||
$application->settings->is_spa = $isSpa;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
if (isset($isAutoDeployEnabled)) {
|
||||
$application->settings->is_auto_deploy_enabled = $isAutoDeployEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
if (isset($isForceHttpsEnabled)) {
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
|
|
@ -3626,17 +3841,29 @@ private function validateDataApplications(Request $request, Server $server)
|
|||
}
|
||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||
$uuid = $request->uuid;
|
||||
$fqdn = $request->domains;
|
||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||
$urls = $request->domains;
|
||||
$urls = str($urls)->replaceEnd(',', '')->trim();
|
||||
$urls = str($urls)->replaceStart(',', '')->trim();
|
||||
$errors = [];
|
||||
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||
$domain = trim($domain);
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
||||
$errors[] = 'Invalid domain: '.$domain;
|
||||
$urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) {
|
||||
$url = trim($url);
|
||||
|
||||
// If "domains" is empty clear all URLs from the fqdn column
|
||||
if (blank($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str($domain)->lower();
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = 'Invalid URL: '.$url;
|
||||
|
||||
return str($url)->lower();
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return str($url)->lower();
|
||||
});
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
|
|
@ -3645,7 +3872,7 @@ private function validateDataApplications(Request $request, Server $server)
|
|||
], 422);
|
||||
}
|
||||
// Check for domain conflicts
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($fqdn, $teamId, $uuid);
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $uuid);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
|
|
|
|||
|
|
@ -86,8 +86,11 @@ function sharedDataApplications()
|
|||
'git_branch' => 'string',
|
||||
'build_pack' => Rule::enum(BuildPackTypes::class),
|
||||
'is_static' => 'boolean',
|
||||
'is_spa' => 'boolean',
|
||||
'is_auto_deploy_enabled' => 'boolean',
|
||||
'is_force_https_enabled' => 'boolean',
|
||||
'static_image' => Rule::enum(StaticImageTypes::class),
|
||||
'domains' => 'string',
|
||||
'domains' => 'string|nullable',
|
||||
'redirect' => Rule::enum(RedirectTypes::class),
|
||||
'git_commit_sha' => 'string',
|
||||
'docker_registry_image_name' => 'string|nullable',
|
||||
|
|
@ -129,6 +132,7 @@ function sharedDataApplications()
|
|||
'manual_webhook_secret_gitlab' => 'string|nullable',
|
||||
'manual_webhook_secret_bitbucket' => 'string|nullable',
|
||||
'manual_webhook_secret_gitea' => 'string|nullable',
|
||||
'dockerfile_location' => 'string|nullable',
|
||||
'docker_compose_location' => 'string',
|
||||
'docker_compose' => 'string|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
|
|
@ -177,6 +181,10 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
|
|||
$request->offsetUnset('private_key_uuid');
|
||||
$request->offsetUnset('use_build_server');
|
||||
$request->offsetUnset('is_static');
|
||||
$request->offsetUnset('is_spa');
|
||||
$request->offsetUnset('is_auto_deploy_enabled');
|
||||
$request->offsetUnset('is_force_https_enabled');
|
||||
$request->offsetUnset('connect_to_docker_network');
|
||||
$request->offsetUnset('force_domain_override');
|
||||
$request->offsetUnset('autogenerate_domain');
|
||||
$request->offsetUnset('is_container_label_escape_enabled');
|
||||
|
|
|
|||
|
|
@ -158,8 +158,7 @@ function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $te
|
|||
return str($domain);
|
||||
});
|
||||
|
||||
// Check applications within the same team
|
||||
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id']);
|
||||
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id', 'docker_compose_domains', 'build_pack']);
|
||||
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->with('service:id,name')->get(['fqdn', 'uuid', 'id', 'service_id']);
|
||||
|
||||
if ($uuid) {
|
||||
|
|
@ -168,23 +167,51 @@ function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $te
|
|||
}
|
||||
|
||||
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('/');
|
||||
if (! is_null($app->fqdn)) {
|
||||
$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)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->name,
|
||||
'resource_uuid' => $app->uuid,
|
||||
'resource_type' => 'application',
|
||||
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
|
||||
];
|
||||
}
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->name,
|
||||
'resource_uuid' => $app->uuid,
|
||||
'resource_type' => 'application',
|
||||
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
|
||||
];
|
||||
}
|
||||
|
||||
if ($app->build_pack === 'dockercompose' && ! empty($app->docker_compose_domains)) {
|
||||
$dockerComposeDomains = json_decode($app->docker_compose_domains, true);
|
||||
if (is_array($dockerComposeDomains)) {
|
||||
foreach ($dockerComposeDomains as $serviceName => $domainConfig) {
|
||||
$domainValue = data_get($domainConfig, 'domain');
|
||||
if (empty($domainValue)) {
|
||||
continue;
|
||||
}
|
||||
$list_of_domains = collect(explode(',', $domainValue))->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)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->name,
|
||||
'resource_uuid' => $app->uuid,
|
||||
'resource_type' => 'application',
|
||||
'service_name' => $serviceName,
|
||||
'message' => "Domain $naked_domain is already in use by application '{$app->name}' (service: {$serviceName})",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
84
openapi.json
84
openapi.json
|
|
@ -135,7 +135,7 @@
|
|||
},
|
||||
"domains": {
|
||||
"type": "string",
|
||||
"description": "The application domains."
|
||||
"description": "The application URLs in a comma-separated list."
|
||||
},
|
||||
"git_commit_sha": {
|
||||
"type": "string",
|
||||
|
|
@ -153,6 +153,18 @@
|
|||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is static."
|
||||
},
|
||||
"is_spa": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
|
||||
},
|
||||
"is_auto_deploy_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
|
||||
},
|
||||
"is_force_https_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
|
||||
},
|
||||
"static_image": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -322,6 +334,10 @@
|
|||
"type": "string",
|
||||
"description": "The Dockerfile content."
|
||||
},
|
||||
"dockerfile_location": {
|
||||
"type": "string",
|
||||
"description": "The Dockerfile location in the repository."
|
||||
},
|
||||
"docker_compose_location": {
|
||||
"type": "string",
|
||||
"description": "The Docker Compose location."
|
||||
|
|
@ -564,7 +580,7 @@
|
|||
},
|
||||
"domains": {
|
||||
"type": "string",
|
||||
"description": "The application domains."
|
||||
"description": "The application URLs in a comma-separated list."
|
||||
},
|
||||
"git_commit_sha": {
|
||||
"type": "string",
|
||||
|
|
@ -582,6 +598,18 @@
|
|||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is static."
|
||||
},
|
||||
"is_spa": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
|
||||
},
|
||||
"is_auto_deploy_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
|
||||
},
|
||||
"is_force_https_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
|
||||
},
|
||||
"static_image": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -751,6 +779,10 @@
|
|||
"type": "string",
|
||||
"description": "The Dockerfile content."
|
||||
},
|
||||
"dockerfile_location": {
|
||||
"type": "string",
|
||||
"description": "The Dockerfile location in the repository"
|
||||
},
|
||||
"docker_compose_location": {
|
||||
"type": "string",
|
||||
"description": "The Docker Compose location."
|
||||
|
|
@ -993,7 +1025,7 @@
|
|||
},
|
||||
"domains": {
|
||||
"type": "string",
|
||||
"description": "The application domains."
|
||||
"description": "The application URLs in a comma-separated list."
|
||||
},
|
||||
"git_commit_sha": {
|
||||
"type": "string",
|
||||
|
|
@ -1011,6 +1043,18 @@
|
|||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is static."
|
||||
},
|
||||
"is_spa": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
|
||||
},
|
||||
"is_auto_deploy_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
|
||||
},
|
||||
"is_force_https_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
|
||||
},
|
||||
"static_image": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -1180,6 +1224,10 @@
|
|||
"type": "string",
|
||||
"description": "The Dockerfile content."
|
||||
},
|
||||
"dockerfile_location": {
|
||||
"type": "string",
|
||||
"description": "The Dockerfile location in the repository."
|
||||
},
|
||||
"docker_compose_location": {
|
||||
"type": "string",
|
||||
"description": "The Docker Compose location."
|
||||
|
|
@ -1410,7 +1458,7 @@
|
|||
},
|
||||
"domains": {
|
||||
"type": "string",
|
||||
"description": "The application domains."
|
||||
"description": "The application URLs in a comma-separated list."
|
||||
},
|
||||
"docker_registry_image_name": {
|
||||
"type": "string",
|
||||
|
|
@ -1562,6 +1610,10 @@
|
|||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application should be deployed instantly."
|
||||
},
|
||||
"is_force_https_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
|
||||
},
|
||||
"use_build_server": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
|
|
@ -1754,7 +1806,7 @@
|
|||
},
|
||||
"domains": {
|
||||
"type": "string",
|
||||
"description": "The application domains."
|
||||
"description": "The application URLs in a comma-separated list."
|
||||
},
|
||||
"ports_mappings": {
|
||||
"type": "string",
|
||||
|
|
@ -1894,6 +1946,10 @@
|
|||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application should be deployed instantly."
|
||||
},
|
||||
"is_force_https_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
|
||||
},
|
||||
"use_build_server": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
|
|
@ -2402,7 +2458,7 @@
|
|||
},
|
||||
"domains": {
|
||||
"type": "string",
|
||||
"description": "The application domains."
|
||||
"description": "The application URLs in a comma-separated list."
|
||||
},
|
||||
"git_commit_sha": {
|
||||
"type": "string",
|
||||
|
|
@ -2420,6 +2476,18 @@
|
|||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is static."
|
||||
},
|
||||
"is_spa": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true."
|
||||
},
|
||||
"is_auto_deploy_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if auto-deploy is enabled on git push. Defaults to true."
|
||||
},
|
||||
"is_force_https_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to indicate if HTTPS is forced. Defaults to true."
|
||||
},
|
||||
"install_command": {
|
||||
"type": "string",
|
||||
"description": "The install command."
|
||||
|
|
@ -2582,6 +2650,10 @@
|
|||
"type": "string",
|
||||
"description": "The Dockerfile content."
|
||||
},
|
||||
"dockerfile_location": {
|
||||
"type": "string",
|
||||
"description": "The Dockerfile location in the repository."
|
||||
},
|
||||
"docker_compose_location": {
|
||||
"type": "string",
|
||||
"description": "The Docker Compose location."
|
||||
|
|
|
|||
66
openapi.yaml
66
openapi.yaml
|
|
@ -97,7 +97,7 @@ paths:
|
|||
description: 'The application description.'
|
||||
domains:
|
||||
type: string
|
||||
description: 'The application domains.'
|
||||
description: 'The application URLs in a comma-separated list.'
|
||||
git_commit_sha:
|
||||
type: string
|
||||
description: 'The git commit SHA.'
|
||||
|
|
@ -110,6 +110,15 @@ paths:
|
|||
is_static:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is static.'
|
||||
is_spa:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
|
||||
is_auto_deploy_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
|
||||
is_force_https_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
|
||||
static_image:
|
||||
type: string
|
||||
enum: ['nginx:alpine']
|
||||
|
|
@ -234,6 +243,9 @@ paths:
|
|||
dockerfile:
|
||||
type: string
|
||||
description: 'The Dockerfile content.'
|
||||
dockerfile_location:
|
||||
type: string
|
||||
description: 'The Dockerfile location in the repository.'
|
||||
docker_compose_location:
|
||||
type: string
|
||||
description: 'The Docker Compose location.'
|
||||
|
|
@ -369,7 +381,7 @@ paths:
|
|||
description: 'The application description.'
|
||||
domains:
|
||||
type: string
|
||||
description: 'The application domains.'
|
||||
description: 'The application URLs in a comma-separated list.'
|
||||
git_commit_sha:
|
||||
type: string
|
||||
description: 'The git commit SHA.'
|
||||
|
|
@ -382,6 +394,15 @@ paths:
|
|||
is_static:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is static.'
|
||||
is_spa:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
|
||||
is_auto_deploy_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
|
||||
is_force_https_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
|
||||
static_image:
|
||||
type: string
|
||||
enum: ['nginx:alpine']
|
||||
|
|
@ -506,6 +527,9 @@ paths:
|
|||
dockerfile:
|
||||
type: string
|
||||
description: 'The Dockerfile content.'
|
||||
dockerfile_location:
|
||||
type: string
|
||||
description: 'The Dockerfile location in the repository'
|
||||
docker_compose_location:
|
||||
type: string
|
||||
description: 'The Docker Compose location.'
|
||||
|
|
@ -641,7 +665,7 @@ paths:
|
|||
description: 'The application description.'
|
||||
domains:
|
||||
type: string
|
||||
description: 'The application domains.'
|
||||
description: 'The application URLs in a comma-separated list.'
|
||||
git_commit_sha:
|
||||
type: string
|
||||
description: 'The git commit SHA.'
|
||||
|
|
@ -654,6 +678,15 @@ paths:
|
|||
is_static:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is static.'
|
||||
is_spa:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
|
||||
is_auto_deploy_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
|
||||
is_force_https_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
|
||||
static_image:
|
||||
type: string
|
||||
enum: ['nginx:alpine']
|
||||
|
|
@ -778,6 +811,9 @@ paths:
|
|||
dockerfile:
|
||||
type: string
|
||||
description: 'The Dockerfile content.'
|
||||
dockerfile_location:
|
||||
type: string
|
||||
description: 'The Dockerfile location in the repository.'
|
||||
docker_compose_location:
|
||||
type: string
|
||||
description: 'The Docker Compose location.'
|
||||
|
|
@ -903,7 +939,7 @@ paths:
|
|||
description: 'The application description.'
|
||||
domains:
|
||||
type: string
|
||||
description: 'The application domains.'
|
||||
description: 'The application URLs in a comma-separated list.'
|
||||
docker_registry_image_name:
|
||||
type: string
|
||||
description: 'The docker registry image name.'
|
||||
|
|
@ -1015,6 +1051,9 @@ paths:
|
|||
instant_deploy:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application should be deployed instantly.'
|
||||
is_force_https_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
|
||||
use_build_server:
|
||||
type: boolean
|
||||
nullable: true
|
||||
|
|
@ -1124,7 +1163,7 @@ paths:
|
|||
description: 'The application description.'
|
||||
domains:
|
||||
type: string
|
||||
description: 'The application domains.'
|
||||
description: 'The application URLs in a comma-separated list.'
|
||||
ports_mappings:
|
||||
type: string
|
||||
description: 'The ports mappings.'
|
||||
|
|
@ -1227,6 +1266,9 @@ paths:
|
|||
instant_deploy:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application should be deployed instantly.'
|
||||
is_force_https_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
|
||||
use_build_server:
|
||||
type: boolean
|
||||
nullable: true
|
||||
|
|
@ -1524,7 +1566,7 @@ paths:
|
|||
description: 'The application description.'
|
||||
domains:
|
||||
type: string
|
||||
description: 'The application domains.'
|
||||
description: 'The application URLs in a comma-separated list.'
|
||||
git_commit_sha:
|
||||
type: string
|
||||
description: 'The git commit SHA.'
|
||||
|
|
@ -1537,6 +1579,15 @@ paths:
|
|||
is_static:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is static.'
|
||||
is_spa:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'
|
||||
is_auto_deploy_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'
|
||||
is_force_https_enabled:
|
||||
type: boolean
|
||||
description: 'The flag to indicate if HTTPS is forced. Defaults to true.'
|
||||
install_command:
|
||||
type: string
|
||||
description: 'The install command.'
|
||||
|
|
@ -1657,6 +1708,9 @@ paths:
|
|||
dockerfile:
|
||||
type: string
|
||||
description: 'The Dockerfile content.'
|
||||
dockerfile_location:
|
||||
type: string
|
||||
description: 'The Dockerfile location in the repository.'
|
||||
docker_compose_location:
|
||||
type: string
|
||||
description: 'The Docker Compose location.'
|
||||
|
|
|
|||
Loading…
Reference in a new issue