From c91f426af3bed032bc6b07a6f66d771af873aacc Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 22 Sep 2023 12:08:51 +0200 Subject: [PATCH] wip: services --- app/Actions/Service/StartService.php | 5 +- .../Livewire/Project/New/DockerCompose.php | 12 +- app/Http/Livewire/Project/Service/Index.php | 1 + app/Models/Service.php | 27 ++- app/Models/ServiceApplication.php | 9 + app/Models/ServiceDatabase.php | 9 + app/View/Components/Services/Explanation.php | 26 +++ bootstrap/helpers/services.php | 194 ------------------ examples/docker-compose-ghost.yaml | 2 +- .../components/services/explanation.blade.php | 8 + .../project/new/docker-compose.blade.php | 13 +- .../livewire/project/new/select.blade.php | 6 +- .../project/service/application.blade.php | 9 +- .../project/service/database.blade.php | 3 +- .../livewire/project/service/index.blade.php | 10 +- .../project/shared/storages/show.blade.php | 11 +- 16 files changed, 103 insertions(+), 242 deletions(-) create mode 100644 app/View/Components/Services/Explanation.php create mode 100644 resources/views/components/services/explanation.blade.php diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index 1dbabc6a5..e82040e74 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -11,7 +11,7 @@ class StartService public function handle(Service $service) { $workdir = service_configuration_dir() . "/{$service->uuid}"; - $commands[] = "echo 'Starting service {$service->name} on {$service->server->name}'"; + $commands[] = "echo 'Starting service {$service->name} on {$service->server->name}.'"; $commands[] = "mkdir -p $workdir"; $commands[] = "cd $workdir"; @@ -22,8 +22,11 @@ public function handle(Service $service) foreach ($envs as $env) { $commands[] = "echo '{$env->key}={$env->value}' >> .env"; } + $commands[] = "echo 'Pulling images and starting containers...'"; $commands[] = "docker compose pull"; $commands[] = "docker compose up -d"; + $commands[] = "echo 'Waiting for containers to start...'"; + $commands[] = "sleep 5"; $commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true"; $activity = remote_process($commands, $service->server); return $activity; diff --git a/app/Http/Livewire/Project/New/DockerCompose.php b/app/Http/Livewire/Project/New/DockerCompose.php index 00faa4863..cfd6dbb86 100644 --- a/app/Http/Livewire/Project/New/DockerCompose.php +++ b/app/Http/Livewire/Project/New/DockerCompose.php @@ -2,20 +2,10 @@ namespace App\Http\Livewire\Project\New; -use App\Models\Application; -use App\Models\EnvironmentVariable; -use App\Models\GithubApp; -use App\Models\LocalPersistentVolume; use App\Models\Project; use App\Models\Service; -use App\Models\ServiceApplication; -use App\Models\ServiceDatabase; -use App\Models\StandaloneDocker; -use App\Models\SwarmDocker; use Livewire\Component; -use Visus\Cuid2\Cuid2; use Illuminate\Support\Str; -use Symfony\Component\Yaml\Yaml; class DockerCompose extends Component { @@ -29,7 +19,7 @@ public function mount() if (isDev()) { $this->dockercompose = 'services: ghost: - documentation: https://docs.ghost.org/docs/config + documentation: https://ghost.org/docs/config image: ghost:5 volumes: - ghost-content-data:/var/lib/ghost/content diff --git a/app/Http/Livewire/Project/Service/Index.php b/app/Http/Livewire/Project/Service/Index.php index 8b70f369f..5b23ec33c 100644 --- a/app/Http/Livewire/Project/Service/Index.php +++ b/app/Http/Livewire/Project/Service/Index.php @@ -33,6 +33,7 @@ public function render() public function save() { $this->service->save(); $this->service->parse(); + $this->service->refresh(); $this->emit('refreshEnvs'); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 4e585aca3..1ae97aa08 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -98,6 +98,8 @@ public function parse(bool $isNew = false): Collection $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) { $container_name = "$serviceName-{$this->uuid}"; $isDatabase = false; + $serviceVariables = collect(data_get($service, 'environment', [])); + // Decide if the service is a database $image = data_get($service, 'image'); if ($image) { @@ -114,10 +116,15 @@ public function parse(bool $isNew = false): Collection 'service_id' => $this->id ]); } else { - $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io"; - if (isDev()) { - $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io"; + if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io"; + if (isDev()) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io"; + } + } else { + $defaultUsableFqdn = null; } + $savedService = ServiceApplication::create([ 'name' => $serviceName, 'fqdn' => $defaultUsableFqdn, @@ -129,6 +136,16 @@ public function parse(bool $isNew = false): Collection $savedService = $this->databases()->whereName($serviceName)->first(); } else { $savedService = $this->applications()->whereName($serviceName)->first(); + if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io"; + if (isDev()) { + $defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io"; + } + } else { + $defaultUsableFqdn = null; + } + $savedService->fqdn = $defaultUsableFqdn; + $savedService->save(); } } $fqdn = data_get($savedService, 'fqdn'); @@ -155,6 +172,7 @@ public function parse(bool $isNew = false): Collection // Collect volumes $serviceVolumes = collect(data_get($service, 'volumes', [])); if ($serviceVolumes->count() > 0) { + LocalPersistentVolume::whereResourceId($savedService->id)->whereResourceType(get_class($savedService))->delete(); foreach ($serviceVolumes as $volume) { if (is_string($volume)) { $volumeName = Str::before($volume, ':'); @@ -189,7 +207,7 @@ public function parse(bool $isNew = false): Collection $composeVolumes->put($volumeName, null); LocalPersistentVolume::updateOrCreate( [ - 'mount_path' => $volumePath, + 'name' => $volumeName, 'resource_id' => $savedService->id, 'resource_type' => get_class($savedService) ], @@ -234,7 +252,6 @@ public function parse(bool $isNew = false): Collection // Get variables from the service - $serviceVariables = collect(data_get($service, 'environment', [])); foreach ($serviceVariables as $variable) { $value = Str::after($variable, '='); if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 79b96c0b2..491058082 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Symfony\Component\Yaml\Yaml; class ServiceApplication extends BaseModel { @@ -14,6 +15,14 @@ public function type() { return 'service'; } + public function documentation() + { + return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs'); + } + public function service() + { + return $this->belongsTo(Service::class); + } public function persistentStorages() { return $this->morphMany(LocalPersistentVolume::class, 'resource'); diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index dc0d85742..e01374ea6 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Symfony\Component\Yaml\Yaml; class ServiceDatabase extends BaseModel { @@ -13,6 +14,14 @@ public function type() { return 'service'; } + public function documentation() + { + return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs'); + } + public function service() + { + return $this->belongsTo(Service::class); + } public function persistentStorages() { return $this->morphMany(LocalPersistentVolume::class, 'resource'); diff --git a/app/View/Components/Services/Explanation.php b/app/View/Components/Services/Explanation.php new file mode 100644 index 000000000..a2242d0e1 --- /dev/null +++ b/app/View/Components/Services/Explanation.php @@ -0,0 +1,26 @@ +clearAll(); -// $template = data_get($service, 'docker_compose_raw'); -// $network = data_get($service, 'destination.network'); -// $yaml = Yaml::parse($template); - -// $services = $service->parse(); -// $volumes = collect(data_get($yaml, 'volumes', [])); -// $composeVolumes = collect([]); -// $env = collect([]); -// $ports = collect([]); - -// foreach ($services as $serviceName => $service) { -// $container_name = generateApplicationContainerName($application); -// $domain = data_get($application, "service_configurations.{$serviceName}.fqdn", null); -// if ($domain === '') { -// $domain = null; -// } -// data_forget($service, 'documentation'); -// // Some default things -// data_set($service, 'restart', RESTART_MODE); -// data_set($service, 'container_name', $container_name); -// $healthcheck = data_get($service, 'healthcheck'); -// if (is_null($healthcheck)) { -// $healthcheck = [ -// 'test' => [ -// 'CMD-SHELL', -// 'exit 0' -// ], -// 'interval' => $application->health_check_interval . 's', -// 'timeout' => $application->health_check_timeout . 's', -// 'retries' => $application->health_check_retries, -// 'start_period' => $application->health_check_start_period . 's' -// ]; -// data_set($service, 'healthcheck', $healthcheck); -// } -// // Labels -// $server = data_get($application, 'destination.server'); -// if ($server->proxyType() === ProxyTypes::TRAEFIK_V2->value) { -// $labels = collect(data_get($service, 'labels', [])); -// $labels = collect([]); -// $labels = $labels->merge(defaultLabels($application->id, $container_name)); -// if (!data_get($service, 'is_database')) { -// if ($domain) { -// $labels = $labels->merge(fqdnLabelsForTraefik($domain, $container_name, $application->settings->is_force_https_enabled)); -// } - -// } -// data_set($service, 'labels', $labels->toArray()); -// } - -// data_forget($service, 'is_database'); - -// // Add volumes to the volumes collection if they don't already exist -// $serviceVolumes = collect(data_get($service, 'volumes', [])); -// if ($serviceVolumes->count() > 0) { -// foreach ($serviceVolumes as $volume) { -// $volumeName = Str::before($volume, ':'); -// $volumePath = Str::after($volume, ':'); -// if (Str::startsWith($volumeName, '/')) { -// continue; -// } -// $volumeExists = $volumes->contains(function ($_, $key) use ($volumeName) { -// return $key == $volumeName; -// }); -// if ($volumeExists) { -// ray('Volume already exists'); -// } else { -// $composeVolumes->put($volumeName, null); -// $volumes->put($volumeName, $volumePath); -// } -// } -// } -// // Add networks to the networks collection if they don't already exist -// $serviceNetworks = collect(data_get($service, 'networks', [])); -// $networkExists = $serviceNetworks->contains(function ($_, $key) use ($network) { -// return $key == $network; -// }); -// if (is_null($networkExists) || !$networkExists) { -// $serviceNetworks->push($network); -// } -// data_set($service, 'networks', $serviceNetworks->toArray()); -// data_set($yaml, "services.{$serviceName}", $service); - -// // Get variables from the service that does not start with SERVICE_* -// $serviceVariables = collect(data_get($service, 'environment', [])); -// foreach ($serviceVariables as $variable) { -// // $key = Str::before($variable, '='); -// $value = Str::after($variable, '='); -// if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { -// if (Str::of($value)->contains(':')) { -// $nakedName = replaceVariables(Str::of($value)->before(':')); -// $nakedValue = replaceVariables(Str::of($value)->after(':')); -// } -// if (Str::of($value)->contains('-')) { -// $nakedName = replaceVariables(Str::of($value)->before('-')); -// $nakedValue = replaceVariables(Str::of($value)->after('-')); -// } -// if (Str::of($value)->contains('+')) { -// $nakedName = replaceVariables(Str::of($value)->before('+')); -// $nakedValue = replaceVariables(Str::of($value)->after('+')); -// } -// if ($nakedValue->startsWith('-')) { -// $nakedValue = Str::of($nakedValue)->after('-'); -// } -// if ($nakedValue->startsWith('+')) { -// $nakedValue = Str::of($nakedValue)->after('+'); -// } -// if (!$env->contains("{$nakedName->value()}={$nakedValue->value()}")) { -// $env->push("$nakedName=$nakedValue"); -// } -// } -// } -// // Get ports from the service -// $servicePorts = collect(data_get($service, 'ports', [])); -// foreach ($servicePorts as $port) { -// $port = Str::of($port)->before(':'); -// $ports->push($port); -// } -// } -// data_set($yaml, 'networks', [ -// $network => [ -// 'name' => $network -// ], -// ]); -// data_set($yaml, 'volumes', $composeVolumes->toArray()); -// $compose = Str::of(Yaml::dump($yaml, 10, 2)); - -// // Replace SERVICE_FQDN_* with the actual FQDN -// preg_match_all(collectRegex('SERVICE_FQDN_'), $compose, $fqdns); -// $fqdns = collect($fqdns)->flatten()->unique()->values(); -// $generatedFqdns = collect([]); -// foreach ($fqdns as $fqdn) { -// $generatedFqdns->put("$fqdn", data_get($application, 'fqdn')); -// } - -// // Replace SERVICE_URL_* -// preg_match_all(collectRegex('SERVICE_URL_'), $compose, $urls); -// $urls = collect($urls)->flatten()->unique()->values(); -// $generatedUrls = collect([]); -// foreach ($urls as $url) { -// $generatedUrls->put("$url", data_get($application, 'url')); -// } - -// // Generate SERVICE_USER_* -// preg_match_all(collectRegex('SERVICE_USER_'), $compose, $users); -// $users = collect($users)->flatten()->unique()->values(); -// $generatedUsers = collect([]); -// foreach ($users as $user) { -// $generatedUsers->put("$user", Str::random(10)); -// } - -// // Generate SERVICE_PASSWORD_* -// preg_match_all(collectRegex('SERVICE_PASSWORD_'), $compose, $passwords); -// $passwords = collect($passwords)->flatten()->unique()->values(); -// $generatedPasswords = collect([]); -// foreach ($passwords as $password) { -// $generatedPasswords->put("$password", Str::password(symbols: false)); -// } - -// // Save .env file -// foreach ($generatedFqdns as $key => $value) { -// $env->push("$key=$value"); -// } -// foreach ($generatedUrls as $key => $value) { -// $env->push("$key=$value"); -// } -// foreach ($generatedUsers as $key => $value) { -// $env->push("$key=$value"); -// } -// foreach ($generatedPasswords as $key => $value) { -// $env->push("$key=$value"); -// } -// return [ -// 'dockercompose' => $compose, -// 'yaml' => Yaml::parse($compose), -// 'envs' => $env, -// 'volumes' => $volumes, -// 'ports' => $ports->values(), -// ]; -// } function replaceRegex(?string $name = null) { diff --git a/examples/docker-compose-ghost.yaml b/examples/docker-compose-ghost.yaml index 9ed0c360e..a5db1e2bd 100644 --- a/examples/docker-compose-ghost.yaml +++ b/examples/docker-compose-ghost.yaml @@ -1,6 +1,6 @@ services: ghost: - documentation: https://docs.ghost.org/docs/config + documentation: https://ghost.org/docs/config image: ghost:5 volumes: - ghost-content-data:/var/lib/ghost/content diff --git a/resources/views/components/services/explanation.blade.php b/resources/views/components/services/explanation.blade.php new file mode 100644 index 000000000..7f8cdbc8c --- /dev/null +++ b/resources/views/components/services/explanation.blade.php @@ -0,0 +1,8 @@ +
+# You can use these variables in your Docker Compose file and Coolify will generate default values or replace them with the values you set on the UI forms.
+#
+# SERVICE_FQDN_*: FQDN - could be changable from the UI. (example: SERVICE_FQDN_GHOST)
+# SERVICE_URL_*: URL parsed from FQDN - could be changable from the UI. (example: SERVICE_URL_GHOST)
+# SERVICE_USER_*: Generated user, not encrypted in database (example: SERVICE_USER_MYSQL)
+# SERVICE_PASSWORD_*: Generated password, encrypted in database (example: SERVICE_PASSWORD_MYSQL)
+
diff --git a/resources/views/livewire/project/new/docker-compose.blade.php b/resources/views/livewire/project/new/docker-compose.blade.php index d6ec91a41..c9823c438 100644 --- a/resources/views/livewire/project/new/docker-compose.blade.php +++ b/resources/views/livewire/project/new/docker-compose.blade.php @@ -4,22 +4,13 @@

Docker Compose

- - Save
-
-# Application generated variables
-# You can use these variables in your docker-compose.yml file and Coolify will create default values or replace them with the values you set in the application creation form.
-# SERVICE_FQDN_*: FQDN coming from your application (https://coolify.io)
-# SERVICE_URL_*: URL coming from your application (coolify.io)
-# SERVICE_USER_*: Generated by your application, username (not encrypted)
-# SERVICE_PASSWORD_*: Generated by your application, password (encrypted)
-        
+ -
  • Select Source Type
  • +
  • Select Resource Type
  • Select a Server
  • Select a Destination
  • @@ -95,7 +95,7 @@ @endif @if ($current_step === 'servers')
      -
    • Select Source Type
    • +
    • Select Resource Type
    • Select a Server
    • Select a Destination
    @@ -123,7 +123,7 @@ @endif @if ($current_step === 'destinations')
      -
    • Select Source Type
    • +
    • Select Resource Type
    • Select a Server
    • Select a Destination
    diff --git a/resources/views/livewire/project/service/application.blade.php b/resources/views/livewire/project/service/application.blade.php index b7e0a62ff..2c098c1c8 100644 --- a/resources/views/livewire/project/service/application.blade.php +++ b/resources/views/livewire/project/service/application.blade.php @@ -1,14 +1,17 @@ -
    +
    @if ($application->human_name)

    {{ Str::headline($application->human_name) }}

    @else

    {{ Str::headline($application->name) }}

    @endif Save + Documentation
    - -
    + @if (isset($application->fqdn)) + + @endisset +
    diff --git a/resources/views/livewire/project/service/database.blade.php b/resources/views/livewire/project/service/database.blade.php index 16f49743c..f01f2bd26 100644 --- a/resources/views/livewire/project/service/database.blade.php +++ b/resources/views/livewire/project/service/database.blade.php @@ -1,11 +1,12 @@
    -
    +
    @if ($database->human_name)

    {{ Str::headline($database->human_name) }}

    @else

    {{ Str::headline($database->name) }}

    @endif Save + Documentation
    diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index cef5d9370..542d27c11 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -2,10 +2,11 @@ + +
    +
    diff --git a/resources/views/livewire/project/shared/storages/show.blade.php b/resources/views/livewire/project/shared/storages/show.blade.php index 23714d74d..5ee4efd33 100644 --- a/resources/views/livewire/project/shared/storages/show.blade.php +++ b/resources/views/livewire/project/shared/storages/show.blade.php @@ -7,21 +7,14 @@ @if ($isReadOnly) - Please modify storage layout in your Compose file. + Please modify storage layout in your
    Docker Compose file. @endif @if ($isReadOnly) -
    - - Update - - - Delete - -
    @else