2023-03-27 08:44:31 +00:00
< ? php
namespace App\Models ;
2023-12-13 11:13:20 +00:00
use App\Enums\ApplicationDeploymentStatus ;
2023-03-30 07:47:04 +00:00
use Illuminate\Database\Eloquent\Casts\Attribute ;
2024-12-04 11:43:52 +00:00
use Illuminate\Database\Eloquent\Factories\HasFactory ;
2023-05-04 20:29:14 +00:00
use Illuminate\Database\Eloquent\Relations\HasMany ;
2023-12-13 11:08:12 +00:00
use Illuminate\Database\Eloquent\SoftDeletes ;
2024-09-23 17:51:31 +00:00
use Illuminate\Process\InvokedProcess ;
2024-03-28 14:05:12 +00:00
use Illuminate\Support\Collection ;
2024-08-09 17:39:21 +00:00
use Illuminate\Support\Facades\Process ;
2024-10-08 13:11:19 +00:00
use Illuminate\Support\Facades\Validator ;
2023-10-09 09:10:04 +00:00
use Illuminate\Support\Str ;
2024-07-09 08:45:10 +00:00
use OpenApi\Attributes as OA ;
2023-11-24 14:48:23 +00:00
use RuntimeException ;
2024-06-10 20:43:34 +00:00
use Spatie\Activitylog\Models\Activity ;
2024-05-23 12:28:03 +00:00
use Spatie\Url\Url ;
2024-03-04 09:13:40 +00:00
use Symfony\Component\Yaml\Yaml ;
2023-11-27 10:54:55 +00:00
use Visus\Cuid2\Cuid2 ;
2023-03-29 10:27:02 +00:00
2024-07-09 08:45:10 +00:00
#[OA\Schema(
description : 'Application model' ,
type : 'object' ,
properties : [
2024-07-09 11:59:54 +00:00
'id' => [ 'type' => 'integer' , 'description' => 'The application identifier in the database.' ],
'description' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'The application description.' ],
'repository_project_id' => [ 'type' => 'integer' , 'nullable' => true , 'description' => 'The repository project identifier.' ],
'uuid' => [ 'type' => 'string' , 'description' => 'The application UUID.' ],
'name' => [ 'type' => 'string' , 'description' => 'The application name.' ],
'fqdn' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'The application domains.' ],
'config_hash' => [ 'type' => 'string' , 'description' => 'Configuration hash.' ],
'git_repository' => [ 'type' => 'string' , 'description' => 'Git repository URL.' ],
'git_branch' => [ 'type' => 'string' , 'description' => 'Git branch.' ],
'git_commit_sha' => [ 'type' => 'string' , 'description' => 'Git commit SHA.' ],
'git_full_url' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Git full URL.' ],
'docker_registry_image_name' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker registry image name.' ],
'docker_registry_image_tag' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker registry image tag.' ],
'build_pack' => [ 'type' => 'string' , 'description' => 'Build pack.' , 'enum' => [ 'nixpacks' , 'static' , 'dockerfile' , 'dockercompose' ]],
'static_image' => [ 'type' => 'string' , 'description' => 'Static image used when static site is deployed.' ],
'install_command' => [ 'type' => 'string' , 'description' => 'Install command.' ],
'build_command' => [ 'type' => 'string' , 'description' => 'Build command.' ],
'start_command' => [ 'type' => 'string' , 'description' => 'Start command.' ],
'ports_exposes' => [ 'type' => 'string' , 'description' => 'Ports exposes.' ],
'ports_mappings' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Ports mappings.' ],
'base_directory' => [ 'type' => 'string' , 'description' => 'Base directory for all commands.' ],
'publish_directory' => [ 'type' => 'string' , 'description' => '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.' ],
'status' => [ 'type' => 'string' , 'description' => 'Application status.' ],
'preview_url_template' => [ 'type' => 'string' , 'description' => 'Preview URL template.' ],
'destination_type' => [ 'type' => 'string' , 'description' => 'Destination type.' ],
'destination_id' => [ 'type' => 'integer' , 'description' => 'Destination identifier.' ],
'source_id' => [ 'type' => 'integer' , 'nullable' => true , 'description' => 'Source identifier.' ],
'private_key_id' => [ 'type' => 'integer' , 'nullable' => true , 'description' => 'Private key identifier.' ],
'environment_id' => [ 'type' => 'integer' , 'description' => 'Environment identifier.' ],
'dockerfile' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Dockerfile content. Used for dockerfile build pack.' ],
'dockerfile_location' => [ 'type' => 'string' , 'description' => 'Dockerfile location.' ],
'custom_labels' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Custom labels.' ],
'dockerfile_target_build' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Dockerfile target build.' ],
'manual_webhook_secret_github' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Manual webhook secret for GitHub.' ],
'manual_webhook_secret_gitlab' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Manual webhook secret for GitLab.' ],
'manual_webhook_secret_bitbucket' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Manual webhook secret for Bitbucket.' ],
'manual_webhook_secret_gitea' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Manual webhook secret for Gitea.' ],
'docker_compose_location' => [ 'type' => 'string' , 'description' => 'Docker compose location.' ],
'docker_compose' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker compose content. Used for docker compose build pack.' ],
'docker_compose_raw' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker compose raw content.' ],
'docker_compose_domains' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker compose domains.' ],
'docker_compose_custom_start_command' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker compose custom start command.' ],
'docker_compose_custom_build_command' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Docker compose custom build command.' ],
'swarm_replicas' => [ 'type' => 'integer' , 'nullable' => true , 'description' => 'Swarm replicas. Only used for swarm deployments.' ],
'swarm_placement_constraints' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Swarm placement constraints. Only used for swarm deployments.' ],
'custom_docker_run_options' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Custom docker run options.' ],
'post_deployment_command' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Post deployment command.' ],
'post_deployment_command_container' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Post deployment command container.' ],
'pre_deployment_command' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Pre deployment command.' ],
'pre_deployment_command_container' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Pre deployment command container.' ],
'watch_paths' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Watch paths.' ],
'custom_healthcheck_found' => [ 'type' => 'boolean' , 'description' => 'Custom healthcheck found.' ],
'redirect' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.' , 'enum' => [ 'www' , 'non-www' , 'both' ]],
'created_at' => [ 'type' => 'string' , 'format' => 'date-time' , 'description' => 'The date and time when the application was created.' ],
'updated_at' => [ 'type' => 'string' , 'format' => 'date-time' , 'description' => 'The date and time when the application was last updated.' ],
'deleted_at' => [ 'type' => 'string' , 'format' => 'date-time' , 'nullable' => true , 'description' => 'The date and time when the application was deleted.' ],
2024-07-11 08:55:15 +00:00
'compose_parsing_version' => [ 'type' => 'string' , 'description' => 'How Coolify parse the compose file.' ],
2024-11-11 13:37:19 +00:00
'custom_nginx_configuration' => [ 'type' => 'string' , 'nullable' => true , 'description' => 'Custom Nginx configuration base64 encoded.' ],
2024-07-09 08:45:10 +00:00
]
)]
2023-03-27 08:44:31 +00:00
class Application extends BaseModel
{
2024-12-04 11:43:52 +00:00
use HasFactory , SoftDeletes ;
2024-06-10 20:43:34 +00:00
2024-09-30 12:15:22 +00:00
private static $parserVersion = '4' ;
2024-08-28 13:45:11 +00:00
2023-08-11 14:13:53 +00:00
protected $guarded = [];
2024-06-10 20:43:34 +00:00
2024-08-05 13:48:15 +00:00
protected $appends = [ 'server_status' ];
2023-08-08 09:51:36 +00:00
protected static function booted ()
{
2023-10-09 09:10:04 +00:00
static :: saving ( function ( $application ) {
2024-11-03 08:02:14 +00:00
$payload = [];
if ( $application -> isDirty ( 'fqdn' )) {
2024-11-11 13:37:19 +00:00
if ( $application -> fqdn === '' ) {
$application -> fqdn = null ;
}
2024-11-03 08:02:14 +00:00
$payload [ 'fqdn' ] = $application -> fqdn ;
}
if ( $application -> isDirty ( 'install_command' )) {
$payload [ 'install_command' ] = str ( $application -> install_command ) -> trim ();
}
if ( $application -> isDirty ( 'build_command' )) {
$payload [ 'build_command' ] = str ( $application -> build_command ) -> trim ();
}
if ( $application -> isDirty ( 'start_command' )) {
$payload [ 'start_command' ] = str ( $application -> start_command ) -> trim ();
}
if ( $application -> isDirty ( 'base_directory' )) {
$payload [ 'base_directory' ] = str ( $application -> base_directory ) -> trim ();
}
if ( $application -> isDirty ( 'publish_directory' )) {
$payload [ 'publish_directory' ] = str ( $application -> publish_directory ) -> trim ();
}
if ( $application -> isDirty ( 'status' )) {
$payload [ 'last_online_at' ] = now ();
}
2024-11-11 13:37:19 +00:00
if ( $application -> isDirty ( 'custom_nginx_configuration' )) {
if ( $application -> custom_nginx_configuration === '' ) {
$payload [ 'custom_nginx_configuration' ] = null ;
}
}
2024-11-03 08:02:14 +00:00
if ( count ( $payload ) > 0 ) {
$application -> forceFill ( $payload );
2023-10-09 09:10:04 +00:00
}
});
2023-08-08 09:51:36 +00:00
static :: created ( function ( $application ) {
ApplicationSetting :: create ([
'application_id' => $application -> id ,
]);
2024-08-28 13:45:11 +00:00
$application -> compose_parsing_version = self :: $parserVersion ;
2024-07-11 08:55:15 +00:00
$application -> save ();
2023-08-08 09:51:36 +00:00
});
2024-07-11 10:38:54 +00:00
static :: forceDeleting ( function ( $application ) {
2024-01-30 08:48:51 +00:00
$application -> update ([ 'fqdn' => null ]);
2023-08-08 09:51:36 +00:00
$application -> settings () -> delete ();
$application -> persistentStorages () -> delete ();
2023-09-19 12:08:20 +00:00
$application -> environment_variables () -> delete ();
$application -> environment_variables_preview () -> delete ();
2024-02-14 14:14:06 +00:00
foreach ( $application -> scheduled_tasks as $task ) {
$task -> delete ();
}
2024-02-02 10:50:28 +00:00
$application -> tags () -> detach ();
2024-08-28 20:05:49 +00:00
$application -> previews () -> delete ();
2024-10-02 07:20:49 +00:00
foreach ( $application -> deployment_queue as $deployment ) {
$deployment -> delete ();
}
2023-08-08 09:51:36 +00:00
});
}
2023-12-04 14:08:24 +00:00
2024-06-26 11:00:36 +00:00
public static function ownedByCurrentTeamAPI ( int $teamId )
{
return Application :: whereRelation ( 'environment.project.team' , 'id' , $teamId ) -> orderBy ( 'name' );
}
2024-11-07 10:09:38 +00:00
public static function ownedByCurrentTeam ()
{
return Application :: whereRelation ( 'environment.project.team' , 'id' , currentTeam () -> id ) -> orderBy ( 'name' );
}
2024-08-09 17:39:21 +00:00
public function getContainersToStop ( bool $previewDeployments = false ) : array
{
$containers = $previewDeployments
? getCurrentApplicationContainerStatus ( $this -> destination -> server , $this -> id , includePullrequests : true )
: getCurrentApplicationContainerStatus ( $this -> destination -> server , $this -> id , 0 );
return $containers -> pluck ( 'Names' ) -> toArray ();
}
public function stopContainers ( array $containerNames , $server , int $timeout = 600 )
{
$processes = [];
foreach ( $containerNames as $containerName ) {
$processes [ $containerName ] = $this -> stopContainer ( $containerName , $server , $timeout );
}
$startTime = time ();
while ( count ( $processes ) > 0 ) {
$finishedProcesses = array_filter ( $processes , function ( $process ) {
2024-09-23 17:51:31 +00:00
return ! $process -> running ();
2024-08-09 17:39:21 +00:00
});
foreach ( $finishedProcesses as $containerName => $process ) {
unset ( $processes [ $containerName ]);
$this -> removeContainer ( $containerName , $server );
}
if ( time () - $startTime >= $timeout ) {
$this -> forceStopRemainingContainers ( array_keys ( $processes ), $server );
break ;
}
usleep ( 100000 );
}
}
public function stopContainer ( string $containerName , $server , int $timeout ) : InvokedProcess
{
return Process :: timeout ( $timeout ) -> start ( " docker stop --time= $timeout $containerName " );
}
public function removeContainer ( string $containerName , $server )
{
instant_remote_process ( command : [ " docker rm -f $containerName " ], server : $server , throwError : false );
}
public function forceStopRemainingContainers ( array $containerNames , $server )
{
foreach ( $containerNames as $containerName ) {
instant_remote_process ( command : [ " docker kill $containerName " ], server : $server , throwError : false );
$this -> removeContainer ( $containerName , $server );
}
}
2024-04-12 08:54:25 +00:00
public function delete_configurations ()
{
$server = data_get ( $this , 'destination.server' );
$workdir = $this -> workdir ();
if ( str ( $workdir ) -> endsWith ( $this -> uuid )) {
2024-06-10 20:43:34 +00:00
instant_remote_process ([ 'rm -rf ' . $this -> workdir ()], $server , false );
2024-04-12 08:54:25 +00:00
}
}
2024-06-10 20:43:34 +00:00
2024-07-11 10:38:54 +00:00
public function delete_volumes ( ? Collection $persistentStorages )
{
if ( $this -> build_pack === 'dockercompose' ) {
$server = data_get ( $this , 'destination.server' );
instant_remote_process ([ " cd { $this -> dirOnServer () } && docker compose down -v " ], $server , false );
} else {
if ( $persistentStorages -> count () === 0 ) {
return ;
}
$server = data_get ( $this , 'destination.server' );
foreach ( $persistentStorages as $storage ) {
instant_remote_process ([ " docker volume rm -f $storage->name " ], $server , false );
}
}
}
2024-08-08 22:30:11 +00:00
public function delete_connected_networks ( $uuid )
{
$server = data_get ( $this , 'destination.server' );
instant_remote_process ([ " docker network disconnect { $uuid } coolify-proxy " ], $server , false );
instant_remote_process ([ " docker network rm { $uuid } " ], $server , false );
}
2024-02-06 14:05:11 +00:00
public function additional_servers ()
{
return $this -> belongsToMany ( Server :: class , 'additional_destinations' )
2024-02-07 13:55:06 +00:00
-> withPivot ( 'standalone_docker_id' , 'status' );
2024-02-06 14:05:11 +00:00
}
2024-06-10 20:43:34 +00:00
2024-02-06 14:05:11 +00:00
public function additional_networks ()
{
return $this -> belongsToMany ( StandaloneDocker :: class , 'additional_destinations' )
2024-02-07 13:55:06 +00:00
-> withPivot ( 'server_id' , 'status' );
2024-02-06 14:05:11 +00:00
}
2024-06-10 20:43:34 +00:00
2024-03-01 17:24:14 +00:00
public function is_public_repository () : bool
{
2024-03-01 10:41:22 +00:00
if ( data_get ( $this , 'source.is_public' )) {
return true ;
}
2024-06-10 20:43:34 +00:00
2024-03-01 10:41:22 +00:00
return false ;
}
2024-06-10 20:43:34 +00:00
2024-01-29 09:43:18 +00:00
public function is_github_based () : bool
{
if ( data_get ( $this , 'source' )) {
return true ;
}
2024-06-10 20:43:34 +00:00
2024-01-29 09:43:18 +00:00
return false ;
}
2024-06-10 20:43:34 +00:00
2024-03-04 10:01:14 +00:00
public function isForceHttpsEnabled ()
2024-03-04 09:46:13 +00:00
{
return data_get ( $this , 'settings.is_force_https_enabled' , false );
}
2024-06-10 20:43:34 +00:00
2024-03-04 10:01:14 +00:00
public function isStripprefixEnabled ()
2024-03-04 09:46:13 +00:00
{
return data_get ( $this , 'settings.is_stripprefix_enabled' , true );
}
2024-06-10 20:43:34 +00:00
2024-03-04 10:01:14 +00:00
public function isGzipEnabled ()
2024-03-04 09:46:13 +00:00
{
return data_get ( $this , 'settings.is_gzip_enabled' , true );
}
2024-06-10 20:43:34 +00:00
2023-11-27 08:39:43 +00:00
public function link ()
{
2023-11-30 11:21:53 +00:00
if ( data_get ( $this , 'environment.project.uuid' )) {
return route ( 'project.application.configuration' , [
'project_uuid' => data_get ( $this , 'environment.project.uuid' ),
'environment_name' => data_get ( $this , 'environment.name' ),
2024-06-10 20:43:34 +00:00
'application_uuid' => data_get ( $this , 'uuid' ),
2023-11-30 11:21:53 +00:00
]);
}
2024-06-10 20:43:34 +00:00
2023-11-30 11:21:53 +00:00
return null ;
2023-11-27 08:39:43 +00:00
}
2024-06-10 20:43:34 +00:00
2024-05-21 13:36:26 +00:00
public function failedTaskLink ( $task_uuid )
{
if ( data_get ( $this , 'environment.project.uuid' )) {
2024-07-25 11:30:38 +00:00
$route = route ( 'project.application.scheduled-tasks' , [
2024-05-21 13:36:26 +00:00
'project_uuid' => data_get ( $this , 'environment.project.uuid' ),
'environment_name' => data_get ( $this , 'environment.name' ),
'application_uuid' => data_get ( $this , 'uuid' ),
2024-06-10 20:43:34 +00:00
'task_uuid' => $task_uuid ,
2024-05-21 13:36:26 +00:00
]);
2024-10-01 08:37:40 +00:00
$settings = instanceSettings ();
2024-07-25 11:30:38 +00:00
if ( data_get ( $settings , 'fqdn' )) {
$url = Url :: fromString ( $route );
$url = $url -> withPort ( null );
$fqdn = data_get ( $settings , 'fqdn' );
$fqdn = str_replace ([ 'http://' , 'https://' ], '' , $fqdn );
$url = $url -> withHost ( $fqdn );
return $url -> __toString ();
}
return $route ;
2024-05-21 13:36:26 +00:00
}
2024-06-10 20:43:34 +00:00
2024-05-21 13:36:26 +00:00
return null ;
}
2024-06-10 20:43:34 +00:00
2023-08-08 09:51:36 +00:00
public function settings ()
{
return $this -> hasOne ( ApplicationSetting :: class );
}
public function persistentStorages ()
{
return $this -> morphMany ( LocalPersistentVolume :: class , 'resource' );
}
2024-06-10 20:43:34 +00:00
2023-10-04 07:58:39 +00:00
public function fileStorages ()
{
return $this -> morphMany ( LocalFileVolume :: class , 'resource' );
}
2023-08-08 09:51:36 +00:00
public function type ()
{
2023-08-07 20:14:21 +00:00
return 'application' ;
}
2023-08-08 09:51:36 +00:00
2023-04-26 11:01:09 +00:00
public function publishDirectory () : Attribute
{
return Attribute :: make (
2024-06-10 20:43:34 +00:00
set : fn ( $value ) => $value ? '/' . ltrim ( $value , '/' ) : null ,
2023-04-26 11:01:09 +00:00
);
}
2023-08-08 09:51:36 +00:00
2023-05-10 10:22:27 +00:00
public function gitBranchLocation () : Attribute
2023-05-08 10:22:45 +00:00
{
return Attribute :: make (
2023-05-10 09:06:54 +00:00
get : function () {
2024-06-10 20:43:34 +00:00
if ( ! is_null ( $this -> source ? -> html_url ) && ! is_null ( $this -> git_repository ) && ! is_null ( $this -> git_branch )) {
2024-07-19 11:41:01 +00:00
if ( str ( $this -> git_repository ) -> contains ( 'bitbucket' )) {
return " { $this -> source -> html_url } / { $this -> git_repository } /src/ { $this -> git_branch } " ;
}
2023-05-10 10:22:27 +00:00
return " { $this -> source -> html_url } / { $this -> git_repository } /tree/ { $this -> git_branch } " ;
}
2024-05-10 14:28:14 +00:00
// Convert the SSH URL to HTTPS URL
if ( strpos ( $this -> git_repository , 'git@' ) === 0 ) {
$git_repository = str_replace ([ 'git@' , ':' , '.git' ], [ '' , '/' , '' ], $this -> git_repository );
2024-06-10 20:43:34 +00:00
2024-07-19 11:41:01 +00:00
if ( str ( $this -> git_repository ) -> contains ( 'bitbucket' )) {
return " https:// { $git_repository } /src/ { $this -> git_branch } " ;
}
2024-05-10 14:28:14 +00:00
return " https:// { $git_repository } /tree/ { $this -> git_branch } " ;
}
2024-06-10 20:43:34 +00:00
2023-10-06 11:46:42 +00:00
return $this -> git_repository ;
2023-05-10 10:22:27 +00:00
}
);
}
2023-08-08 09:51:36 +00:00
2023-11-14 13:07:42 +00:00
public function gitWebhook () : Attribute
{
return Attribute :: make (
get : function () {
2024-06-10 20:43:34 +00:00
if ( ! is_null ( $this -> source ? -> html_url ) && ! is_null ( $this -> git_repository ) && ! is_null ( $this -> git_branch )) {
2023-11-14 13:07:42 +00:00
return " { $this -> source -> html_url } / { $this -> git_repository } /settings/hooks " ;
}
2024-05-10 14:28:14 +00:00
// Convert the SSH URL to HTTPS URL
if ( strpos ( $this -> git_repository , 'git@' ) === 0 ) {
$git_repository = str_replace ([ 'git@' , ':' , '.git' ], [ '' , '/' , '' ], $this -> git_repository );
2024-06-10 20:43:34 +00:00
2024-05-10 14:28:14 +00:00
return " https:// { $git_repository } /settings/hooks " ;
}
2024-06-10 20:43:34 +00:00
2023-11-14 13:07:42 +00:00
return $this -> git_repository ;
}
);
}
2023-05-10 10:22:27 +00:00
public function gitCommits () : Attribute
{
return Attribute :: make (
get : function () {
2024-06-10 20:43:34 +00:00
if ( ! is_null ( $this -> source ? -> html_url ) && ! is_null ( $this -> git_repository ) && ! is_null ( $this -> git_branch )) {
2023-05-10 10:22:27 +00:00
return " { $this -> source -> html_url } / { $this -> git_repository } /commits/ { $this -> git_branch } " ;
2023-05-10 09:06:54 +00:00
}
2024-05-10 14:28:14 +00:00
// Convert the SSH URL to HTTPS URL
if ( strpos ( $this -> git_repository , 'git@' ) === 0 ) {
$git_repository = str_replace ([ 'git@' , ':' , '.git' ], [ '' , '/' , '' ], $this -> git_repository );
2024-06-10 20:43:34 +00:00
2024-05-10 14:28:14 +00:00
return " https:// { $git_repository } /commits/ { $this -> git_branch } " ;
}
2024-06-10 20:43:34 +00:00
2023-10-06 11:46:42 +00:00
return $this -> git_repository ;
2023-05-10 09:06:54 +00:00
}
2023-05-08 10:22:45 +00:00
);
}
2024-06-10 20:43:34 +00:00
2024-05-15 08:45:08 +00:00
public function gitCommitLink ( $link ) : string
{
2024-09-23 17:51:31 +00:00
if ( ! is_null ( data_get ( $this , 'source.html_url' )) && ! is_null ( data_get ( $this , 'git_repository' )) && ! is_null ( data_get ( $this , 'git_branch' ))) {
2024-05-17 08:12:05 +00:00
if ( str ( $this -> source -> html_url ) -> contains ( 'bitbucket' )) {
return " { $this -> source -> html_url } / { $this -> git_repository } /commits/ { $link } " ;
}
2024-06-10 20:43:34 +00:00
2024-05-15 08:45:08 +00:00
return " { $this -> source -> html_url } / { $this -> git_repository } /commit/ { $link } " ;
}
2024-05-23 12:28:03 +00:00
if ( str ( $this -> git_repository ) -> contains ( 'bitbucket' )) {
$git_repository = str_replace ( '.git' , '' , $this -> git_repository );
$url = Url :: fromString ( $git_repository );
$url = $url -> withUserInfo ( '' );
2024-06-10 20:43:34 +00:00
$url = $url -> withPath ( $url -> getPath () . '/commits/' . $link );
2024-05-23 12:28:03 +00:00
return $url -> __toString ();
}
2024-06-12 09:35:07 +00:00
if ( strpos ( $this -> git_repository , 'git@' ) === 0 ) {
$git_repository = str_replace ([ 'git@' , ':' , '.git' ], [ '' , '/' , '' ], $this -> git_repository );
2024-06-21 12:39:22 +00:00
if ( data_get ( $this , 'source.html_url' )) {
return " { $this -> source -> html_url } / { $git_repository } /commit/ { $link } " ;
}
2024-06-12 09:35:55 +00:00
2024-06-21 12:39:22 +00:00
return " { $git_repository } /commit/ { $link } " ;
2024-06-12 09:35:07 +00:00
}
2024-06-10 20:43:34 +00:00
2024-05-15 08:45:08 +00:00
return $this -> git_repository ;
}
2024-06-10 20:43:34 +00:00
2023-10-10 12:02:43 +00:00
public function dockerfileLocation () : Attribute
{
return Attribute :: make (
set : function ( $value ) {
if ( is_null ( $value ) || $value === '' ) {
return '/Dockerfile' ;
} else {
if ( $value !== '/' ) {
return Str :: start ( Str :: replaceEnd ( '/' , '' , $value ), '/' );
}
2024-06-10 20:43:34 +00:00
2023-10-10 12:02:43 +00:00
return Str :: start ( $value , '/' );
}
}
);
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
public function dockerComposeLocation () : Attribute
{
return Attribute :: make (
set : function ( $value ) {
if ( is_null ( $value ) || $value === '' ) {
2023-11-27 14:50:22 +00:00
return '/docker-compose.yaml' ;
} else {
if ( $value !== '/' ) {
return Str :: start ( Str :: replaceEnd ( '/' , '' , $value ), '/' );
}
2024-06-10 20:43:34 +00:00
2023-11-27 14:50:22 +00:00
return Str :: start ( $value , '/' );
}
}
);
}
2024-06-10 20:43:34 +00:00
2023-04-26 11:01:09 +00:00
public function baseDirectory () : Attribute
{
return Attribute :: make (
2024-06-10 20:43:34 +00:00
set : fn ( $value ) => '/' . ltrim ( $value , '/' ),
2023-04-26 11:01:09 +00:00
);
}
2023-08-08 09:51:36 +00:00
2023-04-26 12:29:33 +00:00
public function portsMappings () : Attribute
{
return Attribute :: make (
2024-06-10 20:43:34 +00:00
set : fn ( $value ) => $value === '' ? null : $value ,
2023-04-26 12:29:33 +00:00
);
}
2024-06-10 20:43:34 +00:00
2023-04-24 11:25:02 +00:00
public function portsMappingsArray () : Attribute
2023-03-30 07:47:04 +00:00
{
return Attribute :: make (
2023-08-11 18:48:52 +00:00
get : fn () => is_null ( $this -> ports_mappings )
2023-03-30 07:47:04 +00:00
? []
2023-04-26 12:29:33 +00:00
: explode ( ',' , $this -> ports_mappings ),
2023-03-30 07:47:04 +00:00
);
}
2024-06-10 20:43:34 +00:00
2024-08-01 11:47:58 +00:00
public function isRunning ()
{
return ( bool ) str ( $this -> status ) -> startsWith ( 'running' );
}
2024-02-19 12:22:09 +00:00
public function isExited ()
{
2024-02-16 07:34:30 +00:00
return ( bool ) str ( $this -> status ) -> startsWith ( 'exited' );
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
public function realStatus ()
{
2024-02-12 10:46:36 +00:00
return $this -> getRawOriginal ( 'status' );
2024-02-07 13:55:06 +00:00
}
2024-06-10 20:43:34 +00:00
2024-08-05 13:48:15 +00:00
protected function serverStatus () : Attribute
{
return Attribute :: make (
get : function () {
if ( $this -> additional_servers -> count () === 0 ) {
return $this -> destination -> server -> isFunctional ();
} else {
$additional_servers_status = $this -> additional_servers -> pluck ( 'pivot.status' );
$main_server_status = $this -> destination -> server -> isFunctional ();
foreach ( $additional_servers_status as $status ) {
$server_status = str ( $status ) -> before ( ':' ) -> value ();
2024-08-13 08:57:59 +00:00
if ( $server_status !== 'running' ) {
2024-08-05 13:48:15 +00:00
return false ;
}
}
2024-08-13 08:57:59 +00:00
return $main_server_status ;
2024-08-05 13:48:15 +00:00
}
}
);
}
2024-02-06 16:37:07 +00:00
public function status () : Attribute
{
return Attribute :: make (
set : function ( $value ) {
2024-02-07 13:55:06 +00:00
if ( $this -> additional_servers -> count () === 0 ) {
if ( str ( $value ) -> contains ( '(' )) {
$status = str ( $value ) -> before ( '(' ) -> trim () -> value ();
$health = str ( $value ) -> after ( '(' ) -> before ( ')' ) -> trim () -> value () ? ? 'unhealthy' ;
2024-06-10 20:43:34 +00:00
} elseif ( str ( $value ) -> contains ( ':' )) {
2024-02-07 13:55:06 +00:00
$status = str ( $value ) -> before ( ':' ) -> trim () -> value ();
$health = str ( $value ) -> after ( ':' ) -> trim () -> value () ? ? 'unhealthy' ;
} else {
$status = $value ;
$health = 'unhealthy' ;
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
return " $status : $health " ;
2024-02-06 16:37:07 +00:00
} else {
2024-02-07 13:55:06 +00:00
if ( str ( $value ) -> contains ( '(' )) {
$status = str ( $value ) -> before ( '(' ) -> trim () -> value ();
$health = str ( $value ) -> after ( '(' ) -> before ( ')' ) -> trim () -> value () ? ? 'unhealthy' ;
2024-06-10 20:43:34 +00:00
} elseif ( str ( $value ) -> contains ( ':' )) {
2024-02-07 13:55:06 +00:00
$status = str ( $value ) -> before ( ':' ) -> trim () -> value ();
$health = str ( $value ) -> after ( ':' ) -> trim () -> value () ? ? 'unhealthy' ;
} else {
$status = $value ;
$health = 'unhealthy' ;
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
return " $status : $health " ;
2024-02-06 16:37:07 +00:00
}
},
get : function ( $value ) {
2024-02-07 13:55:06 +00:00
if ( $this -> additional_servers -> count () === 0 ) {
//running (healthy)
if ( str ( $value ) -> contains ( '(' )) {
$status = str ( $value ) -> before ( '(' ) -> trim () -> value ();
$health = str ( $value ) -> after ( '(' ) -> before ( ')' ) -> trim () -> value () ? ? 'unhealthy' ;
2024-06-10 20:43:34 +00:00
} elseif ( str ( $value ) -> contains ( ':' )) {
2024-02-07 13:55:06 +00:00
$status = str ( $value ) -> before ( ':' ) -> trim () -> value ();
$health = str ( $value ) -> after ( ':' ) -> trim () -> value () ? ? 'unhealthy' ;
} else {
$status = $value ;
$health = 'unhealthy' ;
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
return " $status : $health " ;
2024-02-06 16:37:07 +00:00
} else {
2024-02-07 13:55:06 +00:00
$complex_status = null ;
$complex_health = null ;
$complex_status = $main_server_status = str ( $value ) -> before ( ':' ) -> value ();
$complex_health = $main_server_health = str ( $value ) -> after ( ':' ) -> value () ? ? 'unhealthy' ;
$additional_servers_status = $this -> additional_servers -> pluck ( 'pivot.status' );
foreach ( $additional_servers_status as $status ) {
$server_status = str ( $status ) -> before ( ':' ) -> value ();
$server_health = str ( $status ) -> after ( ':' ) -> value () ? ? 'unhealthy' ;
2024-02-22 09:57:05 +00:00
if ( $main_server_status !== $server_status ) {
$complex_status = 'degraded' ;
2024-02-07 13:55:06 +00:00
}
2024-02-22 09:57:05 +00:00
if ( $main_server_health !== $server_health ) {
$complex_health = 'unhealthy' ;
2024-02-07 13:55:06 +00:00
}
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
return " $complex_status : $complex_health " ;
2024-02-06 16:37:07 +00:00
}
},
);
}
2023-08-08 09:51:36 +00:00
2024-11-11 13:37:19 +00:00
public function customNginxConfiguration () : Attribute
{
return Attribute :: make (
set : fn ( $value ) => base64_encode ( $value ),
get : fn ( $value ) => base64_decode ( $value ),
);
}
2023-04-24 11:25:02 +00:00
public function portsExposesArray () : Attribute
2023-03-30 07:47:04 +00:00
{
return Attribute :: make (
2023-08-11 18:48:52 +00:00
get : fn () => is_null ( $this -> ports_exposes )
2023-03-30 07:47:04 +00:00
? []
2023-04-24 11:25:02 +00:00
: explode ( ',' , $this -> ports_exposes )
2023-03-30 07:47:04 +00:00
);
}
2024-06-10 20:43:34 +00:00
2024-02-01 14:38:12 +00:00
public function tags ()
{
return $this -> morphToMany ( Tag :: class , 'taggable' );
}
2024-06-10 20:43:34 +00:00
2024-02-06 14:05:11 +00:00
public function project ()
{
2024-02-03 11:39:07 +00:00
return data_get ( $this , 'environment.project' );
}
2024-06-10 20:43:34 +00:00
2024-01-23 16:13:23 +00:00
public function team ()
{
return data_get ( $this , 'environment.project.team' );
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
public function serviceType ()
{
$found = str ( collect ( SPECIFIC_SERVICES ) -> filter ( function ( $service ) {
return str ( $this -> image ) -> before ( ':' ) -> value () === $service ;
}) -> first ());
if ( $found -> isNotEmpty ()) {
return $found ;
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
return null ;
}
2024-06-10 20:43:34 +00:00
2024-04-26 10:59:51 +00:00
public function main_port ()
{
return $this -> settings -> is_static ? [ 80 ] : $this -> ports_exposes_array ;
}
2024-06-10 20:43:34 +00:00
2023-05-04 20:29:14 +00:00
public function environment_variables () : HasMany
{
2023-09-08 14:16:59 +00:00
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , false ) -> orderBy ( 'key' , 'asc' );
2023-05-04 20:29:14 +00:00
}
2023-08-08 09:51:36 +00:00
2023-05-05 12:48:40 +00:00
public function runtime_environment_variables () : HasMany
{
2023-06-05 10:07:55 +00:00
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , false ) -> where ( 'key' , 'not like' , 'NIXPACKS_%' );
2023-05-05 12:48:40 +00:00
}
2023-08-08 09:51:36 +00:00
// Preview Deployments
2023-05-05 07:02:50 +00:00
public function build_environment_variables () : HasMany
{
2023-06-05 10:07:55 +00:00
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , false ) -> where ( 'is_build_time' , true ) -> where ( 'key' , 'not like' , 'NIXPACKS_%' );
2023-05-05 07:02:50 +00:00
}
2023-08-08 09:51:36 +00:00
2023-05-05 08:51:58 +00:00
public function nixpacks_environment_variables () : HasMany
{
2023-06-05 10:07:55 +00:00
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , false ) -> where ( 'key' , 'like' , 'NIXPACKS_%' );
}
2023-08-08 09:51:36 +00:00
2023-06-05 10:07:55 +00:00
public function environment_variables_preview () : HasMany
{
2023-09-08 14:16:59 +00:00
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , true ) -> orderBy ( 'key' , 'asc' );
2023-06-05 10:07:55 +00:00
}
2023-08-08 09:51:36 +00:00
2023-06-05 10:07:55 +00:00
public function runtime_environment_variables_preview () : HasMany
{
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , true ) -> where ( 'key' , 'not like' , 'NIXPACKS_%' );
}
2023-08-08 09:51:36 +00:00
2023-06-05 10:07:55 +00:00
public function build_environment_variables_preview () : HasMany
{
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , true ) -> where ( 'is_build_time' , true ) -> where ( 'key' , 'not like' , 'NIXPACKS_%' );
}
2023-08-08 09:51:36 +00:00
2023-06-05 10:07:55 +00:00
public function nixpacks_environment_variables_preview () : HasMany
{
return $this -> hasMany ( EnvironmentVariable :: class ) -> where ( 'is_preview' , true ) -> where ( 'key' , 'like' , 'NIXPACKS_%' );
2023-05-05 08:51:58 +00:00
}
2023-08-08 09:51:36 +00:00
2024-01-01 18:33:16 +00:00
public function scheduled_tasks () : HasMany
{
return $this -> hasMany ( ScheduledTask :: class ) -> orderBy ( 'name' , 'asc' );
}
2023-05-10 11:05:32 +00:00
public function private_key ()
{
return $this -> belongsTo ( PrivateKey :: class );
}
2023-08-08 09:51:36 +00:00
2023-04-26 12:29:33 +00:00
public function environment ()
{
return $this -> belongsTo ( Environment :: class );
}
2023-08-08 09:51:36 +00:00
2023-05-30 13:52:17 +00:00
public function previews ()
{
return $this -> hasMany ( ApplicationPreview :: class );
}
2023-08-08 09:51:36 +00:00
2024-10-02 07:20:49 +00:00
public function deployment_queue ()
{
return $this -> hasMany ( ApplicationDeploymentQueue :: class );
}
2023-04-26 12:29:33 +00:00
public function destination ()
{
return $this -> morphTo ();
}
2023-08-08 09:51:36 +00:00
2023-04-26 12:29:33 +00:00
public function source ()
{
return $this -> morphTo ();
}
2024-06-10 20:43:34 +00:00
2023-11-21 14:31:46 +00:00
public function isDeploymentInprogress ()
{
2024-06-09 20:12:13 +00:00
$deployments = ApplicationDeploymentQueue :: where ( 'application_id' , $this -> id ) -> whereIn ( 'status' , [ ApplicationDeploymentStatus :: IN_PROGRESS , ApplicationDeploymentStatus :: QUEUED ]) -> count ();
2023-11-10 09:34:28 +00:00
if ( $deployments > 0 ) {
return true ;
}
2024-06-10 20:43:34 +00:00
2023-11-10 09:34:28 +00:00
return false ;
}
2024-06-10 20:43:34 +00:00
2024-05-17 06:53:25 +00:00
public function get_last_successful_deployment ()
{
2024-06-12 10:05:08 +00:00
return ApplicationDeploymentQueue :: where ( 'application_id' , $this -> id ) -> where ( 'status' , ApplicationDeploymentStatus :: FINISHED ) -> where ( 'pull_request_id' , 0 ) -> orderBy ( 'created_at' , 'desc' ) -> first ();
2024-05-17 06:53:25 +00:00
}
2024-06-10 20:43:34 +00:00
2024-03-01 17:24:14 +00:00
public function get_last_days_deployments ()
{
return ApplicationDeploymentQueue :: where ( 'application_id' , $this -> id ) -> where ( 'created_at' , '>=' , now () -> subDays ( 7 )) -> orderBy ( 'created_at' , 'desc' ) -> get ();
}
2024-06-10 20:43:34 +00:00
2023-05-31 12:24:20 +00:00
public function deployments ( int $skip = 0 , int $take = 10 )
2023-03-29 10:52:22 +00:00
{
2023-05-31 12:57:42 +00:00
$deployments = ApplicationDeploymentQueue :: where ( 'application_id' , $this -> id ) -> orderBy ( 'created_at' , 'desc' );
$count = $deployments -> count ();
$deployments = $deployments -> skip ( $skip ) -> take ( $take ) -> get ();
2024-06-10 20:43:34 +00:00
2023-05-31 12:57:42 +00:00
return [
'count' => $count ,
2024-06-10 20:43:34 +00:00
'deployments' => $deployments ,
2023-05-31 12:57:42 +00:00
];
2023-03-29 10:52:22 +00:00
}
2023-08-08 09:51:36 +00:00
2023-03-29 10:27:02 +00:00
public function get_deployment ( string $deployment_uuid )
2023-03-28 13:47:37 +00:00
{
2023-05-03 06:51:03 +00:00
return Activity :: where ( 'subject_id' , $this -> id ) -> where ( 'properties->type_uuid' , '=' , $deployment_uuid ) -> first ();
2023-03-28 13:47:37 +00:00
}
2023-08-08 09:51:36 +00:00
2023-05-10 09:43:49 +00:00
public function isDeployable () : bool
{
2023-05-31 09:24:02 +00:00
if ( $this -> settings -> is_auto_deploy_enabled ) {
return true ;
}
2024-06-10 20:43:34 +00:00
2023-05-31 09:24:02 +00:00
return false ;
}
2023-08-08 09:51:36 +00:00
2023-05-31 09:24:02 +00:00
public function isPRDeployable () : bool
{
if ( $this -> settings -> is_preview_deployments_enabled ) {
2023-05-10 09:43:49 +00:00
return true ;
}
2024-06-10 20:43:34 +00:00
2023-05-10 09:43:49 +00:00
return false ;
}
2023-08-08 09:51:36 +00:00
2023-05-10 11:05:32 +00:00
public function deploymentType ()
{
2023-12-30 13:20:02 +00:00
if ( isDev () && data_get ( $this , 'private_key_id' ) === 0 ) {
return 'deploy_key' ;
}
2023-07-05 20:10:10 +00:00
if ( data_get ( $this , 'private_key_id' )) {
return 'deploy_key' ;
2024-06-10 20:43:34 +00:00
} elseif ( data_get ( $this , 'source' )) {
2023-05-10 11:05:32 +00:00
return 'source' ;
2023-10-06 11:46:42 +00:00
} else {
return 'other' ;
2023-05-10 11:05:32 +00:00
}
throw new \Exception ( 'No deployment type found' );
}
2024-06-10 20:43:34 +00:00
2023-08-11 20:41:47 +00:00
public function could_set_build_commands () : bool
{
if ( $this -> build_pack === 'nixpacks' ) {
return true ;
}
2024-06-10 20:43:34 +00:00
2023-08-11 20:41:47 +00:00
return false ;
}
2024-06-10 20:43:34 +00:00
2023-08-11 20:41:47 +00:00
public function git_based () : bool
{
2023-09-29 12:26:31 +00:00
if ( $this -> dockerfile ) {
2023-08-11 20:41:47 +00:00
return false ;
}
2023-10-10 12:02:43 +00:00
if ( $this -> build_pack === 'dockerimage' ) {
2023-10-10 09:16:38 +00:00
return false ;
}
2024-06-10 20:43:34 +00:00
2023-08-11 20:41:47 +00:00
return true ;
}
2024-06-10 20:43:34 +00:00
2023-10-01 16:14:13 +00:00
public function isHealthcheckDisabled () : bool
{
2023-10-10 12:02:43 +00:00
if ( data_get ( $this , 'health_check_enabled' ) === false ) {
2023-10-01 16:14:13 +00:00
return true ;
}
2024-06-10 20:43:34 +00:00
2023-10-01 16:14:13 +00:00
return false ;
}
2024-06-10 20:43:34 +00:00
2024-03-04 09:13:40 +00:00
public function workdir ()
{
2024-06-10 20:43:34 +00:00
return application_configuration_dir () . " / { $this -> uuid } " ;
2024-03-04 09:13:40 +00:00
}
2024-06-10 20:43:34 +00:00
2024-03-04 10:01:14 +00:00
public function isLogDrainEnabled ()
2023-11-21 14:31:46 +00:00
{
return data_get ( $this , 'settings.is_log_drain_enabled' , false );
2023-11-17 19:08:21 +00:00
}
2024-06-10 20:43:34 +00:00
2024-02-15 10:55:43 +00:00
public function isConfigurationChanged ( bool $save = false )
2023-10-18 09:20:40 +00:00
{
2024-11-11 13:37:19 +00:00
$newConfigHash = base64_encode ( $this -> fqdn . $this -> git_repository . $this -> git_branch . $this -> git_commit_sha . $this -> build_pack . $this -> static_image . $this -> install_command . $this -> build_command . $this -> start_command . $this -> ports_exposes . $this -> ports_mappings . $this -> base_directory . $this -> publish_directory . $this -> dockerfile . $this -> dockerfile_location . $this -> custom_labels . $this -> custom_docker_run_options . $this -> dockerfile_target_build . $this -> redirect . $this -> custom_nginx_configuration );
2024-01-03 12:20:24 +00:00
if ( $this -> pull_request_id === 0 || $this -> pull_request_id === null ) {
2024-04-15 13:45:50 +00:00
$newConfigHash .= json_encode ( $this -> environment_variables () -> get ( 'value' ) -> sort ());
2023-10-18 09:20:40 +00:00
} else {
2024-04-15 13:45:50 +00:00
$newConfigHash .= json_encode ( $this -> environment_variables_preview -> get ( 'value' ) -> sort ());
2023-10-18 09:20:40 +00:00
}
$newConfigHash = md5 ( $newConfigHash );
$oldConfigHash = data_get ( $this , 'config_hash' );
if ( $oldConfigHash === null ) {
if ( $save ) {
$this -> config_hash = $newConfigHash ;
$this -> save ();
}
2024-06-10 20:43:34 +00:00
2023-10-18 09:20:40 +00:00
return true ;
}
if ( $oldConfigHash === $newConfigHash ) {
return false ;
} else {
if ( $save ) {
$this -> config_hash = $newConfigHash ;
$this -> save ();
}
2024-06-10 20:43:34 +00:00
2023-10-18 09:20:40 +00:00
return true ;
}
}
2024-06-10 20:43:34 +00:00
public function customRepository ()
2023-11-24 14:48:23 +00:00
{
2024-11-12 10:32:18 +00:00
return convertGitUrl ( $this -> git_repository , $this -> deploymentType (), $this -> source );
2023-11-24 14:48:23 +00:00
}
2024-06-10 20:43:34 +00:00
public function generateBaseDir ( string $uuid )
2023-11-24 14:48:23 +00:00
{
return " /artifacts/ { $uuid } " ;
}
2024-06-10 20:43:34 +00:00
2024-07-11 09:30:20 +00:00
public function dirOnServer ()
{
2024-09-23 17:51:31 +00:00
return application_configuration_dir () . " / { $this -> uuid } " ;
2024-07-11 09:30:20 +00:00
}
2024-06-10 20:43:34 +00:00
public function setGitImportSettings ( string $deployment_uuid , string $git_clone_command , bool $public = false )
2023-11-24 14:48:23 +00:00
{
$baseDir = $this -> generateBaseDir ( $deployment_uuid );
2024-02-19 12:22:09 +00:00
2023-11-24 14:48:23 +00:00
if ( $this -> git_commit_sha !== 'HEAD' ) {
2024-02-19 12:22:09 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \" git -c advice.detachedHead=false checkout { $this -> git_commit_sha } >/dev/null 2>&1 " ;
2023-11-24 14:48:23 +00:00
}
if ( $this -> settings -> is_git_submodules_enabled ) {
2024-02-19 12:22:09 +00:00
if ( $public ) {
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && sed -i \" s#git@ \ (.* \ ):#https:// \\ 1/#g \" { $baseDir } /.gitmodules || true " ;
}
2024-04-08 12:07:07 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \" git submodule update --init --recursive " ;
2023-11-24 14:48:23 +00:00
}
if ( $this -> settings -> is_git_lfs_enabled ) {
2024-02-19 12:22:09 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \" git lfs pull " ;
2023-11-24 14:48:23 +00:00
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
return $git_clone_command ;
}
2024-06-10 20:43:34 +00:00
2024-06-16 04:22:44 +00:00
public function getGitRemoteStatus ( string $deployment_uuid )
{
try {
[ 'commands' => $lsRemoteCommand ] = $this -> generateGitLsRemoteCommands ( deployment_uuid : $deployment_uuid , exec_in_docker : false );
instant_remote_process ([ $lsRemoteCommand ], $this -> destination -> server , true );
2024-11-12 10:50:02 +00:00
2024-06-16 04:22:44 +00:00
return [
'is_accessible' => true ,
'error' => null ,
];
} catch ( \RuntimeException $ex ) {
return [
'is_accessible' => false ,
'error' => $ex -> getMessage (),
];
}
}
public function generateGitLsRemoteCommands ( string $deployment_uuid , bool $exec_in_docker = true )
{
$branch = $this -> git_branch ;
[ 'repository' => $customRepository , 'port' => $customPort ] = $this -> customRepository ();
$commands = collect ([]);
2024-11-12 10:50:02 +00:00
$base_command = 'git ls-remote' ;
2024-06-16 04:22:44 +00:00
if ( $this -> deploymentType () === 'source' ) {
$source_html_url = data_get ( $this , 'source.html_url' );
$url = parse_url ( filter_var ( $source_html_url , FILTER_SANITIZE_URL ));
$source_html_url_host = $url [ 'host' ];
$source_html_url_scheme = $url [ 'scheme' ];
if ( $this -> source -> getMorphClass () == 'App\Models\GithubApp' ) {
if ( $this -> source -> is_public ) {
$fullRepoUrl = " { $this -> source -> html_url } / { $customRepository } " ;
$base_command = " { $base_command } { $this -> source -> html_url } / { $customRepository } " ;
} else {
$github_access_token = generate_github_installation_token ( $this -> source );
if ( $exec_in_docker ) {
$base_command = " { $base_command } $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } .git " ;
$fullRepoUrl = " $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } .git " ;
} else {
$base_command = " { $base_command } $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } " ;
$fullRepoUrl = " $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } " ;
}
}
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $base_command ));
} else {
$commands -> push ( $base_command );
}
return [
'commands' => $commands -> implode ( ' && ' ),
'branch' => $branch ,
'fullRepoUrl' => $fullRepoUrl ,
];
}
}
if ( $this -> deploymentType () === 'deploy_key' ) {
$fullRepoUrl = $customRepository ;
$private_key = data_get ( $this , 'private_key.private_key' );
if ( is_null ( $private_key )) {
throw new RuntimeException ( 'Private key not found. Please add a private key to the application and try again.' );
}
$private_key = base64_encode ( $private_key );
$base_comamnd = " GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" { $base_command } { $customRepository } " ;
if ( $exec_in_docker ) {
$commands = collect ([
executeInDocker ( $deployment_uuid , 'mkdir -p /root/.ssh' ),
executeInDocker ( $deployment_uuid , " echo ' { $private_key } ' | base64 -d | tee /root/.ssh/id_rsa > /dev/null " ),
executeInDocker ( $deployment_uuid , 'chmod 600 /root/.ssh/id_rsa' ),
]);
} else {
$commands = collect ([
'mkdir -p /root/.ssh' ,
" echo ' { $private_key } ' | base64 -d | tee /root/.ssh/id_rsa > /dev/null " ,
'chmod 600 /root/.ssh/id_rsa' ,
]);
}
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $base_comamnd ));
} else {
$commands -> push ( $base_comamnd );
}
return [
'commands' => $commands -> implode ( ' && ' ),
'branch' => $branch ,
'fullRepoUrl' => $fullRepoUrl ,
];
}
if ( $this -> deploymentType () === 'other' ) {
$fullRepoUrl = $customRepository ;
$base_command = " { $base_command } { $customRepository } " ;
$base_command = $this -> setGitImportSettings ( $deployment_uuid , $base_command , public : true );
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $base_command ));
} else {
$commands -> push ( $base_command );
}
return [
'commands' => $commands -> implode ( ' && ' ),
'branch' => $branch ,
'fullRepoUrl' => $fullRepoUrl ,
];
}
}
2024-06-10 20:43:34 +00:00
public function generateGitImportCommands ( string $deployment_uuid , int $pull_request_id = 0 , ? string $git_type = null , bool $exec_in_docker = true , bool $only_checkout = false , ? string $custom_base_dir = null , ? string $commit = null )
2023-11-24 14:48:23 +00:00
{
$branch = $this -> git_branch ;
[ 'repository' => $customRepository , 'port' => $customPort ] = $this -> customRepository ();
$baseDir = $custom_base_dir ? ? $this -> generateBaseDir ( $deployment_uuid );
$commands = collect ([]);
2024-04-19 22:10:50 +00:00
$git_clone_command = " git clone -b \" { $this -> git_branch } \" " ;
2023-11-24 14:48:23 +00:00
if ( $only_checkout ) {
2024-04-19 22:10:50 +00:00
$git_clone_command = " git clone --no-checkout -b \" { $this -> git_branch } \" " ;
2023-11-24 14:48:23 +00:00
}
if ( $pull_request_id !== 0 ) {
$pr_branch_name = " pr- { $pull_request_id } -coolify " ;
}
if ( $this -> deploymentType () === 'source' ) {
$source_html_url = data_get ( $this , 'source.html_url' );
$url = parse_url ( filter_var ( $source_html_url , FILTER_SANITIZE_URL ));
$source_html_url_host = $url [ 'host' ];
$source_html_url_scheme = $url [ 'scheme' ];
2024-10-31 14:23:19 +00:00
if ( $this -> source -> getMorphClass () === \App\Models\GithubApp :: class ) {
2023-11-24 14:48:23 +00:00
if ( $this -> source -> is_public ) {
$fullRepoUrl = " { $this -> source -> html_url } / { $customRepository } " ;
$git_clone_command = " { $git_clone_command } { $this -> source -> html_url } / { $customRepository } { $baseDir } " ;
2024-06-10 20:43:34 +00:00
if ( ! $only_checkout ) {
2024-02-19 12:22:09 +00:00
$git_clone_command = $this -> setGitImportSettings ( $deployment_uuid , $git_clone_command , public : true );
2023-11-24 14:48:23 +00:00
}
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $git_clone_command ));
} else {
$commands -> push ( $git_clone_command );
}
} else {
$github_access_token = generate_github_installation_token ( $this -> source );
if ( $exec_in_docker ) {
2024-04-08 12:07:07 +00:00
$git_clone_command = " { $git_clone_command } $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } .git { $baseDir } " ;
2023-11-24 14:48:23 +00:00
$fullRepoUrl = " $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } .git " ;
} else {
2024-04-08 12:07:07 +00:00
$git_clone_command = " { $git_clone_command } $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } { $baseDir } " ;
2023-11-24 14:48:23 +00:00
$fullRepoUrl = " $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $customRepository } " ;
}
2024-06-10 20:43:34 +00:00
if ( ! $only_checkout ) {
2024-05-29 16:01:10 +00:00
$git_clone_command = $this -> setGitImportSettings ( $deployment_uuid , $git_clone_command , public : false );
}
2024-04-08 12:07:07 +00:00
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $git_clone_command ));
} else {
$commands -> push ( $git_clone_command );
}
2023-11-24 14:48:23 +00:00
}
if ( $pull_request_id !== 0 ) {
$branch = " pull/ { $pull_request_id } /head: $pr_branch_name " ;
2024-03-28 08:53:09 +00:00
$git_checkout_command = $this -> buildGitCheckoutCommand ( $pr_branch_name );
2023-11-24 14:48:23 +00:00
if ( $exec_in_docker ) {
2024-03-28 08:53:09 +00:00
$commands -> push ( executeInDocker ( $deployment_uuid , " cd { $baseDir } && git fetch origin { $branch } && $git_checkout_command " ));
2023-11-24 14:48:23 +00:00
} else {
2024-03-28 08:53:09 +00:00
$commands -> push ( " cd { $baseDir } && git fetch origin { $branch } && $git_checkout_command " );
2023-11-24 14:48:23 +00:00
}
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
return [
'commands' => $commands -> implode ( ' && ' ),
'branch' => $branch ,
2024-06-10 20:43:34 +00:00
'fullRepoUrl' => $fullRepoUrl ,
2023-11-24 14:48:23 +00:00
];
}
}
if ( $this -> deploymentType () === 'deploy_key' ) {
$fullRepoUrl = $customRepository ;
$private_key = data_get ( $this , 'private_key.private_key' );
if ( is_null ( $private_key )) {
throw new RuntimeException ( 'Private key not found. Please add a private key to the application and try again.' );
}
$private_key = base64_encode ( $private_key );
$git_clone_command_base = " GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" { $git_clone_command } { $customRepository } { $baseDir } " ;
2023-12-30 13:20:02 +00:00
if ( $only_checkout ) {
$git_clone_command = $git_clone_command_base ;
} else {
2023-11-24 14:48:23 +00:00
$git_clone_command = $this -> setGitImportSettings ( $deployment_uuid , $git_clone_command_base );
}
if ( $exec_in_docker ) {
$commands = collect ([
2024-06-10 20:43:34 +00:00
executeInDocker ( $deployment_uuid , 'mkdir -p /root/.ssh' ),
2024-04-17 08:49:34 +00:00
executeInDocker ( $deployment_uuid , " echo ' { $private_key } ' | base64 -d | tee /root/.ssh/id_rsa > /dev/null " ),
2024-06-10 20:43:34 +00:00
executeInDocker ( $deployment_uuid , 'chmod 600 /root/.ssh/id_rsa' ),
2023-11-24 14:48:23 +00:00
]);
} else {
$commands = collect ([
2024-06-10 20:43:34 +00:00
'mkdir -p /root/.ssh' ,
2024-04-17 08:49:34 +00:00
" echo ' { $private_key } ' | base64 -d | tee /root/.ssh/id_rsa > /dev/null " ,
2024-06-10 20:43:34 +00:00
'chmod 600 /root/.ssh/id_rsa' ,
2023-11-24 14:48:23 +00:00
]);
}
if ( $pull_request_id !== 0 ) {
if ( $git_type === 'gitlab' ) {
$branch = " merge-requests/ { $pull_request_id } /head: $pr_branch_name " ;
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , " echo 'Checking out $branch ' " ));
} else {
$commands -> push ( " echo 'Checking out $branch ' " );
}
2024-06-10 20:43:34 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" git fetch origin $branch && " . $this -> buildGitCheckoutCommand ( $pr_branch_name );
2024-07-07 08:40:05 +00:00
} elseif ( $git_type === 'github' || $git_type === 'gitea' ) {
2023-11-24 14:48:23 +00:00
$branch = " pull/ { $pull_request_id } /head: $pr_branch_name " ;
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , " echo 'Checking out $branch ' " ));
} else {
$commands -> push ( " echo 'Checking out $branch ' " );
}
2024-06-10 20:43:34 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" git fetch origin $branch && " . $this -> buildGitCheckoutCommand ( $pr_branch_name );
} elseif ( $git_type === 'bitbucket' ) {
2024-01-29 15:33:06 +00:00
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , " echo 'Checking out $branch ' " ));
} else {
$commands -> push ( " echo 'Checking out $branch ' " );
}
2024-06-10 20:43:34 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" " . $this -> buildGitCheckoutCommand ( $commit );
2023-11-24 14:48:23 +00:00
}
}
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $git_clone_command ));
} else {
$commands -> push ( $git_clone_command );
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
return [
'commands' => $commands -> implode ( ' && ' ),
'branch' => $branch ,
2024-06-10 20:43:34 +00:00
'fullRepoUrl' => $fullRepoUrl ,
2023-11-24 14:48:23 +00:00
];
}
if ( $this -> deploymentType () === 'other' ) {
$fullRepoUrl = $customRepository ;
$git_clone_command = " { $git_clone_command } { $customRepository } { $baseDir } " ;
2024-02-19 12:22:09 +00:00
$git_clone_command = $this -> setGitImportSettings ( $deployment_uuid , $git_clone_command , public : true );
2024-01-29 09:43:18 +00:00
if ( $pull_request_id !== 0 ) {
if ( $git_type === 'gitlab' ) {
$branch = " merge-requests/ { $pull_request_id } /head: $pr_branch_name " ;
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , " echo 'Checking out $branch ' " ));
} else {
$commands -> push ( " echo 'Checking out $branch ' " );
}
2024-06-10 20:43:34 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" git fetch origin $branch && " . $this -> buildGitCheckoutCommand ( $pr_branch_name );
2024-07-07 08:40:05 +00:00
} elseif ( $git_type === 'github' || $git_type === 'gitea' ) {
2024-01-29 09:43:18 +00:00
$branch = " pull/ { $pull_request_id } /head: $pr_branch_name " ;
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , " echo 'Checking out $branch ' " ));
} else {
$commands -> push ( " echo 'Checking out $branch ' " );
}
2024-06-10 20:43:34 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" git fetch origin $branch && " . $this -> buildGitCheckoutCommand ( $pr_branch_name );
} elseif ( $git_type === 'bitbucket' ) {
2024-01-29 09:43:18 +00:00
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , " echo 'Checking out $branch ' " ));
} else {
$commands -> push ( " echo 'Checking out $branch ' " );
}
2024-06-10 20:43:34 +00:00
$git_clone_command = " { $git_clone_command } && cd { $baseDir } && GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $customPort } -o Port= { $customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" " . $this -> buildGitCheckoutCommand ( $commit );
2024-01-29 09:43:18 +00:00
}
}
2023-11-24 14:48:23 +00:00
if ( $exec_in_docker ) {
$commands -> push ( executeInDocker ( $deployment_uuid , $git_clone_command ));
} else {
$commands -> push ( $git_clone_command );
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
return [
'commands' => $commands -> implode ( ' && ' ),
'branch' => $branch ,
2024-06-10 20:43:34 +00:00
'fullRepoUrl' => $fullRepoUrl ,
2023-11-24 14:48:23 +00:00
];
}
}
2024-06-10 20:43:34 +00:00
2024-08-23 18:57:22 +00:00
public function oldRawParser ()
2024-03-04 09:13:40 +00:00
{
try {
$yaml = Yaml :: parse ( $this -> docker_compose_raw );
} catch ( \Exception $e ) {
throw new \Exception ( $e -> getMessage ());
}
$services = data_get ( $yaml , 'services' );
2024-09-23 17:51:31 +00:00
2024-03-04 09:13:40 +00:00
$commands = collect ([]);
$services = collect ( $services ) -> map ( function ( $service ) use ( $commands ) {
$serviceVolumes = collect ( data_get ( $service , 'volumes' , []));
if ( $serviceVolumes -> count () > 0 ) {
foreach ( $serviceVolumes as $volume ) {
$workdir = $this -> workdir ();
$type = null ;
$source = null ;
if ( is_string ( $volume )) {
2024-06-25 08:37:10 +00:00
$source = str ( $volume ) -> before ( ':' );
2024-03-04 09:13:40 +00:00
if ( $source -> startsWith ( './' ) || $source -> startsWith ( '/' ) || $source -> startsWith ( '~' )) {
2024-06-25 08:37:10 +00:00
$type = str ( 'bind' );
2024-03-04 09:13:40 +00:00
}
2024-06-10 20:43:34 +00:00
} elseif ( is_array ( $volume )) {
2024-03-04 09:13:40 +00:00
$type = data_get_str ( $volume , 'type' );
$source = data_get_str ( $volume , 'source' );
}
2024-03-18 11:40:58 +00:00
if ( $type ? -> value () === 'bind' ) {
2024-06-10 20:43:34 +00:00
if ( $source -> value () === '/var/run/docker.sock' ) {
2024-03-04 09:13:40 +00:00
continue ;
}
if ( $source -> value () === '/tmp' || $source -> value () === '/tmp/' ) {
continue ;
}
if ( $source -> startsWith ( '.' )) {
$source = $source -> after ( '.' );
2024-06-10 20:43:34 +00:00
$source = $workdir . $source ;
2024-03-04 09:13:40 +00:00
}
$commands -> push ( " mkdir -p $source > /dev/null 2>&1 || true " );
}
}
}
$labels = collect ( data_get ( $service , 'labels' , []));
2024-06-10 20:43:34 +00:00
if ( ! $labels -> contains ( 'coolify.managed' )) {
2024-03-04 09:13:40 +00:00
$labels -> push ( 'coolify.managed=true' );
}
2024-06-10 20:43:34 +00:00
if ( ! $labels -> contains ( 'coolify.applicationId' )) {
$labels -> push ( 'coolify.applicationId=' . $this -> id );
2024-03-04 09:13:40 +00:00
}
2024-06-10 20:43:34 +00:00
if ( ! $labels -> contains ( 'coolify.type' )) {
2024-03-04 09:13:40 +00:00
$labels -> push ( 'coolify.type=application' );
}
data_set ( $service , 'labels' , $labels -> toArray ());
2024-06-10 20:43:34 +00:00
2024-03-04 09:13:40 +00:00
return $service ;
});
data_set ( $yaml , 'services' , $services -> toArray ());
$this -> docker_compose_raw = Yaml :: dump ( $yaml , 10 , 2 );
instant_remote_process ( $commands , $this -> destination -> server , false );
}
2024-06-10 20:43:34 +00:00
2024-08-29 10:39:37 +00:00
public function parse ( int $pull_request_id = 0 , ? int $preview_id = null )
2023-11-24 14:48:23 +00:00
{
2024-09-30 12:15:22 +00:00
if (( int ) $this -> compose_parsing_version >= 3 ) {
2024-08-28 16:12:00 +00:00
return newParser ( $this , $pull_request_id , $preview_id );
2024-08-27 14:02:52 +00:00
} elseif ( $this -> docker_compose_raw ) {
2024-06-05 13:14:44 +00:00
return parseDockerComposeFile ( resource : $this , isNew : false , pull_request_id : $pull_request_id , preview_id : $preview_id );
2023-11-24 14:48:23 +00:00
} else {
return collect ([]);
}
}
2024-06-10 20:43:34 +00:00
public function loadComposeFile ( $isInit = false )
2023-11-27 10:54:55 +00:00
{
$initialDockerComposeLocation = $this -> docker_compose_location ;
2023-12-15 10:00:51 +00:00
if ( $isInit && $this -> docker_compose_raw ) {
return ;
}
2024-07-24 19:11:12 +00:00
$uuid = new Cuid2 ;
2023-12-15 10:00:51 +00:00
[ 'commands' => $cloneCommand ] = $this -> generateGitImportCommands ( deployment_uuid : $uuid , only_checkout : true , exec_in_docker : false , custom_base_dir : '.' );
$workdir = rtrim ( $this -> base_directory , '/' );
$composeFile = $this -> docker_compose_location ;
$fileList = collect ([ " . $workdir $composeFile " ]);
2024-06-16 04:22:44 +00:00
$gitRemoteStatus = $this -> getGitRemoteStatus ( deployment_uuid : $uuid );
if ( ! $gitRemoteStatus [ 'is_accessible' ]) {
throw new \RuntimeException ( " Failed to read Git source: \n \n { $gitRemoteStatus [ 'error' ] } " );
}
2023-12-15 10:00:51 +00:00
$commands = collect ([
2023-12-21 19:16:03 +00:00
" rm -rf /tmp/ { $uuid } " ,
2024-05-29 16:01:10 +00:00
" mkdir -p /tmp/ { $uuid } " ,
" cd /tmp/ { $uuid } " ,
2023-12-15 10:00:51 +00:00
$cloneCommand ,
2024-06-10 20:43:34 +00:00
'git sparse-checkout init --cone' ,
2023-12-15 10:00:51 +00:00
" git sparse-checkout set { $fileList -> implode ( ' ' ) } " ,
2024-06-10 20:43:34 +00:00
'git read-tree -mu HEAD' ,
2023-12-15 10:00:51 +00:00
" cat . $workdir $composeFile " ,
]);
2024-07-15 14:39:40 +00:00
try {
$composeFileContent = instant_remote_process ( $commands , $this -> destination -> server );
} catch ( \Exception $e ) {
if ( str ( $e -> getMessage ()) -> contains ( 'No such file' )) {
throw new \RuntimeException ( " Docker Compose file not found at: $workdir $composeFile <br><br>Check if you used the right extension (.yaml or .yml) in the compose file name. " );
}
if ( str ( $e -> getMessage ()) -> contains ( 'fatal: repository' ) && str ( $e -> getMessage ()) -> contains ( 'does not exist' )) {
if ( $this -> deploymentType () === 'deploy_key' ) {
throw new \RuntimeException ( 'Your deploy key does not have access to the repository. Please check your deploy key and try again.' );
}
throw new \RuntimeException ( 'Repository does not exist. Please check your repository URL and try again.' );
}
throw new \RuntimeException ( $e -> getMessage ());
} finally {
2023-12-15 10:00:51 +00:00
$this -> docker_compose_location = $initialDockerComposeLocation ;
$this -> save ();
2024-05-29 16:01:10 +00:00
$commands = collect ([
" rm -rf /tmp/ { $uuid } " ,
]);
instant_remote_process ( $commands , $this -> destination -> server , false );
2024-07-15 14:39:40 +00:00
}
if ( $composeFileContent ) {
2023-12-15 10:00:51 +00:00
$this -> docker_compose_raw = $composeFileContent ;
$this -> save ();
2024-08-29 10:39:37 +00:00
$parsedServices = $this -> parse ();
2024-07-15 14:39:40 +00:00
if ( $this -> docker_compose_domains ) {
$json = collect ( json_decode ( $this -> docker_compose_domains ));
$names = collect ( data_get ( $parsedServices , 'services' )) -> keys () -> toArray ();
$jsonNames = $json -> keys () -> toArray ();
$diff = array_diff ( $jsonNames , $names );
$json = $json -> filter ( function ( $value , $key ) use ( $diff ) {
2024-09-23 17:51:31 +00:00
return ! in_array ( $key , $diff );
2024-07-15 14:39:40 +00:00
});
if ( $json ) {
$this -> docker_compose_domains = json_encode ( $json );
} else {
$this -> docker_compose_domains = null ;
}
$this -> save ();
2023-12-15 09:37:45 +00:00
}
2024-06-10 20:43:34 +00:00
2024-07-15 14:39:40 +00:00
return [
'parsedServices' => $parsedServices ,
'initialDockerComposeLocation' => $this -> docker_compose_location ,
];
} else {
throw new \RuntimeException ( " Docker Compose file not found at: $workdir $composeFile <br><br>Check if you used the right extension (.yaml or .yml) in the compose file name. " );
2023-11-27 10:54:55 +00:00
}
}
2024-06-10 20:43:34 +00:00
public function parseContainerLabels ( ? ApplicationPreview $preview = null )
2023-12-13 08:23:27 +00:00
{
$customLabels = data_get ( $this , 'custom_labels' );
2024-06-10 20:43:34 +00:00
if ( ! $customLabels ) {
2023-12-13 08:23:27 +00:00
return ;
}
if ( base64_encode ( base64_decode ( $customLabels , true )) !== $customLabels ) {
$this -> custom_labels = str ( $customLabels ) -> replace ( ',' , " \n " );
$this -> custom_labels = base64_encode ( $customLabels );
}
$customLabels = base64_decode ( $this -> custom_labels );
if ( mb_detect_encoding ( $customLabels , 'ASCII' , true ) === false ) {
2024-06-11 09:36:42 +00:00
$customLabels = str ( implode ( '|coolify|' , generateLabelsApplication ( $this , $preview ))) -> replace ( '|coolify|' , " \n " );
2023-12-13 08:23:27 +00:00
}
$this -> custom_labels = base64_encode ( $customLabels );
$this -> save ();
2024-06-10 20:43:34 +00:00
2023-12-13 08:23:27 +00:00
return $customLabels ;
}
2024-06-10 20:43:34 +00:00
2024-01-30 08:22:34 +00:00
public function fqdns () : Attribute
{
return Attribute :: make (
get : fn () => is_null ( $this -> fqdn )
? []
: explode ( ',' , $this -> fqdn ),
);
}
2024-06-10 20:43:34 +00:00
2024-04-08 12:07:07 +00:00
protected function buildGitCheckoutCommand ( $target ) : string
{
2024-03-28 08:53:09 +00:00
$command = " git checkout $target " ;
if ( $this -> settings -> is_git_submodules_enabled ) {
2024-06-10 20:43:34 +00:00
$command .= ' && git submodule update --init --recursive' ;
2024-03-28 08:53:09 +00:00
}
return $command ;
2024-04-08 12:07:07 +00:00
}
2024-06-10 20:43:34 +00:00
2024-03-28 14:05:12 +00:00
public function watchPaths () : Attribute
{
return Attribute :: make (
set : function ( $value ) {
if ( $value ) {
return trim ( $value );
}
}
);
}
2024-06-10 20:43:34 +00:00
2024-04-03 12:05:35 +00:00
public function isWatchPathsTriggered ( Collection $modified_files ) : bool
2024-03-28 14:05:12 +00:00
{
2024-04-03 12:05:35 +00:00
if ( is_null ( $this -> watch_paths )) {
return false ;
}
2024-03-28 14:05:12 +00:00
$watch_paths = collect ( explode ( " \n " , $this -> watch_paths ));
$matches = $modified_files -> filter ( function ( $file ) use ( $watch_paths ) {
return $watch_paths -> contains ( function ( $glob ) use ( $file ) {
return fnmatch ( $glob , $file );
});
});
2024-06-10 20:43:34 +00:00
2024-03-28 14:05:12 +00:00
return $matches -> count () > 0 ;
2024-03-28 08:53:09 +00:00
}
2024-04-15 17:47:17 +00:00
public function getFilesFromServer ( bool $isInit = false )
{
getFilesystemVolumesFromServer ( $this , $isInit );
}
2024-04-29 11:33:28 +00:00
2024-05-17 08:12:05 +00:00
public function parseHealthcheckFromDockerfile ( $dockerfile , bool $isInit = false )
{
2024-04-29 11:33:28 +00:00
if ( str ( $dockerfile ) -> contains ( 'HEALTHCHECK' ) && ( $this -> isHealthcheckDisabled () || $isInit )) {
$healthcheckCommand = null ;
$lines = $dockerfile -> toArray ();
foreach ( $lines as $line ) {
$trimmedLine = trim ( $line );
if ( str_starts_with ( $trimmedLine , 'HEALTHCHECK' )) {
$healthcheckCommand .= trim ( $trimmedLine , '\\ ' );
2024-06-10 20:43:34 +00:00
2024-04-29 11:33:28 +00:00
continue ;
}
if ( isset ( $healthcheckCommand ) && str_contains ( $trimmedLine , '\\' )) {
2024-06-10 20:43:34 +00:00
$healthcheckCommand .= ' ' . trim ( $trimmedLine , '\\ ' );
2024-04-29 11:33:28 +00:00
}
2024-06-10 20:43:34 +00:00
if ( isset ( $healthcheckCommand ) && ! str_contains ( $trimmedLine , '\\' ) && ! empty ( $healthcheckCommand )) {
$healthcheckCommand .= ' ' . $trimmedLine ;
2024-04-29 11:33:28 +00:00
break ;
}
}
if ( str ( $healthcheckCommand ) -> isNotEmpty ()) {
$interval = str ( $healthcheckCommand ) -> match ( '/--interval=(\d+)/' );
$timeout = str ( $healthcheckCommand ) -> match ( '/--timeout=(\d+)/' );
$start_period = str ( $healthcheckCommand ) -> match ( '/--start-period=(\d+)/' );
$start_interval = str ( $healthcheckCommand ) -> match ( '/--start-interval=(\d+)/' );
$retries = str ( $healthcheckCommand ) -> match ( '/--retries=(\d+)/' );
if ( $interval -> isNotEmpty ()) {
$this -> health_check_interval = $interval -> toInteger ();
}
if ( $timeout -> isNotEmpty ()) {
$this -> health_check_timeout = $timeout -> toInteger ();
}
if ( $start_period -> isNotEmpty ()) {
$this -> health_check_start_period = $start_period -> toInteger ();
}
// if ($start_interval) {
// $this->health_check_start_interval = $start_interval->value();
// }
if ( $retries -> isNotEmpty ()) {
$this -> health_check_retries = $retries -> toInteger ();
}
if ( $interval || $timeout || $start_period || $start_interval || $retries ) {
$this -> custom_healthcheck_found = true ;
$this -> save ();
}
}
}
}
2024-06-10 20:43:34 +00:00
public function generate_preview_fqdn ( int $pull_request_id )
2024-06-05 13:14:44 +00:00
{
2024-05-30 10:28:29 +00:00
$preview = ApplicationPreview :: findPreviewByApplicationAndPullId ( $this -> id , $pull_request_id );
if ( is_null ( data_get ( $preview , 'fqdn' )) && $this -> fqdn ) {
if ( str ( $this -> fqdn ) -> contains ( ',' )) {
$url = Url :: fromString ( str ( $this -> fqdn ) -> explode ( ',' )[ 0 ]);
$preview_fqdn = getFqdnWithoutPort ( str ( $this -> fqdn ) -> explode ( ',' )[ 0 ]);
} else {
$url = Url :: fromString ( $this -> fqdn );
if ( data_get ( $preview , 'fqdn' )) {
$preview_fqdn = getFqdnWithoutPort ( data_get ( $preview , 'fqdn' ));
}
}
$template = $this -> preview_url_template ;
$host = $url -> getHost ();
$schema = $url -> getScheme ();
2024-07-25 11:31:59 +00:00
$random = new Cuid2 ;
2024-05-30 10:28:29 +00:00
$preview_fqdn = str_replace ( '{{random}}' , $random , $template );
$preview_fqdn = str_replace ( '{{domain}}' , $host , $preview_fqdn );
$preview_fqdn = str_replace ( '{{pr_id}}' , $pull_request_id , $preview_fqdn );
$preview_fqdn = " $schema :// $preview_fqdn " ;
$preview -> fqdn = $preview_fqdn ;
$preview -> save ();
}
2024-06-10 20:43:34 +00:00
2024-05-30 10:28:29 +00:00
return $preview ;
}
2024-06-18 18:59:39 +00:00
public static function getDomainsByUuid ( string $uuid ) : array
{
$application = self :: where ( 'uuid' , $uuid ) -> first ();
if ( $application ) {
return $application -> fqdns ;
}
return [];
2024-06-20 12:07:24 +00:00
}
2024-10-15 13:33:05 +00:00
public function getCpuMetrics ( int $mins = 5 )
2024-06-20 11:17:06 +00:00
{
$server = $this -> destination -> server ;
2024-06-20 11:30:17 +00:00
$container_name = $this -> uuid ;
2024-06-20 11:17:06 +00:00
if ( $server -> isMetricsEnabled ()) {
$from = now () -> subMinutes ( $mins ) -> toIso8601ZuluString ();
2024-10-22 09:39:38 +00:00
$metrics = instant_remote_process ([ " docker exec coolify-sentinel sh -c 'curl -H \" Authorization: Bearer { $server -> settings -> sentinel_token } \" http://localhost:8888/api/container/ { $container_name } /cpu/history?from= $from ' " ], $server , false );
2024-06-20 11:17:06 +00:00
if ( str ( $metrics ) -> contains ( 'error' )) {
$error = json_decode ( $metrics , true );
$error = data_get ( $error , 'error' , 'Something is not okay, are you okay?' );
2024-10-31 14:23:19 +00:00
if ( $error === 'Unauthorized' ) {
2024-06-20 11:17:06 +00:00
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.' ;
}
throw new \Exception ( $error );
}
2024-10-15 13:33:05 +00:00
$metrics = json_decode ( $metrics , true );
$parsedCollection = collect ( $metrics ) -> map ( function ( $metric ) {
2024-10-15 15:03:50 +00:00
return [( int ) $metric [ 'time' ], ( float ) $metric [ 'percent' ]];
2024-10-15 13:33:05 +00:00
});
2024-06-20 11:17:06 +00:00
2024-10-15 13:33:05 +00:00
return $parsedCollection -> toArray ();
}
}
2024-10-15 15:03:50 +00:00
2024-10-15 13:33:05 +00:00
public function getMemoryMetrics ( int $mins = 5 )
{
$server = $this -> destination -> server ;
$container_name = $this -> uuid ;
if ( $server -> isMetricsEnabled ()) {
$from = now () -> subMinutes ( $mins ) -> toIso8601ZuluString ();
2024-10-22 09:39:38 +00:00
$metrics = instant_remote_process ([ " docker exec coolify-sentinel sh -c 'curl -H \" Authorization: Bearer { $server -> settings -> sentinel_token } \" http://localhost:8888/api/container/ { $container_name } /memory/history?from= $from ' " ], $server , false );
2024-10-15 13:33:05 +00:00
if ( str ( $metrics ) -> contains ( 'error' )) {
$error = json_decode ( $metrics , true );
$error = data_get ( $error , 'error' , 'Something is not okay, are you okay?' );
2024-10-31 14:23:19 +00:00
if ( $error === 'Unauthorized' ) {
2024-10-15 13:33:05 +00:00
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.' ;
}
throw new \Exception ( $error );
}
$metrics = json_decode ( $metrics , true );
$parsedCollection = collect ( $metrics ) -> map ( function ( $metric ) {
2024-10-15 15:03:50 +00:00
return [( int ) $metric [ 'time' ], ( float ) $metric [ 'used' ]];
2024-06-20 11:17:06 +00:00
});
return $parsedCollection -> toArray ();
}
2024-06-18 18:59:39 +00:00
}
2024-10-08 13:11:19 +00:00
public function generateConfig ( $is_json = false )
{
$config = collect ([]);
if ( $this -> build_pack = 'nixpacks' ) {
$config = collect ([
'build_pack' => 'nixpacks' ,
'docker_registry_image_name' => $this -> docker_registry_image_name ,
'docker_registry_image_tag' => $this -> docker_registry_image_tag ,
'install_command' => $this -> install_command ,
'build_command' => $this -> build_command ,
'start_command' => $this -> start_command ,
'base_directory' => $this -> base_directory ,
'publish_directory' => $this -> publish_directory ,
'custom_docker_run_options' => $this -> custom_docker_run_options ,
'ports_exposes' => $this -> ports_exposes ,
'ports_mappings' => $this -> ports_mapping ,
'settings' => collect ([
'is_static' => $this -> settings -> is_static ,
]),
]);
}
$config = $config -> filter ( function ( $value ) {
return str ( $value ) -> isNotEmpty ();
});
if ( $is_json ) {
return json_encode ( $config , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
}
return $config ;
}
2024-10-28 12:51:23 +00:00
2024-10-10 16:32:24 +00:00
public function setConfig ( $config )
{
2024-10-08 13:11:19 +00:00
$validator = Validator :: make ([ 'config' => $config ], [
'config' => 'required|json' ,
]);
if ( $validator -> fails ()) {
throw new \Exception ( 'Invalid JSON format' );
}
$config = json_decode ( $config , true );
$deepValidator = Validator :: make ([ 'config' => $config ], [
'config.build_pack' => 'required|string' ,
'config.base_directory' => 'required|string' ,
'config.publish_directory' => 'required|string' ,
'config.ports_exposes' => 'required|string' ,
'config.settings.is_static' => 'required|boolean' ,
]);
if ( $deepValidator -> fails ()) {
throw new \Exception ( 'Invalid data' );
}
$config = $deepValidator -> validated ()[ 'config' ];
try {
$settings = data_get ( $config , 'settings' , []);
data_forget ( $config , 'settings' );
$this -> update ( $config );
$this -> settings () -> update ( $settings );
} catch ( \Exception $e ) {
throw new \Exception ( 'Failed to update application settings' );
}
}
2023-08-07 20:14:21 +00:00
}