diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index 0284b40a9..da642ba77 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -27,6 +27,7 @@ class General extends Component public bool $isConfigurationChanged = false; public ?string $initialDockerComposeLocation = null; + public ?string $initialDockerComposePrLocation = null; public bool $is_static; @@ -57,6 +58,7 @@ class General extends Component 'application.docker_registry_image_tag' => 'nullable', 'application.dockerfile_location' => 'nullable', 'application.docker_compose_location' => 'nullable', + 'application.docker_compose_pr_location' => 'nullable', 'application.docker_compose' => 'nullable', 'application.docker_compose_raw' => 'nullable', 'application.custom_labels' => 'nullable', @@ -84,6 +86,7 @@ class General extends Component 'application.docker_registry_image_tag' => 'Docker registry image tag', 'application.dockerfile_location' => 'Dockerfile location', 'application.docker_compose_location' => 'Docker compose location', + 'application.docker_compose_pr_location' => 'Docker compose location', 'application.docker_compose' => 'Docker compose', 'application.docker_compose_raw' => 'Docker compose raw', 'application.custom_labels' => 'Custom labels', @@ -125,10 +128,11 @@ public function loadComposeFile($isInit = false) if ($isInit && $this->application->docker_compose_raw) { return; } - ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); + ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit); $this->emit('success', 'Docker compose file loaded.'); } catch (\Throwable $e) { $this->application->docker_compose_location = $this->initialDockerComposeLocation; + $this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation; $this->application->save(); return handleError($e, $this); } diff --git a/app/Jobs/ApplicationDeployDockerImageJob.php b/app/Jobs/ApplicationDeployDockerImageJob.php deleted file mode 100644 index 9dfdad1f1..000000000 --- a/app/Jobs/ApplicationDeployDockerImageJob.php +++ /dev/null @@ -1,151 +0,0 @@ -clearAll(); - ray('Deploying Docker Image'); - static::$batch_counter = 0; - try { - $deploymentUuid = data_get($this->deploymentQueueEntry, 'deployment_uuid'); - $pullRequestId = data_get($this->deploymentQueueEntry, 'pull_request_id'); - - $this->server = data_get($this->application->destination, 'server'); - $network = data_get($this->application->destination, 'network'); - - $dockerImage = data_get($this->application, 'docker_registry_image_name'); - $dockerImageTag = data_get($this->application, 'docker_registry_image_tag'); - - $productionImageName = str("{$dockerImage}:{$dockerImageTag}"); - $this->containerName = generateApplicationContainerName($this->application, $pullRequestId); - savePrivateKeyToFs($this->server); - - ray("echo 'Starting deployment of {$productionImageName}.'"); - - $this->deploymentQueueEntry->update([ - 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, - ]); - - $this->deploymentQueueEntry->addLogEntry('Starting deployment of ' . $productionImageName); - - $this->server->executeRemoteCommand( - commands: collect( - [ - [ - "name" => "ls", - "command" => 'ls -la', - "hidden" => true, - ], - [ - "name" => "pwd", - "command" => 'pwd', - "hidden" => true, - ] - ], - ), - loggingModel: $this->deploymentQueueEntry - ); - $this->server->executeRemoteCommand( - commands: prepareHelperContainer($this->server, $network, $deploymentUuid), - loggingModel: $this->deploymentQueueEntry - ); - $this->server->executeRemoteCommand( - commands: generateComposeFile( - deploymentUuid: $deploymentUuid, - server: $this->server, - network: $network, - application: $this->application, - containerName: $this->containerName, - imageName: $productionImageName, - pullRequestId: $pullRequestId - ), - loggingModel: $this->deploymentQueueEntry - ); - $this->deploymentQueueEntry->addLogEntry('----------------------------------------'); - - // Rolling update not possible - if (count($this->application->ports_mappings_array) > 0) { - $this->deploymentQueueEntry->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.'); - $this->deploymentQueueEntry->addLogEntry('Stopping running container.'); - $this->server->stopApplicationRelatedRunningContainers($this->application->id, $this->containerName); - } else { - $this->deploymentQueueEntry->addLogEntry('Rolling update started.'); - // TODO - $this->server->executeRemoteCommand( - commands: startNewApplication(application: $this->application, deploymentUuid: $deploymentUuid, loggingModel: $this->deploymentQueueEntry), - loggingModel: $this->deploymentQueueEntry - ); - // $this->server->executeRemoteCommand( - // commands: healthCheckContainer(application: $this->application, containerName: $this->containerName , loggingModel: $this->deploymentQueueEntry), - // loggingModel: $this->deploymentQueueEntry - // ); - - } - - ray($this->remoteCommandOutputs); - $this->deploymentQueueEntry->update([ - 'status' => ApplicationDeploymentStatus::FINISHED->value, - ]); - } catch (Throwable $e) { - $this->fail($e); - throw $e; - } - } - public function failed(Throwable $exception): void - { - $this->deploymentQueueEntry->addLogEntry('Oops something is not okay, are you okay? 😢', 'error'); - $this->deploymentQueueEntry->addLogEntry($exception->getMessage(), 'error'); - $this->deploymentQueueEntry->addLogEntry('Deployment failed. Removing the new version of your application.'); - - $this->server->executeRemoteCommand( - commands: removeOldDeployment($this->containerName), - loggingModel: $this->deploymentQueueEntry - ); - $this->deploymentQueueEntry->update([ - 'status' => ApplicationDeploymentStatus::FAILED->value, - ]); - } - // 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, - // ]); - // } - // 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)); - // } - // } -} diff --git a/app/Jobs/ApplicationDeploySimpleDockerfileJob.php b/app/Jobs/ApplicationDeploySimpleDockerfileJob.php deleted file mode 100644 index 0cf9f61c4..000000000 --- a/app/Jobs/ApplicationDeploySimpleDockerfileJob.php +++ /dev/null @@ -1,46 +0,0 @@ -applicationDeploymentQueueId = $applicationDeploymentQueueId; - } - public function handle() - { - ray('Deploying Simple Dockerfile'); - $applicationDeploymentQueue = ApplicationDeploymentQueue::find($this->applicationDeploymentQueueId); - $application = Application::find($applicationDeploymentQueue->application_id); - $destination = $application->destination->getMorphClass()::where('id', $application->destination->id)->first(); - $server = data_get($destination, 'server'); - $commands = collect([]); - $commands->push( - [ - 'command' => 'echo "Starting deployment of simple dockerfile."', - ], - [ - 'command' => 'ls -la', - ] - ); - $server->executeRemoteCommand(commands: $commands, logModel: $applicationDeploymentQueue); - } -} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 36907a917..eea334c8e 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -456,7 +456,6 @@ private function deploy_docker_compose_buildpack() if ($this->pull_request_id === 0) { $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); } else { - ray('asd'); $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); } $this->server->executeRemoteCommand( diff --git a/app/Jobs/MultipleApplicationDeploymentJob.php b/app/Jobs/MultipleApplicationDeploymentJob.php deleted file mode 100644 index 4c3cc29c9..000000000 --- a/app/Jobs/MultipleApplicationDeploymentJob.php +++ /dev/null @@ -1,1164 +0,0 @@ -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->build_pack = data_get($this->application, 'build_pack'); - - $this->application_deployment_queue_id = $application_deployment_queue_id; - $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; - $this->commit = $this->application_deployment_queue->commit; - $this->force_rebuild = $this->application_deployment_queue->force_rebuild; - $this->restart_only = $this->application_deployment_queue->restart_only; - - $this->git_type = data_get($this->application_deployment_queue, 'git_type'); - - $source = data_get($this->application, 'source'); - if ($source) { - $this->source = $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->mainServer = $this->destination->server; - $this->serverUser = $this->server->user; - $this->basedir = generateBaseDir($this->deployment_uuid); - $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); - $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; - $this->is_debug_enabled = $this->application->settings->is_debug_enabled; - $this->saved_outputs = collect(); - $this->container_name = generateApplicationContainerName($this->application, 0); - } - - public function handle(): void - { - savePrivateKeyToFs($this->server); - $this->application_deployment_queue->update([ - 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, - ]); - - $this->addHosts = generateHostIpMapping($this->server, $this->destination->network); - - if ($this->application->dockerfile_target_build) { - $this->buildTarget = " --target {$this->application->dockerfile_target_build} "; - } - - // Check custom port - preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches); - if (count($matches) === 1) { - $this->customPort = $matches[0]; - $gitHost = str($this->application->git_repository)->before(':'); - $gitRepo = str($this->application->git_repository)->after('/'); - $this->customRepository = "$gitHost:$gitRepo"; - } else { - $this->customRepository = $this->application->git_repository; - } - try { - if ($this->application->isMultipleServerDeployment()) { - if ($this->application->build_pack === 'dockerimage') { - $this->dockerImage = $this->application->docker_registry_image_name; - $this->dockerImageTag = $this->application->docker_registry_image_tag; - $this->execute_remote_command( - [ - "echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'" - ], - ); - $this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}"); - ray(prepareHelperContainer($this->server, $this->deployment_uuid)); - $this->execute_remote_command( - [prepareHelperContainer($this->server, $this->deployment_uuid)] - ); - } - } else { - throw new RuntimeException('Missing configuration for multiple server deployment.'); - } - // if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { - // $this->just_restart(); - // if ($this->server->isProxyShouldRun()) { - // dispatch(new ContainerStatusJob($this->server)); - // } - // $this->next(ApplicationDeploymentStatus::FINISHED->value); - // $this->application->isConfigurationChanged(true); - // return; - // } else if ($this->application->dockerfile) { - // $this->deploy_simple_dockerfile(); - // } else if ($this->application->build_pack === 'dockerimage') { - // $this->deploy_dockerimage_buildpack(); - // } else if ($this->application->build_pack === 'dockerfile') { - // $this->deploy_dockerfile_buildpack(); - // } else if ($this->application->build_pack === 'static') { - // $this->deploy_static_buildpack(); - // } else { - // $this->deploy_nixpacks_buildpack(); - // } - // if ($this->server->isProxyShouldRun()) { - // dispatch(new ContainerStatusJob($this->server)); - // } - // if ($this->application->docker_registry_image_name) { - // $this->push_to_docker_registry(); - // } - // $this->next(ApplicationDeploymentStatus::FINISHED->value); - // $this->application->isConfigurationChanged(true); - } catch (Exception $e) { - $this->fail($e); - throw $e; - } finally { - // if (isset($this->docker_compose_base64)) { - // $readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at); - // $composeFileName = "$this->configuration_dir/docker-compose.yml"; - // $this->execute_remote_command( - // [ - // "mkdir -p $this->configuration_dir" - // ], - // [ - // "echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName", - // ], - // [ - // "echo '{$readme}' > $this->configuration_dir/README.md", - // ] - // ); - // } - // $this->execute_remote_command( - // [ - // "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1", - // "hidden" => true, - // "ignore_errors" => true, - // ] - // ); - // $this->execute_remote_command( - // [ - // "docker image prune -f >/dev/null 2>&1", - // "hidden" => true, - // "ignore_errors" => true, - // ] - // ); - } - } - private function push_to_docker_registry() - { - try { - instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); - $this->execute_remote_command( - [ - "echo '\n----------------------------------------'", - ], - ["echo -n 'Pushing image to docker registry ({$this->production_image_name}).'"], - [ - executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true - ], - ); - if ($this->application->docker_registry_image_tag) { - // Tag image with latest - $this->execute_remote_command( - ['echo -n "Tagging and pushing image with latest tag."'], - [ - executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true - ], - [ - executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true - ], - ); - } - $this->execute_remote_command([ - "echo -n 'Image pushed to docker registry.'" - ]); - } catch (Exception $e) { - $this->execute_remote_command( - ["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"], - ); - ray($e); - } - } - // private function deploy_docker_compose() - // { - // $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->customRepository}:build"); - // $this->production_image_name = Str::lower("{$this->application->uuid}:latest"); - // $this->save_environment_variables(); - // $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id); - // ray($containers); - // 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], - // ); - // } - private function generate_image_names() - { - if ($this->application->dockerfile) { - if ($this->application->docker_registry_image_name) { - $this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build"); - $this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest"); - } else { - $this->build_image_name = Str::lower("{$this->application->uuid}:build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:latest"); - } - } else if ($this->application->build_pack === 'dockerimage') { - $this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}"); - } else { - $this->dockerImageTag = str($this->commit)->substr(0, 128); - if ($this->application->docker_registry_image_name) { - $this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build"); - $this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}"); - } else { - $this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build"); - $this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}"); - } - } - } - private function just_restart() - { - $this->execute_remote_command( - [ - "echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'" - ], - ); - $this->prepare_builder_image(); - $this->check_git_if_build_needed(); - $this->set_base_dir(); - $this->generate_image_names(); - $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { - $this->generate_compose_file(); - $this->rolling_update(); - return; - } - throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.'); - } - private function check_image_locally_or_remotely() - { - $this->execute_remote_command([ - "docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found" - ]); - if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) { - $this->execute_remote_command([ - "docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true - ]); - $this->execute_remote_command([ - "docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found" - ]); - } - } - // private function save_environment_variables() - // { - // $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") - // ], - // ); - // } - 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( - [ - executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir$this->dockerfile_location") - ], - ); - $this->generate_image_names(); - $this->generate_compose_file(); - $this->generate_build_env_variables(); - $this->add_build_env_variables_to_dockerfile(); - $this->build_image(); - $this->rolling_update(); - } - - private function deploy_dockerimage_buildpack() - { - // $this->dockerImage = $this->application->docker_registry_image_name; - // $this->dockerImageTag = $this->application->docker_registry_image_tag; - // ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"); - // $this->execute_remote_command( - // [ - // "echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'" - // ], - // ); - // $this->generate_image_names(); - // $this->prepare_builder_image(); - $this->generate_compose_file(); - $this->rolling_update(); - } - - private function deploy_dockerfile_buildpack() - { - if (data_get($this->application, 'dockerfile_location')) { - $this->dockerfile_location = $this->application->dockerfile_location; - } - $this->execute_remote_command( - [ - "echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'" - ], - ); - $this->prepare_builder_image(); - $this->check_git_if_build_needed(); - $this->clone_repository(); - $this->set_base_dir(); - $this->generate_image_names(); - $this->cleanup_git(); - $this->generate_compose_file(); - $this->generate_build_env_variables(); - $this->add_build_env_variables_to_dockerfile(); - $this->build_image(); - // if ($this->application->additional_destinations) { - // $this->push_to_docker_registry(); - // $this->deploy_to_additional_destinations(); - // } else { - $this->rolling_update(); - // } - } - private function deploy_nixpacks_buildpack() - { - $this->execute_remote_command( - [ - "echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'" - ], - ); - $this->prepare_builder_image(); - $this->check_git_if_build_needed(); - $this->set_base_dir(); - $this->generate_image_names(); - if (!$this->force_rebuild) { - $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - $this->execute_remote_command([ - "echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'", - ]); - $this->generate_compose_file(); - $this->rolling_update(); - return; - } - if ($this->application->isConfigurationChanged()) { - $this->execute_remote_command([ - "echo 'Configuration changed. Rebuilding image.'", - ]); - } - } - $this->clone_repository(); - $this->cleanup_git(); - $this->generate_nixpacks_confs(); - $this->generate_compose_file(); - $this->generate_build_env_variables(); - $this->add_build_env_variables_to_dockerfile(); - $this->build_image(); - $this->rolling_update(); - } - private function deploy_static_buildpack() - { - $this->execute_remote_command( - [ - "echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'" - ], - ); - $this->prepare_builder_image(); - $this->check_git_if_build_needed(); - $this->set_base_dir(); - $this->generate_image_names(); - $this->clone_repository(); - $this->cleanup_git(); - $this->build_image(); - $this->generate_compose_file(); - $this->rolling_update(); - } - - private function rolling_update() - { - if (count($this->application->ports_mappings_array) > 0) { - $this->execute_remote_command( - [ - "echo '\n----------------------------------------'", - ], - ["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"], - ); - $this->stop_running_container(force: true); - $this->start_by_compose_file(); - } else { - $this->execute_remote_command( - [ - "echo '\n----------------------------------------'", - ], - ["echo -n 'Rolling update started.'"], - ); - $this->start_by_compose_file(); - $this->health_check(); - $this->stop_running_container(); - } - } - private function health_check() - { - if ($this->application->isHealthcheckDisabled()) { - $this->newVersionIsHealthy = true; - return; - } - // ray('New container name: ', $this->container_name); - if ($this->container_name) { - $counter = 1; - $this->execute_remote_command( - [ - "echo 'Waiting for healthcheck to pass on the new container.'" - ] - ); - if ($this->full_healthcheck_url) { - $this->execute_remote_command( - [ - "echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'" - ] - ); - } - while ($counter < $this->application->health_check_retries) { - $this->execute_remote_command( - [ - "docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}", - "hidden" => true, - "save" => "health_check" - ], - - ); - $this->execute_remote_command( - [ - "echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'" - ], - ); - if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) { - $this->newVersionIsHealthy = true; - $this->application->update(['status' => 'running']); - $this->execute_remote_command( - [ - "echo 'New container is healthy.'" - ], - ); - break; - } - $counter++; - sleep($this->application->health_check_interval); - } - } - } - - private function prepare_builder_image() - { - $helperImage = config('coolify.helper_image'); - // Get user home directory - $this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server); - $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); - - if ($this->dockerConfigFileExists === 'OK') { - $runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; - } else { - $runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; - } - $this->execute_remote_command( - [ - "echo -n 'Preparing container with helper image: $helperImage.'", - ], - [ - $runCommand, - "hidden" => true, - ], - [ - "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}") - ], - ); - } - private function deploy_to_additional_destinations() - { - $destination_ids = collect(str($this->application->additional_destinations)->explode(',')); - foreach ($destination_ids as $destination_id) { - $destination = StandaloneDocker::find($destination_id); - $server = $destination->server; - if ($server->team_id !== $this->mainServer->team_id) { - $this->execute_remote_command( - [ - "echo -n 'Skipping deployment to {$server->name}. Not in the same team?!'", - ], - ); - continue; - } - $this->server = $server; - $this->execute_remote_command( - [ - "echo -n 'Deploying to {$this->server->name}.'", - ], - ); - $this->prepare_builder_image(); - $this->generate_image_names(); - $this->rolling_update(); - } - } - private function set_base_dir() - { - $this->execute_remote_command( - [ - "echo -n 'Setting base directory to {$this->workdir}.'" - ], - ); - } - private function check_git_if_build_needed() - { - $this->generate_git_import_commands(); - $private_key = data_get($this->application, 'private_key.private_key'); - if ($private_key) { - $private_key = base64_encode($private_key); - $this->execute_remote_command( - [ - 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_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$this->branch}"), - "hidden" => true, - "save" => "git_commit_sha" - ], - ); - } else { - $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$this->branch}"), - "hidden" => true, - "save" => "git_commit_sha" - ], - ); - } - - if ($this->saved_outputs->get('git_commit_sha')) { - $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); - } - } - private function clone_repository() - { - $importCommands = $this->generate_git_import_commands(); - $this->execute_remote_command( - [ - "echo '\n----------------------------------------'", - ], - [ - "echo -n 'Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '" - ], - [ - $importCommands, "hidden" => true - ] - ); - } - - private function generate_git_import_commands() - { - $this->branch = $this->application->git_branch; - $commands = collect([]); - $git_clone_command = "git clone -q -b {$this->application->git_branch}"; - - 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']; - - if ($this->source->getMorphClass() == 'App\Models\GithubApp') { - if ($this->source->is_public) { - $this->fullRepoUrl = "{$this->source->html_url}/{$this->customRepository}"; - $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->customRepository} {$this->basedir}"; - $git_clone_command = $this->set_git_import_settings($git_clone_command); - - $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); - } else { - $github_access_token = generate_github_installation_token($this->source); - $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->customRepository}.git {$this->basedir}")); - $this->fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git"; - } - return $commands->implode(' && '); - } - } - if ($this->application->deploymentType() === 'deploy_key') { - $this->fullRepoUrl = $this->customRepository; - $private_key = data_get($this->application, '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 {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}"; - $git_clone_command = $this->set_git_import_settings($git_clone_command_base); - $commands = collect([ - 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"), - ]); - - $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); - return $commands->implode(' && '); - } - if ($this->application->deploymentType() === 'other') { - $this->fullRepoUrl = $this->customRepository; - $git_clone_command = "{$git_clone_command} {$this->customRepository} {$this->basedir}"; - $git_clone_command = $this->set_git_import_settings($git_clone_command); - $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); - return $commands->implode(' && '); - } - } - - private function set_git_import_settings($git_clone_command) - { - if ($this->application->git_commit_sha !== 'HEAD') { - $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1"; - } - if ($this->application->settings->is_git_submodules_enabled) { - $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git submodule update --init --recursive"; - } - if ($this->application->settings->is_git_lfs_enabled) { - $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git lfs pull"; - } - return $git_clone_command; - } - - private function cleanup_git() - { - $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "rm -fr {$this->basedir}/.git")], - ); - } - - private function generate_nixpacks_confs() - { - $nixpacks_command = $this->nixpacks_build_cmd(); - $this->execute_remote_command( - [ - "echo -n 'Generating nixpacks configuration with: $nixpacks_command'", - ], - [executeInDocker($this->deployment_uuid, $nixpacks_command)], - [executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")], - [executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")] - ); - } - - private function nixpacks_build_cmd() - { - $this->generate_env_variables(); - $nixpacks_command = "nixpacks build --cache-key '{$this->application->uuid}' -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}"; - return $nixpacks_command; - } - - private function generate_env_variables() - { - $this->env_args = collect([]); - 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); - - if (data_get($this->application, 'custom_labels')) { - $labels = collect(str($this->application->custom_labels)->explode(',')); - $labels = $labels->filter(function ($value, $key) { - return !Str::startsWith($value, 'coolify.'); - }); - $this->application->custom_labels = $labels->implode(','); - $this->application->save(); - } else { - $labels = collect(generateLabelsApplication($this->application, $this->preview)); - } - - $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, 0))->toArray(); - $docker_compose = [ - 'version' => '3.8', - 'services' => [ - $this->container_name => [ - 'image' => $this->production_image_name, - 'container_name' => $this->container_name, - 'restart' => RESTART_MODE, - 'environment' => $environment_variables, - 'labels' => $labels, - 'expose' => $ports, - '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' - ], - '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' => (int) $this->application->limits_cpus, - 'cpuset' => $this->application->limits_cpuset, - 'cpu_shares' => $this->application->limits_cpu_shares, - ] - ], - 'networks' => [ - $this->destination->network => [ - 'external' => true, - 'name' => $this->destination->network, - 'attachable' => true - ] - ] - ]; - if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { - $docker_compose['services'][$this->container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => "tcp://127.0.0.1:24224", - 'fluentd-async' => "true", - 'fluentd-sub-second-precision' => "true", - ] - ]; - } - if ($this->application->settings->is_gpu_enabled) { - ray('asd'); - $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [ - [ - 'driver' => data_get($this->application, 'settings.gpu_driver', 'nvidia'), - 'capabilities' => ['gpu'], - 'options' => data_get($this->application, 'settings.gpu_options', []) - ] - ]; - if (data_get($this->application, 'settings.gpu_count')) { - $count = data_get($this->application, 'settings.gpu_count'); - if ($count === 'all') { - $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count; - } else { - $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count; - } - } else if (data_get($this->application, 'settings.gpu_device_ids')) { - $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this->application, 'settings.gpu_device_ids'); - } - } - if ($this->application->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck'); - } - if (count($persistent_storages) > 0) { - $docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; - } - if (count($volume_names) > 0) { - $docker_compose['volumes'] = $volume_names; - } - // if ($this->build_pack === 'dockerfile') { - // $docker_compose['services'][$this->container_name]['build'] = [ - // 'context' => $this->workdir, - // 'dockerfile' => $this->workdir . $this->dockerfile_location, - // ]; - // } - $this->docker_compose = Yaml::dump($docker_compose, 10); - $this->docker_compose_base64 = base64_encode($this->docker_compose); - $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); - } - - private function generate_local_persistent_volumes() - { - $local_persistent_volumes = []; - foreach ($this->application->persistentStorages as $persistentStorage) { - $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; - $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; - } - return $local_persistent_volumes; - } - - private function generate_local_persistent_volumes_only_volume_names() - { - $local_persistent_volumes_names = []; - foreach ($this->application->persistentStorages as $persistentStorage) { - if ($persistentStorage->host_path) { - continue; - } - $name = $persistentStorage->name; - $local_persistent_volumes_names[$name] = [ - 'name' => $name, - 'external' => false, - ]; - } - return $local_persistent_volumes_names; - } - - private function generate_environment_variables($ports) - { - $environment_variables = collect(); - foreach ($this->application->runtime_environment_variables_preview as $env) { - $environment_variables->push("$env->key=$env->value"); - } - foreach ($this->application->nixpacks_environment_variables_preview as $env) { - $environment_variables->push("$env->key=$env->value"); - } - // Add PORT if not exists, use the first port as default - if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) { - $environment_variables->push("PORT={$ports[0]}"); - } - return $environment_variables->all(); - } - - private function generate_healthcheck_commands() - { - if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') { - // 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'; - } - if (!$this->application->health_check_port) { - $health_check_port = $this->application->ports_exposes_array[0]; - } else { - $health_check_port = $this->application->health_check_port; - } - if ($this->application->health_check_path) { - $this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}"; - $generated_healthchecks_commands = [ - "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" - ]; - } else { - $this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"; - $generated_healthchecks_commands = [ - "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/" - ]; - } - return implode(' ', $generated_healthchecks_commands); - } - private function pull_latest_image($image) - { - $this->execute_remote_command( - ["echo -n 'Pulling latest image ($image) from the registry.'"], - - [ - executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true - ] - ); - } - private function build_image() - { - if ($this->application->build_pack === 'static') { - $this->execute_remote_command([ - "echo -n 'Static deployment. Copying static assets to the image.'", - ]); - } else { - $this->execute_remote_command( - [ - "echo -n 'Building docker image started.'", - ], - ["echo -n 'To check the current progress, click on Show Debug Logs.'"] - ); - } - - if ($this->application->settings->is_static || $this->application->build_pack === 'static') { - if ($this->application->static_image) { - $this->pull_latest_image($this->application->static_image); - } - if ($this->application->build_pack === 'static') { - $dockerfile = base64_encode("FROM {$this->application->static_image} -WORKDIR /usr/share/nginx/html/ -LABEL coolify.deploymentId={$this->deployment_uuid} -COPY . . -RUN rm -f /usr/share/nginx/html/nginx.conf -RUN rm -f /usr/share/nginx/html/Dockerfile -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 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - }"); - } else { - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true - ]); - - $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 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - }"); - } - $this->execute_remote_command( - [ - executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile") - ], - [ - executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") - ], - [ - executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true - ] - ); - } else { - // Pure Dockerfile based deployment - if ($this->application->dockerfile) { - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "docker build --pull $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true - ]); - } else { - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true - ]); - } - } - $this->execute_remote_command([ - "echo -n 'Building docker image completed.'", - ]); - } - - private function stop_running_container(bool $force = false) - { - $this->execute_remote_command(["echo -n 'Removing old container.'"]); - if ($this->newVersionIsHealthy || $force) { - $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, 0); - $containers = $containers->filter(function ($container) { - return data_get($container, 'Names') !== $this->container_name; - }); - $containers->each(function ($container) { - $containerName = data_get($container, 'Names'); - $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true], - ); - }); - $this->execute_remote_command( - [ - "echo 'Rolling update completed.'" - ], - ); - } else { - $this->execute_remote_command( - ["echo -n 'New container is not healthy, rolling back to the old container.'"], - [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true], - ); - } - } - - private function start_by_compose_file() - { - if ($this->application->build_pack === 'dockerimage') { - $this->execute_remote_command( - ["echo -n 'Pulling latest images from the registry.'"], - [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true], - [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], - ); - } else { - $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], - ); - } - } - - private function generate_build_env_variables() - { - $this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]); - foreach ($this->application->build_environment_variables_preview as $env) { - $this->build_args->push("--build-arg {$env->key}=\"{$env->value}\""); - } - $this->build_args = $this->build_args->implode(' '); - } - - private function add_build_env_variables_to_dockerfile() - { - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile' - ]); - $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); - - foreach ($this->application->build_environment_variables as $env) { - $dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}"); - } - $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); - $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"), - "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, - ]); - } - 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)); - } - } - - public function failed(Throwable $exception): void - { - $this->execute_remote_command( - ["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'], - ["echo '{$exception->getMessage()}'", 'type' => 'err'], - ["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'], - [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true] - ); - - $this->next(ApplicationDeploymentStatus::FAILED->value); - } -} diff --git a/app/Models/Application.php b/app/Models/Application.php index 2e58d1891..a7c721800 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -138,7 +138,22 @@ public function dockerComposeLocation(): Attribute return Attribute::make( set: function ($value) { if (is_null($value) || $value === '') { - return '/docker-compose.yml'; + return '/docker-compose.yaml'; + } else { + if ($value !== '/') { + return Str::start(Str::replaceEnd('/', '', $value), '/'); + } + return Str::start($value, '/'); + } + } + ); + } + public function dockerComposePrLocation(): Attribute + { + return Attribute::make( + set: function ($value) { + if (is_null($value) || $value === '') { + return '/docker-compose-pr.yaml'; } else { if ($value !== '/') { return Str::start(Str::replaceEnd('/', '', $value), '/'); @@ -595,6 +610,7 @@ function parseCompose(int $pull_request_id = 0) function loadComposeFile($isInit = false) { $initialDockerComposeLocation = $this->docker_compose_location; + $initialDockerComposePrLocation = $this->docker_compose_pr_location; if ($this->build_pack === 'dockercompose') { if ($isInit && $this->docker_compose_raw) { return; @@ -603,11 +619,12 @@ function loadComposeFile($isInit = false) ['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; + $prComposeFile = $this->docker_compose_pr_location; $commands = collect([ "mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}", $cloneCommand, "git sparse-checkout init --cone", - "git sparse-checkout set .$workdir$composeFile", + "git sparse-checkout set .$workdir$composeFile .$workdir$prComposeFile", "git read-tree -mu HEAD", "cat .$workdir$composeFile", ]); @@ -620,13 +637,26 @@ function loadComposeFile($isInit = false) $this->docker_compose_raw = $composeFileContent; $this->save(); } + $commands = collect([ + "cat .$workdir$prComposeFile", + ]); + $composePrFileContent = instant_remote_process($commands, $this->destination->server, false); + if (!$composePrFileContent) { + $this->docker_compose_pr_location = $initialDockerComposePrLocation; + $this->save(); + throw new \Exception("Could not load compose file from $workdir$prComposeFile"); + } else { + $this->docker_compose_pr_raw = $composePrFileContent; + $this->save(); + } $commands = collect([ "rm -rf /tmp/{$uuid}", ]); instant_remote_process($commands, $this->destination->server, false); return [ 'parsedServices' => $this->parseCompose(), - 'initialDockerComposeLocation' => $this->docker_compose_location + 'initialDockerComposeLocation' => $this->docker_compose_location, + 'initialDockerComposePrLocation' => $this->docker_compose_pr_location, ]; } } diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 17c3aa486..bc0de5c47 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -36,10 +36,6 @@ function queue_application_deployment(int $application_id, string $deployment_uu if ($running_deployments->count() > 0) { return; } - // New deployment - // dispatchDeploymentJob($deployment); - - // Old deployment dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, ))->onConnection('long-running')->onQueue('long-running'); @@ -50,39 +46,11 @@ function queue_next_deployment(Application $application) { $next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first(); if ($next_found) { - // New deployment - // dispatchDeploymentJob($next_found->id); - - // Old deployment dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $next_found->id, ))->onConnection('long-running')->onQueue('long-running'); } } -function dispatchDeploymentJob(ApplicationDeploymentQueue $deploymentQueueEntry) -{ - $application = Application::find($deploymentQueueEntry->application_id); - - $isRestartOnly = data_get($deploymentQueueEntry, 'restart_only'); - $isSimpleDockerFile = data_get($application, 'dockerfile'); - $isDockerImage = data_get($application, 'build_pack') === 'dockerimage'; - - // if ($isRestartOnly) { - // ApplicationRestartJob::dispatch(queue: $deploymentQueueEntry, application: $application)->onConnection('long-running')->onQueue('long-running'); - // } else if ($isSimpleDockerFile) { - // ApplicationDeploySimpleDockerfileJob::dispatch(applicationDeploymentQueueId: $id)->onConnection('long-running')->onQueue('long-running'); - // } else - - if ($isDockerImage) { - ApplicationDeployDockerImageJob::dispatch( - deploymentQueueEntry: $deploymentQueueEntry, - application: $application - )->onConnection('long-running')->onQueue('long-running'); - } else { - throw new Exception('Unknown build pack'); - } -} - // Deployment things function generateHostIpMapping(Server $server, string $network) { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index ecb67cd0c..fcf32d215 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1089,11 +1089,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal return collect([]); } } else if ($resource->getMorphClass() === 'App\Models\Application') { - try { - $yaml = Yaml::parse($resource->docker_compose_raw); - } catch (\Exception $e) { - throw new \Exception($e->getMessage()); + if ($pull_request_id !== 0 && $resource->dockerComposePrLocation() !== $resource->dockerComposeLocation()) { + + } else { + try { + $yaml = Yaml::parse($resource->docker_compose_raw); + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } } + $server = $resource->destination->server; $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); if ($pull_request_id !== 0) { diff --git a/database/migrations/2023_11_24_080341_add_docker_compose_location.php b/database/migrations/2023_11_24_080341_add_docker_compose_location.php index b811aa4d1..eab705ff0 100644 --- a/database/migrations/2023_11_24_080341_add_docker_compose_location.php +++ b/database/migrations/2023_11_24_080341_add_docker_compose_location.php @@ -12,11 +12,15 @@ public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->string('docker_compose_location')->nullable()->default('/docker-compose.yml')->after('dockerfile_location'); - $table->longText('docker_compose')->nullable()->after('docker_compose_location'); - $table->longText('docker_compose_raw')->nullable()->after('docker_compose'); - $table->text('docker_compose_domains')->nullable()->after('docker_compose_raw'); + $table->string('docker_compose_location')->nullable()->default('/docker-compose.yaml')->after('dockerfile_location'); + $table->string('docker_compose_pr_location')->nullable()->default('/docker-compose-pr.yaml')->after('docker_compose_location'); + $table->longText('docker_compose')->nullable()->after('docker_compose_location'); + $table->longText('docker_compose_pr')->nullable()->after('docker_compose_location'); + $table->longText('docker_compose_raw')->nullable()->after('docker_compose'); + $table->longText('docker_compose_pr_raw')->nullable()->after('docker_compose'); + + $table->text('docker_compose_domains')->nullable()->after('docker_compose_raw'); }); } @@ -27,8 +31,11 @@ public function down(): void { Schema::table('applications', function (Blueprint $table) { $table->dropColumn('docker_compose_location'); + $table->dropColumn('docker_compose_pr_location'); $table->dropColumn('docker_compose'); + $table->dropColumn('docker_compose_pr'); $table->dropColumn('docker_compose_raw'); + $table->dropColumn('docker_compose_pr_raw'); $table->dropColumn('docker_compose_domains'); }); } diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 0975dbf4d..b08a9fb60 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -116,9 +116,12 @@ @endif @if ($application->build_pack === 'dockercompose') - + @endif @if ($application->build_pack === 'dockerfile')