From 9e09c449cf63c5d9ec6410ea76a135979dcb3fc9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:03:45 +0100 Subject: [PATCH 1/7] fix: service deletion function --- app/Actions/Service/DeleteService.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php index c42a8ec4a..743158c2d 100644 --- a/app/Actions/Service/DeleteService.php +++ b/app/Actions/Service/DeleteService.php @@ -32,13 +32,14 @@ class DeleteService } $database->forceDelete(); } - foreach ($storagesToDelete as $storage) { - $commands[] = "docker volume rm -f $storage->name"; + if ($server->isFunctional()) { + foreach ($storagesToDelete as $storage) { + $commands[] = "docker volume rm -f $storage->name"; + } + $commands[] = "docker rm -f $service->uuid"; + + instant_remote_process($commands, $server, false); } - $commands[] = "docker rm -f $service->uuid"; - - instant_remote_process($commands, $server, false); - $service->forceDelete(); } } From 5c29ecdf105fcdc261cd03b87ba8257614554eb4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:07:00 +0100 Subject: [PATCH 2/7] feat: add initial support for custom docker run commands --- app/Jobs/ApplicationDeploymentJob.php | 3 + app/Livewire/Project/Application/General.php | 11 ++- bootstrap/helpers/docker.php | 88 ++++++++++++++++++- ...9_145200_add_custom_docker_run_options.php | 28 ++++++ .../project/application/general.blade.php | 8 +- 5 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 database/migrations/2024_01_29_145200_add_custom_docker_run_options.php diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 7e8d8a756..d31695831 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1128,6 +1128,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted data_forget($docker_compose, 'services.' . $this->container_name); + $custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options); + $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose); + $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]); diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 2e02aac60..23bd94fb2 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -61,11 +61,12 @@ class General extends Component 'application.docker_compose_pr' => 'nullable', 'application.docker_compose_raw' => 'nullable', 'application.docker_compose_pr_raw' => 'nullable', - 'application.custom_labels' => 'nullable', 'application.dockerfile_target_build' => 'nullable', - 'application.settings.is_static' => 'boolean|required', 'application.docker_compose_custom_start_command' => 'nullable', 'application.docker_compose_custom_build_command' => 'nullable', + 'application.custom_labels' => 'nullable', + 'application.custom_docker_run_options' => 'nullable', + 'application.settings.is_static' => 'boolean|required', 'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', ]; @@ -97,9 +98,10 @@ class General extends Component 'application.docker_compose_pr_raw' => 'Docker compose raw', 'application.custom_labels' => 'Custom labels', 'application.dockerfile_target_build' => 'Dockerfile target build', - 'application.settings.is_static' => 'Is static', + 'application.custom_docker_run_options' => 'Custom docker run commands', 'application.docker_compose_custom_start_command' => 'Docker compose custom start command', 'application.docker_compose_custom_build_command' => 'Docker compose custom build command', + 'application.settings.is_static' => 'Is static', 'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled', 'application.settings.is_build_server_enabled' => 'Is build server enabled', ]; @@ -249,6 +251,9 @@ class General extends Component $this->application->fqdn = $domains->implode(','); } + if (data_get($this->application, 'custom_docker_run_options')) { + $this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim(); + } if (data_get($this->application, 'dockerfile')) { $port = get_port_from_dockerfile($this->application->dockerfile); if ($port && !$this->application->ports_exposes) { diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 0510e8d58..e06a9318d 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -3,9 +3,7 @@ use App\Models\Application; use App\Models\ApplicationPreview; use App\Models\Server; -use App\Models\Service; use App\Models\ServiceApplication; -use App\Models\ServiceDatabase; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Spatie\Url\Url; @@ -323,3 +321,89 @@ function isDatabaseImage(?string $image = null) } return false; } + +function convert_docker_run_to_compose(?string $custom_docker_run_options = null) +{ + preg_match_all('/(--\w+(?:-\w+)*)(?:\s|=)?([^\s-]+)?/', $custom_docker_run_options, $matches, PREG_SET_ORDER); + $list_options = collect([ + '--cap-add', + '--cap-drop', + '--security-opt', + '--sysctl', + '--ulimit', + '--device' + ]); + $mapping = collect([ + '--cap-add' => 'cap_add', + '--cap-drop' => 'cap_drop', + '--security-opt' => 'security_opt', + '--sysctl' => 'sysctls', + '--device' => 'devices', + '--ulimit' => 'ulimits', + '--init' => 'init', + '--ulimit' => 'ulimits', + '--privileged' => 'privileged', + ]); + $options = []; + foreach ($matches as $match) { + $option = $match[1]; + $value = isset($match[2]) && $match[2] !== '' ? $match[2] : true; + if ($list_options->contains($option)) { + $value = explode(',', $value); + } + if (array_key_exists($option, $options)) { + if (is_array($options[$option])) { + $options[$option][] = $value; + } else { + $options[$option] = [$options[$option], $value]; + } + } else { + $options[$option] = $value; + } + } + $options = collect($options); + $compose_options = collect([]); + // Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js + foreach ($options as $option => $value) { + if (!data_get($mapping, $option)) { + continue; + } + if ($option === '--ulimit') { + $ulimits = collect([]); + collect($value)->map(function ($ulimit) use ($ulimits){ + $ulimit = explode('=', $ulimit); + $type = $ulimit[0]; + $limits = explode(':', $ulimit[1]); + if (count($limits) == 2) { + $soft_limit = $limits[0]; + $hard_limit = $limits[1]; + $ulimits->put($type, [ + 'soft' => $soft_limit, + 'hard' => $hard_limit + ]); + + } else { + $soft_limit = $ulimit[1]; + $ulimits->put($type, [ + 'soft' => $soft_limit, + ]); + } + }); + $compose_options->put($mapping[$option], $ulimits); + } else { + if ($list_options->contains($option)) { + if ($compose_options->has($mapping[$option])) { + $compose_options->put($mapping[$option], $options->get($mapping[$option]) . ',' . $value); + } else { + $compose_options->put($mapping[$option], $value); + } + continue; + } else { + $compose_options->put($mapping[$option], $value); + continue; + } + $compose_options->forget($option); + } + } + return $compose_options->toArray(); +} diff --git a/database/migrations/2024_01_29_145200_add_custom_docker_run_options.php b/database/migrations/2024_01_29_145200_add_custom_docker_run_options.php new file mode 100644 index 000000000..30949ea80 --- /dev/null +++ b/database/migrations/2024_01_29_145200_add_custom_docker_run_options.php @@ -0,0 +1,28 @@ +string('custom_docker_run_options')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index a16953c7e..8d06c449e 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -130,7 +130,6 @@ @endif @if ($application->could_set_build_commands()) @if ($application->build_pack === 'nixpacks') -
@@ -194,6 +193,13 @@ @endif @endif
+
The following options are for advanced use cases. Only modify them if you + know what are + you doing.
+ @endif @endif From 1d1ec20cb7a313f60117dc0ddbbec9c39951376a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:13:04 +0100 Subject: [PATCH 3/7] Update version numbers --- config/sentry.php | 2 +- config/version.php | 2 +- resources/views/livewire/project/application/general.blade.php | 2 +- versions.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/sentry.php b/config/sentry.php index 00f3f45e8..4e79f8e6a 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.201', + 'release' => '4.0.0-beta.202', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 3a5485183..e8285e73e 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ @endif diff --git a/versions.json b/versions.json index 2b29b66d8..fa4d00248 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.201" + "version": "4.0.0-beta.202" } } } From 919e88afb4f7c848e3e00d796ece6121f40712ff Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:21:23 +0100 Subject: [PATCH 4/7] Refactor docker run options to compose format --- app/Jobs/ApplicationDeploymentJob.php | 4 +++- bootstrap/helpers/docker.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index d31695831..34f358367 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1129,7 +1129,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted data_forget($docker_compose, 'services.' . $this->container_name); $custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options); - $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose); + if (count($custom_compose) > 0) { + $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose); + } $this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose_base64 = base64_encode($this->docker_compose); diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index e06a9318d..3ebec43b9 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -324,6 +324,8 @@ function isDatabaseImage(?string $image = null) function convert_docker_run_to_compose(?string $custom_docker_run_options = null) { + $options = []; + $compose_options = collect([]); preg_match_all('/(--\w+(?:-\w+)*)(?:\s|=)?([^\s-]+)?/', $custom_docker_run_options, $matches, PREG_SET_ORDER); $list_options = collect([ '--cap-add', @@ -344,7 +346,6 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null '--ulimit' => 'ulimits', '--privileged' => 'privileged', ]); - $options = []; foreach ($matches as $match) { $option = $match[1]; $value = isset($match[2]) && $match[2] !== '' ? $match[2] : true; @@ -362,7 +363,6 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null } } $options = collect($options); - $compose_options = collect([]); // Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js foreach ($options as $option => $value) { if (!data_get($mapping, $option)) { From 9a127bdc80cb6361bb2438c68beab40c5bf5963b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:31:10 +0100 Subject: [PATCH 5/7] Refactor webhook handling logic to remove duplicate code and improve readability --- routes/webhooks.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/routes/webhooks.php b/routes/webhooks.php index 515b90d84..c831b8818 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -277,14 +277,6 @@ Route::post('/source/bitbucket/events/manual', function () { ]); } foreach ($applications as $application) { - if (!$application->isPRDeployable()) { - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'failed', - 'message' => 'Preview deployments disabled.', - ]); - continue; - } $webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket'); $payload = request()->getContent(); @@ -310,7 +302,7 @@ Route::post('/source/bitbucket/events/manual', function () { continue; } if ($x_bitbucket_event === 'repo:push') { - if ($application->isPRDeployable()) { + if ($application->isDeployable()) { ray('Deploying ' . $application->name . ' with branch ' . $branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( @@ -328,7 +320,7 @@ Route::post('/source/bitbucket/events/manual', function () { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', - 'message' => 'Preview deployments disabled.', + 'message' => 'Auto deployment disabled.', ]); } } From ff7d0d442d4b909eddf718729c909e2c33fd1476 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:33:01 +0100 Subject: [PATCH 6/7] Update button text in private key create view --- resources/views/livewire/security/private-key/create.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/security/private-key/create.blade.php b/resources/views/livewire/security/private-key/create.blade.php index ea9e7a58f..15a63fac4 100644 --- a/resources/views/livewire/security/private-key/create.blade.php +++ b/resources/views/livewire/security/private-key/create.blade.php @@ -14,7 +14,7 @@ ~/.ssh/authorized_keys file - Save Private Key + Continue From 09b9305aa3082e8cbd3c5042705ed238618f2a10 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 29 Jan 2024 16:33:06 +0100 Subject: [PATCH 7/7] Refactor git_clone_command in generateGitImportCommands function --- app/Models/Application.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index ab45e1b1e..9e9626ec2 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -905,8 +905,7 @@ class Application extends BaseModel $commands->push("echo 'Checking out $branch'"); } $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; - } - if ($git_type === 'github') { + } else if ($git_type === 'github') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); @@ -914,6 +913,13 @@ class Application extends BaseModel $commands->push("echo 'Checking out $branch'"); } $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; + } else if ($git_type === 'bitbucket') { + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); + } else { + $commands->push("echo 'Checking out $branch'"); + } + $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit"; } }