Merge pull request #1746 from coollabsio/next

v4.0.0-beta.217
This commit is contained in:
Andras Bacsai 2024-02-15 12:54:38 +01:00 committed by GitHub
commit 0e81ff970f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 209 additions and 47 deletions

View File

@ -125,6 +125,9 @@ class Init extends Command
// Cleanup any failed deployments
try {
if (isCloud()) {
return;
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
ray($deployment->id, $deployment->status);

View File

@ -133,6 +133,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
ray('New container name: ', $this->container_name);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@ -711,9 +713,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
}
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
}
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
@ -1199,13 +1206,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// ];
// }
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
}
} else {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
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);
@ -1490,6 +1502,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
);
}
} else {
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
$this->application_deployment_queue->update([

View File

@ -8,20 +8,25 @@ use Livewire\Component;
class Advanced extends Component
{
public Application $application;
public bool $is_force_https_enabled;
protected $rules = [
'application.settings.is_git_submodules_enabled' => 'boolean|required',
'application.settings.is_git_lfs_enabled' => 'boolean|required',
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
'application.settings.is_force_https_enabled' => 'boolean|required',
'is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
];
public function mount() {
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
public function instantSave()
{
if ($this->application->isLogDrainEnabled()) {
@ -31,7 +36,8 @@ class Advanced extends Component
return;
}
}
if ($this->application->settings->is_force_https_enabled) {
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
$this->dispatch('resetDefaultLabels', false);
}
$this->application->settings->save();

View File

@ -126,7 +126,6 @@ class General extends Component
$this->application->save();
}
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
$this->checkLabelUpdates();
}
public function instantSave()
{
@ -164,6 +163,7 @@ class General extends Component
}
return $domain;
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
@ -184,15 +184,6 @@ class General extends Component
$this->submit();
$this->dispatch('build_pack_updated');
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode("|", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$this->labelsChanged = false;
}
}
public function getWildcardDomain()
{
$server = data_get($this->application, 'destination.server');
@ -212,6 +203,13 @@ class General extends Component
public function updatedApplicationFqdn()
{
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->application->save();
$this->resetDefaultLabels(false);
// $this->dispatch('success', 'Labels reset to default!');
}
@ -238,22 +236,17 @@ class General extends Component
]);
}
if (data_get($this->application, 'fqdn')) {
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$domains = $domains->unique();
$domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.","Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
}
}
}
check_fqdn_usage($this->application);
$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();
}
@ -279,7 +272,6 @@ class General extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}

View File

@ -470,7 +470,7 @@ class Application extends BaseModel
{
return data_get($this, 'settings.is_log_drain_enabled', false);
}
public function isConfigurationChanged($save = false)
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {

View File

@ -123,10 +123,14 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
{
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
$now = now()->format('Hisu');
if ($pull_request_id !== 0 && $pull_request_id !== null) {
return $application->uuid . '-pr-' . $pull_request_id;
} else {
if ($consistent_container_name) {
return $application->uuid;
}
return $application->uuid . '-' . $now;
}
}
@ -209,15 +213,48 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
}
return $payload;
}
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null)
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
$labels->push("traefik.http.middlewares.gzip.compress=true");
$labels->push("traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https");
$basic_auth = false;
$basic_auth_middleware = null;
$redirect = false;
$redirect_middleware = null;
if ($serviceLabels) {
$basic_auth = $serviceLabels->contains(function ($value) {
return str_contains($value, 'basicauth');
});
if ($basic_auth) {
$basic_auth_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.basicauth\.users/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
$redirect = $serviceLabels->contains(function ($value) {
return str_contains($value, 'redirectregex');
});
if ($redirect) {
$redirect_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.redirectregex\.regex/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
}
foreach ($domains as $loop => $domain) {
try {
$uuid = new Cuid2(7);
// $uuid = new Cuid2(7);
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
@ -239,11 +276,24 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
}
if ($path !== '/') {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix,gzip");
$middlewares = "gzip,{$https_label}-stripprefix";
if ($basic_auth && $basic_auth_middleware) {
$middlewares = $middlewares . ',' . $basic_auth_middleware;
}
if ($redirect && $redirect_middleware) {
$middlewares = $middlewares . ',' . $redirect_middleware;
}
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
} else {
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
$middlewares = "gzip";
if ($basic_auth && $basic_auth_middleware) {
$middlewares = $middlewares . ',' . $basic_auth_middleware;
}
if ($redirect && $redirect_middleware) {
$middlewares = $middlewares . ',' . $redirect_middleware;
}
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
}
$labels->push("traefik.http.routers.{$https_label}.tls=true");
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
@ -267,16 +317,29 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
}
if ($path !== '/') {
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix,gzip");
$middlewares = "gzip,{$http_label}-stripprefix";
if ($basic_auth && $basic_auth_middleware) {
$middlewares = $middlewares . ',' . $basic_auth_middleware;
}
if ($redirect && $redirect_middleware) {
$middlewares = $middlewares . ',' . $redirect_middleware;
}
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
} else {
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
$middlewares = "gzip";
if ($basic_auth && $basic_auth_middleware) {
$middlewares = $middlewares . ',' . $basic_auth_middleware;
}
if ($redirect && $redirect_middleware) {
$middlewares = $middlewares . ',' . $redirect_middleware;
}
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
}
}
} catch (\Throwable $e) {
continue;
}
}
return $labels->sort();
}
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array

View File

@ -1039,7 +1039,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true, serviceLabels: $serviceLabels));
}
}
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
@ -1480,7 +1480,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $preview_fqdn;
});
}
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns,serviceLabels: $serviceLabels));
}
}
}

View File

@ -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.216',
'release' => '4.0.0-beta.217',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.216';
return '4.0.0-beta.217';

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->boolean('is_consistent_container_name_enabled')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_consistent_container_name_enabled');
});
}
};

View File

@ -1,7 +1,7 @@
version: '3.8'
services:
coolify:
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:latest}"
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}"
volumes:
- type: bind
source: /data/coolify/source/.env

View File

@ -15,7 +15,11 @@
@endif
<x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..."
instantSave id="application.settings.is_force_https_enabled" label="Force Https" />
instantSave id="is_force_https_enabled" label="Force Https" />
<x-forms.checkbox
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.is_consistent_container_name_enabled"
label="Consistent Container Names" />
<h4>Logs</h4>
@if (!$application->settings->is_raw_compose_deployment_enabled)
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."

View File

@ -6,8 +6,11 @@
Save
</x-forms.button>
@if ($isConfigurationChanged && !is_null($application->config_hash))
<div class="font-bold text-warning">Configuration not applied to the running application. You need to
redeploy.</div>
<div title="Configuration not applied to the running application. You need to redeploy.">
<svg class="w-6 h-6 text-warning" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"/>
</svg>
</div>
@endif
</div>
<div>General configuration for your application.</div>

View File

@ -0,0 +1,35 @@
# documentation: https://www.metabase.com/docs/latest/installation-and-operation/running-metabase-on-docker
# slogan: Fast analytics with the friendly UX and integrated tooling to let your company explore data on their own.
# tags: analytics,bi,business,intelligence
services:
metabase:
image: metabase/metabase:latest
volumes:
- /dev/urandom:/dev/random:ro
environment:
- SERVICE_FQDN_METABASE
- MB_DB_TYPE=postgres
- MB_DB_HOST=postgresql
- MB_DB_PORT=5432
- MB_DB_DBNAME=${POSTGRESQL_DATABASE:-metabase}
- MB_DB_USER=$SERVICE_USER_POSTGRESQL
- MB_DB_PASS=$SERVICE_PASSWORD_POSTGRESQL
healthcheck:
test: curl --fail -I http://localhost:3000/api/health || exit 1
interval: 5s
timeout: 20s
retries: 10
postgresql:
image: postgres:16-alpine
volumes:
- metabase-postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- POSTGRES_DB=${POSTGRESQL_DATABASE:-metabase}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10

View File

@ -329,6 +329,17 @@
"meilisearch"
]
},
"metabase": {
"documentation": "https:\/\/www.metabase.com\/docs\/latest\/installation-and-operation\/running-metabase-on-docker",
"slogan": "Fast analytics with the friendly UX and integrated tooling to let your company explore data on their own.",
"compose": "c2VydmljZXM6CiAgbWV0YWJhc2U6CiAgICBpbWFnZTogJ21ldGFiYXNlL21ldGFiYXNlOmxhdGVzdCcKICAgIHZvbHVtZXM6CiAgICAgIC0gJy9kZXYvdXJhbmRvbTovZGV2L3JhbmRvbTpybycKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NRVRBQkFTRQogICAgICAtIE1CX0RCX1RZUEU9cG9zdGdyZXMKICAgICAgLSBNQl9EQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBNQl9EQl9QT1JUPTU0MzIKICAgICAgLSAnTUJfREJfREJOQU1FPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotbWV0YWJhc2V9JwogICAgICAtIE1CX0RCX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gTUJfREJfUEFTUz0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ2N1cmwgLS1mYWlsIC1JIGh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9hcGkvaGVhbHRoIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21ldGFiYXNlLXBvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LW1ldGFiYXNlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
"tags": [
"analytics",
"bi",
"business",
"intelligence"
]
},
"metube": {
"documentation": "https:\/\/github.com\/alexta69\/metube",
"slogan": "A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.",

View File

@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.216"
"version": "4.0.0-beta.217"
}
}
}