2024-06-21 14:46:13 +00:00
< ? php
namespace App\Http\Controllers\Api ;
2024-07-02 14:12:04 +00:00
use App\Actions\Application\LoadComposeFile ;
2024-06-21 14:46:13 +00:00
use App\Actions\Application\StopApplication ;
2024-07-02 14:12:04 +00:00
use App\Actions\Service\StartService ;
2024-06-28 13:05:37 +00:00
use App\Enums\BuildPackTypes ;
2024-06-21 14:46:13 +00:00
use App\Http\Controllers\Controller ;
2024-06-26 11:00:36 +00:00
use App\Jobs\DeleteResourceJob ;
2024-06-21 14:46:13 +00:00
use App\Models\Application ;
2024-06-26 11:00:36 +00:00
use App\Models\EnvironmentVariable ;
2024-06-30 09:30:31 +00:00
use App\Models\GithubApp ;
use App\Models\PrivateKey ;
2024-06-21 14:46:13 +00:00
use App\Models\Project ;
2024-06-28 13:05:37 +00:00
use App\Models\Server ;
2024-07-01 14:26:50 +00:00
use App\Models\Service ;
2025-08-22 12:38:21 +00:00
use App\Rules\ValidGitBranch ;
use App\Rules\ValidGitRepositoryUrl ;
2024-06-21 14:46:13 +00:00
use Illuminate\Http\Request ;
2024-06-26 11:00:36 +00:00
use Illuminate\Validation\Rule ;
2024-07-09 08:45:10 +00:00
use OpenApi\Attributes as OA ;
2025-03-10 10:18:14 +00:00
use Spatie\Url\Url ;
2024-07-01 14:26:50 +00:00
use Symfony\Component\Yaml\Yaml ;
2024-06-21 14:46:13 +00:00
use Visus\Cuid2\Cuid2 ;
2024-07-01 14:26:50 +00:00
class ApplicationsController extends Controller
2024-06-21 14:46:13 +00:00
{
2024-07-02 10:15:58 +00:00
private function removeSensitiveData ( $application )
{
2024-07-04 11:45:06 +00:00
$application -> makeHidden ([
'id' ,
2024-12-17 09:38:32 +00:00
'resourceable' ,
'resourceable_id' ,
'resourceable_type' ,
2024-07-04 11:45:06 +00:00
]);
2024-12-09 10:10:35 +00:00
if ( request () -> attributes -> get ( 'can_read_sensitive' , false ) === false ) {
$application -> makeHidden ([
'custom_labels' ,
'dockerfile' ,
'docker_compose' ,
'docker_compose_raw' ,
'manual_webhook_secret_bitbucket' ,
'manual_webhook_secret_gitea' ,
'manual_webhook_secret_github' ,
'manual_webhook_secret_gitlab' ,
'private_key_id' ,
'value' ,
'real_value' ,
2025-04-23 11:22:01 +00:00
'http_basic_auth_password' ,
2024-12-09 10:10:35 +00:00
]);
2024-07-02 10:15:58 +00:00
}
return serializeApiResponse ( $application );
}
2024-07-09 08:45:10 +00:00
#[OA\Get(
summary : 'List' ,
description : 'List all applications.' ,
path : '/applications' ,
2024-09-04 08:09:10 +00:00
operationId : 'list-applications' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
responses : [
new OA\Response (
response : 200 ,
description : 'Get all applications.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'array' ,
items : new OA\Items ( ref : '#/components/schemas/Application' )
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 08:45:10 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function applications ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
2025-01-07 14:31:43 +00:00
$projects = Project :: where ( 'team_id' , $teamId ) -> get ();
2024-06-21 14:46:13 +00:00
$applications = collect ();
$applications -> push ( $projects -> pluck ( 'applications' ) -> flatten ());
$applications = $applications -> flatten ();
2024-07-01 14:26:50 +00:00
$applications = $applications -> map ( function ( $application ) {
2024-07-02 10:15:58 +00:00
return $this -> removeSensitiveData ( $application );
2024-07-01 14:26:50 +00:00
});
2024-06-21 14:46:13 +00:00
2024-07-03 11:13:38 +00:00
return response () -> json ( $applications );
2024-06-21 14:46:13 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Post(
summary : 'Create (Public)' ,
description : 'Create new application based on a public git repository.' ,
path : '/applications/public' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-public-application' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
requestBody : new OA\RequestBody (
description : 'Application object that needs to be created.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
2024-12-17 12:42:16 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'environment_uuid' , 'git_repository' , 'git_branch' , 'build_pack' , 'ports_exposes' ],
2024-07-09 11:19:21 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
2024-12-17 12:42:16 +00:00
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.' ],
'environment_uuid' => [ 'type' => 'string' , 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' ],
2024-07-09 12:12:36 +00:00
'git_repository' => [ 'type' => 'string' , 'description' => 'The git repository URL.' ],
'git_branch' => [ 'type' => 'string' , 'description' => 'The git branch.' ],
'build_pack' => [ 'type' => 'string' , 'enum' => [ 'nixpacks' , 'static' , 'dockerfile' , 'dockercompose' ], 'description' => 'The build pack type.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'The ports to expose.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'domains' => [ 'type' => 'string' , 'description' => 'The application domains.' ],
'git_commit_sha' => [ 'type' => 'string' , 'description' => 'The git commit SHA.' ],
'docker_registry_image_name' => [ 'type' => 'string' , 'description' => 'The docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'description' => 'The docker registry image tag.' ],
'is_static' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application is static.' ],
2024-10-10 07:33:29 +00:00
'static_image' => [ 'type' => 'string' , 'enum' => [ 'nginx:alpine' ], 'description' => 'The static image.' ],
2024-07-09 12:12:36 +00:00
'install_command' => [ 'type' => 'string' , 'description' => 'The install command.' ],
'build_command' => [ 'type' => 'string' , 'description' => 'The build command.' ],
'start_command' => [ 'type' => 'string' , 'description' => 'The start command.' ],
'ports_mappings' => [ 'type' => 'string' , 'description' => 'The ports mappings.' ],
'base_directory' => [ 'type' => 'string' , 'description' => 'The base directory for all commands.' ],
'publish_directory' => [ 'type' => 'string' , 'description' => 'The publish directory.' ],
'health_check_enabled' => [ 'type' => 'boolean' , 'description' => 'Health check enabled.' ],
'health_check_path' => [ 'type' => 'string' , 'description' => 'Health check path.' ],
'health_check_port' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check port.' ],
'health_check_host' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check host.' ],
'health_check_method' => [ 'type' => 'string' , 'description' => 'Health check method.' ],
'health_check_return_code' => [ 'type' => 'integer' , 'description' => 'Health check return code.' ],
'health_check_scheme' => [ 'type' => 'string' , 'description' => 'Health check scheme.' ],
'health_check_response_text' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check response text.' ],
'health_check_interval' => [ 'type' => 'integer' , 'description' => 'Health check interval in seconds.' ],
'health_check_timeout' => [ 'type' => 'integer' , 'description' => 'Health check timeout in seconds.' ],
'health_check_retries' => [ 'type' => 'integer' , 'description' => 'Health check retries count.' ],
'health_check_start_period' => [ 'type' => 'integer' , 'description' => 'Health check start period in seconds.' ],
'limits_memory' => [ 'type' => 'string' , 'description' => 'Memory limit.' ],
'limits_memory_swap' => [ 'type' => 'string' , 'description' => 'Memory swap limit.' ],
'limits_memory_swappiness' => [ 'type' => 'integer' , 'description' => 'Memory swappiness.' ],
'limits_memory_reservation' => [ 'type' => 'string' , 'description' => 'Memory reservation.' ],
'limits_cpus' => [ 'type' => 'string' , 'description' => 'CPU limit.' ],
'limits_cpuset' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'CPU set.' ],
'limits_cpu_shares' => [ 'type' => 'integer' , 'description' => 'CPU shares.' ],
'custom_labels' => [ 'type' => 'string' , 'description' => 'Custom labels.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Pre deployment command container.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Github.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitlab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitea.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
// 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
'dockerfile' => [ 'type' => 'string' , 'description' => 'The Dockerfile content.' ],
'docker_compose_location' => [ 'type' => 'string' , 'description' => 'The Docker Compose location.' ],
'docker_compose_raw' => [ 'type' => 'string' , 'description' => 'The Docker Compose raw content.' ],
'docker_compose_custom_start_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom start command.' ],
'docker_compose_custom_build_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom build command.' ],
'docker_compose_domains' => [ 'type' => 'array' , 'description' => 'The Docker Compose domains.' ],
'watch_paths' => [ 'type' => 'string' , 'description' => 'The watch paths.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-04-23 11:22:01 +00:00
'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' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 11:19:21 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 11:19:21 +00:00
responses : [
new OA\Response (
2024-12-14 01:54:36 +00:00
response : 201 ,
2024-07-09 11:19:21 +00:00
description : 'Application created successfully.' ,
2024-12-14 01:54:36 +00:00
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
)
2024-07-09 11:19:21 +00:00
),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 11:19:21 +00:00
]
)]
2024-07-04 11:45:06 +00:00
public function create_public_application ( Request $request )
{
2024-07-09 11:19:21 +00:00
return $this -> create_application ( $request , 'public' );
2024-07-04 11:45:06 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Post(
summary : 'Create (Private - GH App)' ,
description : 'Create new application based on a private repository through a Github App.' ,
2024-08-13 11:27:50 +00:00
path : '/applications/private-github-app' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-private-github-app-application' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
requestBody : new OA\RequestBody (
description : 'Application object that needs to be created.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
2024-12-17 12:42:16 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'environment_uuid' , 'github_app_uuid' , 'git_repository' , 'git_branch' , 'build_pack' , 'ports_exposes' ],
2024-07-09 11:19:21 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
2024-12-17 12:42:16 +00:00
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.' ],
'environment_uuid' => [ 'type' => 'string' , 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' ],
2024-07-09 12:12:36 +00:00
'github_app_uuid' => [ 'type' => 'string' , 'description' => 'The Github App UUID.' ],
'git_repository' => [ 'type' => 'string' , 'description' => 'The git repository URL.' ],
'git_branch' => [ 'type' => 'string' , 'description' => 'The git branch.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'The ports to expose.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID.' ],
'build_pack' => [ 'type' => 'string' , 'enum' => [ 'nixpacks' , 'static' , 'dockerfile' , 'dockercompose' ], 'description' => 'The build pack type.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'domains' => [ 'type' => 'string' , 'description' => 'The application domains.' ],
'git_commit_sha' => [ 'type' => 'string' , 'description' => 'The git commit SHA.' ],
'docker_registry_image_name' => [ 'type' => 'string' , 'description' => 'The docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'description' => 'The docker registry image tag.' ],
'is_static' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application is static.' ],
2024-10-10 07:33:29 +00:00
'static_image' => [ 'type' => 'string' , 'enum' => [ 'nginx:alpine' ], 'description' => 'The static image.' ],
2024-07-09 12:12:36 +00:00
'install_command' => [ 'type' => 'string' , 'description' => 'The install command.' ],
'build_command' => [ 'type' => 'string' , 'description' => 'The build command.' ],
'start_command' => [ 'type' => 'string' , 'description' => 'The start command.' ],
'ports_mappings' => [ 'type' => 'string' , 'description' => 'The ports mappings.' ],
'base_directory' => [ 'type' => 'string' , 'description' => 'The base directory for all commands.' ],
'publish_directory' => [ 'type' => 'string' , 'description' => 'The publish directory.' ],
'health_check_enabled' => [ 'type' => 'boolean' , 'description' => 'Health check enabled.' ],
'health_check_path' => [ 'type' => 'string' , 'description' => 'Health check path.' ],
'health_check_port' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check port.' ],
'health_check_host' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check host.' ],
'health_check_method' => [ 'type' => 'string' , 'description' => 'Health check method.' ],
'health_check_return_code' => [ 'type' => 'integer' , 'description' => 'Health check return code.' ],
'health_check_scheme' => [ 'type' => 'string' , 'description' => 'Health check scheme.' ],
'health_check_response_text' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check response text.' ],
'health_check_interval' => [ 'type' => 'integer' , 'description' => 'Health check interval in seconds.' ],
'health_check_timeout' => [ 'type' => 'integer' , 'description' => 'Health check timeout in seconds.' ],
'health_check_retries' => [ 'type' => 'integer' , 'description' => 'Health check retries count.' ],
'health_check_start_period' => [ 'type' => 'integer' , 'description' => 'Health check start period in seconds.' ],
'limits_memory' => [ 'type' => 'string' , 'description' => 'Memory limit.' ],
'limits_memory_swap' => [ 'type' => 'string' , 'description' => 'Memory swap limit.' ],
'limits_memory_swappiness' => [ 'type' => 'integer' , 'description' => 'Memory swappiness.' ],
'limits_memory_reservation' => [ 'type' => 'string' , 'description' => 'Memory reservation.' ],
'limits_cpus' => [ 'type' => 'string' , 'description' => 'CPU limit.' ],
'limits_cpuset' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'CPU set.' ],
'limits_cpu_shares' => [ 'type' => 'integer' , 'description' => 'CPU shares.' ],
'custom_labels' => [ 'type' => 'string' , 'description' => 'Custom labels.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Pre deployment command container.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Github.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitlab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitea.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
'dockerfile' => [ 'type' => 'string' , 'description' => 'The Dockerfile content.' ],
'docker_compose_location' => [ 'type' => 'string' , 'description' => 'The Docker Compose location.' ],
'docker_compose_raw' => [ 'type' => 'string' , 'description' => 'The Docker Compose raw content.' ],
'docker_compose_custom_start_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom start command.' ],
'docker_compose_custom_build_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom build command.' ],
'docker_compose_domains' => [ 'type' => 'array' , 'description' => 'The Docker Compose domains.' ],
'watch_paths' => [ 'type' => 'string' , 'description' => 'The watch paths.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-04-23 11:22:01 +00:00
'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' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 11:19:21 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 11:19:21 +00:00
responses : [
new OA\Response (
2024-12-14 01:54:36 +00:00
response : 201 ,
2024-07-09 11:19:21 +00:00
description : 'Application created successfully.' ,
2024-12-14 01:54:36 +00:00
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
)
2024-07-09 11:19:21 +00:00
),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 11:19:21 +00:00
]
)]
2024-07-04 11:45:06 +00:00
public function create_private_gh_app_application ( Request $request )
{
2024-07-09 11:19:21 +00:00
return $this -> create_application ( $request , 'private-gh-app' );
2024-07-04 11:45:06 +00:00
}
2024-07-09 08:45:10 +00:00
#[OA\Post(
2024-07-09 11:19:21 +00:00
summary : 'Create (Private - Deploy Key)' ,
description : 'Create new application based on a private repository through a Deploy Key.' ,
path : '/applications/private-deploy-key' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-private-deploy-key-application' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
requestBody : new OA\RequestBody (
description : 'Application object that needs to be created.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
2024-12-17 12:42:16 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'environment_uuid' , 'private_key_uuid' , 'git_repository' , 'git_branch' , 'build_pack' , 'ports_exposes' ],
2024-07-09 08:45:10 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
2024-12-17 12:42:16 +00:00
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.' ],
'environment_uuid' => [ 'type' => 'string' , 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' ],
2024-07-09 12:12:36 +00:00
'private_key_uuid' => [ 'type' => 'string' , 'description' => 'The private key UUID.' ],
'git_repository' => [ 'type' => 'string' , 'description' => 'The git repository URL.' ],
'git_branch' => [ 'type' => 'string' , 'description' => 'The git branch.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'The ports to expose.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID.' ],
'build_pack' => [ 'type' => 'string' , 'enum' => [ 'nixpacks' , 'static' , 'dockerfile' , 'dockercompose' ], 'description' => 'The build pack type.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'domains' => [ 'type' => 'string' , 'description' => 'The application domains.' ],
'git_commit_sha' => [ 'type' => 'string' , 'description' => 'The git commit SHA.' ],
'docker_registry_image_name' => [ 'type' => 'string' , 'description' => 'The docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'description' => 'The docker registry image tag.' ],
'is_static' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application is static.' ],
2024-10-10 07:33:29 +00:00
'static_image' => [ 'type' => 'string' , 'enum' => [ 'nginx:alpine' ], 'description' => 'The static image.' ],
2024-07-09 12:12:36 +00:00
'install_command' => [ 'type' => 'string' , 'description' => 'The install command.' ],
'build_command' => [ 'type' => 'string' , 'description' => 'The build command.' ],
'start_command' => [ 'type' => 'string' , 'description' => 'The start command.' ],
'ports_mappings' => [ 'type' => 'string' , 'description' => 'The ports mappings.' ],
'base_directory' => [ 'type' => 'string' , 'description' => 'The base directory for all commands.' ],
'publish_directory' => [ 'type' => 'string' , 'description' => 'The publish directory.' ],
'health_check_enabled' => [ 'type' => 'boolean' , 'description' => 'Health check enabled.' ],
'health_check_path' => [ 'type' => 'string' , 'description' => 'Health check path.' ],
'health_check_port' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check port.' ],
'health_check_host' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check host.' ],
'health_check_method' => [ 'type' => 'string' , 'description' => 'Health check method.' ],
'health_check_return_code' => [ 'type' => 'integer' , 'description' => 'Health check return code.' ],
'health_check_scheme' => [ 'type' => 'string' , 'description' => 'Health check scheme.' ],
'health_check_response_text' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check response text.' ],
'health_check_interval' => [ 'type' => 'integer' , 'description' => 'Health check interval in seconds.' ],
'health_check_timeout' => [ 'type' => 'integer' , 'description' => 'Health check timeout in seconds.' ],
'health_check_retries' => [ 'type' => 'integer' , 'description' => 'Health check retries count.' ],
'health_check_start_period' => [ 'type' => 'integer' , 'description' => 'Health check start period in seconds.' ],
'limits_memory' => [ 'type' => 'string' , 'description' => 'Memory limit.' ],
'limits_memory_swap' => [ 'type' => 'string' , 'description' => 'Memory swap limit.' ],
'limits_memory_swappiness' => [ 'type' => 'integer' , 'description' => 'Memory swappiness.' ],
'limits_memory_reservation' => [ 'type' => 'string' , 'description' => 'Memory reservation.' ],
'limits_cpus' => [ 'type' => 'string' , 'description' => 'CPU limit.' ],
'limits_cpuset' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'CPU set.' ],
'limits_cpu_shares' => [ 'type' => 'integer' , 'description' => 'CPU shares.' ],
'custom_labels' => [ 'type' => 'string' , 'description' => 'Custom labels.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Pre deployment command container.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Github.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitlab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitea.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
'dockerfile' => [ 'type' => 'string' , 'description' => 'The Dockerfile content.' ],
'docker_compose_location' => [ 'type' => 'string' , 'description' => 'The Docker Compose location.' ],
'docker_compose_raw' => [ 'type' => 'string' , 'description' => 'The Docker Compose raw content.' ],
'docker_compose_custom_start_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom start command.' ],
'docker_compose_custom_build_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom build command.' ],
'docker_compose_domains' => [ 'type' => 'array' , 'description' => 'The Docker Compose domains.' ],
'watch_paths' => [ 'type' => 'string' , 'description' => 'The watch paths.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-04-23 11:22:01 +00:00
'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' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 08:45:10 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 08:45:10 +00:00
responses : [
new OA\Response (
2024-12-14 01:54:36 +00:00
response : 201 ,
2024-07-09 11:19:21 +00:00
description : 'Application created successfully.' ,
2024-12-14 01:54:36 +00:00
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
)
2024-07-09 11:19:21 +00:00
),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 11:19:21 +00:00
]
)]
public function create_private_deploy_key_application ( Request $request )
{
return $this -> create_application ( $request , 'private-deploy-key' );
}
#[OA\Post(
summary : 'Create (Dockerfile)' ,
description : 'Create new application based on a simple Dockerfile.' ,
path : '/applications/dockerfile' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-dockerfile-application' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
requestBody : new OA\RequestBody (
description : 'Application object that needs to be created.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
2024-12-17 12:42:16 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'environment_uuid' , 'dockerfile' ],
2024-07-09 11:19:21 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
2024-12-17 12:42:16 +00:00
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.' ],
'environment_uuid' => [ 'type' => 'string' , 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' ],
2024-07-09 12:12:36 +00:00
'dockerfile' => [ 'type' => 'string' , 'description' => 'The Dockerfile content.' ],
'build_pack' => [ 'type' => 'string' , 'enum' => [ 'nixpacks' , 'static' , 'dockerfile' , 'dockercompose' ], 'description' => 'The build pack type.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'The ports to expose.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'domains' => [ 'type' => 'string' , 'description' => 'The application domains.' ],
'docker_registry_image_name' => [ 'type' => 'string' , 'description' => 'The docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'description' => 'The docker registry image tag.' ],
'ports_mappings' => [ 'type' => 'string' , 'description' => 'The ports mappings.' ],
'base_directory' => [ 'type' => 'string' , 'description' => 'The base directory for all commands.' ],
'health_check_enabled' => [ 'type' => 'boolean' , 'description' => 'Health check enabled.' ],
'health_check_path' => [ 'type' => 'string' , 'description' => 'Health check path.' ],
'health_check_port' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check port.' ],
'health_check_host' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check host.' ],
'health_check_method' => [ 'type' => 'string' , 'description' => 'Health check method.' ],
'health_check_return_code' => [ 'type' => 'integer' , 'description' => 'Health check return code.' ],
'health_check_scheme' => [ 'type' => 'string' , 'description' => 'Health check scheme.' ],
'health_check_response_text' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check response text.' ],
'health_check_interval' => [ 'type' => 'integer' , 'description' => 'Health check interval in seconds.' ],
'health_check_timeout' => [ 'type' => 'integer' , 'description' => 'Health check timeout in seconds.' ],
'health_check_retries' => [ 'type' => 'integer' , 'description' => 'Health check retries count.' ],
'health_check_start_period' => [ 'type' => 'integer' , 'description' => 'Health check start period in seconds.' ],
'limits_memory' => [ 'type' => 'string' , 'description' => 'Memory limit.' ],
'limits_memory_swap' => [ 'type' => 'string' , 'description' => 'Memory swap limit.' ],
'limits_memory_swappiness' => [ 'type' => 'integer' , 'description' => 'Memory swappiness.' ],
'limits_memory_reservation' => [ 'type' => 'string' , 'description' => 'Memory reservation.' ],
'limits_cpus' => [ 'type' => 'string' , 'description' => 'CPU limit.' ],
'limits_cpuset' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'CPU set.' ],
'limits_cpu_shares' => [ 'type' => 'integer' , 'description' => 'CPU shares.' ],
'custom_labels' => [ 'type' => 'string' , 'description' => 'Custom labels.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Pre deployment command container.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Github.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitlab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitea.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-04-23 11:22:01 +00:00
'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' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 11:19:21 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 11:19:21 +00:00
responses : [
new OA\Response (
2024-12-14 01:54:36 +00:00
response : 201 ,
2024-07-09 11:19:21 +00:00
description : 'Application created successfully.' ,
2024-12-14 01:54:36 +00:00
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
)
2024-07-09 11:19:21 +00:00
),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 11:19:21 +00:00
]
)]
public function create_dockerfile_application ( Request $request )
{
return $this -> create_application ( $request , 'dockerfile' );
}
#[OA\Post(
summary : 'Create (Docker Image)' ,
description : 'Create new application based on a prebuilt docker image' ,
path : '/applications/dockerimage' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-dockerimage-application' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
requestBody : new OA\RequestBody (
description : 'Application object that needs to be created.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
2024-12-17 12:42:16 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'environment_uuid' , 'docker_registry_image_name' , 'ports_exposes' ],
2024-07-09 11:19:21 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
2024-12-17 12:42:16 +00:00
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.' ],
'environment_uuid' => [ 'type' => 'string' , 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' ],
2024-07-09 12:12:36 +00:00
'docker_registry_image_name' => [ 'type' => 'string' , 'description' => 'The docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'description' => 'The docker registry image tag.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'The ports to expose.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'domains' => [ 'type' => 'string' , 'description' => 'The application domains.' ],
'ports_mappings' => [ 'type' => 'string' , 'description' => 'The ports mappings.' ],
'health_check_enabled' => [ 'type' => 'boolean' , 'description' => 'Health check enabled.' ],
'health_check_path' => [ 'type' => 'string' , 'description' => 'Health check path.' ],
'health_check_port' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check port.' ],
'health_check_host' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check host.' ],
'health_check_method' => [ 'type' => 'string' , 'description' => 'Health check method.' ],
'health_check_return_code' => [ 'type' => 'integer' , 'description' => 'Health check return code.' ],
'health_check_scheme' => [ 'type' => 'string' , 'description' => 'Health check scheme.' ],
'health_check_response_text' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check response text.' ],
'health_check_interval' => [ 'type' => 'integer' , 'description' => 'Health check interval in seconds.' ],
'health_check_timeout' => [ 'type' => 'integer' , 'description' => 'Health check timeout in seconds.' ],
'health_check_retries' => [ 'type' => 'integer' , 'description' => 'Health check retries count.' ],
'health_check_start_period' => [ 'type' => 'integer' , 'description' => 'Health check start period in seconds.' ],
'limits_memory' => [ 'type' => 'string' , 'description' => 'Memory limit.' ],
'limits_memory_swap' => [ 'type' => 'string' , 'description' => 'Memory swap limit.' ],
'limits_memory_swappiness' => [ 'type' => 'integer' , 'description' => 'Memory swappiness.' ],
'limits_memory_reservation' => [ 'type' => 'string' , 'description' => 'Memory reservation.' ],
'limits_cpus' => [ 'type' => 'string' , 'description' => 'CPU limit.' ],
'limits_cpuset' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'CPU set.' ],
'limits_cpu_shares' => [ 'type' => 'integer' , 'description' => 'CPU shares.' ],
'custom_labels' => [ 'type' => 'string' , 'description' => 'Custom labels.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Pre deployment command container.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Github.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitlab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitea.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-04-23 11:22:01 +00:00
'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' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 11:19:21 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 11:19:21 +00:00
responses : [
new OA\Response (
2024-12-14 01:54:36 +00:00
response : 201 ,
2024-07-09 11:19:21 +00:00
description : 'Application created successfully.' ,
2024-12-14 01:54:36 +00:00
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
)
2024-07-09 11:19:21 +00:00
),
2024-07-09 08:45:10 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 08:45:10 +00:00
]
)]
2024-07-09 11:19:21 +00:00
public function create_dockerimage_application ( Request $request )
{
return $this -> create_application ( $request , 'dockerimage' );
}
#[OA\Post(
summary : 'Create (Docker Compose)' ,
description : 'Create new application based on a docker-compose file.' ,
path : '/applications/dockercompose' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-dockercompose-application' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
requestBody : new OA\RequestBody (
description : 'Application object that needs to be created.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
2024-12-17 12:42:16 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'environment_uuid' , 'docker_compose_raw' ],
2024-07-09 11:19:21 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
2024-12-17 12:42:16 +00:00
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.' ],
'environment_uuid' => [ 'type' => 'string' , 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.' ],
2024-07-09 12:12:36 +00:00
'docker_compose_raw' => [ 'type' => 'string' , 'description' => 'The Docker Compose raw content.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID if the server has more than one destinations.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 11:19:21 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 11:19:21 +00:00
responses : [
new OA\Response (
2024-12-14 01:54:36 +00:00
response : 201 ,
2024-07-09 11:19:21 +00:00
description : 'Application created successfully.' ,
2024-12-14 01:54:36 +00:00
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
)
2024-07-09 11:19:21 +00:00
),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 11:19:21 +00:00
]
)]
public function create_dockercompose_application ( Request $request )
{
return $this -> create_application ( $request , 'dockercompose' );
}
2024-07-04 11:45:06 +00:00
private function create_application ( Request $request , $type )
2024-06-28 13:05:37 +00:00
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-28 13:05:37 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-28 13:05:37 +00:00
}
2024-07-01 14:26:50 +00:00
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'create' , Application :: class );
2024-07-01 14:26:50 +00:00
$return = validateIncomingRequest ( $request );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 14:26:50 +00:00
return $return ;
2024-06-28 13:05:37 +00:00
}
2025-08-28 09:21:30 +00:00
$allowedFields = [ 'project_uuid' , 'environment_name' , 'environment_uuid' , 'server_uuid' , 'destination_uuid' , 'type' , 'name' , 'description' , 'is_static' , 'domains' , 'git_repository' , 'git_branch' , 'git_commit_sha' , 'private_key_uuid' , 'docker_registry_image_name' , 'docker_registry_image_tag' , 'build_pack' , 'install_command' , 'build_command' , 'start_command' , 'ports_exposes' , 'ports_mappings' , 'base_directory' , 'publish_directory' , 'health_check_enabled' , 'health_check_path' , 'health_check_port' , 'health_check_host' , 'health_check_method' , 'health_check_return_code' , 'health_check_scheme' , 'health_check_response_text' , 'health_check_interval' , 'health_check_timeout' , 'health_check_retries' , 'health_check_start_period' , 'limits_memory' , 'limits_memory_swap' , 'limits_memory_swappiness' , 'limits_memory_reservation' , 'limits_cpus' , 'limits_cpuset' , 'limits_cpu_shares' , 'custom_labels' , 'custom_docker_run_options' , 'post_deployment_command' , 'post_deployment_command_container' , 'pre_deployment_command' , 'pre_deployment_command_container' , 'manual_webhook_secret_github' , 'manual_webhook_secret_gitlab' , 'manual_webhook_secret_bitbucket' , 'manual_webhook_secret_gitea' , 'redirect' , 'github_app_uuid' , 'instant_deploy' , 'dockerfile' , 'docker_compose_location' , 'docker_compose_raw' , 'docker_compose_custom_start_command' , 'docker_compose_custom_build_command' , 'docker_compose_domains' , 'watch_paths' , 'use_build_server' , 'static_image' , 'custom_nginx_configuration' , 'is_http_basic_auth_enabled' , 'http_basic_auth_username' , 'http_basic_auth_password' , 'connect_to_docker_network' , 'force_domain_override' ];
2025-04-23 11:22:01 +00:00
2024-06-28 13:05:37 +00:00
$validator = customApiValidator ( $request -> all (), [
'name' => 'string|max:255' ,
'description' => 'string|nullable' ,
'project_uuid' => 'string|required' ,
2024-12-17 12:42:16 +00:00
'environment_name' => 'string|nullable' ,
'environment_uuid' => 'string|nullable' ,
2024-06-28 13:05:37 +00:00
'server_uuid' => 'string|required' ,
'destination_uuid' => 'string' ,
2025-04-23 11:22:01 +00:00
'is_http_basic_auth_enabled' => 'boolean' ,
'http_basic_auth_username' => 'string|nullable' ,
'http_basic_auth_password' => 'string|nullable' ,
2024-06-28 13:05:37 +00:00
]);
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-06-28 13:05:37 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-06-28 13:05:37 +00:00
}
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
2024-06-30 09:30:31 +00:00
2024-12-17 12:42:16 +00:00
$environmentUuid = $request -> environment_uuid ;
$environmentName = $request -> environment_name ;
if ( blank ( $environmentUuid ) && blank ( $environmentName )) {
return response () -> json ([ 'message' => 'You need to provide at least one of environment_name or environment_uuid.' ], 422 );
}
2024-06-28 13:05:37 +00:00
$serverUuid = $request -> server_uuid ;
$fqdn = $request -> domains ;
$instantDeploy = $request -> instant_deploy ;
2024-06-30 09:30:31 +00:00
$githubAppUuid = $request -> github_app_uuid ;
2024-09-28 04:11:43 +00:00
$useBuildServer = $request -> use_build_server ;
2024-10-10 07:33:29 +00:00
$isStatic = $request -> is_static ;
2025-05-27 13:07:31 +00:00
$connectToDockerNetwork = $request -> connect_to_docker_network ;
2024-11-11 14:00:37 +00:00
$customNginxConfiguration = $request -> custom_nginx_configuration ;
if ( ! is_null ( $customNginxConfiguration )) {
if ( ! isBase64Encoded ( $customNginxConfiguration )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.' ,
],
], 422 );
}
$customNginxConfiguration = base64_decode ( $customNginxConfiguration );
if ( mb_detect_encoding ( $customNginxConfiguration , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.' ,
],
], 422 );
}
}
2024-06-30 09:30:31 +00:00
2024-06-28 13:05:37 +00:00
$project = Project :: whereTeamId ( $teamId ) -> whereUuid ( $request -> project_uuid ) -> first ();
if ( ! $project ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Project not found.' ], 404 );
2024-06-28 13:05:37 +00:00
}
2024-12-17 12:42:16 +00:00
$environment = $project -> environments () -> where ( 'name' , $environmentName ) -> first ();
if ( ! $environment ) {
$environment = $project -> environments () -> where ( 'uuid' , $environmentUuid ) -> first ();
}
2024-06-28 13:05:37 +00:00
if ( ! $environment ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Environment not found.' ], 404 );
2024-06-28 13:05:37 +00:00
}
$server = Server :: whereTeamId ( $teamId ) -> whereUuid ( $serverUuid ) -> first ();
if ( ! $server ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
2024-06-28 13:05:37 +00:00
}
$destinations = $server -> destinations ();
if ( $destinations -> count () == 0 ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Server has no destinations.' ], 400 );
2024-06-28 13:05:37 +00:00
}
if ( $destinations -> count () > 1 && ! $request -> has ( 'destination_uuid' )) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Server has multiple destinations and you do not set destination_uuid.' ], 400 );
2024-06-28 13:05:37 +00:00
}
$destination = $destinations -> first ();
if ( $type === 'public' ) {
2024-10-10 07:33:29 +00:00
$validationRules = [
2025-08-22 12:38:21 +00:00
'git_repository' => [ 'string' , 'required' , new ValidGitRepositoryUrl ],
'git_branch' => [ 'string' , 'required' , new ValidGitBranch ],
2024-07-03 15:10:00 +00:00
'build_pack' => [ 'required' , Rule :: enum ( BuildPackTypes :: class )],
2024-06-28 13:05:37 +00:00
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required' ,
2024-07-02 14:12:04 +00:00
'docker_compose_location' => 'string' ,
'docker_compose_raw' => 'string|nullable' ,
'docker_compose_domains' => 'array|nullable' ,
2024-10-10 07:33:29 +00:00
];
2025-03-10 10:18:14 +00:00
// ports_exposes is not required for dockercompose
if ( $request -> build_pack === 'dockercompose' ) {
$validationRules [ 'ports_exposes' ] = 'string' ;
$request -> offsetSet ( 'ports_exposes' , '80' );
}
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2024-06-28 13:05:37 +00:00
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
2024-12-17 12:42:16 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , generate_application_name ( $request -> git_repository , $request -> git_branch ));
}
2024-06-30 09:30:31 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-06-28 13:05:37 +00:00
return $return ;
}
2025-01-07 14:31:43 +00:00
2024-07-24 19:11:12 +00:00
$application = new Application ;
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2025-01-07 14:31:43 +00:00
2024-06-28 13:05:37 +00:00
$application -> fill ( $request -> all ());
2024-07-02 14:12:04 +00:00
$dockerComposeDomainsJson = collect ();
if ( $request -> has ( 'docker_compose_domains' )) {
$dockerComposeDomains = collect ( $request -> docker_compose_domains );
if ( $dockerComposeDomains -> count () > 0 ) {
$dockerComposeDomains -> each ( function ( $domain , $key ) use ( $dockerComposeDomainsJson ) {
$dockerComposeDomainsJson -> put ( data_get ( $domain , 'name' ), [ 'domain' => data_get ( $domain , 'domain' )]);
});
}
$request -> offsetUnset ( 'docker_compose_domains' );
}
if ( $dockerComposeDomainsJson -> count () > 0 ) {
$application -> docker_compose_domains = $dockerComposeDomainsJson ;
}
2025-03-10 10:18:14 +00:00
$repository_url_parsed = Url :: fromString ( $request -> git_repository );
$git_host = $repository_url_parsed -> getHost ();
if ( $git_host === 'github.com' ) {
$application -> source_type = GithubApp :: class ;
$application -> source_id = GithubApp :: find ( 0 ) -> id ;
}
2025-08-22 12:38:21 +00:00
$application -> git_repository = str ( $repository_url_parsed -> getSegment ( 1 ) . '/' . $repository_url_parsed -> getSegment ( 2 )) -> trim () -> toString ();
2024-06-28 13:05:37 +00:00
$application -> fqdn = $fqdn ;
$application -> destination_id = $destination -> id ;
$application -> destination_type = $destination -> getMorphClass ();
$application -> environment_id = $environment -> id ;
2024-10-10 07:33:29 +00:00
$application -> save ();
if ( isset ( $isStatic )) {
$application -> settings -> is_static = $isStatic ;
$application -> settings -> save ();
}
2025-05-27 13:07:31 +00:00
if ( isset ( $connectToDockerNetwork )) {
$application -> settings -> connect_to_docker_network = $connectToDockerNetwork ;
$application -> settings -> save ();
}
2024-10-03 13:09:56 +00:00
if ( isset ( $useBuildServer )) {
$application -> settings -> is_build_server_enabled = $useBuildServer ;
$application -> settings -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2025-01-08 16:05:11 +00:00
if ( $application -> settings -> is_container_label_readonly_enabled ) {
2024-07-17 12:52:40 +00:00
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2025-01-07 14:31:43 +00:00
2024-06-28 13:05:37 +00:00
if ( $instantDeploy ) {
2024-07-25 11:31:59 +00:00
$deployment_uuid = new Cuid2 ;
2025-01-07 14:31:43 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-06-28 13:05:37 +00:00
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2025-01-07 14:31:43 +00:00
} else {
if ( $application -> build_pack === 'dockercompose' ) {
LoadComposeFile :: dispatch ( $application );
}
2024-06-28 13:05:37 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
2024-12-14 01:54:36 +00:00
])) -> setStatusCode ( 201 );
2025-01-07 14:31:43 +00:00
} elseif ( $type === 'private-gh-app' ) {
2024-10-10 07:33:29 +00:00
$validationRules = [
2024-06-30 09:30:31 +00:00
'git_repository' => 'string|required' ,
2025-08-22 12:38:21 +00:00
'git_branch' => [ 'string' , 'required' , new ValidGitBranch ],
2024-06-30 09:30:31 +00:00
'build_pack' => [ 'required' , Rule :: enum ( BuildPackTypes :: class )],
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required' ,
'github_app_uuid' => 'string|required' ,
2024-07-02 14:12:04 +00:00
'watch_paths' => 'string|nullable' ,
'docker_compose_location' => 'string' ,
'docker_compose_raw' => 'string|nullable' ,
2024-10-10 07:33:29 +00:00
];
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2025-01-07 14:31:43 +00:00
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2024-06-30 09:30:31 +00:00
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
2025-01-07 14:31:43 +00:00
2024-12-17 12:42:16 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , generate_application_name ( $request -> git_repository , $request -> git_branch ));
}
if ( $request -> build_pack === 'dockercompose' ) {
$request -> offsetSet ( 'ports_exposes' , '80' );
}
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-06-30 09:30:31 +00:00
return $return ;
}
$githubApp = GithubApp :: whereTeamId ( $teamId ) -> where ( 'uuid' , $githubAppUuid ) -> first ();
if ( ! $githubApp ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Github App not found.' ], 404 );
2024-06-30 09:30:31 +00:00
}
2025-03-24 13:29:17 +00:00
$token = generateGithubInstallationToken ( $githubApp );
if ( ! $token ) {
return response () -> json ([ 'message' => 'Failed to generate Github App token.' ], 400 );
}
$repositories = collect ();
$page = 1 ;
$repositories = loadRepositoryByPage ( $githubApp , $token , $page );
if ( $repositories [ 'total_count' ] > 0 ) {
while ( count ( $repositories [ 'repositories' ]) < $repositories [ 'total_count' ]) {
$page ++ ;
$repositories = loadRepositoryByPage ( $githubApp , $token , $page );
}
}
2024-06-30 09:30:31 +00:00
$gitRepository = $request -> git_repository ;
if ( str ( $gitRepository ) -> startsWith ( 'http' ) || str ( $gitRepository ) -> contains ( 'github.com' )) {
$gitRepository = str ( $gitRepository ) -> replace ( 'https://' , '' ) -> replace ( 'http://' , '' ) -> replace ( 'github.com/' , '' );
}
2025-03-24 13:29:17 +00:00
$gitRepositoryFound = collect ( $repositories [ 'repositories' ]) -> firstWhere ( 'full_name' , $gitRepository );
if ( ! $gitRepositoryFound ) {
return response () -> json ([ 'message' => 'Repository not found.' ], 404 );
}
$repository_project_id = data_get ( $gitRepositoryFound , 'id' );
2024-07-24 19:11:12 +00:00
$application = new Application ;
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
$application -> fill ( $request -> all ());
2025-01-07 14:31:43 +00:00
2024-07-02 14:12:04 +00:00
$dockerComposeDomainsJson = collect ();
if ( $request -> has ( 'docker_compose_domains' )) {
2025-05-19 11:29:23 +00:00
if ( ! $request -> has ( 'docker_compose_raw' )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The base64 encoded docker_compose_raw is required.' ,
],
], 422 );
}
if ( ! isBase64Encoded ( $request -> docker_compose_raw )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
],
], 422 );
}
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
if ( mb_detect_encoding ( $dockerComposeRaw , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
],
], 422 );
}
$yaml = Yaml :: parse ( $dockerComposeRaw );
2024-07-02 14:12:04 +00:00
$services = data_get ( $yaml , 'services' );
$dockerComposeDomains = collect ( $request -> docker_compose_domains );
if ( $dockerComposeDomains -> count () > 0 ) {
$dockerComposeDomains -> each ( function ( $domain , $key ) use ( $services , $dockerComposeDomainsJson ) {
$name = data_get ( $domain , 'name' );
if ( data_get ( $services , $name )) {
$dockerComposeDomainsJson -> put ( $name , [ 'domain' => data_get ( $domain , 'domain' )]);
}
});
}
$request -> offsetUnset ( 'docker_compose_domains' );
}
if ( $dockerComposeDomainsJson -> count () > 0 ) {
$application -> docker_compose_domains = $dockerComposeDomainsJson ;
}
2024-06-30 09:30:31 +00:00
$application -> fqdn = $fqdn ;
2025-08-22 12:38:21 +00:00
$application -> git_repository = str ( $gitRepository ) -> trim () -> toString ();
2024-06-30 09:30:31 +00:00
$application -> destination_id = $destination -> id ;
$application -> destination_type = $destination -> getMorphClass ();
$application -> environment_id = $environment -> id ;
$application -> source_type = $githubApp -> getMorphClass ();
$application -> source_id = $githubApp -> id ;
2025-03-24 13:29:17 +00:00
$application -> repository_project_id = $repository_project_id ;
2024-12-14 01:58:11 +00:00
$application -> save ();
$application -> refresh ();
2024-10-03 13:09:56 +00:00
if ( isset ( $useBuildServer )) {
$application -> settings -> is_build_server_enabled = $useBuildServer ;
$application -> settings -> save ();
}
2025-01-08 16:05:11 +00:00
if ( $application -> settings -> is_container_label_readonly_enabled ) {
2024-07-17 12:52:40 +00:00
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
if ( $instantDeploy ) {
2024-07-25 11:31:59 +00:00
$deployment_uuid = new Cuid2 ;
2025-01-07 14:31:43 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-06-30 09:30:31 +00:00
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2025-01-07 14:31:43 +00:00
} else {
if ( $application -> build_pack === 'dockercompose' ) {
LoadComposeFile :: dispatch ( $application );
}
2024-06-30 09:30:31 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
2024-12-14 01:54:36 +00:00
])) -> setStatusCode ( 201 );
2025-01-07 14:31:43 +00:00
} elseif ( $type === 'private-deploy-key' ) {
2024-10-10 07:33:29 +00:00
$validationRules = [
2025-08-22 12:38:21 +00:00
'git_repository' => [ 'string' , 'required' , new ValidGitRepositoryUrl ],
'git_branch' => [ 'string' , 'required' , new ValidGitBranch ],
2024-06-30 09:30:31 +00:00
'build_pack' => [ 'required' , Rule :: enum ( BuildPackTypes :: class )],
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required' ,
'private_key_uuid' => 'string|required' ,
2024-07-02 14:12:04 +00:00
'watch_paths' => 'string|nullable' ,
'docker_compose_location' => 'string' ,
'docker_compose_raw' => 'string|nullable' ,
2024-10-10 07:33:29 +00:00
];
2025-01-07 14:31:43 +00:00
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
2024-12-17 12:42:16 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , generate_application_name ( $request -> git_repository , $request -> git_branch ));
}
if ( $request -> build_pack === 'dockercompose' ) {
$request -> offsetSet ( 'ports_exposes' , '80' );
}
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-06-30 09:30:31 +00:00
return $return ;
}
$privateKey = PrivateKey :: whereTeamId ( $teamId ) -> where ( 'uuid' , $request -> private_key_uuid ) -> first ();
if ( ! $privateKey ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Private Key not found.' ], 404 );
2024-06-30 09:30:31 +00:00
}
2025-01-07 14:31:43 +00:00
2024-07-24 19:11:12 +00:00
$application = new Application ;
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
$application -> fill ( $request -> all ());
2025-01-07 14:31:43 +00:00
2024-07-02 14:12:04 +00:00
$dockerComposeDomainsJson = collect ();
if ( $request -> has ( 'docker_compose_domains' )) {
2025-05-19 11:30:31 +00:00
if ( ! $request -> has ( 'docker_compose_raw' )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The base64 encoded docker_compose_raw is required.' ,
],
], 422 );
}
if ( ! isBase64Encoded ( $request -> docker_compose_raw )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
],
], 422 );
}
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
if ( mb_detect_encoding ( $dockerComposeRaw , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
],
], 422 );
}
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
$yaml = Yaml :: parse ( $dockerComposeRaw );
2024-07-02 14:12:04 +00:00
$services = data_get ( $yaml , 'services' );
$dockerComposeDomains = collect ( $request -> docker_compose_domains );
if ( $dockerComposeDomains -> count () > 0 ) {
$dockerComposeDomains -> each ( function ( $domain , $key ) use ( $services , $dockerComposeDomainsJson ) {
$name = data_get ( $domain , 'name' );
if ( data_get ( $services , $name )) {
$dockerComposeDomainsJson -> put ( $name , [ 'domain' => data_get ( $domain , 'domain' )]);
}
});
}
$request -> offsetUnset ( 'docker_compose_domains' );
}
if ( $dockerComposeDomainsJson -> count () > 0 ) {
$application -> docker_compose_domains = $dockerComposeDomainsJson ;
}
2024-06-30 09:30:31 +00:00
$application -> fqdn = $fqdn ;
$application -> private_key_id = $privateKey -> id ;
$application -> destination_id = $destination -> id ;
$application -> destination_type = $destination -> getMorphClass ();
$application -> environment_id = $environment -> id ;
2024-12-14 01:58:11 +00:00
$application -> save ();
$application -> refresh ();
2024-10-03 13:09:56 +00:00
if ( isset ( $useBuildServer )) {
$application -> settings -> is_build_server_enabled = $useBuildServer ;
$application -> settings -> save ();
}
2025-01-08 16:05:11 +00:00
if ( $application -> settings -> is_container_label_readonly_enabled ) {
2024-07-17 12:52:40 +00:00
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2025-01-07 14:31:43 +00:00
2024-06-30 09:30:31 +00:00
if ( $instantDeploy ) {
2024-07-25 11:31:59 +00:00
$deployment_uuid = new Cuid2 ;
2025-01-07 14:31:43 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-06-30 09:30:31 +00:00
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2025-01-07 14:31:43 +00:00
} else {
if ( $application -> build_pack === 'dockercompose' ) {
LoadComposeFile :: dispatch ( $application );
}
2024-06-30 09:30:31 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
2024-12-14 01:54:36 +00:00
])) -> setStatusCode ( 201 );
2025-01-07 14:31:43 +00:00
} elseif ( $type === 'dockerfile' ) {
2024-10-10 07:33:29 +00:00
$validationRules = [
2024-07-01 09:39:10 +00:00
'dockerfile' => 'string|required' ,
2024-10-10 07:33:29 +00:00
];
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2025-01-07 14:31:43 +00:00
2024-07-01 09:39:10 +00:00
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
2024-12-17 12:42:16 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , 'dockerfile-' . new Cuid2 );
}
2025-01-07 14:31:43 +00:00
2024-07-01 09:39:10 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 09:39:10 +00:00
return $return ;
}
if ( ! isBase64Encoded ( $request -> dockerfile )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'dockerfile' => 'The dockerfile should be base64 encoded.' ,
],
], 422 );
}
$dockerFile = base64_decode ( $request -> dockerfile );
if ( mb_detect_encoding ( $dockerFile , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'dockerfile' => 'The dockerfile should be base64 encoded.' ,
],
], 422 );
}
$dockerFile = base64_decode ( $request -> dockerfile );
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2025-01-07 14:31:43 +00:00
2024-07-01 09:39:10 +00:00
$port = get_port_from_dockerfile ( $request -> dockerfile );
if ( ! $port ) {
$port = 80 ;
}
2025-01-07 14:31:43 +00:00
2024-07-24 19:11:12 +00:00
$application = new Application ;
2024-07-01 09:39:10 +00:00
$application -> fill ( $request -> all ());
$application -> fqdn = $fqdn ;
$application -> ports_exposes = $port ;
$application -> build_pack = 'dockerfile' ;
$application -> dockerfile = $dockerFile ;
$application -> destination_id = $destination -> id ;
$application -> destination_type = $destination -> getMorphClass ();
$application -> environment_id = $environment -> id ;
2025-01-07 14:31:43 +00:00
2024-07-01 09:39:10 +00:00
$application -> git_repository = 'coollabsio/coolify' ;
$application -> git_branch = 'main' ;
$application -> save ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-12-14 01:58:11 +00:00
if ( isset ( $useBuildServer )) {
$application -> settings -> is_build_server_enabled = $useBuildServer ;
$application -> settings -> save ();
}
2025-01-08 16:05:11 +00:00
if ( $application -> settings -> is_container_label_readonly_enabled ) {
2024-07-17 12:52:40 +00:00
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2025-01-07 14:31:43 +00:00
2024-07-01 09:39:10 +00:00
if ( $instantDeploy ) {
2024-07-25 11:31:59 +00:00
$deployment_uuid = new Cuid2 ;
2024-07-01 09:39:10 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-07-01 09:39:10 +00:00
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2024-07-01 09:39:10 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
2024-12-14 01:54:36 +00:00
])) -> setStatusCode ( 201 );
2025-01-07 14:31:43 +00:00
} elseif ( $type === 'dockerimage' ) {
2024-10-10 07:33:29 +00:00
$validationRules = [
2024-07-01 14:26:50 +00:00
'docker_registry_image_name' => 'string|required' ,
'docker_registry_image_tag' => 'string' ,
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required' ,
2024-10-10 07:33:29 +00:00
];
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
2024-12-17 12:42:16 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , 'docker-image-' . new Cuid2 );
}
2024-07-01 14:26:50 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 14:26:50 +00:00
return $return ;
}
if ( ! $request -> docker_registry_image_tag ) {
$request -> offsetSet ( 'docker_registry_image_tag' , 'latest' );
}
2024-07-24 19:11:12 +00:00
$application = new Application ;
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
$application -> fill ( $request -> all ());
$application -> fqdn = $fqdn ;
$application -> build_pack = 'dockerimage' ;
$application -> destination_id = $destination -> id ;
$application -> destination_type = $destination -> getMorphClass ();
$application -> environment_id = $environment -> id ;
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
$application -> git_repository = 'coollabsio/coolify' ;
$application -> git_branch = 'main' ;
$application -> save ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-12-14 01:58:11 +00:00
if ( isset ( $useBuildServer )) {
$application -> settings -> is_build_server_enabled = $useBuildServer ;
$application -> settings -> save ();
}
2025-01-08 16:05:11 +00:00
if ( $application -> settings -> is_container_label_readonly_enabled ) {
2024-07-17 12:52:40 +00:00
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
if ( $instantDeploy ) {
2024-07-25 11:31:59 +00:00
$deployment_uuid = new Cuid2 ;
2024-07-01 14:26:50 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-07-01 14:26:50 +00:00
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2024-07-01 14:26:50 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
2024-12-14 01:54:36 +00:00
])) -> setStatusCode ( 201 );
2025-01-07 14:31:43 +00:00
} elseif ( $type === 'dockercompose' ) {
2025-08-28 09:21:30 +00:00
$allowedFields = [ 'project_uuid' , 'environment_name' , 'environment_uuid' , 'server_uuid' , 'destination_uuid' , 'type' , 'name' , 'description' , 'instant_deploy' , 'docker_compose_raw' , 'force_domain_override' ];
2025-01-07 14:31:43 +00:00
2024-07-02 14:12:04 +00:00
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-07-02 14:12:04 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-07-02 14:12:04 +00:00
}
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
2024-07-01 14:26:50 +00:00
if ( ! $request -> has ( 'name' )) {
2024-07-25 11:31:59 +00:00
$request -> offsetSet ( 'name' , 'service' . new Cuid2 );
2024-07-01 14:26:50 +00:00
}
2024-10-10 07:33:29 +00:00
$validationRules = [
2024-07-02 14:12:04 +00:00
'docker_compose_raw' => 'string|required' ,
2024-10-10 07:33:29 +00:00
];
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 14:26:50 +00:00
return $return ;
}
2024-07-02 14:12:04 +00:00
if ( ! isBase64Encoded ( $request -> docker_compose_raw )) {
2024-07-01 14:26:50 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
2024-07-02 14:12:04 +00:00
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
2024-07-01 14:26:50 +00:00
],
], 422 );
}
2024-07-02 14:12:04 +00:00
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
if ( mb_detect_encoding ( $dockerComposeRaw , 'ASCII' , true ) === false ) {
2024-07-01 14:26:50 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
2024-07-02 14:12:04 +00:00
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
2024-07-01 14:26:50 +00:00
],
], 422 );
}
2024-07-02 14:12:04 +00:00
$dockerCompose = base64_decode ( $request -> docker_compose_raw );
2024-07-01 14:26:50 +00:00
$dockerComposeRaw = Yaml :: dump ( Yaml :: parse ( $dockerCompose ), 10 , 2 , Yaml :: DUMP_MULTI_LINE_LITERAL_BLOCK );
2025-01-07 14:31:43 +00:00
2024-07-24 19:11:12 +00:00
$service = new Service ;
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2024-07-02 14:12:04 +00:00
$service -> fill ( $request -> all ());
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
$service -> docker_compose_raw = $dockerComposeRaw ;
$service -> environment_id = $environment -> id ;
$service -> server_id = $server -> id ;
$service -> destination_id = $destination -> id ;
$service -> destination_type = $destination -> getMorphClass ();
$service -> save ();
2025-01-07 14:31:43 +00:00
2024-07-01 14:26:50 +00:00
$service -> parse ( isNew : true );
2024-07-09 11:19:21 +00:00
if ( $instantDeploy ) {
StartService :: dispatch ( $service );
}
2024-07-01 14:26:50 +00:00
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $service , 'uuid' ),
2024-07-04 11:45:06 +00:00
'domains' => data_get ( $service , 'domains' ),
2024-12-14 01:54:36 +00:00
])) -> setStatusCode ( 201 );
2024-06-28 13:05:37 +00:00
}
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Invalid type.' ], 400 );
2024-06-28 13:05:37 +00:00
}
2024-07-09 08:45:10 +00:00
#[OA\Get(
summary : 'Get' ,
description : 'Get application by UUID.' ,
path : '/applications/{uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'get-application-by-uuid' ,
2024-07-09 08:45:10 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
2024-07-09 11:19:21 +00:00
description : 'Get application by UUID.' ,
2024-07-09 08:45:10 +00:00
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
ref : '#/components/schemas/Application'
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 08:45:10 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function application_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-07-01 14:26:50 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'view' , $application );
2024-07-03 11:13:38 +00:00
return response () -> json ( $this -> removeSensitiveData ( $application ));
2024-06-26 11:00:36 +00:00
}
2025-02-01 15:09:59 +00:00
#[OA\Get(
summary : 'Get application logs.' ,
description : 'Get application logs by UUID.' ,
path : '/applications/{uuid}/logs' ,
operationId : 'get-application-logs-by-uuid' ,
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
2025-02-02 13:43:31 +00:00
new OA\Parameter (
name : 'lines' ,
in : 'query' ,
description : 'Number of lines to show from the end of the logs.' ,
required : false ,
schema : new OA\Schema (
type : 'integer' ,
format : 'int32' ,
default : 100 ,
)
),
2025-02-01 15:09:59 +00:00
],
responses : [
new OA\Response (
response : 200 ,
description : 'Get application logs by UUID.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
2025-02-04 02:56:15 +00:00
type : 'object' ,
properties : [
'logs' => [ 'type' => 'string' ],
]
2025-02-01 15:09:59 +00:00
)
),
]
),
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
public function logs_by_uuid ( Request $request )
{
$teamId = getTeamIdFromToken ();
if ( is_null ( $teamId )) {
return invalidTokenResponse ();
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
}
2025-02-01 17:21:38 +00:00
2025-02-02 10:01:01 +00:00
$containers = getCurrentApplicationContainerStatus ( $application -> destination -> server , $application -> id );
if ( $containers -> count () == 0 ) {
return response () -> json ([
'message' => 'Application is not running.' ,
], 400 );
}
$container = $containers -> first ();
2025-02-01 17:21:38 +00:00
2025-02-02 10:01:01 +00:00
$status = getContainerStatus ( $application -> destination -> server , $container [ 'Names' ]);
2025-02-01 17:21:38 +00:00
if ( $status !== 'running' ) {
return response () -> json ([
'message' => 'Application is not running.' ,
], 400 );
}
2025-02-02 13:43:31 +00:00
$lines = $request -> query -> get ( 'lines' , 100 ) ? : 100 ;
$logs = getContainerLogs ( $application -> destination -> server , $container [ 'ID' ], $lines );
2025-02-01 17:21:38 +00:00
return response () -> json ([
2025-02-02 10:01:01 +00:00
'logs' => $logs ,
2025-02-01 17:21:38 +00:00
]);
2025-02-01 15:09:59 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Delete(
summary : 'Delete' ,
description : 'Delete application by UUID.' ,
path : '/applications/{uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'delete-application-by-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
2024-10-01 07:04:01 +00:00
description : 'UUID of the application.' ,
2024-07-09 11:19:21 +00:00
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
2024-10-01 07:02:16 +00:00
new OA\Parameter ( name : 'delete_configurations' , in : 'query' , required : false , description : 'Delete configurations.' , schema : new OA\Schema ( type : 'boolean' , default : true )),
new OA\Parameter ( name : 'delete_volumes' , in : 'query' , required : false , description : 'Delete volumes.' , schema : new OA\Schema ( type : 'boolean' , default : true )),
new OA\Parameter ( name : 'docker_cleanup' , in : 'query' , required : false , description : 'Run docker cleanup.' , schema : new OA\Schema ( type : 'boolean' , default : true )),
new OA\Parameter ( name : 'delete_connected_networks' , in : 'query' , required : false , description : 'Delete connected networks.' , schema : new OA\Schema ( type : 'boolean' , default : true )),
2024-07-09 11:19:21 +00:00
],
responses : [
new OA\Response (
response : 200 ,
description : 'Application deleted.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Application deleted.' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function delete_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2025-01-07 14:31:43 +00:00
$cleanup = filter_var ( $request -> query -> get ( 'cleanup' , true ), FILTER_VALIDATE_BOOLEAN );
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-26 11:00:36 +00:00
}
2024-07-11 08:17:20 +00:00
if ( ! $request -> uuid ) {
return response () -> json ([ 'message' => 'UUID is required.' ], 404 );
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2024-10-01 07:02:16 +00:00
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'delete' , $application );
2024-07-11 10:50:12 +00:00
DeleteResourceJob :: dispatch (
resource : $application ,
2024-10-01 07:02:16 +00:00
deleteVolumes : $request -> query -> get ( 'delete_volumes' , true ),
2025-08-04 19:15:56 +00:00
deleteConnectedNetworks : $request -> query -> get ( 'delete_connected_networks' , true ),
deleteConfigurations : $request -> query -> get ( 'delete_configurations' , true ),
dockerCleanup : $request -> query -> get ( 'docker_cleanup' , true )
2024-10-01 07:02:16 +00:00
);
2024-06-26 11:00:36 +00:00
return response () -> json ([
'message' => 'Application deletion request queued.' ,
]);
2024-06-21 14:46:13 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Patch(
summary : 'Update' ,
description : 'Update application by UUID.' ,
2024-07-25 09:53:57 +00:00
path : '/applications/{uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'update-application-by-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
2025-04-09 16:52:12 +00:00
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
2024-07-09 11:19:21 +00:00
requestBody : new OA\RequestBody (
description : 'Application updated.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
2024-07-09 12:12:36 +00:00
'project_uuid' => [ 'type' => 'string' , 'description' => 'The project UUID.' ],
'server_uuid' => [ 'type' => 'string' , 'description' => 'The server UUID.' ],
'environment_name' => [ 'type' => 'string' , 'description' => 'The environment name.' ],
'github_app_uuid' => [ 'type' => 'string' , 'description' => 'The Github App UUID.' ],
'git_repository' => [ 'type' => 'string' , 'description' => 'The git repository URL.' ],
'git_branch' => [ 'type' => 'string' , 'description' => 'The git branch.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'The ports to expose.' ],
'destination_uuid' => [ 'type' => 'string' , 'description' => 'The destination UUID.' ],
'build_pack' => [ 'type' => 'string' , 'enum' => [ 'nixpacks' , 'static' , 'dockerfile' , 'dockercompose' ], 'description' => 'The build pack type.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'description' => [ 'type' => 'string' , 'description' => 'The application description.' ],
'domains' => [ 'type' => 'string' , 'description' => 'The application domains.' ],
'git_commit_sha' => [ 'type' => 'string' , 'description' => 'The git commit SHA.' ],
'docker_registry_image_name' => [ 'type' => 'string' , 'description' => 'The docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'description' => 'The docker registry image tag.' ],
'is_static' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application is static.' ],
'install_command' => [ 'type' => 'string' , 'description' => 'The install command.' ],
'build_command' => [ 'type' => 'string' , 'description' => 'The build command.' ],
'start_command' => [ 'type' => 'string' , 'description' => 'The start command.' ],
'ports_mappings' => [ 'type' => 'string' , 'description' => 'The ports mappings.' ],
'base_directory' => [ 'type' => 'string' , 'description' => 'The base directory for all commands.' ],
'publish_directory' => [ 'type' => 'string' , 'description' => 'The publish directory.' ],
'health_check_enabled' => [ 'type' => 'boolean' , 'description' => 'Health check enabled.' ],
'health_check_path' => [ 'type' => 'string' , 'description' => 'Health check path.' ],
'health_check_port' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check port.' ],
'health_check_host' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check host.' ],
'health_check_method' => [ 'type' => 'string' , 'description' => 'Health check method.' ],
'health_check_return_code' => [ 'type' => 'integer' , 'description' => 'Health check return code.' ],
'health_check_scheme' => [ 'type' => 'string' , 'description' => 'Health check scheme.' ],
'health_check_response_text' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Health check response text.' ],
'health_check_interval' => [ 'type' => 'integer' , 'description' => 'Health check interval in seconds.' ],
'health_check_timeout' => [ 'type' => 'integer' , 'description' => 'Health check timeout in seconds.' ],
'health_check_retries' => [ 'type' => 'integer' , 'description' => 'Health check retries count.' ],
'health_check_start_period' => [ 'type' => 'integer' , 'description' => 'Health check start period in seconds.' ],
'limits_memory' => [ 'type' => 'string' , 'description' => 'Memory limit.' ],
'limits_memory_swap' => [ 'type' => 'string' , 'description' => 'Memory swap limit.' ],
'limits_memory_swappiness' => [ 'type' => 'integer' , 'description' => 'Memory swappiness.' ],
'limits_memory_reservation' => [ 'type' => 'string' , 'description' => 'Memory reservation.' ],
'limits_cpus' => [ 'type' => 'string' , 'description' => 'CPU limit.' ],
'limits_cpuset' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'CPU set.' ],
'limits_cpu_shares' => [ 'type' => 'integer' , 'description' => 'CPU shares.' ],
'custom_labels' => [ 'type' => 'string' , 'description' => 'Custom labels.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'description' => 'Pre deployment command container.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Github.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitlab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'description' => 'Manual webhook secret for Gitea.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
'instant_deploy' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the application should be deployed instantly.' ],
'dockerfile' => [ 'type' => 'string' , 'description' => 'The Dockerfile content.' ],
'docker_compose_location' => [ 'type' => 'string' , 'description' => 'The Docker Compose location.' ],
'docker_compose_raw' => [ 'type' => 'string' , 'description' => 'The Docker Compose raw content.' ],
'docker_compose_custom_start_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom start command.' ],
'docker_compose_custom_build_command' => [ 'type' => 'string' , 'description' => 'The Docker Compose custom build command.' ],
'docker_compose_domains' => [ 'type' => 'array' , 'description' => 'The Docker Compose domains.' ],
'watch_paths' => [ 'type' => 'string' , 'description' => 'The watch paths.' ],
2024-09-28 04:11:43 +00:00
'use_build_server' => [ 'type' => 'boolean' , 'nullable' => true , 'description' => 'Use build server.' ],
2025-05-27 13:07:31 +00:00
'connect_to_docker_network' => [ 'type' => 'boolean' , 'description' => 'The flag to connect the service to the predefined Docker network.' ],
2025-08-28 09:21:30 +00:00
'force_domain_override' => [ 'type' => 'boolean' , 'description' => 'Force domain usage even if conflicts are detected. Default is false.' ],
2024-07-09 11:19:21 +00:00
],
2024-12-07 13:26:44 +00:00
)
),
]
),
2024-07-09 11:19:21 +00:00
responses : [
new OA\Response (
response : 200 ,
description : 'Application updated.' ,
2024-07-10 08:29:52 +00:00
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
2025-08-28 09:21:30 +00:00
new OA\Response (
response : 409 ,
description : 'Domain conflicts detected.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ],
'warning' => [ 'type' => 'string' , 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ],
'conflicts' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
'domain' => [ 'type' => 'string' , 'example' => 'example.com' ],
'resource_name' => [ 'type' => 'string' , 'example' => 'My Application' ],
'resource_uuid' => [ 'type' => 'string' , 'nullable' => true , 'example' => 'abc123-def456' ],
'resource_type' => [ 'type' => 'string' , 'enum' => [ 'application' , 'service' , 'instance' ], 'example' => 'application' ],
'message' => [ 'type' => 'string' , 'example' => 'Domain example.com is already in use by application \'My Application\'' ],
]
),
],
]
)
),
]
),
2024-07-09 11:19:21 +00:00
]
)]
2024-06-21 19:35:02 +00:00
public function update_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 19:35:02 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 19:35:02 +00:00
}
2024-07-01 14:26:50 +00:00
$return = validateIncomingRequest ( $request );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 14:26:50 +00:00
return $return ;
}
2024-06-21 19:35:02 +00:00
2025-04-23 11:22:01 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 19:35:02 +00:00
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'update' , $application );
2024-06-25 19:22:23 +00:00
$server = $application -> destination -> server ;
2025-08-28 09:21:30 +00:00
$allowedFields = [ 'name' , 'description' , 'is_static' , 'domains' , 'git_repository' , 'git_branch' , 'git_commit_sha' , 'docker_registry_image_name' , 'docker_registry_image_tag' , 'build_pack' , 'static_image' , 'install_command' , 'build_command' , 'start_command' , 'ports_exposes' , 'ports_mappings' , 'base_directory' , 'publish_directory' , 'health_check_enabled' , 'health_check_path' , 'health_check_port' , 'health_check_host' , 'health_check_method' , 'health_check_return_code' , 'health_check_scheme' , 'health_check_response_text' , 'health_check_interval' , 'health_check_timeout' , 'health_check_retries' , 'health_check_start_period' , 'limits_memory' , 'limits_memory_swap' , 'limits_memory_swappiness' , 'limits_memory_reservation' , 'limits_cpus' , 'limits_cpuset' , 'limits_cpu_shares' , 'custom_labels' , 'custom_docker_run_options' , 'post_deployment_command' , 'post_deployment_command_container' , 'pre_deployment_command' , 'pre_deployment_command_container' , 'watch_paths' , 'manual_webhook_secret_github' , 'manual_webhook_secret_gitlab' , 'manual_webhook_secret_bitbucket' , 'manual_webhook_secret_gitea' , 'docker_compose_location' , 'docker_compose_raw' , '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' ];
2024-06-26 11:32:36 +00:00
2024-10-10 07:33:29 +00:00
$validationRules = [
2024-06-25 13:05:51 +00:00
'name' => 'string|max:255' ,
2024-06-25 19:22:23 +00:00
'description' => 'string|nullable' ,
'static_image' => 'string' ,
'watch_paths' => 'string|nullable' ,
'docker_compose_location' => 'string' ,
'docker_compose_raw' => 'string|nullable' ,
2024-07-02 14:12:04 +00:00
'docker_compose_domains' => 'array|nullable' ,
2024-06-25 19:22:23 +00:00
'docker_compose_custom_start_command' => 'string|nullable' ,
'docker_compose_custom_build_command' => 'string|nullable' ,
2024-11-11 13:37:19 +00:00
'custom_nginx_configuration' => 'string|nullable' ,
2025-04-23 11:22:01 +00:00
'is_http_basic_auth_enabled' => 'boolean|nullable' ,
'http_basic_auth_username' => 'string' ,
'http_basic_auth_password' => 'string' ,
2024-10-10 07:33:29 +00:00
];
2024-12-17 12:42:16 +00:00
$validationRules = array_merge ( sharedDataApplications (), $validationRules );
2024-10-10 07:33:29 +00:00
$validator = customApiValidator ( $request -> all (), $validationRules );
2024-06-25 19:22:23 +00:00
// Validate ports_exposes
if ( $request -> has ( 'ports_exposes' )) {
$ports = explode ( ',' , $request -> ports_exposes );
foreach ( $ports as $port ) {
if ( ! is_numeric ( $port )) {
return response () -> json ([
2024-06-26 11:00:36 +00:00
'message' => 'Validation failed.' ,
2024-06-25 19:22:23 +00:00
'errors' => [
'ports_exposes' => 'The ports_exposes should be a comma separated list of numbers.' ,
],
], 422 );
}
}
}
2024-11-11 13:37:19 +00:00
if ( $request -> has ( 'custom_nginx_configuration' )) {
if ( ! isBase64Encoded ( $request -> custom_nginx_configuration )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.' ,
],
], 422 );
}
$customNginxConfiguration = base64_decode ( $request -> custom_nginx_configuration );
if ( mb_detect_encoding ( $customNginxConfiguration , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.' ,
],
], 422 );
}
}
2024-06-30 09:30:31 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-06-28 13:05:37 +00:00
return $return ;
2024-06-25 19:22:23 +00:00
}
2024-06-25 13:05:51 +00:00
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-06-25 13:05:51 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-06-25 13:05:51 +00:00
}
return response () -> json ([
2024-06-26 11:00:36 +00:00
'message' => 'Validation failed.' ,
2024-06-25 13:05:51 +00:00
'errors' => $errors ,
], 422 );
}
2025-04-23 11:22:01 +00:00
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 = [];
if ( blank ( $request -> http_basic_auth_username )) {
$validationErrors [ 'http_basic_auth_username' ] = 'The http_basic_auth_username is required.' ;
}
if ( blank ( $request -> http_basic_auth_password )) {
$validationErrors [ 'http_basic_auth_password' ] = 'The http_basic_auth_password is required.' ;
}
if ( count ( $validationErrors ) > 0 ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validationErrors ,
], 422 );
}
}
}
if ( $request -> has ( 'is_http_basic_auth_enabled' ) && $application -> is_container_label_readonly_enabled === false ) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-06-28 13:05:37 +00:00
$domains = $request -> domains ;
2025-02-02 07:52:05 +00:00
$requestHasDomains = $request -> has ( 'domains' );
if ( $requestHasDomains && $server -> isProxyShouldRun ()) {
2024-12-02 21:49:41 +00:00
$uuid = $request -> uuid ;
2024-06-25 13:05:51 +00:00
$fqdn = $request -> domains ;
$fqdn = str ( $fqdn ) -> replaceEnd ( ',' , '' ) -> trim ();
$fqdn = str ( $fqdn ) -> replaceStart ( ',' , '' ) -> trim ();
2024-12-02 21:49:41 +00:00
$errors = [];
$fqdn = str ( $fqdn ) -> trim () -> explode ( ',' ) -> map ( function ( $domain ) use ( & $errors ) {
$domain = trim ( $domain );
2024-12-07 13:26:44 +00:00
if ( filter_var ( $domain , FILTER_VALIDATE_URL ) === false || ! preg_match ( '/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/' , $domain )) {
2024-12-02 21:49:41 +00:00
$errors [] = 'Invalid domain: ' . $domain ;
}
2024-12-07 13:26:44 +00:00
2024-12-02 21:49:41 +00:00
return $domain ;
});
2025-01-07 14:31:43 +00:00
if ( count ( $errors ) > 0 ) {
2024-12-02 21:49:41 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
2025-08-28 09:21:30 +00:00
// Check for domain conflicts
$result = checkIfDomainIsAlreadyUsedViaAPI ( $fqdn , $teamId , $uuid );
if ( isset ( $result [ 'error' ])) {
2024-12-02 21:49:41 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
2025-08-28 09:21:30 +00:00
'errors' => [ 'domains' => $result [ 'error' ]],
2024-12-02 21:49:41 +00:00
], 422 );
2024-07-17 12:52:40 +00:00
}
2025-08-28 09:21:30 +00:00
// If there are conflicts and force is not enabled, return warning
if ( $result [ 'hasConflicts' ] && ! $request -> boolean ( 'force_domain_override' )) {
return response () -> json ([
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ,
'conflicts' => $result [ 'conflicts' ],
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ,
], 409 );
}
2024-06-25 13:05:51 +00:00
}
2024-06-28 13:05:37 +00:00
2024-07-02 14:12:04 +00:00
$dockerComposeDomainsJson = collect ();
if ( $request -> has ( 'docker_compose_domains' )) {
2025-05-19 11:29:23 +00:00
if ( ! $request -> has ( 'docker_compose_raw' )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The base64 encoded docker_compose_raw is required.' ,
],
], 422 );
}
if ( ! isBase64Encoded ( $request -> docker_compose_raw )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
],
], 422 );
}
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
if ( mb_detect_encoding ( $dockerComposeRaw , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
],
], 422 );
}
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
$yaml = Yaml :: parse ( $dockerComposeRaw );
2024-07-02 14:12:04 +00:00
$services = data_get ( $yaml , 'services' );
$dockerComposeDomains = collect ( $request -> docker_compose_domains );
if ( $dockerComposeDomains -> count () > 0 ) {
$dockerComposeDomains -> each ( function ( $domain , $key ) use ( $services , $dockerComposeDomainsJson ) {
$name = data_get ( $domain , 'name' );
if ( data_get ( $services , $name )) {
$dockerComposeDomainsJson -> put ( $name , [ 'domain' => data_get ( $domain , 'domain' )]);
}
});
}
$request -> offsetUnset ( 'docker_compose_domains' );
}
2024-08-15 10:49:50 +00:00
$instantDeploy = $request -> instant_deploy ;
2024-10-21 07:51:19 +00:00
$isStatic = $request -> is_static ;
2025-05-27 13:07:31 +00:00
$connectToDockerNetwork = $request -> connect_to_docker_network ;
2024-10-21 07:51:19 +00:00
$useBuildServer = $request -> use_build_server ;
2024-08-15 10:49:50 +00:00
2024-10-21 07:51:19 +00:00
if ( isset ( $useBuildServer )) {
$application -> settings -> is_build_server_enabled = $useBuildServer ;
$application -> settings -> save ();
}
2024-10-03 13:09:56 +00:00
2024-10-21 07:51:19 +00:00
if ( isset ( $isStatic )) {
$application -> settings -> is_static = $isStatic ;
2024-10-03 13:09:56 +00:00
$application -> settings -> save ();
2025-05-27 13:07:31 +00:00
}
if ( isset ( $connectToDockerNetwork )) {
$application -> settings -> connect_to_docker_network = $connectToDockerNetwork ;
$application -> settings -> save ();
2024-10-03 13:09:56 +00:00
}
2024-09-28 04:11:43 +00:00
2024-08-15 10:49:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2024-06-28 13:05:37 +00:00
$data = $request -> all ();
2025-02-02 07:52:05 +00:00
if ( $requestHasDomains && $server -> isProxyShouldRun ()) {
2024-12-16 08:56:33 +00:00
data_set ( $data , 'fqdn' , $domains );
}
2024-07-02 14:12:04 +00:00
if ( $dockerComposeDomainsJson -> count () > 0 ) {
data_set ( $data , 'docker_compose_domains' , json_encode ( $dockerComposeDomainsJson ));
}
2024-06-28 13:05:37 +00:00
$application -> fill ( $data );
2025-08-31 13:40:48 +00:00
if ( $application -> settings -> is_container_label_readonly_enabled && $requestHasDomains && $server -> isProxyShouldRun ()) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
}
2024-06-25 13:05:51 +00:00
$application -> save ();
2024-06-21 19:35:02 +00:00
2024-08-15 10:49:50 +00:00
if ( $instantDeploy ) {
2025-01-07 14:31:43 +00:00
$deployment_uuid = new Cuid2 ;
2024-08-15 10:49:50 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-08-15 10:49:50 +00:00
application : $application ,
2025-01-07 14:31:43 +00:00
deployment_uuid : $deployment_uuid ,
2024-08-15 10:49:50 +00:00
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2024-08-15 10:49:50 +00:00
}
2024-07-10 08:29:52 +00:00
return response () -> json ([
'uuid' => $application -> uuid ,
]);
2024-06-26 11:00:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Get(
summary : 'List Envs' ,
description : 'List all envs by application UUID.' ,
path : '/applications/{uuid}/envs' ,
2024-09-04 08:09:10 +00:00
operationId : 'list-envs-by-application-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
description : 'All environment variables by application UUID.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'array' ,
items : new OA\Items ( ref : '#/components/schemas/EnvironmentVariable' )
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-07-04 11:45:06 +00:00
public function envs ( Request $request )
2024-06-26 11:00:36 +00:00
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'view' , $application );
2024-06-26 11:00:36 +00:00
$envs = $application -> environment_variables -> sortBy ( 'id' ) -> merge ( $application -> environment_variables_preview -> sortBy ( 'id' ));
2024-07-04 11:45:06 +00:00
$envs = $envs -> map ( function ( $env ) {
$env -> makeHidden ([
'service_id' ,
'standalone_clickhouse_id' ,
'standalone_dragonfly_id' ,
'standalone_keydb_id' ,
'standalone_mariadb_id' ,
'standalone_mongodb_id' ,
'standalone_mysql_id' ,
'standalone_postgresql_id' ,
'standalone_redis_id' ,
]);
2024-10-31 17:20:11 +00:00
return $this -> removeSensitiveData ( $env );
2024-07-04 11:45:06 +00:00
});
return response () -> json ( $envs );
2024-06-26 11:00:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Patch(
summary : 'Update Env' ,
description : 'Update env by application UUID.' ,
path : '/applications/{uuid}/envs' ,
2024-09-04 08:09:10 +00:00
operationId : 'update-env-by-application-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
requestBody : new OA\RequestBody (
description : 'Env updated.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
required : [ 'key' , 'value' ],
properties : [
2024-07-09 12:12:36 +00:00
'key' => [ 'type' => 'string' , 'description' => 'The key of the environment variable.' ],
'value' => [ 'type' => 'string' , 'description' => 'The value of the environment variable.' ],
'is_preview' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is used in preview deployments.' ],
'is_literal' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.' ],
'is_multiline' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is multiline.' ],
'is_shown_once' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.' ],
2024-07-09 11:19:21 +00:00
],
),
),
],
),
responses : [
new OA\Response (
response : 201 ,
description : 'Environment variable updated.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Environment variable updated.' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function update_env_by_uuid ( Request $request )
{
2025-09-11 14:51:56 +00:00
$allowedFields = [ 'key' , 'value' , 'is_preview' , 'is_literal' ];
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
}
$return = validateIncomingRequest ( $request );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 14:26:50 +00:00
return $return ;
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'manageEnvironment' , $application );
2024-06-26 11:32:36 +00:00
$validator = customApiValidator ( $request -> all (), [
2024-06-26 11:00:36 +00:00
'key' => 'string|required' ,
'value' => 'string|nullable' ,
'is_preview' => 'boolean' ,
'is_literal' => 'boolean' ,
2024-07-04 11:45:06 +00:00
'is_multiline' => 'boolean' ,
'is_shown_once' => 'boolean' ,
2024-06-26 11:00:36 +00:00
]);
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-06-26 11:00:36 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-06-26 11:00:36 +00:00
}
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
$is_preview = $request -> is_preview ? ? false ;
$is_literal = $request -> is_literal ? ? false ;
2024-12-17 09:38:32 +00:00
$key = str ( $request -> key ) -> trim () -> replace ( ' ' , '_' ) -> value ;
2024-06-26 11:00:36 +00:00
if ( $is_preview ) {
2024-12-17 09:38:32 +00:00
$env = $application -> environment_variables_preview -> where ( 'key' , $key ) -> first ();
2024-06-26 11:00:36 +00:00
if ( $env ) {
$env -> value = $request -> value ;
if ( $env -> is_literal != $is_literal ) {
$env -> is_literal = $is_literal ;
}
if ( $env -> is_preview != $is_preview ) {
$env -> is_preview = $is_preview ;
}
2024-07-04 11:45:06 +00:00
if ( $env -> is_multiline != $request -> is_multiline ) {
$env -> is_multiline = $request -> is_multiline ;
}
if ( $env -> is_shown_once != $request -> is_shown_once ) {
$env -> is_shown_once = $request -> is_shown_once ;
}
2025-09-11 15:38:16 +00:00
if ( $request -> has ( 'is_buildtime_only' ) && $env -> is_buildtime_only != $request -> is_buildtime_only ) {
$env -> is_buildtime_only = $request -> is_buildtime_only ;
}
2024-06-26 11:00:36 +00:00
$env -> save ();
2024-07-09 11:19:21 +00:00
return response () -> json ( $this -> removeSensitiveData ( $env )) -> setStatusCode ( 201 );
2025-01-07 14:31:43 +00:00
} else {
return response () -> json ([
'message' => 'Environment variable not found.' ,
], 404 );
2024-06-26 11:00:36 +00:00
}
2025-01-07 14:31:43 +00:00
} else {
$env = $application -> environment_variables -> where ( 'key' , $key ) -> first ();
if ( $env ) {
$env -> value = $request -> value ;
if ( $env -> is_literal != $is_literal ) {
$env -> is_literal = $is_literal ;
}
if ( $env -> is_preview != $is_preview ) {
$env -> is_preview = $is_preview ;
}
if ( $env -> is_multiline != $request -> is_multiline ) {
$env -> is_multiline = $request -> is_multiline ;
}
if ( $env -> is_shown_once != $request -> is_shown_once ) {
$env -> is_shown_once = $request -> is_shown_once ;
}
2025-09-11 15:38:16 +00:00
if ( $request -> has ( 'is_buildtime_only' ) && $env -> is_buildtime_only != $request -> is_buildtime_only ) {
$env -> is_buildtime_only = $request -> is_buildtime_only ;
}
2025-01-07 14:31:43 +00:00
$env -> save ();
2024-06-26 11:00:36 +00:00
2025-01-07 14:31:43 +00:00
return response () -> json ( $this -> removeSensitiveData ( $env )) -> setStatusCode ( 201 );
} else {
return response () -> json ([
'message' => 'Environment variable not found.' ,
], 404 );
2024-06-26 11:00:36 +00:00
}
}
return response () -> json ([
2025-01-07 14:31:43 +00:00
'message' => 'Something is not okay. Are you okay?' ,
], 500 );
2024-06-26 11:00:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Patch(
summary : 'Update Envs (Bulk)' ,
description : 'Update multiple envs by application UUID.' ,
path : '/applications/{uuid}/envs/bulk' ,
2024-09-04 08:09:10 +00:00
operationId : 'update-envs-by-application-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
requestBody : new OA\RequestBody (
description : 'Bulk envs updated.' ,
required : true ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
required : [ 'data' ],
properties : [
'data' => [
'type' => 'array' ,
'items' => new OA\Schema (
type : 'object' ,
properties : [
2024-07-09 12:12:36 +00:00
'key' => [ 'type' => 'string' , 'description' => 'The key of the environment variable.' ],
'value' => [ 'type' => 'string' , 'description' => 'The value of the environment variable.' ],
'is_preview' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is used in preview deployments.' ],
'is_literal' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.' ],
'is_multiline' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is multiline.' ],
'is_shown_once' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.' ],
2024-07-09 11:19:21 +00:00
],
),
],
],
),
),
],
),
responses : [
new OA\Response (
response : 201 ,
description : 'Environment variables updated.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Environment variables updated.' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-26 11:32:36 +00:00
public function create_bulk_envs ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:32:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
}
$return = validateIncomingRequest ( $request );
2025-01-07 14:31:43 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
2024-07-01 14:26:50 +00:00
return $return ;
2024-06-26 11:32:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'manageEnvironment' , $application );
2024-06-26 11:32:36 +00:00
$bulk_data = $request -> get ( 'data' );
if ( ! $bulk_data ) {
return response () -> json ([
'message' => 'Bulk data is required.' ,
], 400 );
}
$bulk_data = collect ( $bulk_data ) -> map ( function ( $item ) {
2025-09-11 14:51:56 +00:00
return collect ( $item ) -> only ([ 'key' , 'value' , 'is_preview' , 'is_literal' ]);
2024-06-26 11:32:36 +00:00
});
2024-12-17 09:38:32 +00:00
$returnedEnvs = collect ();
2024-06-26 11:32:36 +00:00
foreach ( $bulk_data as $item ) {
$validator = customApiValidator ( $item , [
'key' => 'string|required' ,
'value' => 'string|nullable' ,
'is_preview' => 'boolean' ,
'is_literal' => 'boolean' ,
2024-07-04 11:45:06 +00:00
'is_multiline' => 'boolean' ,
'is_shown_once' => 'boolean' ,
2024-06-26 11:32:36 +00:00
]);
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
$is_preview = $item -> get ( 'is_preview' ) ? ? false ;
$is_literal = $item -> get ( 'is_literal' ) ? ? false ;
2024-07-04 11:45:06 +00:00
$is_multi_line = $item -> get ( 'is_multiline' ) ? ? false ;
$is_shown_once = $item -> get ( 'is_shown_once' ) ? ? false ;
2024-12-17 09:38:32 +00:00
$key = str ( $item -> get ( 'key' )) -> trim () -> replace ( ' ' , '_' ) -> value ;
2024-06-26 11:32:36 +00:00
if ( $is_preview ) {
2024-12-17 09:38:32 +00:00
$env = $application -> environment_variables_preview -> where ( 'key' , $key ) -> first ();
2024-06-26 11:32:36 +00:00
if ( $env ) {
$env -> value = $item -> get ( 'value' );
2025-09-11 14:51:56 +00:00
2024-06-26 11:32:36 +00:00
if ( $env -> is_literal != $is_literal ) {
$env -> is_literal = $is_literal ;
}
2024-07-04 11:45:06 +00:00
if ( $env -> is_multiline != $item -> get ( 'is_multiline' )) {
$env -> is_multiline = $item -> get ( 'is_multiline' );
}
if ( $env -> is_shown_once != $item -> get ( 'is_shown_once' )) {
$env -> is_shown_once = $item -> get ( 'is_shown_once' );
}
2025-09-11 15:38:16 +00:00
if ( $item -> has ( 'is_buildtime_only' ) && $env -> is_buildtime_only != $item -> get ( 'is_buildtime_only' )) {
$env -> is_buildtime_only = $item -> get ( 'is_buildtime_only' );
}
2024-06-26 11:32:36 +00:00
$env -> save ();
} else {
$env = $application -> environment_variables () -> create ([
'key' => $item -> get ( 'key' ),
'value' => $item -> get ( 'value' ),
'is_preview' => $is_preview ,
'is_literal' => $is_literal ,
2024-07-04 11:45:06 +00:00
'is_multiline' => $is_multi_line ,
'is_shown_once' => $is_shown_once ,
2025-09-11 15:38:16 +00:00
'is_buildtime_only' => $item -> get ( 'is_buildtime_only' , false ),
2024-12-17 09:38:32 +00:00
'resourceable_type' => get_class ( $application ),
'resourceable_id' => $application -> id ,
2024-06-26 11:32:36 +00:00
]);
}
} else {
2024-12-17 09:38:32 +00:00
$env = $application -> environment_variables -> where ( 'key' , $key ) -> first ();
2024-06-26 11:32:36 +00:00
if ( $env ) {
$env -> value = $item -> get ( 'value' );
if ( $env -> is_literal != $is_literal ) {
$env -> is_literal = $is_literal ;
}
2024-07-04 11:45:06 +00:00
if ( $env -> is_multiline != $item -> get ( 'is_multiline' )) {
$env -> is_multiline = $item -> get ( 'is_multiline' );
}
if ( $env -> is_shown_once != $item -> get ( 'is_shown_once' )) {
$env -> is_shown_once = $item -> get ( 'is_shown_once' );
}
2025-09-11 15:38:16 +00:00
if ( $item -> has ( 'is_buildtime_only' ) && $env -> is_buildtime_only != $item -> get ( 'is_buildtime_only' )) {
$env -> is_buildtime_only = $item -> get ( 'is_buildtime_only' );
}
2024-06-26 11:32:36 +00:00
$env -> save ();
} else {
$env = $application -> environment_variables () -> create ([
'key' => $item -> get ( 'key' ),
'value' => $item -> get ( 'value' ),
'is_preview' => $is_preview ,
'is_literal' => $is_literal ,
2024-07-04 11:45:06 +00:00
'is_multiline' => $is_multi_line ,
'is_shown_once' => $is_shown_once ,
2025-09-11 15:38:16 +00:00
'is_buildtime_only' => $item -> get ( 'is_buildtime_only' , false ),
2024-12-17 09:38:32 +00:00
'resourceable_type' => get_class ( $application ),
'resourceable_id' => $application -> id ,
2024-06-26 11:32:36 +00:00
]);
}
}
2024-12-17 09:38:32 +00:00
$returnedEnvs -> push ( $this -> removeSensitiveData ( $env ));
2024-06-26 11:32:36 +00:00
}
2024-12-17 09:38:32 +00:00
return response () -> json ( $returnedEnvs ) -> setStatusCode ( 201 );
2024-06-26 11:32:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Post(
summary : 'Create Env' ,
description : 'Create env by application UUID.' ,
path : '/applications/{uuid}/envs' ,
2024-09-04 08:09:10 +00:00
operationId : 'create-env-by-application-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
requestBody : new OA\RequestBody (
required : true ,
description : 'Env created.' ,
content : new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
2024-07-09 12:12:36 +00:00
'key' => [ 'type' => 'string' , 'description' => 'The key of the environment variable.' ],
'value' => [ 'type' => 'string' , 'description' => 'The value of the environment variable.' ],
'is_preview' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is used in preview deployments.' ],
'is_literal' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.' ],
'is_multiline' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable is multiline.' ],
'is_shown_once' => [ 'type' => 'boolean' , 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.' ],
2024-07-09 11:19:21 +00:00
],
),
),
),
responses : [
new OA\Response (
response : 201 ,
description : 'Environment variable created.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' , 'example' => 'nc0k04gk8g0cgsk440g0koko' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function create_env ( Request $request )
{
2025-09-11 14:51:56 +00:00
$allowedFields = [ 'key' , 'value' , 'is_preview' , 'is_literal' ];
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'manageEnvironment' , $application );
2024-06-26 11:32:36 +00:00
$validator = customApiValidator ( $request -> all (), [
2024-06-26 11:00:36 +00:00
'key' => 'string|required' ,
'value' => 'string|nullable' ,
'is_preview' => 'boolean' ,
'is_literal' => 'boolean' ,
2024-07-04 11:45:06 +00:00
'is_multiline' => 'boolean' ,
'is_shown_once' => 'boolean' ,
2024-06-26 11:00:36 +00:00
]);
$extraFields = array_diff ( array_keys ( $request -> all ()), $allowedFields );
2025-01-07 14:31:43 +00:00
if ( $validator -> fails () || ! empty ( $extraFields )) {
2024-06-26 11:00:36 +00:00
$errors = $validator -> errors ();
2025-01-07 14:31:43 +00:00
if ( ! empty ( $extraFields )) {
foreach ( $extraFields as $field ) {
$errors -> add ( $field , 'This field is not allowed.' );
}
2024-06-26 11:00:36 +00:00
}
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
$is_preview = $request -> is_preview ? ? false ;
2024-12-17 09:38:32 +00:00
$key = str ( $request -> key ) -> trim () -> replace ( ' ' , '_' ) -> value ;
2024-06-26 11:00:36 +00:00
if ( $is_preview ) {
2024-12-17 09:38:32 +00:00
$env = $application -> environment_variables_preview -> where ( 'key' , $key ) -> first ();
2024-06-26 11:00:36 +00:00
if ( $env ) {
return response () -> json ([
'message' => 'Environment variable already exists. Use PATCH request to update it.' ,
], 409 );
2025-01-07 14:31:43 +00:00
} else {
$env = $application -> environment_variables () -> create ([
'key' => $request -> key ,
'value' => $request -> value ,
'is_preview' => $request -> is_preview ? ? false ,
'is_literal' => $request -> is_literal ? ? false ,
'is_multiline' => $request -> is_multiline ? ? false ,
'is_shown_once' => $request -> is_shown_once ? ? false ,
2025-09-11 15:38:16 +00:00
'is_buildtime_only' => $request -> is_buildtime_only ? ? false ,
2025-01-07 14:31:43 +00:00
'resourceable_type' => get_class ( $application ),
'resourceable_id' => $application -> id ,
]);
return response () -> json ([
'uuid' => $env -> uuid ,
]) -> setStatusCode ( 201 );
2024-06-26 11:00:36 +00:00
}
2025-01-07 14:31:43 +00:00
} else {
$env = $application -> environment_variables -> where ( 'key' , $key ) -> first ();
if ( $env ) {
return response () -> json ([
'message' => 'Environment variable already exists. Use PATCH request to update it.' ,
], 409 );
} else {
$env = $application -> environment_variables () -> create ([
'key' => $request -> key ,
'value' => $request -> value ,
'is_preview' => $request -> is_preview ? ? false ,
'is_literal' => $request -> is_literal ? ? false ,
'is_multiline' => $request -> is_multiline ? ? false ,
'is_shown_once' => $request -> is_shown_once ? ? false ,
2025-09-11 15:38:16 +00:00
'is_buildtime_only' => $request -> is_buildtime_only ? ? false ,
2025-01-07 14:31:43 +00:00
'resourceable_type' => get_class ( $application ),
'resourceable_id' => $application -> id ,
]);
2024-06-26 11:00:36 +00:00
2025-01-07 14:31:43 +00:00
return response () -> json ([
'uuid' => $env -> uuid ,
]) -> setStatusCode ( 201 );
}
2024-06-26 11:00:36 +00:00
}
}
2024-07-09 11:19:21 +00:00
#[OA\Delete(
summary : 'Delete Env' ,
description : 'Delete env by UUID.' ,
path : '/applications/{uuid}/envs/{env_uuid}' ,
2024-09-04 08:09:10 +00:00
operationId : 'delete-env-by-application-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
new OA\Parameter (
name : 'env_uuid' ,
in : 'path' ,
description : 'UUID of the environment variable.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
description : 'Environment variable deleted.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Environment variable deleted.' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function delete_env_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found.' ,
], 404 );
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'manageEnvironment' , $application );
2025-01-07 14:31:43 +00:00
$found_env = EnvironmentVariable :: where ( 'uuid' , $request -> env_uuid )
2024-12-17 09:38:32 +00:00
-> where ( 'resourceable_type' , Application :: class )
-> where ( 'resourceable_id' , $application -> id )
-> first ();
2024-06-26 11:00:36 +00:00
if ( ! $found_env ) {
return response () -> json ([
'message' => 'Environment variable not found.' ,
], 404 );
}
2024-07-02 14:12:04 +00:00
$found_env -> forceDelete ();
2024-06-26 11:00:36 +00:00
2024-06-21 19:35:02 +00:00
return response () -> json ([
2024-06-26 11:00:36 +00:00
'message' => 'Environment variable deleted.' ,
2024-06-21 19:35:02 +00:00
]);
}
2024-07-09 11:19:21 +00:00
#[OA\Get(
summary : 'Start' ,
description : 'Start application. `Post` request is also accepted.' ,
path : '/applications/{uuid}/start' ,
2024-09-04 08:09:10 +00:00
operationId : 'start-application-by-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
new OA\Parameter (
name : 'force' ,
in : 'query' ,
description : 'Force rebuild.' ,
schema : new OA\Schema (
type : 'boolean' ,
default : false ,
)
),
new OA\Parameter (
name : 'instant_deploy' ,
in : 'query' ,
description : 'Instant deploy (skip queuing).' ,
schema : new OA\Schema (
type : 'boolean' ,
default : false ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
description : 'Start application.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
2024-07-09 12:12:36 +00:00
'message' => [ 'type' => 'string' , 'example' => 'Deployment request queued.' , 'description' => 'Message.' ],
'deployment_uuid' => [ 'type' => 'string' , 'example' => 'doogksw' , 'description' => 'UUID of the deployment.' ],
2024-12-07 13:26:44 +00:00
]
)
2024-07-09 11:19:21 +00:00
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function action_deploy ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$force = $request -> query -> get ( 'force' ) ? ? false ;
$instant_deploy = $request -> query -> get ( 'instant_deploy' ) ? ? false ;
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-06-21 14:46:13 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'deploy' , $application );
2025-01-07 14:31:43 +00:00
$deployment_uuid = new Cuid2 ;
2024-06-21 14:46:13 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-06-21 14:46:13 +00:00
application : $application ,
2025-01-07 14:31:43 +00:00
deployment_uuid : $deployment_uuid ,
2024-06-21 14:46:13 +00:00
force_rebuild : $force ,
is_api : true ,
no_questions_asked : $instant_deploy
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json (
[
'message' => $result [ 'message' ],
],
200
);
}
2024-06-21 14:46:13 +00:00
return response () -> json (
[
'message' => 'Deployment request queued.' ,
2025-01-07 14:31:43 +00:00
'deployment_uuid' => $deployment_uuid -> toString (),
2024-06-21 14:46:13 +00:00
],
200
);
}
2024-07-09 11:19:21 +00:00
#[OA\Get(
summary : 'Stop' ,
description : 'Stop application. `Post` request is also accepted.' ,
path : '/applications/{uuid}/stop' ,
2024-09-04 08:09:10 +00:00
operationId : 'stop-application-by-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
description : 'Stop application.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Application stopping request queued.' ],
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function action_stop ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-06-21 14:46:13 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'deploy' , $application );
2024-07-02 14:12:04 +00:00
StopApplication :: dispatch ( $application );
2024-06-21 14:46:13 +00:00
2024-07-02 14:12:04 +00:00
return response () -> json (
[
'message' => 'Application stopping request queued.' ,
],
);
2024-06-21 14:46:13 +00:00
}
2024-07-09 11:19:21 +00:00
#[OA\Get(
summary : 'Restart' ,
description : 'Restart application. `Post` request is also accepted.' ,
path : '/applications/{uuid}/restart' ,
2024-09-04 08:09:10 +00:00
operationId : 'restart-application-by-uuid' ,
2024-07-09 11:19:21 +00:00
security : [
[ 'bearerAuth' => []],
],
tags : [ 'Applications' ],
parameters : [
new OA\Parameter (
name : 'uuid' ,
in : 'path' ,
description : 'UUID of the application.' ,
required : true ,
schema : new OA\Schema (
type : 'string' ,
format : 'uuid' ,
)
),
],
responses : [
new OA\Response (
response : 200 ,
description : 'Restart application.' ,
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'message' => [ 'type' => 'string' , 'example' => 'Restart request queued.' ],
2024-07-09 12:12:36 +00:00
'deployment_uuid' => [ 'type' => 'string' , 'example' => 'doogksw' , 'description' => 'UUID of the deployment.' ],
2024-07-09 11:19:21 +00:00
]
)
),
2024-12-07 13:26:44 +00:00
]
),
2024-07-09 11:19:21 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
new OA\Response (
response : 404 ,
ref : '#/components/responses/404' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function action_restart ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-06-21 14:46:13 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
2025-08-23 16:51:10 +00:00
$this -> authorize ( 'deploy' , $application );
2025-01-07 14:31:43 +00:00
$deployment_uuid = new Cuid2 ;
2024-06-21 14:46:13 +00:00
2025-04-11 13:27:56 +00:00
$result = queue_application_deployment (
2024-06-21 14:46:13 +00:00
application : $application ,
2025-01-07 14:31:43 +00:00
deployment_uuid : $deployment_uuid ,
2024-06-21 14:46:13 +00:00
restart_only : true ,
is_api : true ,
);
2025-04-11 13:27:56 +00:00
if ( $result [ 'status' ] === 'skipped' ) {
return response () -> json ([
'message' => $result [ 'message' ],
], 200 );
}
2024-06-21 14:46:13 +00:00
return response () -> json (
[
'message' => 'Restart request queued.' ,
2025-01-07 14:31:43 +00:00
'deployment_uuid' => $deployment_uuid -> toString (),
2024-06-21 14:46:13 +00:00
],
);
}
2024-06-30 09:30:31 +00:00
2025-04-09 16:52:12 +00:00
private function validateDataApplications ( Request $request , Server $server )
{
$teamId = getTeamIdFromToken ();
// Validate ports_mappings
if ( $request -> has ( 'ports_mappings' )) {
$ports = [];
foreach ( explode ( ',' , $request -> ports_mappings ) as $portMapping ) {
$port = explode ( ':' , $portMapping );
if ( in_array ( $port [ 0 ], $ports )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'ports_mappings' => 'The first number before : should be unique between mappings.' ,
],
], 422 );
}
$ports [] = $port [ 0 ];
}
}
// Validate custom_labels
if ( $request -> has ( 'custom_labels' )) {
if ( ! isBase64Encoded ( $request -> custom_labels )) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'custom_labels' => 'The custom_labels should be base64 encoded.' ,
],
], 422 );
}
$customLabels = base64_decode ( $request -> custom_labels );
if ( mb_detect_encoding ( $customLabels , 'ASCII' , true ) === false ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'custom_labels' => 'The custom_labels should be base64 encoded.' ,
],
], 422 );
}
}
if ( $request -> has ( 'domains' ) && $server -> isProxyShouldRun ()) {
$uuid = $request -> uuid ;
$fqdn = $request -> domains ;
$fqdn = str ( $fqdn ) -> replaceEnd ( ',' , '' ) -> trim ();
$fqdn = str ( $fqdn ) -> replaceStart ( ',' , '' ) -> trim ();
$errors = [];
$fqdn = str ( $fqdn ) -> trim () -> explode ( ',' ) -> map ( function ( $domain ) use ( & $errors ) {
if ( filter_var ( $domain , FILTER_VALIDATE_URL ) === false ) {
$errors [] = 'Invalid domain: ' . $domain ;
}
return str ( $domain ) -> trim () -> lower ();
});
if ( count ( $errors ) > 0 ) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $errors ,
], 422 );
}
2025-08-28 09:21:30 +00:00
// Check for domain conflicts
$result = checkIfDomainIsAlreadyUsedViaAPI ( $fqdn , $teamId , $uuid );
if ( isset ( $result [ 'error' ])) {
2025-04-09 16:52:12 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
2025-08-28 09:21:30 +00:00
'errors' => [ 'domains' => $result [ 'error' ]],
2025-04-09 16:52:12 +00:00
], 422 );
}
2025-08-28 09:21:30 +00:00
// If there are conflicts and force is not enabled, return warning
if ( $result [ 'hasConflicts' ] && ! $request -> boolean ( 'force_domain_override' )) {
return response () -> json ([
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.' ,
'conflicts' => $result [ 'conflicts' ],
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' ,
], 409 );
}
2025-04-09 16:52:12 +00:00
}
}
2024-06-21 14:46:13 +00:00
}