Ctrl K
ComunidadGuías…

app/Models/ApplicationSetting.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

namespace App\Models;

use App\Services\Access\AccessResolver;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

#[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'],
        'traefik_certificate_resolver' => ['type' => 'string', 'nullable' => true, 'description' => 'Traefik certificate resolver override. Null uses the server setting, "none" uses no resolver.'],
        'is_stripprefix_enabled' => ['type' => 'boolean'],
        'is_permanent_redirect_enabled' => ['type' => 'boolean', 'description' => 'Use permanent (301/308) instead of temporary (302/307) proxy redirects.'],
        'connect_to_docker_network' => ['type' => 'boolean'],
        'custom_internal_name' => ['type' => 'string', 'nullable' => true],
        'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true],
        'is_container_label_escape_enabled' => ['type' => 'boolean'],
        'is_env_sorting_enabled' => ['type' => 'boolean'],
        'include_coolify_default_variables' => ['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'],
        'redact_all_env_values_in_logs' => ['type' => 'boolean', 'description' => 'Hide every environment variable value in logs. When false, only locked values are hidden.'],
        'inject_build_args_to_dockerfile' => ['type' => 'boolean'],
        'include_source_commit_in_build' => ['type' => 'boolean'],
        'include_network_hosts_in_build' => ['type' => 'boolean', 'description' => 'Add the containers of the destination network as build hosts (--add-host). Turn off to keep the build cache when resources on the network change.'],
        'docker_images_to_keep' => ['type' => 'integer'],
        'stop_grace_period' => ['type' => 'integer', 'nullable' => true],
        'rolling_update_drain_seconds' => ['type' => 'integer', 'description' => 'Seconds to wait after the new container is healthy before old containers stop during a rolling update. 0 turns the wait off.'],
    ]
)]
class ApplicationSetting extends Model
{
    /**
     * Keeps generated names (prefix, timestamp and for compose apps the service name) well below the
     * 63 character DNS label limit, with room for a longer suffix in the future.
     */
    public const MAX_CONTAINER_NAME_PREFIX_LENGTH = 30;

    public const GPU_COUNT_PATTERN = '/^(all|\d+)$/';

    protected $attributes = [
        'is_image_scan_enabled' => true,
    ];

    protected $casts = [
        'is_static' => 'boolean',
        'is_spa' => 'boolean',
        'is_build_server_enabled' => 'boolean',
        'is_preserve_repository_enabled' => 'boolean',
        'is_container_label_escape_enabled' => 'boolean',
        'is_container_label_readonly_enabled' => 'boolean',
        'use_build_secrets' => 'boolean',
        'inject_build_args_to_dockerfile' => 'boolean',
        'include_source_commit_in_build' => 'boolean',
        'include_network_hosts_in_build' => 'boolean',
        'is_auto_deploy_enabled' => 'boolean',
        'is_force_https_enabled' => 'boolean',
        'is_debug_enabled' => 'boolean',
        'is_preview_deployments_enabled' => 'boolean',
        'is_pr_deployments_public_enabled' => 'boolean',
        'is_git_submodules_enabled' => 'boolean',
        'is_git_lfs_enabled' => 'boolean',
        'is_git_shallow_clone_enabled' => 'boolean',
        'docker_images_to_keep' => 'integer',
        'stop_grace_period' => 'integer',
        'rolling_update_drain_seconds' => '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',
        'is_permanent_redirect_enabled' => 'boolean',
        'connect_to_docker_network' => 'boolean',
        'is_env_sorting_enabled' => 'boolean',
        'include_coolify_default_variables' => 'boolean',
        'redact_all_env_values_in_logs' => 'boolean',
        'disable_build_cache' => 'boolean',
        'is_image_scan_enabled' => 'boolean',
        'image_builds_by_team' => 'boolean',
        'is_auto_cancel_queued_deployments_enabled' => 'boolean',
        'is_auto_cancel_building_deployments_enabled' => 'boolean',
    ];

    protected $fillable = [
        'application_id',
        'is_image_scan_enabled',
        'image_builds_by_team',
        'is_static',
        'is_git_submodules_enabled',
        'is_git_lfs_enabled',
        'is_auto_deploy_enabled',
        'is_force_https_enabled',
        'is_debug_enabled',
        'is_preview_deployments_enabled',
        'is_log_drain_enabled',
        'is_gpu_enabled',
        'gpu_driver',
        'gpu_count',
        'gpu_device_ids',
        'gpu_options',
        'is_include_timestamps',
        'is_swarm_only_worker_nodes',
        'is_raw_compose_deployment_enabled',
        'is_build_server_enabled',
        'is_consistent_container_name_enabled',
        'is_gzip_enabled',
        'traefik_certificate_resolver',
        'is_stripprefix_enabled',
        'is_permanent_redirect_enabled',
        'connect_to_docker_network',
        'custom_internal_name',
        'custom_container_name_prefix',
        'is_container_label_escape_enabled',
        'is_env_sorting_enabled',
        'include_coolify_default_variables',
        'is_container_label_readonly_enabled',
        'is_preserve_repository_enabled',
        'disable_build_cache',
        'is_spa',
        'is_git_shallow_clone_enabled',
        'is_pr_deployments_public_enabled',
        'use_build_secrets',
        'redact_all_env_values_in_logs',
        'inject_build_args_to_dockerfile',
        'include_source_commit_in_build',
        'include_network_hosts_in_build',
        'docker_images_to_keep',
        'stop_grace_period',
        'rolling_update_drain_seconds',
        'is_auto_cancel_queued_deployments_enabled',
        'is_auto_cancel_building_deployments_enabled',
    ];

    /**
     * Like custom container names, a prefix must be unique per server so that uuid, custom container
     * name and prefix each identify one container when resolving connections.
     */
    public static function isContainerNamePrefixInUse(string $prefix, Server $server, ?int $ignoreApplicationId = null): bool
    {
        // Hidden applications of a scoped member still count, or two names on one server could collide.
        return app(AccessResolver::class)->withoutVisibilityScope(fn () => $server->applications()->contains(function (Application $application) use ($prefix, $ignoreApplicationId) {
            return $application->id !== $ignoreApplicationId
                && in_array($prefix, [$application->uuid, $application->settings->custom_container_name_prefix, $application->settings->custom_internal_name], true);
        }));
    }

    public function stopGracePeriodSeconds(): int
    {
        if (
            $this->stop_grace_period >= MIN_STOP_GRACE_PERIOD_SECONDS &&
            $this->stop_grace_period <= MAX_STOP_GRACE_PERIOD_SECONDS
        ) {
            return $this->stop_grace_period;
        }

        return DEFAULT_STOP_GRACE_PERIOD_SECONDS;
    }

    public function rollingUpdateDrainSeconds(): int
    {
        return min(max($this->rolling_update_drain_seconds ?? DEFAULT_ROLLING_UPDATE_DRAIN_SECONDS, 0), MAX_ROLLING_UPDATE_DRAIN_SECONDS);
    }

    public function deploymentStopGracePeriodSeconds(): int
    {
        if (isDev() && $this->stop_grace_period === null) {
            return MIN_STOP_GRACE_PERIOD_SECONDS;
        }

        return $this->stopGracePeriodSeconds();
    }

    /**
     * The compose device request (deploy.resources.reservations.devices item) for the GPU settings.
     * A count wins over device IDs. Options that cannot be parsed are left out.
     *
     * @return array{capabilities: list<string>, driver?: string, count?: int|string, device_ids?: list<string>, options?: array<string, string>}
     */
    public function gpuDeviceReservation(): array
    {
        $device = ['capabilities' => ['gpu']];
        $driver = trim((string) $this->gpu_driver);
        if ($driver !== '') {
            $device['driver'] = $driver;
        }

        $count = trim((string) $this->gpu_count);
        $deviceIds = str((string) $this->gpu_device_ids)->explode(',')->map(fn (string $id) => trim($id))->filter(fn (string $id) => $id !== '')->values()->all();
        if ($count !== '') {
            $device['count'] = $count === 'all' ? 'all' : (int) $count;
        } elseif ($deviceIds !== []) {
            $device['device_ids'] = $deviceIds;
        }

        $options = self::parseGpuOptions($this->gpu_options);
        if ($options) {
            $device['options'] = $options;
        }

        return $device;
    }

    /**
     * Parse GPU driver options written as a YAML/JSON mapping or as key=value pairs, one per line.
     *
     * @return array<string, string>|null null when the value is not a flat mapping
     */
    public static function parseGpuOptions(?string $options): ?array
    {
        $options = trim((string) $options);
        if ($options === '') {
            return [];
        }

        try {
            $parsed = Yaml::parse($options);
        } catch (ParseException) {
            $parsed = null;
        }
        if (is_array($parsed) && $parsed !== [] && ! array_is_list($parsed)) {
            $mapping = [];
            foreach ($parsed as $key => $value) {
                if (! is_scalar($value)) {
                    return null;
                }
                $mapping[(string) $key] = is_bool($value) ? ($value ? 'true' : 'false') : (string) $value;
            }

            return $mapping;
        }

        $mapping = [];
        foreach (preg_split('/\R/', $options) as $pair) {
            $pair = trim($pair);
            if ($pair === '') {
                continue;
            }
            [$key, $value] = array_pad(explode('=', $pair, 2), 2, null);
            if ($value === null || trim($key) === '') {
                return null;
            }
            $mapping[trim($key)] = trim($value);
        }

        return $mapping;
    }

    public function isStatic(): Attribute
    {
        return Attribute::make(
            set: function ($value) {
                if ($value) {
                    $this->application->ports_exposes = 80;
                }
                $this->application->save();

                return $value;
            }
        );
    }

    public function application()
    {
        return $this->belongsTo(Application::class);
    }
}