2023-03-31 11:32:07 +00:00
< ? php
namespace App\Jobs ;
2023-06-30 20:24:39 +00:00
use App\Enums\ApplicationDeploymentStatus ;
use App\Enums\ProxyTypes ;
2023-03-31 11:32:07 +00:00
use App\Models\Application ;
2023-05-24 12:26:50 +00:00
use App\Models\ApplicationDeploymentQueue ;
2023-05-30 13:52:17 +00:00
use App\Models\ApplicationPreview ;
2023-06-30 20:24:39 +00:00
use App\Models\GithubApp ;
use App\Models\GitlabApp ;
use App\Models\Server ;
use App\Models\StandaloneDocker ;
use App\Models\SwarmDocker ;
2023-07-28 08:55:26 +00:00
use App\Notifications\Application\DeploymentFailed ;
2023-08-08 09:51:36 +00:00
use App\Notifications\Application\DeploymentSuccess ;
2023-06-30 20:24:39 +00:00
use App\Traits\ExecuteRemoteCommand ;
2023-08-09 12:44:36 +00:00
use Exception ;
2023-03-31 11:32:07 +00:00
use Illuminate\Bus\Queueable ;
2023-09-14 08:12:44 +00:00
use Illuminate\Contracts\Queue\ShouldBeEncrypted ;
2023-03-31 11:32:07 +00:00
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Bus\Dispatchable ;
use Illuminate\Queue\InteractsWithQueue ;
use Illuminate\Queue\SerializesModels ;
2023-04-04 12:11:53 +00:00
use Illuminate\Support\Collection ;
2023-06-30 20:24:39 +00:00
use Illuminate\Support\Str ;
2023-08-08 09:51:36 +00:00
use Spatie\Url\Url ;
2023-06-30 20:24:39 +00:00
use Symfony\Component\Yaml\Yaml ;
2023-06-28 16:20:01 +00:00
use Throwable ;
2023-05-30 13:52:17 +00:00
use Visus\Cuid2\Cuid2 ;
2023-03-31 11:32:07 +00:00
2023-09-14 08:12:44 +00:00
class ApplicationDeploymentJob implements ShouldQueue , ShouldBeEncrypted
2023-03-31 11:32:07 +00:00
{
2023-06-30 20:24:39 +00:00
use Dispatchable , InteractsWithQueue , Queueable , SerializesModels , ExecuteRemoteCommand ;
public static int $batch_counter = 0 ;
private int $application_deployment_queue_id ;
2023-03-31 11:32:07 +00:00
2023-09-22 13:29:19 +00:00
private bool $newVersionIsHealthy = false ;
2023-05-24 12:26:50 +00:00
private ApplicationDeploymentQueue $application_deployment_queue ;
2023-06-30 20:24:39 +00:00
private Application $application ;
private string $deployment_uuid ;
private int $pull_request_id ;
private string $commit ;
private bool $force_rebuild ;
private GithubApp | GitlabApp $source ;
private StandaloneDocker | SwarmDocker $destination ;
private Server $server ;
private ApplicationPreview | null $preview = null ;
2023-05-23 07:53:24 +00:00
2023-06-30 20:24:39 +00:00
private string $container_name ;
2023-08-21 16:00:12 +00:00
private string | null $currently_running_container_name = null ;
2023-05-24 12:26:50 +00:00
private string $workdir ;
2023-08-09 12:44:36 +00:00
private string $configuration_dir ;
2023-07-07 12:56:20 +00:00
private string $build_workdir ;
2023-05-30 13:52:17 +00:00
private string $build_image_name ;
private string $production_image_name ;
2023-06-30 20:24:39 +00:00
private bool $is_debug_enabled ;
private $build_args ;
private $env_args ;
private $docker_compose ;
2023-08-09 12:44:36 +00:00
private $docker_compose_base64 ;
2023-05-24 12:26:50 +00:00
2023-06-30 20:24:39 +00:00
private $log_model ;
private Collection $saved_outputs ;
2023-08-08 09:51:36 +00:00
2023-09-04 14:03:11 +00:00
public $tries = 1 ;
2023-06-30 20:24:39 +00:00
public function __construct ( int $application_deployment_queue_id )
{
ray () -> clearScreen ();
$this -> application_deployment_queue = ApplicationDeploymentQueue :: find ( $application_deployment_queue_id );
$this -> log_model = $this -> application_deployment_queue ;
$this -> application = Application :: find ( $this -> application_deployment_queue -> application_id );
$this -> application_deployment_queue_id = $application_deployment_queue_id ;
$this -> deployment_uuid = $this -> application_deployment_queue -> deployment_uuid ;
$this -> pull_request_id = $this -> application_deployment_queue -> pull_request_id ;
$this -> commit = $this -> application_deployment_queue -> commit ;
$this -> force_rebuild = $this -> application_deployment_queue -> force_rebuild ;
$this -> source = $this -> application -> source -> getMorphClass () :: where ( 'id' , $this -> application -> source -> id ) -> first ();
$this -> destination = $this -> application -> destination -> getMorphClass () :: where ( 'id' , $this -> application -> destination -> id ) -> first ();
$this -> server = $this -> destination -> server ;
$this -> workdir = " /artifacts/ { $this -> deployment_uuid } " ;
2023-08-09 12:44:36 +00:00
$this -> configuration_dir = application_configuration_dir () . " / { $this -> application -> uuid } " ;
2023-07-07 12:56:20 +00:00
$this -> build_workdir = " { $this -> workdir } " . rtrim ( $this -> application -> base_directory , '/' );
2023-06-30 20:24:39 +00:00
$this -> is_debug_enabled = $this -> application -> settings -> is_debug_enabled ;
2023-05-30 13:52:17 +00:00
2023-09-19 13:51:13 +00:00
$this -> container_name = generateApplicationContainerName ( $this -> application );
2023-09-18 11:01:01 +00:00
savePrivateKeyToFs ( $this -> server );
2023-06-30 20:24:39 +00:00
$this -> saved_outputs = collect ();
2023-05-30 13:52:17 +00:00
2023-06-30 20:24:39 +00:00
// Set preview fqdn
2023-06-05 10:07:55 +00:00
if ( $this -> pull_request_id !== 0 ) {
2023-05-30 13:52:17 +00:00
$this -> preview = ApplicationPreview :: findPreviewByApplicationAndPullId ( $this -> application -> id , $this -> pull_request_id );
2023-06-30 20:24:39 +00:00
if ( $this -> application -> fqdn ) {
2023-09-22 12:47:25 +00:00
$preview_fqdn = getOnlyFqdn ( data_get ( $this -> preview , 'fqdn' ));
2023-06-30 20:24:39 +00:00
$template = $this -> application -> preview_url_template ;
$url = Url :: fromString ( $this -> application -> fqdn );
$host = $url -> getHost ();
$schema = $url -> getScheme ();
$random = new Cuid2 ( 7 );
$preview_fqdn = str_replace ( '{{random}}' , $random , $template );
$preview_fqdn = str_replace ( '{{domain}}' , $host , $preview_fqdn );
$preview_fqdn = str_replace ( '{{pr_id}}' , $this -> pull_request_id , $preview_fqdn );
$preview_fqdn = " $schema :// $preview_fqdn " ;
$this -> preview -> fqdn = $preview_fqdn ;
$this -> preview -> save ();
}
2023-05-30 13:52:17 +00:00
}
2023-03-31 12:30:08 +00:00
}
2023-06-30 20:24:39 +00:00
2023-03-31 11:32:07 +00:00
public function handle () : void
{
2023-07-14 11:01:55 +00:00
// ray()->measure();
2023-08-21 16:00:12 +00:00
$containers = getCurrentApplicationContainerStatus ( $this -> server , $this -> application -> id );
if ( $containers -> count () > 0 ) {
$this -> currently_running_container_name = data_get ( $containers [ 0 ], 'Names' );
}
2023-09-14 13:52:04 +00:00
if ( $this -> pull_request_id !== 0 && $this -> pull_request_id !== null ) {
$this -> currently_running_container_name = $this -> container_name ;
}
2023-06-30 20:24:39 +00:00
$this -> application_deployment_queue -> update ([
'status' => ApplicationDeploymentStatus :: IN_PROGRESS -> value ,
]);
2023-04-14 19:09:38 +00:00
try {
2023-08-11 20:41:47 +00:00
if ( $this -> application -> dockerfile ) {
$this -> deploy_simple_dockerfile ();
2023-05-30 13:52:17 +00:00
} else {
2023-08-11 20:41:47 +00:00
if ( $this -> pull_request_id !== 0 ) {
$this -> deploy_pull_request ();
} else {
$this -> deploy ();
}
2023-05-30 13:52:17 +00:00
}
2023-09-14 10:45:50 +00:00
if ( $this -> server -> isProxyShouldRun ()) {
dispatch ( new ContainerStatusJob ( $this -> server ));
}
2023-06-30 20:24:39 +00:00
$this -> next ( ApplicationDeploymentStatus :: FINISHED -> value );
2023-08-09 12:44:36 +00:00
} catch ( Exception $e ) {
2023-06-28 16:20:01 +00:00
ray ( $e );
2023-07-07 12:56:20 +00:00
$this -> fail ( $e );
2023-08-24 19:09:58 +00:00
throw $e ;
2023-05-30 13:52:17 +00:00
} finally {
2023-08-09 12:44:36 +00:00
if ( isset ( $this -> docker_compose_base64 )) {
$readme = generate_readme_file ( $this -> application -> name , $this -> application_deployment_queue -> updated_at );
$this -> execute_remote_command (
[
" mkdir -p $this->configuration_dir "
],
[
" echo ' { $this -> docker_compose_base64 } ' | base64 -d > $this->configuration_dir /docker-compose.yml " ,
],
[
" echo ' { $readme } ' > $this->configuration_dir /README.md " ,
]
);
2023-07-07 12:56:20 +00:00
}
2023-06-30 20:24:39 +00:00
$this -> execute_remote_command (
[
" docker rm -f { $this -> deployment_uuid } >/dev/null 2>&1 " ,
" hidden " => true ,
]
);
2023-05-30 13:52:17 +00:00
}
}
2023-09-20 13:42:41 +00:00
private function deploy_docker_compose ()
{
2023-09-19 13:51:13 +00:00
$dockercompose_base64 = base64_encode ( $this -> application -> dockercompose );
$this -> execute_remote_command (
[
" echo 'Starting deployment of { $this -> application -> name } .' "
],
);
$this -> prepare_builder_image ();
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' $dockercompose_base64 ' | base64 -d > $this->workdir /docker-compose.yaml " )
],
);
$this -> build_image_name = Str :: lower ( " { $this -> application -> git_repository } :build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } :latest " );
$this -> save_environment_variables ();
2023-09-20 13:42:41 +00:00
$containers = getCurrentApplicationContainerStatus ( $this -> application -> destination -> server , $this -> application -> id );
if ( $containers -> count () > 0 ) {
foreach ( $containers as $container ) {
$containerName = data_get ( $container , 'Names' );
if ( $containerName ) {
instant_remote_process (
[ " docker rm -f { $containerName } " ],
$this -> application -> destination -> server
);
}
}
}
$this -> execute_remote_command (
[ " echo -n 'Starting services (could take a while)...' " ],
[ executeInDocker ( $this -> deployment_uuid , " docker compose --project-directory { $this -> workdir } up -d " ), " hidden " => true ],
);
2023-09-19 13:51:13 +00:00
}
2023-09-20 13:42:41 +00:00
private function save_environment_variables ()
{
2023-09-19 13:51:13 +00:00
$envs = collect ([]);
foreach ( $this -> application -> environment_variables as $env ) {
$envs -> push ( $env -> key . '=' . $env -> value );
}
$envs_base64 = base64_encode ( $envs -> implode ( " \n " ));
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' $envs_base64 ' | base64 -d > $this->workdir /.env " )
],
);
}
2023-08-11 20:41:47 +00:00
private function deploy_simple_dockerfile ()
{
$dockerfile_base64 = base64_encode ( $this -> application -> dockerfile );
$this -> execute_remote_command (
[
" echo 'Starting deployment of { $this -> application -> name } .' "
],
);
$this -> prepare_builder_image ();
$this -> execute_remote_command (
[
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' $dockerfile_base64 ' | base64 -d > $this->workdir /Dockerfile " )
2023-08-11 20:41:47 +00:00
],
);
2023-09-05 09:05:54 +00:00
$this -> build_image_name = Str :: lower ( " { $this -> application -> git_repository } :build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } :latest " );
2023-08-11 20:41:47 +00:00
ray ( 'Build Image Name: ' . $this -> build_image_name . ' & Production Image Name: ' . $this -> production_image_name ) -> green ();
$this -> generate_compose_file ();
$this -> generate_build_env_variables ();
$this -> add_build_env_variables_to_dockerfile ();
$this -> build_image ();
2023-08-21 16:00:12 +00:00
$this -> rolling_update ();
2023-08-11 20:41:47 +00:00
}
2023-08-21 16:00:12 +00:00
2023-08-11 20:41:47 +00:00
private function deploy ()
{
$this -> execute_remote_command (
[
" echo 'Starting deployment of { $this -> application -> git_repository } : { $this -> application -> git_branch } .' "
],
);
$this -> prepare_builder_image ();
$this -> clone_repository ();
$tag = Str :: of ( " { $this -> commit } - { $this -> application -> id } - { $this -> pull_request_id } " );
if ( strlen ( $tag ) > 128 ) {
$tag = $tag -> substr ( 0 , 128 );
}
2023-09-05 09:05:54 +00:00
$this -> build_image_name = Str :: lower ( " { $this -> application -> git_repository } : { $tag } -build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } : { $tag } " );
2023-08-11 20:41:47 +00:00
ray ( 'Build Image Name: ' . $this -> build_image_name . ' & Production Image Name: ' . $this -> production_image_name ) -> green ();
2023-04-26 12:29:33 +00:00
2023-08-11 20:41:47 +00:00
if ( ! $this -> force_rebuild ) {
$this -> execute_remote_command ([
" docker images -q { $this -> production_image_name } 2>/dev/null " , " hidden " => true , " save " => " local_image_found "
]);
if ( Str :: of ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty ()) {
$this -> execute_remote_command ([
" echo 'Docker Image found locally with the same Git Commit SHA { $this -> application -> uuid } : { $this -> commit } . Build step skipped...' "
]);
$this -> generate_compose_file ();
2023-08-21 16:00:12 +00:00
$this -> rolling_update ();
2023-08-11 20:41:47 +00:00
return ;
}
}
$this -> cleanup_git ();
if ( $this -> application -> build_pack === 'nixpacks' ) {
$this -> generate_nixpacks_confs ();
}
$this -> generate_compose_file ();
$this -> generate_build_env_variables ();
$this -> add_build_env_variables_to_dockerfile ();
$this -> build_image ();
2023-08-21 16:00:12 +00:00
$this -> rolling_update ();
}
private function rolling_update ()
{
2023-08-11 20:41:47 +00:00
$this -> start_by_compose_file ();
2023-08-21 16:00:12 +00:00
$this -> health_check ();
$this -> stop_running_container ();
}
private function health_check ()
{
2023-09-04 14:03:11 +00:00
ray ( 'New container name: ' , $this -> container_name );
2023-08-21 16:00:12 +00:00
if ( $this -> container_name ) {
$counter = 0 ;
$this -> execute_remote_command (
[
2023-09-24 08:32:41 +00:00
" echo 'Waiting for healthcheck to pass on the new version of your application.' "
2023-08-21 16:00:12 +00:00
],
);
while ( $counter < $this -> application -> health_check_retries ) {
$this -> execute_remote_command (
[
" echo 'Attempt { $counter } of { $this -> application -> health_check_retries } ' "
],
[
" docker inspect --format=' { { json .State.Health.Status}}' { $this -> container_name } " ,
" hidden " => true ,
" save " => " health_check "
],
);
$this -> execute_remote_command (
[
2023-09-24 08:32:41 +00:00
" echo 'New version healthcheck status: { $this -> saved_outputs -> get ( 'health_check' ) } ' "
2023-08-21 16:00:12 +00:00
],
);
if ( Str :: of ( $this -> saved_outputs -> get ( 'health_check' )) -> contains ( 'healthy' )) {
2023-09-22 13:29:19 +00:00
$this -> newVersionIsHealthy = true ;
2023-08-21 16:00:12 +00:00
$this -> execute_remote_command (
2023-09-22 13:29:19 +00:00
[
" echo 'New version of your application is healthy.' "
],
2023-08-21 16:00:12 +00:00
[
" echo 'Rolling update completed.' "
],
);
2023-09-14 10:45:50 +00:00
$this -> application -> update ([ 'status' => 'running' ]);
2023-08-21 16:00:12 +00:00
break ;
}
$counter ++ ;
sleep ( $this -> application -> health_check_interval );
}
}
2023-08-11 20:41:47 +00:00
}
2023-06-30 20:24:39 +00:00
private function deploy_pull_request ()
2023-05-23 13:48:05 +00:00
{
2023-09-05 09:05:54 +00:00
$this -> build_image_name = Str :: lower ( " { $this -> application -> uuid } :pr- { $this -> pull_request_id } -build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } :pr- { $this -> pull_request_id } " );
2023-06-30 20:24:39 +00:00
ray ( 'Build Image Name: ' . $this -> build_image_name . ' & Production Image Name: ' . $this -> production_image_name ) -> green ();
$this -> execute_remote_command ([
" echo 'Starting pull request (# { $this -> pull_request_id } ) deployment of { $this -> application -> git_repository } : { $this -> application -> git_branch } .' " ,
]);
$this -> prepare_builder_image ();
$this -> clone_repository ();
$this -> cleanup_git ();
2023-08-11 20:41:47 +00:00
if ( $this -> application -> build_pack === 'nixpacks' ) {
$this -> generate_nixpacks_confs ();
}
2023-06-30 20:24:39 +00:00
$this -> generate_compose_file ();
// Needs separate preview variables
// $this->generate_build_env_variables();
// $this->add_build_env_variables_to_dockerfile();
$this -> build_image ();
2023-09-14 13:52:04 +00:00
$this -> stop_running_container ();
$this -> execute_remote_command (
[ " echo -n 'Starting preview deployment.' " ],
2023-09-15 13:34:25 +00:00
[ executeInDocker ( $this -> deployment_uuid , " docker compose --project-directory { $this -> workdir } up -d >/dev/null " ), " hidden " => true ],
2023-09-14 13:52:04 +00:00
);
2023-05-24 12:26:50 +00:00
}
2023-08-08 09:51:36 +00:00
private function prepare_builder_image ()
2023-03-31 11:32:07 +00:00
{
2023-09-04 14:03:11 +00:00
$pull = " --pull=always " ;
if ( isDev ()) {
$pull = " --pull=never " ;
}
2023-09-05 06:49:33 +00:00
$helperImage = config ( 'coolify.helper_image' );
$runCommand = " docker run { $pull } -d --network { $this -> destination -> network } -v /:/host --name { $this -> deployment_uuid } --rm -v /var/run/docker.sock:/var/run/docker.sock { $helperImage } " ;
2023-09-04 14:03:11 +00:00
2023-06-30 20:24:39 +00:00
$this -> execute_remote_command (
2023-08-08 09:51:36 +00:00
[
2023-09-05 06:49:33 +00:00
" echo -n 'Pulling helper image from $helperImage .' " ,
2023-08-08 09:51:36 +00:00
],
[
2023-09-04 14:03:11 +00:00
$runCommand ,
2023-08-08 09:51:36 +00:00
" hidden " => true ,
],
[
2023-09-15 13:34:25 +00:00
" command " => executeInDocker ( $this -> deployment_uuid , " mkdir -p { $this -> workdir } " )
2023-08-08 09:51:36 +00:00
],
2023-06-30 20:24:39 +00:00
);
2023-03-31 11:32:07 +00:00
}
2023-08-08 09:51:36 +00:00
2023-09-15 13:34:25 +00:00
2023-08-08 09:51:36 +00:00
private function clone_repository ()
2023-05-05 07:02:50 +00:00
{
2023-06-30 20:24:39 +00:00
$this -> execute_remote_command (
2023-08-08 09:51:36 +00:00
[
" echo -n 'Importing { $this -> application -> git_repository } : { $this -> application -> git_branch } to { $this -> workdir } . ' "
],
[
$this -> importing_git_repository ()
],
[
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " cd { $this -> workdir } && git rev-parse HEAD " ),
2023-08-08 09:51:36 +00:00
" hidden " => true ,
" save " => " git_commit_sha "
],
2023-06-30 20:24:39 +00:00
);
2023-08-08 09:51:36 +00:00
$this -> commit = $this -> saved_outputs -> get ( 'git_commit_sha' );
2023-05-05 07:02:50 +00:00
}
2023-06-30 20:24:39 +00:00
2023-08-08 09:51:36 +00:00
private function importing_git_repository ()
{
$commands = collect ([]);
$git_clone_command = " git clone -q -b { $this -> application -> git_branch } " ;
if ( $this -> pull_request_id !== 0 ) {
$pr_branch_name = " pr- { $this -> pull_request_id } -coolify " ;
}
2023-06-30 20:24:39 +00:00
2023-08-08 09:51:36 +00:00
if ( $this -> application -> deploymentType () === 'source' ) {
$source_html_url = data_get ( $this -> application , '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' ];
2023-07-06 12:00:19 +00:00
2023-08-08 09:51:36 +00:00
if ( $this -> source -> getMorphClass () == 'App\Models\GithubApp' ) {
if ( $this -> source -> is_public ) {
$git_clone_command = " { $git_clone_command } { $this -> source -> html_url } / { $this -> application -> git_repository } { $this -> workdir } " ;
$git_clone_command = $this -> set_git_import_settings ( $git_clone_command );
2023-07-28 08:55:26 +00:00
2023-09-15 13:34:25 +00:00
$commands -> push ( executeInDocker ( $this -> deployment_uuid , $git_clone_command ));
2023-08-08 09:51:36 +00:00
} else {
$github_access_token = generate_github_installation_token ( $this -> source );
2023-09-15 13:34:25 +00:00
$commands -> push ( executeInDocker ( $this -> deployment_uuid , " git clone -q -b { $this -> application -> git_branch } $source_html_url_scheme ://x-access-token: $github_access_token @ $source_html_url_host / { $this -> application -> git_repository } .git { $this -> workdir } " ));
2023-07-06 12:00:19 +00:00
}
2023-08-08 09:51:36 +00:00
if ( $this -> pull_request_id !== 0 ) {
2023-09-15 13:34:25 +00:00
$commands -> push ( executeInDocker ( $this -> deployment_uuid , " cd { $this -> workdir } && git fetch origin pull/ { $this -> pull_request_id } /head: $pr_branch_name && git checkout $pr_branch_name " ));
2023-07-06 12:00:19 +00:00
}
2023-08-08 09:51:36 +00:00
return $commands -> implode ( ' && ' );
}
}
if ( $this -> application -> deploymentType () === 'deploy_key' ) {
$private_key = base64_encode ( $this -> application -> private_key -> private_key );
$git_clone_command = " GIT_SSH_COMMAND= \" ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa \" { $git_clone_command } { $this -> application -> git_full_url } { $this -> workdir } " ;
$git_clone_command = $this -> set_git_import_settings ( $git_clone_command );
$commands = collect ([
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " mkdir -p /root/.ssh " ),
executeInDocker ( $this -> deployment_uuid , " echo ' { $private_key } ' | base64 -d > /root/.ssh/id_rsa " ),
executeInDocker ( $this -> deployment_uuid , " chmod 600 /root/.ssh/id_rsa " ),
executeInDocker ( $this -> deployment_uuid , $git_clone_command )
2023-06-30 20:24:39 +00:00
]);
2023-08-08 09:51:36 +00:00
return $commands -> implode ( ' && ' );
2023-05-05 08:51:58 +00:00
}
2023-06-30 20:24:39 +00:00
}
2023-06-05 10:07:55 +00:00
2023-08-08 09:51:36 +00:00
private function set_git_import_settings ( $git_clone_command )
2023-05-05 08:51:58 +00:00
{
2023-08-08 09:51:36 +00:00
if ( $this -> application -> git_commit_sha !== 'HEAD' ) {
$git_clone_command = " { $git_clone_command } && cd { $this -> workdir } && git -c advice.detachedHead=false checkout { $this -> application -> git_commit_sha } >/dev/null 2>&1 " ;
2023-05-05 08:51:58 +00:00
}
2023-08-08 09:51:36 +00:00
if ( $this -> application -> settings -> is_git_submodules_enabled ) {
$git_clone_command = " { $git_clone_command } && cd { $this -> workdir } && git submodule update --init --recursive " ;
}
if ( $this -> application -> settings -> is_git_lfs_enabled ) {
$git_clone_command = " { $git_clone_command } && cd { $this -> workdir } && git lfs pull " ;
}
return $git_clone_command ;
2023-05-05 08:51:58 +00:00
}
2023-05-05 07:02:50 +00:00
2023-08-08 09:51:36 +00:00
private function cleanup_git ()
2023-03-31 11:32:07 +00:00
{
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command (
2023-09-15 13:34:25 +00:00
[ executeInDocker ( $this -> deployment_uuid , " rm -fr { $this -> workdir } /.git " )],
2023-08-08 09:51:36 +00:00
);
}
2023-06-05 10:07:55 +00:00
2023-08-11 20:41:47 +00:00
private function generate_nixpacks_confs ()
2023-08-08 09:51:36 +00:00
{
$this -> execute_remote_command (
[
" echo -n 'Generating nixpacks configuration.' " ,
],
[ $this -> nixpacks_build_cmd ()],
2023-09-15 13:34:25 +00:00
[ executeInDocker ( $this -> deployment_uuid , " cp { $this -> workdir } /.nixpacks/Dockerfile { $this -> workdir } /Dockerfile " )],
[ executeInDocker ( $this -> deployment_uuid , " rm -f { $this -> workdir } /.nixpacks/Dockerfile " )]
2023-08-08 09:51:36 +00:00
);
}
2023-06-05 10:07:55 +00:00
2023-08-08 09:51:36 +00:00
private function nixpacks_build_cmd ()
{
$this -> generate_env_variables ();
$nixpacks_command = " nixpacks build -o { $this -> workdir } { $this -> env_args } --no-error-without-start " ;
if ( $this -> application -> build_command ) {
$nixpacks_command .= " --build-cmd \" { $this -> application -> build_command } \" " ;
}
if ( $this -> application -> start_command ) {
$nixpacks_command .= " --start-cmd \" { $this -> application -> start_command } \" " ;
}
if ( $this -> application -> install_command ) {
$nixpacks_command .= " --install-cmd \" { $this -> application -> install_command } \" " ;
}
$nixpacks_command .= " { $this -> workdir } " ;
2023-09-15 13:34:25 +00:00
return executeInDocker ( $this -> deployment_uuid , $nixpacks_command );
2023-08-08 09:51:36 +00:00
}
private function generate_env_variables ()
{
$this -> env_args = collect ([]);
if ( $this -> pull_request_id === 0 ) {
foreach ( $this -> application -> nixpacks_environment_variables as $env ) {
$this -> env_args -> push ( " --env { $env -> key } = { $env -> value } " );
}
} else {
foreach ( $this -> application -> nixpacks_environment_variables_preview as $env ) {
$this -> env_args -> push ( " --env { $env -> key } = { $env -> value } " );
}
}
$this -> env_args = $this -> env_args -> implode ( ' ' );
}
private function generate_compose_file ()
{
$ports = $this -> application -> settings -> is_static ? [ 80 ] : $this -> application -> ports_exposes_array ;
$persistent_storages = $this -> generate_local_persistent_volumes ();
$volume_names = $this -> generate_local_persistent_volumes_only_volume_names ();
$environment_variables = $this -> generate_environment_variables ( $ports );
$docker_compose = [
'version' => '3.8' ,
'services' => [
$this -> container_name => [
'image' => $this -> production_image_name ,
'container_name' => $this -> container_name ,
2023-08-21 16:00:12 +00:00
'restart' => RESTART_MODE ,
2023-08-08 09:51:36 +00:00
'environment' => $environment_variables ,
2023-09-19 13:51:13 +00:00
'labels' => generateLabelsApplication ( $this -> application , $this -> preview ),
2023-09-22 13:29:19 +00:00
// 'expose' => $ports,
2023-03-31 11:32:07 +00:00
'networks' => [
$this -> destination -> network ,
],
'healthcheck' => [
'test' => [
'CMD-SHELL' ,
$this -> generate_healthcheck_commands ()
],
'interval' => $this -> application -> health_check_interval . 's' ,
'timeout' => $this -> application -> health_check_timeout . 's' ,
'retries' => $this -> application -> health_check_retries ,
'start_period' => $this -> application -> health_check_start_period . 's'
],
2023-05-17 09:59:48 +00:00
'mem_limit' => $this -> application -> limits_memory ,
'memswap_limit' => $this -> application -> limits_memory_swap ,
'mem_swappiness' => $this -> application -> limits_memory_swappiness ,
'mem_reservation' => $this -> application -> limits_memory_reservation ,
'cpus' => $this -> application -> limits_cpus ,
'cpuset' => $this -> application -> limits_cpuset ,
'cpu_shares' => $this -> application -> limits_cpu_shares ,
2023-03-31 11:32:07 +00:00
]
],
'networks' => [
$this -> destination -> network => [
2023-09-05 06:49:33 +00:00
'external' => true ,
2023-03-31 11:32:07 +00:00
'name' => $this -> destination -> network ,
2023-09-13 10:08:44 +00:00
'attachable' => true
2023-03-31 11:32:07 +00:00
]
]
];
2023-06-05 10:07:55 +00:00
if ( count ( $this -> application -> ports_mappings_array ) > 0 && $this -> pull_request_id === 0 ) {
2023-05-30 13:52:17 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'ports' ] = $this -> application -> ports_mappings_array ;
2023-03-31 11:32:07 +00:00
}
2023-05-30 13:52:17 +00:00
if ( count ( $persistent_storages ) > 0 ) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ] = $persistent_storages ;
2023-04-04 13:25:42 +00:00
}
if ( count ( $volume_names ) > 0 ) {
$docker_compose [ 'volumes' ] = $volume_names ;
}
2023-06-30 20:24:39 +00:00
$this -> docker_compose = Yaml :: dump ( $docker_compose , 10 );
2023-08-09 12:44:36 +00:00
$this -> docker_compose_base64 = base64_encode ( $this -> docker_compose );
2023-09-15 13:34:25 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> docker_compose_base64 } ' | base64 -d > { $this -> workdir } /docker-compose.yml " ), " hidden " => true ]);
2023-03-31 11:32:07 +00:00
}
2023-08-08 09:51:36 +00:00
2023-04-04 13:25:42 +00:00
private function generate_local_persistent_volumes ()
{
2023-06-05 19:17:44 +00:00
$local_persistent_volumes = [];
2023-04-04 13:25:42 +00:00
foreach ( $this -> application -> persistentStorages as $persistentStorage ) {
$volume_name = $persistentStorage -> host_path ? ? $persistentStorage -> name ;
2023-06-05 10:07:55 +00:00
if ( $this -> pull_request_id !== 0 ) {
$volume_name = $volume_name . '-pr-' . $this -> pull_request_id ;
}
2023-04-04 13:25:42 +00:00
$local_persistent_volumes [] = $volume_name . ':' . $persistentStorage -> mount_path ;
}
2023-06-05 19:17:44 +00:00
return $local_persistent_volumes ;
2023-04-04 13:25:42 +00:00
}
2023-08-08 09:51:36 +00:00
2023-04-04 13:25:42 +00:00
private function generate_local_persistent_volumes_only_volume_names ()
{
2023-06-05 19:17:44 +00:00
$local_persistent_volumes_names = [];
2023-04-04 13:25:42 +00:00
foreach ( $this -> application -> persistentStorages as $persistentStorage ) {
if ( $persistentStorage -> host_path ) {
continue ;
}
2023-06-05 10:07:55 +00:00
$name = $persistentStorage -> name ;
if ( $this -> pull_request_id !== 0 ) {
$name = $name . '-pr-' . $this -> pull_request_id ;
}
$local_persistent_volumes_names [ $name ] = [
'name' => $name ,
2023-04-04 13:25:42 +00:00
'external' => false ,
];
}
2023-06-05 19:17:44 +00:00
return $local_persistent_volumes_names ;
2023-04-04 13:25:42 +00:00
}
2023-08-08 09:51:36 +00:00
2023-06-30 20:24:39 +00:00
private function generate_environment_variables ( $ports )
{
$environment_variables = collect ();
ray ( 'Generate Environment Variables' ) -> green ();
if ( $this -> pull_request_id === 0 ) {
ray ( $this -> application -> runtime_environment_variables ) -> green ();
foreach ( $this -> application -> runtime_environment_variables as $env ) {
$environment_variables -> push ( " $env->key = $env->value " );
}
} else {
ray ( $this -> application -> runtime_environment_variables_preview ) -> green ();
foreach ( $this -> application -> runtime_environment_variables_preview as $env ) {
$environment_variables -> push ( " $env->key = $env->value " );
}
}
// Add PORT if not exists, use the first port as default
2023-08-11 18:48:52 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => Str :: of ( $env ) -> contains ( 'PORT' )) -> isEmpty ()) {
2023-06-30 20:24:39 +00:00
$environment_variables -> push ( " PORT= { $ports [ 0 ] } " );
}
return $environment_variables -> all ();
}
2023-08-08 09:51:36 +00:00
private function generate_healthcheck_commands ()
2023-06-30 20:24:39 +00:00
{
2023-09-15 10:30:25 +00:00
if ( $this -> application -> dockerfile || $this -> application -> build_pack === 'dockerfile' ) {
2023-09-13 10:08:44 +00:00
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0' ;
}
2023-08-08 09:51:36 +00:00
if ( ! $this -> application -> health_check_port ) {
2023-09-22 13:29:19 +00:00
$health_check_port = $this -> application -> ports_exposes_array [ 0 ];
} else {
$health_check_port = $this -> application -> health_check_port ;
2023-04-14 11:18:55 +00:00
}
2023-08-08 09:51:36 +00:00
if ( $this -> application -> health_check_path ) {
$generated_healthchecks_commands = [
2023-09-22 13:29:19 +00:00
" curl -s -X { $this -> application -> health_check_method } -f { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } { $this -> application -> health_check_path } > /dev/null "
2023-08-08 09:51:36 +00:00
];
} else {
$generated_healthchecks_commands = [
2023-09-22 13:29:19 +00:00
" curl -s -X { $this -> application -> health_check_method } -f { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } / "
2023-08-08 09:51:36 +00:00
];
2023-03-31 13:51:50 +00:00
}
2023-08-08 09:51:36 +00:00
return implode ( ' ' , $generated_healthchecks_commands );
2023-06-30 20:24:39 +00:00
}
2023-08-08 09:51:36 +00:00
private function build_image ()
2023-06-30 20:24:39 +00:00
{
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command ([
2023-09-04 14:03:11 +00:00
" echo -n 'Building docker image for your application.' " ,
2023-08-08 09:51:36 +00:00
]);
if ( $this -> application -> settings -> is_static ) {
$this -> execute_remote_command ([
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " docker build --network host -f { $this -> workdir } /Dockerfile { $this -> build_args } --progress plain -t $this->build_image_name { $this -> workdir } " ), " hidden " => true
2023-08-08 09:51:36 +00:00
]);
$dockerfile = base64_encode ( " FROM { $this -> application -> static_image }
WORKDIR / usr / share / nginx / html /
LABEL coolify . deploymentId = { $this -> deployment_uuid }
COPY -- from = $this -> build_image_name / app / { $this -> application -> publish_directory } .
COPY ./ nginx . conf / etc / nginx / conf . d / default . conf " );
$nginx_config = base64_encode ( " server {
listen 80 ;
listen [ :: ] : 80 ;
server_name localhost ;
location / {
root / usr / share / nginx / html ;
index index . html ;
try_files \ $uri \ $uri . html \ $uri / index . html \ $uri / / index . html = 404 ;
}
error_page 500 502 503 504 / 50 x . html ;
location = / 50 x . html {
root / usr / share / nginx / html ;
}
} " );
$this -> execute_remote_command (
[
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile } ' | base64 -d > { $this -> workdir } /Dockerfile-prod " )
2023-08-08 09:51:36 +00:00
],
[
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $nginx_config } ' | base64 -d > { $this -> workdir } /nginx.conf " )
2023-08-08 09:51:36 +00:00
],
[
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " docker build --network host -f { $this -> workdir } /Dockerfile-prod { $this -> build_args } --progress plain -t $this->production_image_name { $this -> workdir } " ), " hidden " => true
2023-08-08 09:51:36 +00:00
]
);
2023-06-30 20:24:39 +00:00
} else {
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command ([
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " docker build --network host -f { $this -> workdir } /Dockerfile { $this -> build_args } --progress plain -t $this->production_image_name { $this -> workdir } " ), " hidden " => true
2023-08-08 09:51:36 +00:00
]);
2023-04-14 19:09:38 +00:00
}
2023-08-08 09:51:36 +00:00
}
2023-06-30 20:24:39 +00:00
2023-08-08 09:51:36 +00:00
private function stop_running_container ()
{
2023-08-21 16:00:12 +00:00
if ( $this -> currently_running_container_name ) {
2023-09-22 13:29:19 +00:00
if ( $this -> newVersionIsHealthy ) {
$this -> execute_remote_command (
[ " echo -n 'Removing old version of your application.' " ],
[ executeInDocker ( $this -> deployment_uuid , " docker rm -f $this->currently_running_container_name >/dev/null 2>&1 " ), " hidden " => true ],
);
} else {
$this -> execute_remote_command (
[ " echo -n 'New version is not healthy, rolling back to the old version.' " ],
[ executeInDocker ( $this -> deployment_uuid , " docker rm -f $this->container_name >/dev/null 2>&1 " ), " hidden " => true ],
);
}
2023-08-21 16:00:12 +00:00
}
2023-06-30 20:24:39 +00:00
}
2023-08-08 09:51:36 +00:00
private function start_by_compose_file ()
2023-06-30 20:24:39 +00:00
{
$this -> execute_remote_command (
2023-08-21 16:00:12 +00:00
[ " echo -n 'Rolling update started.' " ],
2023-09-15 13:34:25 +00:00
[ executeInDocker ( $this -> deployment_uuid , " docker compose --project-directory { $this -> workdir } up -d >/dev/null " ), " hidden " => true ],
2023-06-30 20:24:39 +00:00
);
}
2023-08-08 09:51:36 +00:00
private function generate_build_env_variables ()
2023-03-31 13:51:50 +00:00
{
2023-08-08 09:51:36 +00:00
$this -> build_args = collect ([ " --build-arg SOURCE_COMMIT= { $this -> commit } " ]);
if ( $this -> pull_request_id === 0 ) {
foreach ( $this -> application -> build_environment_variables as $env ) {
$this -> build_args -> push ( " --build-arg { $env -> key } = { $env -> value } " );
}
} else {
foreach ( $this -> application -> build_environment_variables_preview as $env ) {
$this -> build_args -> push ( " --build-arg { $env -> key } = { $env -> value } " );
}
2023-05-30 13:52:17 +00:00
}
2023-07-06 12:00:19 +00:00
2023-08-08 09:51:36 +00:00
$this -> build_args = $this -> build_args -> implode ( ' ' );
}
2023-05-10 11:05:32 +00:00
2023-08-08 09:51:36 +00:00
private function add_build_env_variables_to_dockerfile ()
{
$this -> execute_remote_command ([
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } /Dockerfile " ), " hidden " => true , " save " => 'dockerfile'
2023-08-08 09:51:36 +00:00
]);
$dockerfile = collect ( Str :: of ( $this -> saved_outputs -> get ( 'dockerfile' )) -> trim () -> explode ( " \n " ));
2023-05-30 13:52:17 +00:00
2023-08-08 09:51:36 +00:00
foreach ( $this -> application -> build_environment_variables as $env ) {
$dockerfile -> splice ( 1 , 0 , " ARG { $env -> key } = { $env -> value } " );
2023-03-31 13:51:50 +00:00
}
2023-08-08 09:51:36 +00:00
$dockerfile_base64 = base64_encode ( $dockerfile -> implode ( " \n " ));
$this -> execute_remote_command ([
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile_base64 } ' | base64 -d > { $this -> workdir } /Dockerfile " ),
2023-08-08 09:51:36 +00:00
" hidden " => true
]);
}
private function next ( string $status )
{
// If the deployment is cancelled by the user, don't update the status
if ( $this -> application_deployment_queue -> status !== ApplicationDeploymentStatus :: CANCELLED_BY_USER -> value ) {
$this -> application_deployment_queue -> update ([
'status' => $status ,
2023-06-30 20:24:39 +00:00
]);
2023-08-08 09:51:36 +00:00
}
queue_next_deployment ( $this -> application );
if ( $status === ApplicationDeploymentStatus :: FINISHED -> value ) {
$this -> application -> environment -> project -> team -> notify ( new DeploymentSuccess ( $this -> application , $this -> deployment_uuid , $this -> preview ));
}
if ( $status === ApplicationDeploymentStatus :: FAILED -> value ) {
$this -> application -> environment -> project -> team -> notify ( new DeploymentFailed ( $this -> application , $this -> deployment_uuid , $this -> preview ));
2023-03-31 13:51:50 +00:00
}
2023-03-31 12:30:08 +00:00
}
2023-08-08 09:51:36 +00:00
public function failed ( Throwable $exception ) : void
2023-05-24 12:26:50 +00:00
{
2023-06-30 20:24:39 +00:00
$this -> execute_remote_command (
2023-08-08 09:51:36 +00:00
[ " echo 'Oops something is not okay, are you okay? 😢' " ],
[ " echo ' { $exception -> getMessage () } ' " ]
2023-06-30 20:24:39 +00:00
);
2023-08-08 09:51:36 +00:00
$this -> next ( ApplicationDeploymentStatus :: FAILED -> value );
2023-05-24 12:26:50 +00:00
}
2023-08-08 09:51:36 +00:00
}