id;
$deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}");
$deployment_url = $deployment_link->getPath();
$server_id = $application->destination->server->id;
$server_name = $application->destination->server->name;
$destination_id = $application->destination->id;
if ($server) {
$server_id = $server->id;
$server_name = $server->name;
}
if ($destination) {
$destination_id = $destination->id;
}
$deployment = ApplicationDeploymentQueue::create([
'application_id' => $application_id,
'application_name' => $application->name,
'server_id' => $server_id,
'server_name' => $server_name,
'destination_id' => $destination_id,
'deployment_uuid' => $deployment_uuid,
'deployment_url' => $deployment_url,
'pull_request_id' => $pull_request_id,
'force_rebuild' => $force_rebuild,
'is_webhook' => $is_webhook,
'is_api' => $is_api,
'restart_only' => $restart_only,
'commit' => $commit,
'rollback' => $rollback,
'git_type' => $git_type,
'only_this_server' => $only_this_server,
]);
if ($no_questions_asked) {
ApplicationDeploymentJob::dispatch(
application_deployment_queue_id: $deployment->id,
);
} elseif (next_queuable($server_id, $application_id)) {
ApplicationDeploymentJob::dispatch(
application_deployment_queue_id: $deployment->id,
);
}
}
function force_start_deployment(ApplicationDeploymentQueue $deployment)
{
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
ApplicationDeploymentJob::dispatch(
application_deployment_queue_id: $deployment->id,
);
}
function queue_next_deployment(Application $application)
{
$server_id = $application->destination->server_id;
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', ApplicationDeploymentStatus::QUEUED)->get()->sortBy('created_at')->first();
if ($next_found) {
$next_found->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
ApplicationDeploymentJob::dispatch(
application_deployment_queue_id: $next_found->id,
);
}
}
function next_queuable(string $server_id, string $application_id): bool
{
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at');
$same_application_deployments = $deployments->where('application_id', $application_id);
$in_progress = $same_application_deployments->filter(function ($value, $key) {
return $value->status === 'in_progress';
});
if ($in_progress->count() > 0) {
return false;
}
$server = Server::find($server_id);
$concurrent_builds = $server->settings->concurrent_builds;
if ($deployments->count() > $concurrent_builds) {
return false;
}
return true;
}
function next_after_cancel(?Server $server = null)
{
if ($server) {
$next_found = ApplicationDeploymentQueue::where('server_id', data_get($server, 'id'))->where('status', ApplicationDeploymentStatus::QUEUED)->get()->sortBy('created_at');
if ($next_found->count() > 0) {
foreach ($next_found as $next) {
$server = Server::find($next->server_id);
$concurrent_builds = $server->settings->concurrent_builds;
$inprogress_deployments = ApplicationDeploymentQueue::where('server_id', $next->server_id)->whereIn('status', [ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at');
if ($inprogress_deployments->count() < $concurrent_builds) {
$next->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
ApplicationDeploymentJob::dispatch(
application_deployment_queue_id: $next->id,
);
}
break;
}
}
}
}
function configValidator(string $config)
{
$validator = Validator::make(['config' => $config], [
'config' => 'required|json',
]);
if ($validator->fails()) {
throw new \Exception('Invalid JSON format');
}
$config = json_decode($config, true);
$messages = [
'config.coolify.project_uuid.required' => 'Project UUID is required (coolify.project_uuid) in the coolify configuration.',
'config.coolify.environment_uuid.required' => 'Environment UUID is required (coolify.environment_uuid) in the coolify configuration.',
'config.coolify.destination_type.required' => 'Destination type is required (coolify.destination_type) in the coolify configuration.',
'config.coolify.destination_id.required' => 'Destination ID is required (coolify.destination_id) in the coolify configuration.',
'config.build.build_pack.required' => 'Build pack is required (build.build_pack) in the build configuration.',
'config.build.build_pack.in' => 'Build pack must be one of: nixpacks, static, dockerfile, or dockercompose (build.build_pack).',
'config.source.git_repository.required_if' => 'Git repository is required (source.git_repository) when using nixpacks or static build pack.',
'config.source.git_branch.required_if' => 'Git branch is required (source.git_branch) when using nixpacks or static build pack.',
'config.build.docker_compose.content.required_if' => 'Docker compose content is required (build.docker_compose.content) when using dockercompose build pack.',
'config.network.domains.redirect.in' => 'Domain redirect must be one of: www, non-www, or both (network.domains.redirect).',
'config.settings.gpu.driver.in' => 'GPU driver must be nvidia (settings.gpu.driver).',
'config.environment_variables.production.*.key.required' => 'Environment variable key is required (environment_variables.production[].key).',
'config.environment_variables.production.*.value.required' => 'Environment variable value is required (environment_variables.production[].value).',
'config.environment_variables.preview.*.key.required' => 'Preview environment variable key is required (environment_variables.preview[].key).',
'config.environment_variables.preview.*.value.required' => 'Preview environment variable value is required (environment_variables.preview[].value).',
'config.persistent_storages.*.mount_path.required' => 'Persistent storage mount path is required (persistent_storages[].mount_path).',
'config.persistent_storages.*.host_path.required' => 'Persistent storage host path is required (persistent_storages[].host_path).',
'config.scheduled_tasks.*.name.required' => 'Scheduled task name is required (scheduled_tasks[].name).',
'config.scheduled_tasks.*.command.required' => 'Scheduled task command is required (scheduled_tasks[].command).',
'config.scheduled_tasks.*.frequency.required' => 'Scheduled task frequency is required (scheduled_tasks[].frequency).',
'config.name.string' => 'Name must be a string (name).',
'config.description.string' => 'Description must be a string (description).',
'config.coolify.array' => 'Coolify configuration must be an object (coolify).',
'config.build.base_directory.string' => 'Base directory must be a string (build.base_directory).',
'config.build.publish_directory.string' => 'Publish directory must be a string (build.publish_directory).',
'config.network.ports.expose.string' => 'Exposed ports must be a string (network.ports.expose).',
'config.settings.array' => 'Settings must be an object (settings).',
'config.settings.deployment.array' => 'Deployment settings must be an object (settings.deployment).',
'config.settings.git.array' => 'Git settings must be an object (settings.git).',
'config.settings.docker.array' => 'Docker settings must be an object (settings.docker).',
'config.settings.gpu.array' => 'GPU settings must be an object (settings.gpu).',
'config.settings.logging.array' => 'Logging settings must be an object (settings.logging).',
'config.settings.environment.array' => 'Environment settings must be an object (settings.environment).',
'config.settings.proxy.array' => 'Proxy settings must be an object (settings.proxy).',
'config.settings.type.array' => 'Type settings must be an object (settings.type).',
'config.environment_variables.production.array' => 'Production environment variables must be an array (environment_variables.production).',
'config.environment_variables.preview.array' => 'Preview environment variables must be an array (environment_variables.preview).',
'config.health_check.path.string' => 'Health check path must be a string (health_check.path).',
'config.health_check.port.string' => 'Health check port must be a string (health_check.port).',
'config.health_check.method.string' => 'Health check method must be a string (health_check.method).',
'config.health_check.return_code.integer' => 'Health check return code must be an integer (health_check.return_code).',
'config.health_check.interval.integer' => 'Health check interval must be an integer (health_check.interval).',
'config.health_check.timeout.integer' => 'Health check timeout must be an integer (health_check.timeout).',
'config.health_check.retries.integer' => 'Health check retries must be an integer (health_check.retries).',
'config.resources.memory.limit.string' => 'Memory limit must be a string (resources.memory.limit).',
'config.resources.memory.swap.string' => 'Memory swap must be a string (resources.memory.swap).',
'config.resources.memory.swappiness.integer' => 'Memory swappiness must be an integer (resources.memory.swappiness).',
'config.resources.cpu.limit.string' => 'CPU limit must be a string (resources.cpu.limit).',
'config.resources.cpu.shares.integer' => 'CPU shares must be an integer (resources.cpu.shares).',
'config.network.domains.fqdn.string' => 'FQDN must be a string (network.domains.fqdn).',
'config.deployment.pre_deployment.command.string' => 'Pre-deployment command must be a string',
'config.deployment.pre_deployment.container.string' => 'Pre-deployment container must be a string',
'config.deployment.post_deployment.command.string' => 'Post-deployment command must be a string',
'config.deployment.post_deployment.container.string' => 'Post-deployment container must be a string',
'config.webhooks.secrets.github.string' => 'Github webhook secret must be a string',
'config.webhooks.secrets.gitlab.string' => 'Gitlab webhook secret must be a string',
'config.webhooks.secrets.bitbucket.string' => 'Bitbucket webhook secret must be a string',
'config.webhooks.secrets.gitea.string' => 'Gitea webhook secret must be a string',
];
$deepValidator = Validator::make(['config' => $config], [
'config.name' => 'string',
'config.description' => 'nullable|string',
'config.coolify' => 'required|array',
'config.coolify.project_uuid' => 'required|string',
'config.coolify.environment_uuid' => 'required|string',
'config.coolify.destination_uuid' => 'required|string',
'config.source' => 'array',
'config.source.git_repository' => 'required_if:config.build.build_pack,nixpacks,static|string',
'config.source.git_branch' => 'required_if:config.build.build_pack,nixpacks,static|string',
'config.source.git_commit_sha' => 'nullable|string',
'config.source.repository_project_id' => 'nullable|integer',
'config.build' => 'required|array',
'config.build.build_pack' => 'required|string|in:nixpacks,static,dockerfile,dockercompose',
'config.build.static_image' => 'nullable|string',
'config.build.base_directory' => 'nullable|string',
'config.build.publish_directory' => 'nullable|string',
'config.build.install_command' => 'nullable|string',
'config.build.build_command' => 'nullable|string',
'config.build.start_command' => 'nullable|string',
'config.build.watch_paths' => 'nullable|string',
'config.build.dockerfile' => 'array',
// 'config.build.dockerfile.content' => 'nullable|string',
'config.build.dockerfile.location' => 'nullable|string',
'config.build.dockerfile.target_build' => 'nullable|string',
'config.build.docker_compose' => 'array',
// 'config.build.docker_compose.content' => 'required_if:config.build.build_pack,dockercompose|string',
'config.build.docker_compose.location' => 'nullable|string',
// 'config.build.docker_compose.parsing_version' => 'nullable|string',
'config.build.docker_compose.domains' => 'nullable|string',
'config.build.docker_compose.custom_start_command' => 'nullable|string',
'config.build.docker_compose.custom_build_command' => 'nullable|string',
'config.build.docker' => 'array',
'config.build.docker.custom_options' => 'nullable|string',
'config.build.docker.custom_labels' => 'nullable|string',
'config.network' => 'array',
'config.network.domains' => 'array',
'config.network.domains.fqdn' => 'nullable|string',
'config.network.domains.redirect' => 'nullable|string|in:www,non-www,both',
'config.network.domains.custom_nginx_configuration' => 'nullable|string',
'config.network.ports' => 'array',
'config.network.ports.expose' => 'nullable|string',
'config.network.ports.mappings' => 'nullable|string',
'config.health_check' => 'array',
'config.health_check.enabled' => 'boolean',
'config.health_check.path' => 'nullable|string',
'config.health_check.port' => 'nullable|string',
'config.health_check.host' => 'nullable|string',
'config.health_check.method' => 'nullable|string',
'config.health_check.return_code' => 'nullable|integer',
'config.health_check.scheme' => 'nullable|string|in:http,https',
'config.health_check.response_text' => 'nullable|string',
'config.health_check.interval' => 'nullable|integer',
'config.health_check.timeout' => 'nullable|integer',
'config.health_check.retries' => 'nullable|integer',
'config.health_check.start_period' => 'nullable|integer',
'config.resources' => 'array',
'config.resources.memory' => 'array',
'config.resources.memory.limit' => 'nullable|string',
'config.resources.memory.swap' => 'nullable|string',
'config.resources.memory.swappiness' => 'nullable|integer',
'config.resources.memory.reservation' => 'nullable|string',
'config.resources.cpu' => 'array',
'config.resources.cpu.limit' => 'nullable|string',
'config.resources.cpu.shares' => 'nullable|integer',
'config.resources.cpu.set' => 'nullable|string',
'config.preview' => 'array',
'config.preview.url_template' => 'nullable|string',
'config.environment_variables' => 'array',
'config.environment_variables.production' => 'array',
'config.environment_variables.production.*.key' => 'required|string',
'config.environment_variables.production.*.value' => 'required|string',
'config.environment_variables.production.*.is_required' => 'boolean',
'config.environment_variables.production.*.is_build_time' => 'boolean',
'config.environment_variables.production.*.is_preview' => 'boolean',
'config.environment_variables.production.*.is_multiline' => 'boolean',
'config.environment_variables.preview' => 'array',
'config.environment_variables.preview.*.key' => 'required|string',
'config.environment_variables.preview.*.value' => 'required|string',
'config.environment_variables.preview.*.is_build_time' => 'boolean',
'config.environment_variables.preview.*.is_preview' => 'boolean',
'config.environment_variables.preview.*.is_multiline' => 'boolean',
'config.settings' => 'array',
'config.settings.deployment' => 'array',
'config.settings.deployment.auto_deploy' => 'boolean',
'config.settings.deployment.preview_deployments' => 'boolean',
'config.settings.deployment.preserve_repository' => 'boolean',
'config.settings.deployment.build_server' => 'boolean',
'config.settings.deployment.debug_mode' => 'boolean',
'config.settings.git' => 'array',
'config.settings.git.lfs_enabled' => 'boolean',
'config.settings.git.submodules_enabled' => 'boolean',
'config.settings.docker' => 'array',
'config.settings.docker.network' => 'array',
'config.settings.docker.network.connect' => 'boolean',
'config.settings.docker.network.custom_internal_name' => 'nullable|string',
'config.settings.docker.container' => 'array',
'config.settings.docker.container.consistent_name' => 'boolean',
'config.settings.docker.container.label_escape' => 'boolean',
'config.settings.docker.container.label_readonly' => 'boolean',
'config.settings.docker.build' => 'array',
'config.settings.docker.build.disable_cache' => 'boolean',
'config.settings.docker.build.raw_compose_deployment' => 'boolean',
'config.settings.gpu' => 'array',
'config.settings.gpu.enabled' => 'boolean',
'config.settings.gpu.driver' => 'string|in:nvidia',
'config.settings.logging' => 'array',
'config.settings.logging.drain_enabled' => 'boolean',
'config.settings.logging.include_timestamps' => 'boolean',
'config.settings.environment' => 'array',
'config.settings.environment.sort_variables' => 'boolean',
'config.settings.proxy' => 'array',
'config.settings.proxy.force_https' => 'boolean',
'config.settings.proxy.gzip' => 'boolean',
'config.settings.proxy.stripprefix' => 'boolean',
'config.settings.type' => 'array',
'config.settings.type.static' => 'boolean',
'config.persistent_storages' => 'array',
'config.persistent_storages.*.mount_path' => 'required|string',
'config.persistent_storages.*.host_path' => 'string',
'config.scheduled_tasks' => 'array',
'config.scheduled_tasks.*.enabled' => 'boolean',
'config.scheduled_tasks.*.name' => 'required|string',
'config.scheduled_tasks.*.command' => 'required|string',
'config.scheduled_tasks.*.frequency' => 'required|string',
'config.tags' => 'array',
'config.tags.*' => 'string',
'config.deployment' => 'array',
'config.deployment.pre_deployment' => 'array',
'config.deployment.pre_deployment.command' => 'nullable|string',
'config.deployment.pre_deployment.container' => 'nullable|string',
'config.deployment.post_deployment' => 'array',
'config.deployment.post_deployment.command' => 'nullable|string',
'config.deployment.post_deployment.container' => 'nullable|string',
'config.webhooks' => 'array',
'config.webhooks.secrets' => 'array',
'config.webhooks.secrets.github' => 'nullable|string',
'config.webhooks.secrets.gitlab' => 'nullable|string',
'config.webhooks.secrets.bitbucket' => 'nullable|string',
'config.webhooks.secrets.gitea' => 'nullable|string',
'config.docker_custom_options' => 'nullable|string',
'config.labels' => 'nullable|array',
'config.labels.*' => 'string',
], $messages);
if ($deepValidator->fails()) {
$errors = $deepValidator->errors()->all();
throw new \Exception('Validation failed:'.PHP_EOL.'- '.implode(PHP_EOL.'- ', $errors));
}
// Custom validation for dockerfile content
// $buildPack = data_get($config, 'build.build_pack');
// $gitRepository = data_get($config, 'source.git_repository');
// $gitBranch = data_get($config, 'source.git_branch');
// $dockerfileContent = data_get($config, 'build.dockerfile.content');
// if ($buildPack === 'dockerfile' && ! $gitRepository && ! $gitBranch) {
// throw new \Exception('Validation failed:'.PHP_EOL.PHP_EOL.'- When using dockerfile build pack without git repository and branch, the Dockerfile content is required (build.dockerfile.content)');
// }
// validate domains
$domains = data_get($config, 'network.domains.fqdn');
if ($domains) {
$domains = explode(',', $domains);
foreach ($domains as $domain) {
if (! str_starts_with($domain, 'http://') && ! str_starts_with($domain, 'https://')) {
throw new \Exception('Validation failed:'.PHP_EOL.PHP_EOL.'- Domain must start with http:// or https://
Your domain: '.$domain);
}
$parsedDomain = parse_url($domain, PHP_URL_HOST);
if (! $parsedDomain || ! filter_var($parsedDomain, FILTER_VALIDATE_DOMAIN)) {
throw new \Exception('Validation failed:'.PHP_EOL.PHP_EOL.'- Invalid domain: '.$domain);
}
}
}
return $config;
}