feat(api): add application settings to application endpoints

This commit is contained in:
Andras Bacsai 2026-07-14 13:44:45 +02:00
parent 99e255a572
commit 34e6a6dd5d
8 changed files with 1594 additions and 12 deletions

View file

@ -35,6 +35,36 @@ class ApplicationsController extends Controller
{
use Concerns\HandlesTagsApi;
private const APPLICATION_SETTING_FIELDS = [
'is_git_submodules_enabled',
'is_git_lfs_enabled',
'is_git_shallow_clone_enabled',
'disable_build_cache',
'inject_build_args_to_dockerfile',
'include_source_commit_in_build',
'is_env_sorting_enabled',
'is_pr_deployments_public_enabled',
'stop_grace_period',
'docker_images_to_keep',
'is_gzip_enabled',
'is_stripprefix_enabled',
'is_raw_compose_deployment_enabled',
];
private const BOOLEAN_APPLICATION_SETTING_FIELDS = [
'is_git_submodules_enabled',
'is_git_lfs_enabled',
'is_git_shallow_clone_enabled',
'disable_build_cache',
'inject_build_args_to_dockerfile',
'include_source_commit_in_build',
'is_env_sorting_enabled',
'is_pr_deployments_public_enabled',
'is_gzip_enabled',
'is_stripprefix_enabled',
'is_raw_compose_deployment_enabled',
];
protected function findTaggableResource(string $uuid, int|string $teamId): mixed
{
return Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $uuid)->first();
@ -87,9 +117,48 @@ private function removeSensitiveData($application)
$application->makeHidden(['value', 'real_value']);
}
if ($application->relationLoaded('settings')) {
$application->settings?->makeHidden(['id', 'application_id', 'created_at', 'updated_at']);
}
return serializeApiResponse($application);
}
private function applicationSettingsFromRequest(Request $request): array
{
$settings = [];
foreach (self::APPLICATION_SETTING_FIELDS as $field) {
if (! array_key_exists($field, $request->all())) {
continue;
}
$settings[$field] = in_array($field, self::BOOLEAN_APPLICATION_SETTING_FIELDS, true)
? $request->boolean($field)
: $request->input($field);
}
return $settings;
}
private function applyApplicationSettings(Application $application, array $settings): void
{
if ($settings === []) {
return;
}
$regenerateLabels = ! $application->wasRecentlyCreated
&& $application->settings->is_container_label_readonly_enabled
&& (array_key_exists('is_gzip_enabled', $settings) || array_key_exists('is_stripprefix_enabled', $settings));
$application->settings->fill($settings)->save();
if ($regenerateLabels) {
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
$application->save();
}
}
/**
* Expose sensitive fields on eager-loaded nested Server + ServerSetting
* relations for callers with the `read:sensitive` or `root` token ability.
@ -285,6 +354,20 @@ public function applications(Request $request)
],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'use_build_secrets' => ['type' => 'boolean', 'default' => false, 'description' => 'Use Docker Build Secrets for build-time environment variables.'],
'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'],
'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'],
'disable_build_cache' => ['type' => 'boolean', 'description' => 'Disable the build cache.'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
'is_gzip_enabled' => ['type' => 'boolean', 'description' => 'Enable gzip compression.'],
'is_stripprefix_enabled' => ['type' => 'boolean', 'description' => 'Enable path prefix stripping.'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean', 'description' => 'Deploy the raw Docker Compose definition.'],
'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'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
@ -453,6 +536,20 @@ public function create_public_application(Request $request)
],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'use_build_secrets' => ['type' => 'boolean', 'default' => false, 'description' => 'Use Docker Build Secrets for build-time environment variables.'],
'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'],
'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'],
'disable_build_cache' => ['type' => 'boolean', 'description' => 'Disable the build cache.'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
'is_gzip_enabled' => ['type' => 'boolean', 'description' => 'Enable gzip compression.'],
'is_stripprefix_enabled' => ['type' => 'boolean', 'description' => 'Enable path prefix stripping.'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean', 'description' => 'Deploy the raw Docker Compose definition.'],
'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'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
@ -621,6 +718,20 @@ public function create_private_gh_app_application(Request $request)
],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'use_build_secrets' => ['type' => 'boolean', 'default' => false, 'description' => 'Use Docker Build Secrets for build-time environment variables.'],
'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'],
'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'],
'disable_build_cache' => ['type' => 'boolean', 'description' => 'Disable the build cache.'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
'is_gzip_enabled' => ['type' => 'boolean', 'description' => 'Enable gzip compression.'],
'is_stripprefix_enabled' => ['type' => 'boolean', 'description' => 'Enable path prefix stripping.'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean', 'description' => 'Deploy the raw Docker Compose definition.'],
'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'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
@ -761,6 +872,20 @@ public function create_private_deploy_key_application(Request $request)
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'use_build_secrets' => ['type' => 'boolean', 'default' => false, 'description' => 'Use Docker Build Secrets for build-time environment variables.'],
'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'],
'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'],
'disable_build_cache' => ['type' => 'boolean', 'description' => 'Disable the build cache.'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
'is_gzip_enabled' => ['type' => 'boolean', 'description' => 'Enable gzip compression.'],
'is_stripprefix_enabled' => ['type' => 'boolean', 'description' => 'Enable path prefix stripping.'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean', 'description' => 'Deploy the raw Docker Compose definition.'],
'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'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
@ -897,6 +1022,20 @@ public function create_dockerfile_application(Request $request)
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'use_build_secrets' => ['type' => 'boolean', 'default' => false, 'description' => 'Use Docker Build Secrets for build-time environment variables.'],
'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'],
'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'],
'disable_build_cache' => ['type' => 'boolean', 'description' => 'Disable the build cache.'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
'is_gzip_enabled' => ['type' => 'boolean', 'description' => 'Enable gzip compression.'],
'is_stripprefix_enabled' => ['type' => 'boolean', 'description' => 'Enable path prefix stripping.'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean', 'description' => 'Deploy the raw Docker Compose definition.'],
'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'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
@ -981,7 +1120,7 @@ private function create_application(Request $request, $type)
if ($return instanceof JsonResponse) {
return $return;
}
$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', 'is_preview_deployments_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_type', 'health_check_command', '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', 'tags', 'is_preserve_repository_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', 'is_preview_deployments_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_type', 'health_check_command', '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', 'use_build_secrets', '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', 'tags', 'is_preserve_repository_enabled', ...self::APPLICATION_SETTING_FIELDS];
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255',
@ -1036,6 +1175,7 @@ private function create_application(Request $request, $type)
$instantDeploy = $request->instant_deploy;
$githubAppUuid = $request->github_app_uuid;
$useBuildServer = $request->use_build_server;
$useBuildSecrets = $request->use_build_secrets;
$isStatic = $request->is_static;
$isSpa = $request->is_spa;
$isAutoDeployEnabled = $request->is_auto_deploy_enabled;
@ -1045,6 +1185,19 @@ private function create_application(Request $request, $type)
$customNginxConfiguration = $request->custom_nginx_configuration;
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true);
$isPreserveRepositoryEnabled = $request->boolean('is_preserve_repository_enabled', false);
$applicationSettings = $this->applicationSettingsFromRequest($request);
$requestedBuildPack = in_array($type, ['public', 'private-gh-app', 'private-deploy-key'], true)
? $request->input('build_pack')
: $type;
if (($applicationSettings['is_raw_compose_deployment_enabled'] ?? false) && $requestedBuildPack !== 'dockercompose') {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'is_raw_compose_deployment_enabled' => 'Raw compose deployment can only be enabled for Docker Compose applications.',
],
], 422);
}
if (! is_null($customNginxConfiguration)) {
if (! isBase64Encoded($customNginxConfiguration)) {
@ -1232,6 +1385,7 @@ private function create_application(Request $request, $type)
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->save();
$this->applyApplicationSettings($application, $applicationSettings);
if (isset($isStatic)) {
$application->settings->is_static = $isStatic;
$application->settings->save();
@ -1260,6 +1414,10 @@ private function create_application(Request $request, $type)
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($useBuildSecrets)) {
$application->settings->use_build_secrets = $useBuildSecrets;
$application->settings->save();
}
if (isset($isContainerLabelEscapeEnabled)) {
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
$application->settings->save();
@ -1478,6 +1636,7 @@ private function create_application(Request $request, $type)
$application->repository_project_id = $repository_project_id;
$application->save();
$this->applyApplicationSettings($application, $applicationSettings);
$application->refresh();
// Auto-generate domain if requested and no custom domain provided
if ($autogenerateDomain && blank($fqdn)) {
@ -1512,6 +1671,10 @@ private function create_application(Request $request, $type)
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($useBuildSecrets)) {
$application->settings->use_build_secrets = $useBuildSecrets;
$application->settings->save();
}
if (isset($isContainerLabelEscapeEnabled)) {
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
$application->settings->save();
@ -1694,6 +1857,7 @@ private function create_application(Request $request, $type)
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->save();
$this->applyApplicationSettings($application, $applicationSettings);
$application->refresh();
// Auto-generate domain if requested and no custom domain provided
if ($autogenerateDomain && blank($fqdn)) {
@ -1728,6 +1892,10 @@ private function create_application(Request $request, $type)
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($useBuildSecrets)) {
$application->settings->use_build_secrets = $useBuildSecrets;
$application->settings->save();
}
if (isset($isContainerLabelEscapeEnabled)) {
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
$application->settings->save();
@ -1837,6 +2005,7 @@ private function create_application(Request $request, $type)
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
$application->save();
$this->applyApplicationSettings($application, $applicationSettings);
$application->refresh();
// Auto-generate domain if requested and no custom domain provided
if ($autogenerateDomain && blank($fqdn)) {
@ -1859,6 +2028,10 @@ private function create_application(Request $request, $type)
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($useBuildSecrets)) {
$application->settings->use_build_secrets = $useBuildSecrets;
$application->settings->save();
}
if (isset($isContainerLabelEscapeEnabled)) {
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
$application->settings->save();
@ -1963,6 +2136,7 @@ private function create_application(Request $request, $type)
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
$application->save();
$this->applyApplicationSettings($application, $applicationSettings);
$application->refresh();
// Auto-generate domain if requested and no custom domain provided
if ($autogenerateDomain && blank($fqdn)) {
@ -1985,6 +2159,10 @@ private function create_application(Request $request, $type)
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($useBuildSecrets)) {
$application->settings->use_build_secrets = $useBuildSecrets;
$application->settings->save();
}
if (isset($isContainerLabelEscapeEnabled)) {
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
$application->settings->save();
@ -2090,7 +2268,7 @@ public function application_by_uuid(Request $request)
if (! $uuid) {
return response()->json(['message' => 'UUID is required.'], 400);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first();
$application = Application::ownedByCurrentTeamAPI($teamId)->with('settings')->where('uuid', $request->route('uuid'))->first();
if (! $application) {
return response()->json(['message' => 'Application not found.'], 404);
}
@ -2405,11 +2583,24 @@ public function delete_by_uuid(Request $request)
],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
'use_build_secrets' => ['type' => 'boolean', 'description' => 'Use Docker Build Secrets for build-time environment variables.'],
'is_git_submodules_enabled' => ['type' => 'boolean', 'description' => 'Clone Git submodules.'],
'is_git_lfs_enabled' => ['type' => 'boolean', 'description' => 'Enable Git LFS.'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean', 'description' => 'Use a shallow Git clone.'],
'disable_build_cache' => ['type' => 'boolean', 'description' => 'Disable the build cache.'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean', 'description' => 'Inject build arguments into the Dockerfile build.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include the source commit in the build.'],
'is_env_sorting_enabled' => ['type' => 'boolean', 'description' => 'Sort environment variables.'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean', 'description' => 'Make pull request deployments public.'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true, 'minimum' => 1, 'maximum' => 3600, 'description' => 'Container stop grace period in seconds.'],
'docker_images_to_keep' => ['type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'description' => 'Number of Docker images to retain.'],
'is_gzip_enabled' => ['type' => 'boolean', 'description' => 'Enable gzip compression.'],
'is_stripprefix_enabled' => ['type' => 'boolean', 'description' => 'Enable path prefix stripping.'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean', 'description' => 'Deploy the raw Docker Compose definition.'],
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
'is_preserve_repository_enabled' => ['type' => 'boolean', 'description' => 'Preserve git repository during application update. If false, the existing repository will be removed and replaced with the new one. If true, the existing repository will be kept and the new one will be ignored. Default is false.'],
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include source commit information in the build. Default is false.'],
],
)
),
@ -2495,7 +2686,7 @@ public function update_by_uuid(Request $request)
$this->authorize('update', $application);
$server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_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_type', 'health_check_command', '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', 'dockerfile_target_build', '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', 'is_preserve_repository_enabled', 'include_source_commit_in_build'];
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_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_type', 'health_check_command', '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', 'dockerfile_target_build', 'docker_compose_location', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'use_build_secrets', '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', 'is_preserve_repository_enabled', ...self::APPLICATION_SETTING_FIELDS];
$validationRules = [
'name' => 'string|max:255',
@ -2574,6 +2765,17 @@ public function update_by_uuid(Request $request)
], 422);
}
$applicationSettings = $this->applicationSettingsFromRequest($request);
$requestedBuildPack = $request->input('build_pack', $application->build_pack);
if (($applicationSettings['is_raw_compose_deployment_enabled'] ?? false) && $requestedBuildPack !== 'dockercompose') {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'is_raw_compose_deployment_enabled' => 'Raw compose deployment can only be enabled for Docker Compose applications.',
],
], 422);
}
if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) {
if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) {
$validationErrors = [];
@ -2728,6 +2930,7 @@ public function update_by_uuid(Request $request)
$isPreviewDeploymentsEnabled = $request->is_preview_deployments_enabled;
$connectToDockerNetwork = $request->connect_to_docker_network;
$useBuildServer = $request->use_build_server;
$useBuildSecrets = $request->use_build_secrets;
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled');
$isPreserveRepositoryEnabled = $request->boolean('is_preserve_repository_enabled');
$includeSourceCommitInBuild = $request->boolean('include_source_commit_in_build');
@ -2735,6 +2938,10 @@ public function update_by_uuid(Request $request)
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
if (isset($useBuildSecrets)) {
$application->settings->use_build_secrets = $useBuildSecrets;
$application->settings->save();
}
if (isset($isStatic)) {
$application->settings->is_static = $isStatic;
@ -2778,6 +2985,7 @@ public function update_by_uuid(Request $request)
$application->settings->include_source_commit_in_build = $includeSourceCommitInBuild;
$application->settings->save();
}
$this->applyApplicationSettings($application, $applicationSettings);
removeUnnecessaryFieldsFromRequest($request);
$data = $request->only($allowedFields);
@ -4023,7 +4231,7 @@ public function action_restart(Request $request)
),
]
)]
public function move_by_uuid(Request $request): \Illuminate\Http\JsonResponse
public function move_by_uuid(Request $request): JsonResponse
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {

View file

@ -111,6 +111,7 @@
'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'],
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
'settings' => new OA\Property(ref: '#/components/schemas/ApplicationSetting'),
]
)]

View file

@ -4,7 +4,49 @@
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA;
#[OA\Schema(
description: 'Application settings.',
type: 'object',
properties: [
'is_static' => ['type' => 'boolean'],
'is_git_submodules_enabled' => ['type' => 'boolean'],
'is_git_lfs_enabled' => ['type' => 'boolean'],
'is_auto_deploy_enabled' => ['type' => 'boolean'],
'is_force_https_enabled' => ['type' => 'boolean'],
'is_debug_enabled' => ['type' => 'boolean'],
'is_preview_deployments_enabled' => ['type' => 'boolean'],
'is_log_drain_enabled' => ['type' => 'boolean'],
'is_gpu_enabled' => ['type' => 'boolean'],
'gpu_driver' => ['type' => 'string', 'nullable' => true],
'gpu_count' => ['type' => 'string', 'nullable' => true],
'gpu_device_ids' => ['type' => 'string', 'nullable' => true],
'gpu_options' => ['type' => 'string', 'nullable' => true],
'is_include_timestamps' => ['type' => 'boolean'],
'is_swarm_only_worker_nodes' => ['type' => 'boolean'],
'is_raw_compose_deployment_enabled' => ['type' => 'boolean'],
'is_build_server_enabled' => ['type' => 'boolean'],
'is_consistent_container_name_enabled' => ['type' => 'boolean'],
'is_gzip_enabled' => ['type' => 'boolean'],
'is_stripprefix_enabled' => ['type' => 'boolean'],
'connect_to_docker_network' => ['type' => 'boolean'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true],
'is_container_label_escape_enabled' => ['type' => 'boolean'],
'is_env_sorting_enabled' => ['type' => 'boolean'],
'is_container_label_readonly_enabled' => ['type' => 'boolean'],
'is_preserve_repository_enabled' => ['type' => 'boolean'],
'disable_build_cache' => ['type' => 'boolean'],
'is_spa' => ['type' => 'boolean'],
'is_git_shallow_clone_enabled' => ['type' => 'boolean'],
'is_pr_deployments_public_enabled' => ['type' => 'boolean'],
'use_build_secrets' => ['type' => 'boolean'],
'inject_build_args_to_dockerfile' => ['type' => 'boolean'],
'include_source_commit_in_build' => ['type' => 'boolean'],
'docker_images_to_keep' => ['type' => 'integer'],
'stop_grace_period' => ['type' => 'integer', 'nullable' => true],
]
)]
class ApplicationSetting extends Model
{
protected $casts = [
@ -27,6 +69,17 @@ class ApplicationSetting extends Model
'is_git_shallow_clone_enabled' => 'boolean',
'docker_images_to_keep' => 'integer',
'stop_grace_period' => 'integer',
'is_log_drain_enabled' => 'boolean',
'is_gpu_enabled' => 'boolean',
'is_include_timestamps' => 'boolean',
'is_swarm_only_worker_nodes' => 'boolean',
'is_raw_compose_deployment_enabled' => 'boolean',
'is_consistent_container_name_enabled' => 'boolean',
'is_gzip_enabled' => 'boolean',
'is_stripprefix_enabled' => 'boolean',
'connect_to_docker_network' => 'boolean',
'is_env_sorting_enabled' => 'boolean',
'disable_build_cache' => 'boolean',
];
protected $fillable = [

View file

@ -117,6 +117,20 @@ function sharedDataApplications()
'is_auto_deploy_enabled' => 'boolean',
'is_force_https_enabled' => 'boolean',
'is_preview_deployments_enabled' => 'boolean',
'use_build_secrets' => 'boolean',
'is_git_submodules_enabled' => 'boolean',
'is_git_lfs_enabled' => 'boolean',
'is_git_shallow_clone_enabled' => 'boolean',
'disable_build_cache' => 'boolean',
'inject_build_args_to_dockerfile' => 'boolean',
'include_source_commit_in_build' => 'boolean',
'is_env_sorting_enabled' => 'boolean',
'is_pr_deployments_public_enabled' => 'boolean',
'is_gzip_enabled' => 'boolean',
'is_stripprefix_enabled' => 'boolean',
'is_raw_compose_deployment_enabled' => 'boolean',
'stop_grace_period' => 'nullable|integer|min:'.MIN_STOP_GRACE_PERIOD_SECONDS.'|max:'.MAX_STOP_GRACE_PERIOD_SECONDS,
'docker_images_to_keep' => 'integer|min:0|max:100',
'static_image' => Rule::enum(StaticImageTypes::class),
'domains' => ValidationPatterns::applicationDomainRules(),
'redirect' => Rule::enum(RedirectTypes::class),
@ -272,6 +286,7 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('github_app_uuid');
$request->offsetUnset('private_key_uuid');
$request->offsetUnset('use_build_server');
$request->offsetUnset('use_build_secrets');
$request->offsetUnset('is_static');
$request->offsetUnset('is_spa');
$request->offsetUnset('is_auto_deploy_enabled');
@ -283,6 +298,18 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('is_container_label_escape_enabled');
$request->offsetUnset('is_preserve_repository_enabled');
$request->offsetUnset('include_source_commit_in_build');
$request->offsetUnset('is_git_submodules_enabled');
$request->offsetUnset('is_git_lfs_enabled');
$request->offsetUnset('is_git_shallow_clone_enabled');
$request->offsetUnset('disable_build_cache');
$request->offsetUnset('inject_build_args_to_dockerfile');
$request->offsetUnset('is_env_sorting_enabled');
$request->offsetUnset('is_pr_deployments_public_enabled');
$request->offsetUnset('stop_grace_period');
$request->offsetUnset('docker_images_to_keep');
$request->offsetUnset('is_gzip_enabled');
$request->offsetUnset('is_stripprefix_enabled');
$request->offsetUnset('is_raw_compose_deployment_enabled');
$request->offsetUnset('docker_compose_raw');
$request->offsetUnset('tags');
}

View file

@ -380,6 +380,68 @@
"nullable": true,
"description": "Use build server."
},
"use_build_secrets": {
"type": "boolean",
"default": false,
"description": "Use Docker Build Secrets for build-time environment variables."
},
"is_git_submodules_enabled": {
"type": "boolean",
"description": "Clone Git submodules."
},
"is_git_lfs_enabled": {
"type": "boolean",
"description": "Enable Git LFS."
},
"is_git_shallow_clone_enabled": {
"type": "boolean",
"description": "Use a shallow Git clone."
},
"disable_build_cache": {
"type": "boolean",
"description": "Disable the build cache."
},
"inject_build_args_to_dockerfile": {
"type": "boolean",
"description": "Inject build arguments into the Dockerfile build."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include the source commit in the build."
},
"is_env_sorting_enabled": {
"type": "boolean",
"description": "Sort environment variables."
},
"is_pr_deployments_public_enabled": {
"type": "boolean",
"description": "Make pull request deployments public."
},
"stop_grace_period": {
"type": "integer",
"nullable": true,
"minimum": 1,
"maximum": 3600,
"description": "Container stop grace period in seconds."
},
"docker_images_to_keep": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Number of Docker images to retain."
},
"is_gzip_enabled": {
"type": "boolean",
"description": "Enable gzip compression."
},
"is_stripprefix_enabled": {
"type": "boolean",
"description": "Enable path prefix stripping."
},
"is_raw_compose_deployment_enabled": {
"type": "boolean",
"description": "Deploy the raw Docker Compose definition."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
@ -841,6 +903,68 @@
"nullable": true,
"description": "Use build server."
},
"use_build_secrets": {
"type": "boolean",
"default": false,
"description": "Use Docker Build Secrets for build-time environment variables."
},
"is_git_submodules_enabled": {
"type": "boolean",
"description": "Clone Git submodules."
},
"is_git_lfs_enabled": {
"type": "boolean",
"description": "Enable Git LFS."
},
"is_git_shallow_clone_enabled": {
"type": "boolean",
"description": "Use a shallow Git clone."
},
"disable_build_cache": {
"type": "boolean",
"description": "Disable the build cache."
},
"inject_build_args_to_dockerfile": {
"type": "boolean",
"description": "Inject build arguments into the Dockerfile build."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include the source commit in the build."
},
"is_env_sorting_enabled": {
"type": "boolean",
"description": "Sort environment variables."
},
"is_pr_deployments_public_enabled": {
"type": "boolean",
"description": "Make pull request deployments public."
},
"stop_grace_period": {
"type": "integer",
"nullable": true,
"minimum": 1,
"maximum": 3600,
"description": "Container stop grace period in seconds."
},
"docker_images_to_keep": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Number of Docker images to retain."
},
"is_gzip_enabled": {
"type": "boolean",
"description": "Enable gzip compression."
},
"is_stripprefix_enabled": {
"type": "boolean",
"description": "Enable path prefix stripping."
},
"is_raw_compose_deployment_enabled": {
"type": "boolean",
"description": "Deploy the raw Docker Compose definition."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
@ -1302,6 +1426,68 @@
"nullable": true,
"description": "Use build server."
},
"use_build_secrets": {
"type": "boolean",
"default": false,
"description": "Use Docker Build Secrets for build-time environment variables."
},
"is_git_submodules_enabled": {
"type": "boolean",
"description": "Clone Git submodules."
},
"is_git_lfs_enabled": {
"type": "boolean",
"description": "Enable Git LFS."
},
"is_git_shallow_clone_enabled": {
"type": "boolean",
"description": "Use a shallow Git clone."
},
"disable_build_cache": {
"type": "boolean",
"description": "Disable the build cache."
},
"inject_build_args_to_dockerfile": {
"type": "boolean",
"description": "Inject build arguments into the Dockerfile build."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include the source commit in the build."
},
"is_env_sorting_enabled": {
"type": "boolean",
"description": "Sort environment variables."
},
"is_pr_deployments_public_enabled": {
"type": "boolean",
"description": "Make pull request deployments public."
},
"stop_grace_period": {
"type": "integer",
"nullable": true,
"minimum": 1,
"maximum": 3600,
"description": "Container stop grace period in seconds."
},
"docker_images_to_keep": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Number of Docker images to retain."
},
"is_gzip_enabled": {
"type": "boolean",
"description": "Enable gzip compression."
},
"is_stripprefix_enabled": {
"type": "boolean",
"description": "Enable path prefix stripping."
},
"is_raw_compose_deployment_enabled": {
"type": "boolean",
"description": "Deploy the raw Docker Compose definition."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
@ -1668,6 +1854,68 @@
"nullable": true,
"description": "Use build server."
},
"use_build_secrets": {
"type": "boolean",
"default": false,
"description": "Use Docker Build Secrets for build-time environment variables."
},
"is_git_submodules_enabled": {
"type": "boolean",
"description": "Clone Git submodules."
},
"is_git_lfs_enabled": {
"type": "boolean",
"description": "Enable Git LFS."
},
"is_git_shallow_clone_enabled": {
"type": "boolean",
"description": "Use a shallow Git clone."
},
"disable_build_cache": {
"type": "boolean",
"description": "Disable the build cache."
},
"inject_build_args_to_dockerfile": {
"type": "boolean",
"description": "Inject build arguments into the Dockerfile build."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include the source commit in the build."
},
"is_env_sorting_enabled": {
"type": "boolean",
"description": "Sort environment variables."
},
"is_pr_deployments_public_enabled": {
"type": "boolean",
"description": "Make pull request deployments public."
},
"stop_grace_period": {
"type": "integer",
"nullable": true,
"minimum": 1,
"maximum": 3600,
"description": "Container stop grace period in seconds."
},
"docker_images_to_keep": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Number of Docker images to retain."
},
"is_gzip_enabled": {
"type": "boolean",
"description": "Enable gzip compression."
},
"is_stripprefix_enabled": {
"type": "boolean",
"description": "Enable path prefix stripping."
},
"is_raw_compose_deployment_enabled": {
"type": "boolean",
"description": "Deploy the raw Docker Compose definition."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
@ -2014,6 +2262,68 @@
"nullable": true,
"description": "Use build server."
},
"use_build_secrets": {
"type": "boolean",
"default": false,
"description": "Use Docker Build Secrets for build-time environment variables."
},
"is_git_submodules_enabled": {
"type": "boolean",
"description": "Clone Git submodules."
},
"is_git_lfs_enabled": {
"type": "boolean",
"description": "Enable Git LFS."
},
"is_git_shallow_clone_enabled": {
"type": "boolean",
"description": "Use a shallow Git clone."
},
"disable_build_cache": {
"type": "boolean",
"description": "Disable the build cache."
},
"inject_build_args_to_dockerfile": {
"type": "boolean",
"description": "Inject build arguments into the Dockerfile build."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include the source commit in the build."
},
"is_env_sorting_enabled": {
"type": "boolean",
"description": "Sort environment variables."
},
"is_pr_deployments_public_enabled": {
"type": "boolean",
"description": "Make pull request deployments public."
},
"stop_grace_period": {
"type": "integer",
"nullable": true,
"minimum": 1,
"maximum": 3600,
"description": "Container stop grace period in seconds."
},
"docker_images_to_keep": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Number of Docker images to retain."
},
"is_gzip_enabled": {
"type": "boolean",
"description": "Enable gzip compression."
},
"is_stripprefix_enabled": {
"type": "boolean",
"description": "Enable path prefix stripping."
},
"is_raw_compose_deployment_enabled": {
"type": "boolean",
"description": "Deploy the raw Docker Compose definition."
},
"is_http_basic_auth_enabled": {
"type": "boolean",
"description": "HTTP Basic Authentication enabled."
@ -2596,6 +2906,67 @@
"nullable": true,
"description": "Use build server."
},
"use_build_secrets": {
"type": "boolean",
"description": "Use Docker Build Secrets for build-time environment variables."
},
"is_git_submodules_enabled": {
"type": "boolean",
"description": "Clone Git submodules."
},
"is_git_lfs_enabled": {
"type": "boolean",
"description": "Enable Git LFS."
},
"is_git_shallow_clone_enabled": {
"type": "boolean",
"description": "Use a shallow Git clone."
},
"disable_build_cache": {
"type": "boolean",
"description": "Disable the build cache."
},
"inject_build_args_to_dockerfile": {
"type": "boolean",
"description": "Inject build arguments into the Dockerfile build."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include the source commit in the build."
},
"is_env_sorting_enabled": {
"type": "boolean",
"description": "Sort environment variables."
},
"is_pr_deployments_public_enabled": {
"type": "boolean",
"description": "Make pull request deployments public."
},
"stop_grace_period": {
"type": "integer",
"nullable": true,
"minimum": 1,
"maximum": 3600,
"description": "Container stop grace period in seconds."
},
"docker_images_to_keep": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "Number of Docker images to retain."
},
"is_gzip_enabled": {
"type": "boolean",
"description": "Enable gzip compression."
},
"is_stripprefix_enabled": {
"type": "boolean",
"description": "Enable path prefix stripping."
},
"is_raw_compose_deployment_enabled": {
"type": "boolean",
"description": "Deploy the raw Docker Compose definition."
},
"connect_to_docker_network": {
"type": "boolean",
"description": "The flag to connect the service to the predefined Docker network."
@ -2612,10 +2983,6 @@
"is_preserve_repository_enabled": {
"type": "boolean",
"description": "Preserve git repository during application update. If false, the existing repository will be removed and replaced with the new one. If true, the existing repository will be kept and the new one will be ignored. Default is false."
},
"include_source_commit_in_build": {
"type": "boolean",
"description": "Include source commit information in the build. Default is false."
}
},
"type": "object"
@ -15146,6 +15513,9 @@
"type": "string",
"nullable": true,
"description": "Password for HTTP Basic Authentication"
},
"": {
"$ref": "#\/components\/schemas\/ApplicationSetting"
}
},
"type": "object"
@ -15241,6 +15611,123 @@
},
"type": "object"
},
"ApplicationSetting": {
"description": "Application settings.",
"properties": {
"is_static": {
"type": "boolean"
},
"is_git_submodules_enabled": {
"type": "boolean"
},
"is_git_lfs_enabled": {
"type": "boolean"
},
"is_auto_deploy_enabled": {
"type": "boolean"
},
"is_force_https_enabled": {
"type": "boolean"
},
"is_debug_enabled": {
"type": "boolean"
},
"is_preview_deployments_enabled": {
"type": "boolean"
},
"is_log_drain_enabled": {
"type": "boolean"
},
"is_gpu_enabled": {
"type": "boolean"
},
"gpu_driver": {
"type": "string",
"nullable": true
},
"gpu_count": {
"type": "string",
"nullable": true
},
"gpu_device_ids": {
"type": "string",
"nullable": true
},
"gpu_options": {
"type": "string",
"nullable": true
},
"is_include_timestamps": {
"type": "boolean"
},
"is_swarm_only_worker_nodes": {
"type": "boolean"
},
"is_raw_compose_deployment_enabled": {
"type": "boolean"
},
"is_build_server_enabled": {
"type": "boolean"
},
"is_consistent_container_name_enabled": {
"type": "boolean"
},
"is_gzip_enabled": {
"type": "boolean"
},
"is_stripprefix_enabled": {
"type": "boolean"
},
"connect_to_docker_network": {
"type": "boolean"
},
"custom_internal_name": {
"type": "string",
"nullable": true
},
"is_container_label_escape_enabled": {
"type": "boolean"
},
"is_env_sorting_enabled": {
"type": "boolean"
},
"is_container_label_readonly_enabled": {
"type": "boolean"
},
"is_preserve_repository_enabled": {
"type": "boolean"
},
"disable_build_cache": {
"type": "boolean"
},
"is_spa": {
"type": "boolean"
},
"is_git_shallow_clone_enabled": {
"type": "boolean"
},
"is_pr_deployments_public_enabled": {
"type": "boolean"
},
"use_build_secrets": {
"type": "boolean"
},
"inject_build_args_to_dockerfile": {
"type": "boolean"
},
"include_source_commit_in_build": {
"type": "boolean"
},
"docker_images_to_keep": {
"type": "integer"
},
"stop_grace_period": {
"type": "integer",
"nullable": true
}
},
"type": "object"
},
"Environment": {
"description": "Environment model",
"properties": {

View file

@ -268,6 +268,54 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
use_build_secrets:
type: boolean
default: false
description: 'Use Docker Build Secrets for build-time environment variables.'
is_git_submodules_enabled:
type: boolean
description: 'Clone Git submodules.'
is_git_lfs_enabled:
type: boolean
description: 'Enable Git LFS.'
is_git_shallow_clone_enabled:
type: boolean
description: 'Use a shallow Git clone.'
disable_build_cache:
type: boolean
description: 'Disable the build cache.'
inject_build_args_to_dockerfile:
type: boolean
description: 'Inject build arguments into the Dockerfile build.'
include_source_commit_in_build:
type: boolean
description: 'Include the source commit in the build.'
is_env_sorting_enabled:
type: boolean
description: 'Sort environment variables.'
is_pr_deployments_public_enabled:
type: boolean
description: 'Make pull request deployments public.'
stop_grace_period:
type: integer
nullable: true
minimum: 1
maximum: 3600
description: 'Container stop grace period in seconds.'
docker_images_to_keep:
type: integer
minimum: 0
maximum: 100
description: 'Number of Docker images to retain.'
is_gzip_enabled:
type: boolean
description: 'Enable gzip compression.'
is_stripprefix_enabled:
type: boolean
description: 'Enable path prefix stripping.'
is_raw_compose_deployment_enabled:
type: boolean
description: 'Deploy the raw Docker Compose definition.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
@ -562,6 +610,54 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
use_build_secrets:
type: boolean
default: false
description: 'Use Docker Build Secrets for build-time environment variables.'
is_git_submodules_enabled:
type: boolean
description: 'Clone Git submodules.'
is_git_lfs_enabled:
type: boolean
description: 'Enable Git LFS.'
is_git_shallow_clone_enabled:
type: boolean
description: 'Use a shallow Git clone.'
disable_build_cache:
type: boolean
description: 'Disable the build cache.'
inject_build_args_to_dockerfile:
type: boolean
description: 'Inject build arguments into the Dockerfile build.'
include_source_commit_in_build:
type: boolean
description: 'Include the source commit in the build.'
is_env_sorting_enabled:
type: boolean
description: 'Sort environment variables.'
is_pr_deployments_public_enabled:
type: boolean
description: 'Make pull request deployments public.'
stop_grace_period:
type: integer
nullable: true
minimum: 1
maximum: 3600
description: 'Container stop grace period in seconds.'
docker_images_to_keep:
type: integer
minimum: 0
maximum: 100
description: 'Number of Docker images to retain.'
is_gzip_enabled:
type: boolean
description: 'Enable gzip compression.'
is_stripprefix_enabled:
type: boolean
description: 'Enable path prefix stripping.'
is_raw_compose_deployment_enabled:
type: boolean
description: 'Deploy the raw Docker Compose definition.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
@ -856,6 +952,54 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
use_build_secrets:
type: boolean
default: false
description: 'Use Docker Build Secrets for build-time environment variables.'
is_git_submodules_enabled:
type: boolean
description: 'Clone Git submodules.'
is_git_lfs_enabled:
type: boolean
description: 'Enable Git LFS.'
is_git_shallow_clone_enabled:
type: boolean
description: 'Use a shallow Git clone.'
disable_build_cache:
type: boolean
description: 'Disable the build cache.'
inject_build_args_to_dockerfile:
type: boolean
description: 'Inject build arguments into the Dockerfile build.'
include_source_commit_in_build:
type: boolean
description: 'Include the source commit in the build.'
is_env_sorting_enabled:
type: boolean
description: 'Sort environment variables.'
is_pr_deployments_public_enabled:
type: boolean
description: 'Make pull request deployments public.'
stop_grace_period:
type: integer
nullable: true
minimum: 1
maximum: 3600
description: 'Container stop grace period in seconds.'
docker_images_to_keep:
type: integer
minimum: 0
maximum: 100
description: 'Number of Docker images to retain.'
is_gzip_enabled:
type: boolean
description: 'Enable gzip compression.'
is_stripprefix_enabled:
type: boolean
description: 'Enable path prefix stripping.'
is_raw_compose_deployment_enabled:
type: boolean
description: 'Deploy the raw Docker Compose definition.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
@ -1091,6 +1235,54 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
use_build_secrets:
type: boolean
default: false
description: 'Use Docker Build Secrets for build-time environment variables.'
is_git_submodules_enabled:
type: boolean
description: 'Clone Git submodules.'
is_git_lfs_enabled:
type: boolean
description: 'Enable Git LFS.'
is_git_shallow_clone_enabled:
type: boolean
description: 'Use a shallow Git clone.'
disable_build_cache:
type: boolean
description: 'Disable the build cache.'
inject_build_args_to_dockerfile:
type: boolean
description: 'Inject build arguments into the Dockerfile build.'
include_source_commit_in_build:
type: boolean
description: 'Include the source commit in the build.'
is_env_sorting_enabled:
type: boolean
description: 'Sort environment variables.'
is_pr_deployments_public_enabled:
type: boolean
description: 'Make pull request deployments public.'
stop_grace_period:
type: integer
nullable: true
minimum: 1
maximum: 3600
description: 'Container stop grace period in seconds.'
docker_images_to_keep:
type: integer
minimum: 0
maximum: 100
description: 'Number of Docker images to retain.'
is_gzip_enabled:
type: boolean
description: 'Enable gzip compression.'
is_stripprefix_enabled:
type: boolean
description: 'Enable path prefix stripping.'
is_raw_compose_deployment_enabled:
type: boolean
description: 'Deploy the raw Docker Compose definition.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
@ -1312,6 +1504,54 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
use_build_secrets:
type: boolean
default: false
description: 'Use Docker Build Secrets for build-time environment variables.'
is_git_submodules_enabled:
type: boolean
description: 'Clone Git submodules.'
is_git_lfs_enabled:
type: boolean
description: 'Enable Git LFS.'
is_git_shallow_clone_enabled:
type: boolean
description: 'Use a shallow Git clone.'
disable_build_cache:
type: boolean
description: 'Disable the build cache.'
inject_build_args_to_dockerfile:
type: boolean
description: 'Inject build arguments into the Dockerfile build.'
include_source_commit_in_build:
type: boolean
description: 'Include the source commit in the build.'
is_env_sorting_enabled:
type: boolean
description: 'Sort environment variables.'
is_pr_deployments_public_enabled:
type: boolean
description: 'Make pull request deployments public.'
stop_grace_period:
type: integer
nullable: true
minimum: 1
maximum: 3600
description: 'Container stop grace period in seconds.'
docker_images_to_keep:
type: integer
minimum: 0
maximum: 100
description: 'Number of Docker images to retain.'
is_gzip_enabled:
type: boolean
description: 'Enable gzip compression.'
is_stripprefix_enabled:
type: boolean
description: 'Enable path prefix stripping.'
is_raw_compose_deployment_enabled:
type: boolean
description: 'Deploy the raw Docker Compose definition.'
is_http_basic_auth_enabled:
type: boolean
description: 'HTTP Basic Authentication enabled.'
@ -1688,6 +1928,53 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
use_build_secrets:
type: boolean
description: 'Use Docker Build Secrets for build-time environment variables.'
is_git_submodules_enabled:
type: boolean
description: 'Clone Git submodules.'
is_git_lfs_enabled:
type: boolean
description: 'Enable Git LFS.'
is_git_shallow_clone_enabled:
type: boolean
description: 'Use a shallow Git clone.'
disable_build_cache:
type: boolean
description: 'Disable the build cache.'
inject_build_args_to_dockerfile:
type: boolean
description: 'Inject build arguments into the Dockerfile build.'
include_source_commit_in_build:
type: boolean
description: 'Include the source commit in the build.'
is_env_sorting_enabled:
type: boolean
description: 'Sort environment variables.'
is_pr_deployments_public_enabled:
type: boolean
description: 'Make pull request deployments public.'
stop_grace_period:
type: integer
nullable: true
minimum: 1
maximum: 3600
description: 'Container stop grace period in seconds.'
docker_images_to_keep:
type: integer
minimum: 0
maximum: 100
description: 'Number of Docker images to retain.'
is_gzip_enabled:
type: boolean
description: 'Enable gzip compression.'
is_stripprefix_enabled:
type: boolean
description: 'Enable path prefix stripping.'
is_raw_compose_deployment_enabled:
type: boolean
description: 'Deploy the raw Docker Compose definition.'
connect_to_docker_network:
type: boolean
description: 'The flag to connect the service to the predefined Docker network.'
@ -1701,9 +1988,6 @@ paths:
is_preserve_repository_enabled:
type: boolean
description: 'Preserve git repository during application update. If false, the existing repository will be removed and replaced with the new one. If true, the existing repository will be kept and the new one will be ignored. Default is false.'
include_source_commit_in_build:
type: boolean
description: 'Include source commit information in the build. Default is false.'
type: object
responses:
'200':
@ -9683,6 +9967,8 @@ components:
type: string
nullable: true
description: 'Password for HTTP Basic Authentication'
'':
$ref: '#/components/schemas/ApplicationSetting'
type: object
ApplicationDeploymentQueue:
description: 'Project model'
@ -9746,6 +10032,86 @@ components:
commit_message:
type: string
type: object
ApplicationSetting:
description: 'Application settings.'
properties:
is_static:
type: boolean
is_git_submodules_enabled:
type: boolean
is_git_lfs_enabled:
type: boolean
is_auto_deploy_enabled:
type: boolean
is_force_https_enabled:
type: boolean
is_debug_enabled:
type: boolean
is_preview_deployments_enabled:
type: boolean
is_log_drain_enabled:
type: boolean
is_gpu_enabled:
type: boolean
gpu_driver:
type: string
nullable: true
gpu_count:
type: string
nullable: true
gpu_device_ids:
type: string
nullable: true
gpu_options:
type: string
nullable: true
is_include_timestamps:
type: boolean
is_swarm_only_worker_nodes:
type: boolean
is_raw_compose_deployment_enabled:
type: boolean
is_build_server_enabled:
type: boolean
is_consistent_container_name_enabled:
type: boolean
is_gzip_enabled:
type: boolean
is_stripprefix_enabled:
type: boolean
connect_to_docker_network:
type: boolean
custom_internal_name:
type: string
nullable: true
is_container_label_escape_enabled:
type: boolean
is_env_sorting_enabled:
type: boolean
is_container_label_readonly_enabled:
type: boolean
is_preserve_repository_enabled:
type: boolean
disable_build_cache:
type: boolean
is_spa:
type: boolean
is_git_shallow_clone_enabled:
type: boolean
is_pr_deployments_public_enabled:
type: boolean
use_build_secrets:
type: boolean
inject_build_args_to_dockerfile:
type: boolean
include_source_commit_in_build:
type: boolean
docker_images_to_keep:
type: integer
stop_grace_period:
type: integer
nullable: true
type: object
Environment:
description: 'Environment model'
properties:

View file

@ -0,0 +1,257 @@
<?php
use App\Models\Application;
use App\Models\Environment;
use App\Models\GithubApp;
use App\Models\InstanceSettings;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
uses(RefreshDatabase::class);
beforeEach(function () {
Storage::fake('ssh-keys');
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
$this->team = Team::factory()->create();
$this->user = User::factory()->create();
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
session(['currentTeam' => $this->team]);
$this->bearerToken = $this->user->createToken('build-secrets-api-test', ['*'])->plainTextToken;
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
$this->application = Application::factory()->create([
'environment_id' => $this->environment->id,
'destination_id' => $this->destination->id,
'destination_type' => $this->destination->getMorphClass(),
]);
});
function buildSecretsApiHeaders(string $bearerToken): array
{
return [
'Authorization' => 'Bearer '.$bearerToken,
'Content-Type' => 'application/json',
];
}
function buildSecretsGithubPrivateKey(): string
{
$key = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($key, $privateKey);
return $privateKey;
}
describe('PATCH /api/v1/applications/{uuid} use_build_secrets', function () {
test('updates the application setting', function () {
expect($this->application->settings->use_build_secrets)->toBeFalse();
$this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'use_build_secrets' => true,
])
->assertOk();
expect($this->application->fresh()->settings->use_build_secrets)->toBeTrue();
$this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'use_build_secrets' => false,
])
->assertOk();
expect($this->application->fresh()->settings->use_build_secrets)->toBeFalse();
});
test('rejects non boolean values', function () {
$this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'use_build_secrets' => 'not-a-boolean',
])
->assertUnprocessable()
->assertJsonValidationErrors('use_build_secrets');
});
test('does not change the setting when omitted', function () {
$this->application->settings->update(['use_build_secrets' => true]);
$this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'name' => 'updated-name',
])
->assertOk();
expect($this->application->fresh()->settings->use_build_secrets)->toBeTrue();
});
});
describe('POST /api/v1/applications/public use_build_secrets', function () {
test('creates an application with the requested build secrets setting', function (bool $useBuildSecrets) {
$response = $this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/public', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'git_repository' => 'https://gitlab.com/coolify/build-secrets-test',
'git_branch' => 'main',
'build_pack' => 'nixpacks',
'ports_exposes' => '3000',
'use_build_secrets' => $useBuildSecrets,
'autogenerate_domain' => false,
])
->assertCreated();
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
expect($application->settings->use_build_secrets)->toBe($useBuildSecrets);
})->with([
'enabled' => true,
'disabled' => false,
]);
test('rejects non boolean values', function () {
$this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/public', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'git_repository' => 'https://gitlab.com/coolify/build-secrets-test',
'git_branch' => 'main',
'build_pack' => 'nixpacks',
'ports_exposes' => '3000',
'use_build_secrets' => 'not-a-boolean',
'autogenerate_domain' => false,
])
->assertUnprocessable()
->assertJsonValidationErrors('use_build_secrets');
});
});
describe('other application creation endpoints use_build_secrets', function () {
test('creates a Dockerfile application with build secrets enabled', function () {
$response = $this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/dockerfile', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'dockerfile' => base64_encode("FROM nginx:alpine\nEXPOSE 80"),
'use_build_secrets' => true,
'autogenerate_domain' => false,
])
->assertCreated();
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
expect($application->settings->use_build_secrets)->toBeTrue();
});
test('creates a Docker image application with build secrets enabled', function () {
$response = $this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/dockerimage', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'docker_registry_image_name' => 'nginx',
'docker_registry_image_tag' => 'alpine',
'ports_exposes' => '80',
'use_build_secrets' => true,
'autogenerate_domain' => false,
])
->assertCreated();
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
expect($application->settings->use_build_secrets)->toBeTrue();
});
test('creates a private deploy key application with build secrets enabled', function () {
$privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id]);
$response = $this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/private-deploy-key', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'private_key_uuid' => $privateKey->uuid,
'git_repository' => 'git@gitlab.com:coolify/build-secrets-test.git',
'git_branch' => 'main',
'build_pack' => 'nixpacks',
'ports_exposes' => '3000',
'use_build_secrets' => true,
'autogenerate_domain' => false,
])
->assertCreated();
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
expect($application->settings->use_build_secrets)->toBeTrue();
});
test('creates a private GitHub App application with build secrets enabled', function () {
$privateKey = PrivateKey::create([
'name' => 'GitHub App Key',
'private_key' => buildSecretsGithubPrivateKey(),
'team_id' => $this->team->id,
]);
$githubApp = GithubApp::create([
'name' => 'Build Secrets GitHub App',
'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com',
'app_id' => 12345,
'installation_id' => 67890,
'client_id' => 'build-secrets-client-id',
'client_secret' => 'build-secrets-client-secret',
'webhook_secret' => 'build-secrets-webhook-secret',
'private_key_id' => $privateKey->id,
'team_id' => $this->team->id,
'is_system_wide' => false,
'is_public' => false,
]);
Http::fake([
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
'Date' => now()->toRfc7231String(),
]),
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
'token' => 'github-installation-token',
], 201),
'https://api.github.com/repos/coolify/build-secrets-test' => Http::response([
'id' => 123456,
]),
]);
$response = $this->withHeaders(buildSecretsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/private-github-app', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'github_app_uuid' => $githubApp->uuid,
'git_repository' => 'coolify/build-secrets-test',
'git_branch' => 'main',
'build_pack' => 'nixpacks',
'ports_exposes' => '3000',
'use_build_secrets' => true,
'autogenerate_domain' => false,
])
->assertCreated();
$application = Application::where('uuid', $response->json('uuid'))->firstOrFail();
expect($application->settings->use_build_secrets)->toBeTrue();
});
});

View file

@ -0,0 +1,183 @@
<?php
use App\Models\Application;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
uses(RefreshDatabase::class);
beforeEach(function () {
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
$this->team = Team::factory()->create();
$this->user = User::factory()->create();
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
session(['currentTeam' => $this->team]);
$this->bearerToken = $this->user->createToken('application-settings-api-test', ['*'])->plainTextToken;
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
$this->application = Application::factory()->create([
'environment_id' => $this->environment->id,
'destination_id' => $this->destination->id,
'destination_type' => $this->destination->getMorphClass(),
]);
});
function applicationSettingsApiHeaders(string $bearerToken): array
{
return [
'Authorization' => 'Bearer '.$bearerToken,
'Content-Type' => 'application/json',
];
}
function recommendedApplicationSettingsPayload(): array
{
return [
'is_git_submodules_enabled' => false,
'is_git_lfs_enabled' => false,
'is_git_shallow_clone_enabled' => false,
'disable_build_cache' => true,
'inject_build_args_to_dockerfile' => false,
'include_source_commit_in_build' => true,
'is_env_sorting_enabled' => true,
'is_pr_deployments_public_enabled' => true,
'stop_grace_period' => 45,
'docker_images_to_keep' => 7,
'is_gzip_enabled' => false,
'is_stripprefix_enabled' => false,
'is_raw_compose_deployment_enabled' => true,
];
}
test('GET /api/v1/applications/{uuid} includes settings without internal metadata', function () {
$this->application->settings->update(recommendedApplicationSettingsPayload());
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->getJson("/api/v1/applications/{$this->application->uuid}")
->assertOk()
->assertJsonPath('settings.disable_build_cache', true)
->assertJsonPath('settings.stop_grace_period', 45)
->assertJsonMissingPath('settings.id')
->assertJsonMissingPath('settings.application_id')
->assertJsonMissingPath('settings.created_at')
->assertJsonMissingPath('settings.updated_at');
});
test('PATCH /api/v1/applications/{uuid} updates application settings', function () {
$this->application->update(['build_pack' => 'dockercompose']);
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", recommendedApplicationSettingsPayload())
->assertOk();
$settings = $this->application->fresh()->settings;
foreach (recommendedApplicationSettingsPayload() as $field => $value) {
expect($settings->{$field})->toBe($value);
}
});
test('application creation accepts application settings', function () {
Queue::fake();
$response = $this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->postJson('/api/v1/applications/public', array_merge([
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'server_uuid' => $this->server->uuid,
'git_repository' => 'https://gitlab.com/coolify/application-settings-test',
'git_branch' => 'main',
'build_pack' => 'dockercompose',
'autogenerate_domain' => false,
], recommendedApplicationSettingsPayload()))
->assertCreated();
$settings = Application::where('uuid', $response->json('uuid'))->firstOrFail()->settings;
foreach (recommendedApplicationSettingsPayload() as $field => $value) {
expect($settings->{$field})->toBe($value);
}
});
test('proxy settings regenerate managed labels', function () {
$this->application->settings->update([
'is_container_label_readonly_enabled' => true,
'is_gzip_enabled' => true,
'is_stripprefix_enabled' => true,
]);
$this->application->update(['custom_labels' => base64_encode('sentinel-label=true')]);
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'is_gzip_enabled' => false,
'is_stripprefix_enabled' => false,
])
->assertOk();
expect(base64_decode($this->application->fresh()->custom_labels))->not->toContain('sentinel-label=true');
});
test('rejects invalid boolean application settings', function () {
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'disable_build_cache' => 'not-a-boolean',
])
->assertUnprocessable()
->assertJsonValidationErrors('disable_build_cache');
});
test('validates stop grace period bounds', function (int $stopGracePeriod) {
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'stop_grace_period' => $stopGracePeriod,
])
->assertUnprocessable()
->assertJsonValidationErrors('stop_grace_period');
})->with([
'below minimum' => 0,
'above maximum' => 3601,
]);
test('validates Docker image retention bounds', function (int $dockerImagesToKeep) {
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'docker_images_to_keep' => $dockerImagesToKeep,
])
->assertUnprocessable()
->assertJsonValidationErrors('docker_images_to_keep');
})->with([
'below minimum' => -1,
'above maximum' => 101,
]);
test('stop grace period can be reset to null', function () {
$this->application->settings->update(['stop_grace_period' => 45]);
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'stop_grace_period' => null,
])
->assertOk();
expect($this->application->fresh()->settings->stop_grace_period)->toBeNull();
});
test('raw compose deployment can only be enabled for Docker Compose applications', function () {
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
->patchJson("/api/v1/applications/{$this->application->uuid}", [
'is_raw_compose_deployment_enabled' => true,
])
->assertUnprocessable()
->assertJsonValidationErrors('is_raw_compose_deployment_enabled');
});