From f0abdcc2da4f2e7a1f95a19c0462119c365a9828 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 27 Sep 2023 12:45:53 +0200 Subject: [PATCH] okay, now it is way better --- app/Actions/Service/StartService.php | 2 +- .../Livewire/Project/New/DockerCompose.php | 88 +- .../Livewire/Project/Service/Application.php | 14 +- .../Livewire/Project/Service/Database.php | 2 +- .../Livewire/Project/Service/FileStorage.php | 11 +- app/Http/Livewire/Project/Service/Index.php | 43 +- app/Models/Service.php | 1096 +++++++++++------ app/Models/ServiceApplication.php | 10 + app/Models/ServiceDatabase.php | 10 + bootstrap/helpers/services.php | 25 +- bootstrap/helpers/shared.php | 8 + examples/docker-compose-weird.yaml | 76 ++ .../project/new/docker-compose.blade.php | 2 +- .../project/service/application.blade.php | 8 - .../project/service/database.blade.php | 8 - .../project/service/file-storage.blade.php | 12 +- .../livewire/project/service/index.blade.php | 84 +- .../livewire/project/service/show.blade.php | 20 + .../project/shared/storages/show.blade.php | 4 +- tests/Feature/ParseServiceTest.php | 7 + 20 files changed, 1005 insertions(+), 525 deletions(-) create mode 100644 examples/docker-compose-weird.yaml create mode 100644 tests/Feature/ParseServiceTest.php diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index 4ca2b7114..61f6f3731 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -17,7 +17,7 @@ public function handle(Service $service) $commands[] = "echo '####### Pulling images.'"; $commands[] = "docker compose pull"; $commands[] = "echo '####### Starting containers.'"; - $commands[] = "docker compose up -d"; + $commands[] = "docker compose up -d --remove-orphans"; $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 a43fd0c66..ae3a1c08c 100644 --- a/app/Http/Livewire/Project/New/DockerCompose.php +++ b/app/Http/Livewire/Project/New/DockerCompose.php @@ -22,18 +22,82 @@ public function mount() $this->query = request()->query(); if (isDev()) { $this->dockerComposeRaw = 'services: - plausible_events_db: - image: clickhouse/clickhouse-server:23.3.7.5-alpine - restart: always - volumes: - - event-data:/var/lib/clickhouse - - ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro - - ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro - ulimits: - nofile: - soft: 262144 - hard: 262144 -'; + ghost: + image: ghost:5 + volumes: + - ~/configs:/etc/configs/:ro + - ./var/lib/ghost/content:/tmp/ghost2/content:ro + - /var/lib/ghost/content:/tmp/ghost/content:rw + - ghost-content-data:/var/lib/ghost/content + - type: volume + source: mydata + target: /data + volume: + nocopy: true + - type: bind + source: ./var/lib/ghost/data + target: /data + - type: bind + source: /tmp + target: /tmp + labels: + - "test.label=true" + ports: + - "3000" + - "3000-3005" + - "8000:8000" + - "9090-9091:8080-8081" + - "49100:22" + - "127.0.0.1:8001:8001" + - "127.0.0.1:5000-5010:5000-5010" + - "127.0.0.1::5000" + - "6060:6060/udp" + - "12400-12500:1240" + - target: 80 + published: 8080 + protocol: tcp + mode: host + networks: + - some-network + - other-network + environment: + - database__client=${DATABASE_CLIENT:-mysql} + - database__connection__database=${MYSQL_DATABASE:-ghost} + - database__connection__host=${DATABASE_CONNECTION_HOST:-mysql} + - test=${TEST:?true} + - url=$SERVICE_FQDN_GHOST + - database__connection__user=$SERVICE_USER_MYSQL + - database__connection__password=$SERVICE_PASSWORD_MYSQL + depends_on: + - mysql + mysql: + image: mysql:8.0 + volumes: + - ghost-mysql-data:/var/lib/mysql + environment: + - MYSQL_USER=${SERVICE_USER_MYSQL} + - MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL} + - MYSQL_DATABASE=$MYSQL_DATABASE + - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT} + - SESSION_SECRET + minio: + image: minio/minio + environment: + RACK_ENV: development + A: $A + SHOW: ${SHOW} + SHOW1: ${SHOW2-show1} + SHOW2: ${SHOW3:-show2} + SHOW3: ${SHOW4?show3} + SHOW4: ${SHOW5:?show4} + SHOW5: ${SERVICE_USER_MINIO} + SHOW6: ${SERVICE_PASSWORD_MINIO} + SHOW7: ${SERVICE_PASSWORD_64_MINIO} + SHOW8: ${SERVICE_BASE64_64_MINIO} + SHOW9: ${SERVICE_BASE64_128_MINIO} + SHOW10: ${SERVICE_BASE64_MINIO} + SHOW11: + '; } } public function submit() diff --git a/app/Http/Livewire/Project/Service/Application.php b/app/Http/Livewire/Project/Service/Application.php index cf464b442..701a90c19 100644 --- a/app/Http/Livewire/Project/Service/Application.php +++ b/app/Http/Livewire/Project/Service/Application.php @@ -3,15 +3,12 @@ namespace App\Http\Livewire\Project\Service; use App\Models\ServiceApplication; -use Illuminate\Support\Collection; use Livewire\Component; class Application extends Component { public ServiceApplication $application; public $parameters; - public $fileStorages; - protected $listeners = ["refreshFileStorages"]; protected $rules = [ 'application.human_name' => 'nullable', 'application.description' => 'nullable', @@ -28,10 +25,7 @@ public function instantSave() { $this->submit(); } - public function refreshFileStorages() - { - $this->fileStorages = $this->application->fileStorages()->get(); - } + public function delete() { try { @@ -45,18 +39,16 @@ public function delete() public function mount() { $this->parameters = get_route_parameters(); - $this->fileStorages = collect(); - $this->refreshFileStorages(); } public function submit() { try { $this->validate(); $this->application->save(); - switchImage($this->application); + updateCompose($this->application); $this->emit('success', 'Application saved successfully.'); } catch (\Throwable $e) { - ray($e); + return handleError($e, $this); } finally { $this->emit('generateDockerCompose'); } diff --git a/app/Http/Livewire/Project/Service/Database.php b/app/Http/Livewire/Project/Service/Database.php index 0a1b13dbc..e7eab5a3a 100644 --- a/app/Http/Livewire/Project/Service/Database.php +++ b/app/Http/Livewire/Project/Service/Database.php @@ -35,7 +35,7 @@ public function submit() try { $this->validate(); $this->database->save(); - switchImage($this->database); + updateCompose($this->database); $this->emit('success', 'Database saved successfully.'); } catch (\Throwable $e) { ray($e); diff --git a/app/Http/Livewire/Project/Service/FileStorage.php b/app/Http/Livewire/Project/Service/FileStorage.php index 9e80b663b..39e250f66 100644 --- a/app/Http/Livewire/Project/Service/FileStorage.php +++ b/app/Http/Livewire/Project/Service/FileStorage.php @@ -20,13 +20,17 @@ class FileStorage extends Component 'fileStorage.mount_path' => 'required', 'fileStorage.content' => 'nullable', ]; - public function mount() { + public function mount() + { $this->service = $this->fileStorage->service; $this->fs_path = Str::of($this->fileStorage->fs_path)->beforeLast('/'); $file = Str::of($this->fileStorage->fs_path)->afterLast('/'); if (Str::of($this->fs_path)->startsWith('.')) { $this->fs_path = Str::of($this->fs_path)->after('.'); - $this->fs_path = $this->service->service->workdir() . $this->fs_path . "/" .$file; + $this->fs_path = $this->service->service->workdir() . $this->fs_path . "/" . $file; + } + if ($this->fileStorage->is_directory) { + $this->fs_path = Str::of($this->fileStorage->fs_path); } } public function submit() @@ -40,7 +44,8 @@ public function submit() return handleError($e, $this); } } - public function instantSave() { + public function instantSave() + { $this->submit(); } public function render() diff --git a/app/Http/Livewire/Project/Service/Index.php b/app/Http/Livewire/Project/Service/Index.php index 827175bb2..0d44632c7 100644 --- a/app/Http/Livewire/Project/Service/Index.php +++ b/app/Http/Livewire/Project/Service/Index.php @@ -34,19 +34,11 @@ public function refreshStack() { $this->applications = $this->service->applications->sort(); $this->applications->each(function ($application) { - $application->fileStorages()->get()->each(function ($fileStorage) use ($application) { - if (!$fileStorage->is_directory && $fileStorage->content == null) { - $application->hasMissingFiles = true; - } - }); + $application->configuration_required = $application->configurationRequired(); }); $this->databases = $this->service->databases->sort(); $this->databases->each(function ($database) { - $database->fileStorages()->get()->each(function ($fileStorage) use ($database) { - if (!$fileStorage->is_directory && $fileStorage->content == null) { - $database->hasMissingFiles = true; - } - }); + $database->configuration_required = $database->configurationRequired(); }); $this->emit('success', 'Stack refreshed successfully.'); } @@ -61,24 +53,29 @@ public function render() { return view('livewire.project.service.index'); } - public function save() - { - try { - $this->service->save(); - $this->service->parse(); - $this->service->refresh(); - $this->emit('refreshEnvs'); - $this->emit('success', 'Service saved successfully.'); - $this->service->saveComposeConfigs(); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } + // public function save() + // { + // try { + // $this->service->save(); + // $this->service->parse(); + // $this->service->refresh(); + // $this->emit('refreshEnvs'); + // $this->emit('success', 'Service saved successfully.'); + // $this->service->saveComposeConfigs(); + // } catch (\Throwable $e) { + // return handleError($e, $this); + // } + // } public function submit() { try { $this->validate(); $this->service->save(); + $this->service->parse(); + $this->service->refresh(); + $this->service->saveComposeConfigs(); + $this->refreshStack(); + $this->emit('refreshEnvs'); $this->emit('success', 'Service saved successfully.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Models/Service.php b/app/Models/Service.php index b820b4f2d..554ab3464 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -139,11 +139,9 @@ private function generateFqdn($serviceVariables, $serviceName, Collection $confi } return $defaultUsableFqdn ?? null; } - public function parse(bool $isNew = false, ?Collection $configuration = null): Collection + public function parse(bool $isNew = false): Collection { - if (!$configuration) { - $configuration = collect([]); - } + ray()->clearAll(); if ($this->docker_compose_raw) { try { $yaml = Yaml::parse($this->docker_compose_raw); @@ -151,20 +149,160 @@ public function parse(bool $isNew = false, ?Collection $configuration = null): C throw new \Exception($e->getMessage()); } - $composeVolumes = collect(data_get($yaml, 'volumes', [])); - $composeNetworks = collect(data_get($yaml, 'networks', [])); + $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); + $topLevelNetworks = collect(data_get($yaml, 'networks', [])); $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; $services = data_get($yaml, 'services'); $definedNetwork = $this->uuid; - $volumes = collect([]); - $envs = collect([]); - $ports = collect([]); - - $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew, $configuration) { - $container_name = "$serviceName-{$this->uuid}"; - $isDatabase = false; + $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew) { + $serviceVolumes = collect(data_get($service, 'volumes', [])); + $servicePorts = collect(data_get($service, 'ports', [])); + $serviceNetworks = collect(data_get($service, 'networks', [])); $serviceVariables = collect(data_get($service, 'environment', [])); + $serviceLabels = collect(data_get($service, 'labels', [])); + + $containerName = "$serviceName-{$this->uuid}"; + + // Decide if the service is a database + $isDatabase = false; + $image = data_get_str($service, 'image'); + if ($image->contains(':')) { + $image = Str::of($image); + } else { + $image = Str::of($image)->append(':latest'); + } + $imageName = $image->before(':'); + + if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { + $isDatabase = true; + } + data_set($service, 'is_database', $isDatabase); + + // Create new serviceApplication or serviceDatabase + if ($isDatabase) { + if ($isNew) { + $savedService = ServiceDatabase::create([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $this->id + ]); + } else { + $savedService = ServiceDatabase::where([ + 'name' => $serviceName, + 'service_id' => $this->id + ])->first(); + } + } else { + if ($isNew) { + $savedService = ServiceApplication::create([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $this->id + ]); + } else { + $savedService = ServiceApplication::where([ + 'name' => $serviceName, + 'service_id' => $this->id + ])->first(); + } + } + + // Collect/create/update networks + if ($serviceNetworks->count() > 0) { + foreach ($serviceNetworks as $networkName => $networkDetails) { + $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (!$networkExists) { + $topLevelNetworks->put($networkDetails, null); + } + } + } + + // Collect/create/update ports + $collectedPorts = collect([]); + if ($servicePorts->count() > 0) { + foreach ($servicePorts as $sport) { + if (is_string($sport) || is_numeric($sport)) { + $collectedPorts->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $protocol = data_get($sport, 'protocol'); + $collectedPorts->push("$target:$published/$protocol"); + } + } + } + $savedService->ports = $collectedPorts->implode(','); + $savedService->save(); + + // Add Coolify specific networks + $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { + return $value == $definedNetwork; + }); + if (!$definedNetworkExists) { + $topLevelNetworks->put($definedNetwork, [ + 'name' => $definedNetwork, + 'external' => false + ]); + } + $networks = $serviceNetworks->toArray(); + $networks = array_merge($networks, [$definedNetwork]); + data_set($service, 'networks', $networks); + + // Collect/create/update volumes + if ($serviceVolumes->count() > 0) { + foreach ($serviceVolumes as $volume) { + $type = null; + $source = null; + $target = null; + if (is_string($volume)) { + $source = Str::of($volume)->before(':'); + $target = Str::of($volume)->after(':')->beforeLast(':'); + if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) { + $type = Str::of('bind'); + } else { + $type = Str::of('volume'); + } + } else if (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + } + if ($type->value() === 'bind') { + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ] + ); + } else if ($type->value() === 'volume') { + $topLevelVolumes->put($source->value(), null); + LocalPersistentVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ], + [ + 'name' => Str::slug($source, '-'), + 'mount_path' => $target, + 'resource_id' => $savedService->id, + 'resource_type' => get_class($savedService) + ] + ); + } + } + } // Add env_file with at least .env to the service $envFile = collect(data_get($service, 'env_file', [])); @@ -177,430 +315,588 @@ public function parse(bool $isNew = false, ?Collection $configuration = null): C } data_set($service, 'env_file', $envFile->toArray()); - // Decide if the service is a database - $image = data_get($service, 'image'); - if ($image) { - $imageName = Str::of($image)->before(':'); - if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { - $isDatabase = true; - data_set($service, 'is_database', true); - } - } - if ($isDatabase) { - $savedService = ServiceDatabase::where([ - 'name' => $serviceName, - 'service_id' => $this->id - ])->first(); - } else { - $savedService = ServiceApplication::where([ - 'name' => $serviceName, - 'service_id' => $this->id - ])->first(); - } - if ($isNew || is_null($savedService)) { - if ($isDatabase) { - $savedService = ServiceDatabase::create([ - 'name' => $serviceName, - 'image' => $image, - 'service_id' => $this->id - ]); - } else { - $savedService = ServiceApplication::create([ - 'name' => $serviceName, - 'fqdn' => $this->generateFqdn($serviceVariables, $serviceName, $configuration), - 'image' => $image, - 'service_id' => $this->id - ]); - } - if ($configuration->count() > 0) { - foreach ($configuration as $requiredFqdn) { - $requiredFqdn = (array)$requiredFqdn; - $name = data_get($requiredFqdn, 'name'); - if ($serviceName === $name) { - $savedService->required_fqdn = true; - $savedService->save(); - break; - } - } - } - } else { - if ($isDatabase) { - $savedService = $this->databases()->whereName($serviceName)->first(); - } else { - $savedService = $this->applications()->whereName($serviceName)->first(); - if (data_get($savedService, 'fqdn')) { - $defaultUsableFqdn = data_get($savedService, 'fqdn', null); - } else { - $defaultUsableFqdn = $this->generateFqdn($serviceVariables, $serviceName, $configuration); - } - $savedService->fqdn = $defaultUsableFqdn; - $savedService->save(); - } - } - - $fqdns = data_get($savedService, 'fqdn'); - if ($fqdns) { - $fqdns = collect(Str::of($fqdns)->explode(',')); - } - // Collect ports - $servicePorts = collect(data_get($service, 'ports', [])); - $ports->put($serviceName, $servicePorts); - $collectedPorts = collect([]); - if ($servicePorts->count() > 0) { - foreach ($servicePorts as $sport) { - if (is_string($sport) || is_numeric($sport)) { - $collectedPorts->push($sport); - } - if (is_array($sport)) { - $target = data_get($sport, 'target'); - $published = data_get($sport, 'published'); - $collectedPorts->push("$target:$published"); - } - } - } - $savedService->ports = $collectedPorts->implode(','); - $savedService->save(); - - // 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)) { - if (Str::startsWith($volume, './')) { - $fsPath = Str::before($volume, ':'); - $volumePath = Str::of($volume)->after(':')->beforeLast(':'); - LocalFileVolume::updateOrCreate( - [ - 'mount_path' => $volumePath, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ], - [ - 'fs_path' => $fsPath, - 'mount_path' => $volumePath, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ] - ); - $savedService->saveFileVolumes(); - continue; - } - $volumeName = Str::before($volume, ':'); - $volumePath = Str::after($volume, ':'); - } - if (is_array($volume)) { - $volumeName = data_get($volume, 'source'); - $volumePath = data_get($volume, 'target'); - $volumeContent = data_get($volume, 'content'); - if (Str::startsWith($volumeName, './')) { - $payload = [ - 'fs_path' => $volumeName, - 'mount_path' => $volumePath, - - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ]; - if ($volumeContent) { - $payload['content'] = $volumeContent; - } - LocalFileVolume::updateOrCreate( - [ - 'mount_path' => $volumePath, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ], - $payload - ); - if ($volumeContent) { - $volume = data_forget($volume, 'content'); - } - $savedService->saveFileVolumes(); - continue; - } - } - - $volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) { - return $key == $volumeName; - }); - if (!$volumeExists) { - if (Str::startsWith($volumeName, '/')) { - $volumes->put($volumeName, $volumePath); - LocalPersistentVolume::updateOrCreate( - [ - 'mount_path' => $volumePath, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ], - [ - 'name' => Str::slug($volumeName, '-'), - 'mount_path' => $volumePath, - 'host_path' => $volumeName, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ] - ); - } else { - $composeVolumes->put($volumeName, null); - LocalPersistentVolume::updateOrCreate( - [ - 'name' => $volumeName, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ], - [ - 'name' => $volumeName, - 'mount_path' => $volumePath, - 'host_path' => null, - 'resource_id' => $savedService->id, - 'resource_type' => get_class($savedService) - ] - ); - } - } - } - } - - // Collect and add networks - $serviceNetworks = collect(data_get($service, 'networks', [])); - if ($serviceNetworks->count() > 0) { - foreach ($serviceNetworks as $networkName => $networkDetails) { - $networkExists = $composeNetworks->contains(function ($value, $key) use ($networkName) { - return $value == $networkName || $key == $networkName; - }); - if (!$networkExists) { - $composeNetworks->put($networkDetails, null); - } - } - } - // Add Coolify specific networks - $definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) { - return $value == $definedNetwork; - }); - if (!$definedNetworkExists) { - $composeNetworks->put($definedNetwork, [ - 'name' => $definedNetwork, - 'external' => false - ]); - } - $networks = $serviceNetworks->toArray(); - $networks = array_merge($networks, [$definedNetwork]); - data_set($service, 'networks', $networks); - - // Get variables from the service - foreach ($serviceVariables as $variable) { - $value = Str::after($variable, '='); - // if (!Str::of($val)->contains($value)) { - // EnvironmentVariable::updateOrCreate([ - // 'key' => $variable, - // 'service_id' => $this->id, - // ], [ - // 'value' => $val, - // 'is_build_time' => false, - // 'service_id' => $this->id, - // 'is_preview' => false, - // ]); - // continue; - // } - if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { - $value = Str::of(replaceVariables(Str::of($value))); - $nakedName = $nakedValue = null; - if ($value->contains(':')) { - $nakedName = $value->before(':'); - $nakedValue = $value->after(':'); - } else if ($value->contains('-')) { - $nakedName = $value->before('-'); - $nakedValue = $value->after('-'); - } else if ($value->contains('+')) { - $nakedName = $value->before('+'); - $nakedValue = $value->after('+'); + foreach ($serviceVariables as $variableName => $variable) { + if (is_numeric($variableName)) { + $variable = Str::of($variable); + if ($variable->contains('=')) { + // - SESSION_SECRET=123 + // - SESSION_SECRET= + $key = $variable->before('='); + $value = $variable->after('='); } else { - $nakedName = $value; + // - SESSION_SECRET + $key = $variable; + $value = null; } - if (isset($nakedName)) { - if (isset($nakedValue)) { - if ($nakedValue->startsWith('-')) { - $nakedValue = Str::of($nakedValue)->after('-'); - } - if ($nakedValue->startsWith('+')) { - $nakedValue = Str::of($nakedValue)->after('+'); - } - if (!$envs->has($nakedName->value())) { - $envs->put($nakedName->value(), $nakedValue->value()); - EnvironmentVariable::updateOrCreate([ - 'key' => $nakedName->value(), - 'service_id' => $this->id, - ], [ - 'value' => $nakedValue->value(), + } else { + // SESSION_SECRET: 123 + // SESSION_SECRET: + $key = $variableName; + $value = Str::of($variable); + } + if ($value?->startsWith('$')) { + $value = Str::of(replaceVariables($value)); + $key = $value; + $foundEnv = EnvironmentVariable::where([ + 'key' => $key, + 'service_id' => $this->id, + ])->first(); + if ($value->startsWith('SERVICE_')) { + $command = $value->after('SERVICE_')->beforeLast('_'); + $forService = $value->afterLast('_'); + $generatedValue = null; + if ($command->value() === 'FQDN' || $command->value() === 'URL') { + $sslip = $this->sslip($this->server); + $fqdn = "http://$containerName.$sslip"; + if ($foundEnv) { + $fqdn = data_get($foundEnv, 'value'); + } else { + EnvironmentVariable::create([ + 'key' => $key, + 'value' => $fqdn, 'is_build_time' => false, 'service_id' => $this->id, 'is_preview' => false, ]); } - } else { - if (!$envs->has($nakedName->value())) { - $envs->put($nakedName->value(), null); - $envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists(); - if (!$envExists) { - EnvironmentVariable::create([ - 'key' => $nakedName->value(), - 'value' => null, - 'service_id' => $this->id, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - } - } - } - } else { - $variableName = Str::of(replaceVariables(Str::of($value))); - $generatedValue = null; - if ($variableName->startsWith('SERVICE_USER')) { - $variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); - if (!$variableDefined) { - $generatedValue = Str::random(10); - } else { - $generatedValue = $variableDefined->value; - } - if (!$envs->has($variableName->value())) { - $envs->put($variableName->value(), $generatedValue); - EnvironmentVariable::updateOrCreate([ - 'key' => $variableName->value(), - 'service_id' => $this->id, - ], [ - 'value' => $generatedValue, - 'is_build_time' => false, - 'service_id' => $this->id, - 'is_preview' => false, - ]); - } - } else if ($variableName->startsWith('SERVICE_PASSWORD')) { - $variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); - if (!$variableDefined) { - if ($variableName->startsWith('SERVICE_PASSWORD64')) { - $generatedValue = Str::password(length: 64, symbols: false); - } else { - $generatedValue = Str::password(symbols: false); + + if (!$isDatabase) { + $savedService->fqdn = $fqdn; + $savedService->save(); } } else { - $generatedValue = $variableDefined->value; - } - if (!$envs->has($variableName->value())) { - $envs->put($variableName->value(), $generatedValue); - EnvironmentVariable::updateOrCreate([ - 'key' => $variableName->value(), - 'service_id' => $this->id, - ], [ - 'value' => $generatedValue, - 'is_build_time' => false, - 'service_id' => $this->id, - 'is_preview' => false, - ]); - } - } else if ($variableName->startsWith('SERVICE_BASE64')) { - $variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); - $length = Str::of($variableName)->after('SERVICE_BASE64_')->beforeLast('_')->value(); - if (is_numeric($length)) { - $length = (int) $length; - } else { - $length = 1; - } - if (!$variableDefined) { - $generatedValue = base64_encode(Str::password(length: $length, symbols: false)); - } else { - $generatedValue = $variableDefined->value; - } - if (!$envs->has($variableName->value())) { - $envs->put($variableName->value(), $generatedValue); - EnvironmentVariable::updateOrCreate([ - 'key' => $variableName->value(), - 'service_id' => $this->id, - ], [ - 'value' => $generatedValue, - 'is_build_time' => false, - 'service_id' => $this->id, - 'is_preview' => false, - ]); - } - } else if ($variableName->startsWith('SERVICE_FQDN')) { - if ($fqdns) { - $number = Str::of($variableName)->after('SERVICE_FQDN')->afterLast('_')->value(); - if (is_numeric($number)) { - $number = (int) $number - 1; - } else { - $number = 0; + switch ($command) { + case 'PASSWORD': + $generatedValue = Str::password(symbols: false); + break; + case 'PASSWORD_64': + $generatedValue = Str::password(length: 64, symbols: false); + break; + case 'BASE64_64': + $generatedValue = Str::random(64); + break; + case 'BASE64_128': + $generatedValue = Str::random(128); + break; + case 'BASE64': + $generatedValue = Str::random(32); + break; + case 'USER': + $generatedValue = Str::random(16); + break; } - $fqdn = getFqdnWithoutPort(data_get($fqdns, $number, $fqdns->first())); - $environments = collect(data_get($service, 'environment')); - $environments = $environments->map(function ($envValue) use ($value, $fqdn) { - $envValue = Str::of($envValue)->replace($value, $fqdn); - return $envValue->value(); - }); - $service['environment'] = $environments->toArray(); - } - } else if ($variableName->startsWith('SERVICE_URL')) { - if ($fqdns) { - $number = Str::of($variableName)->after('SERVICE_URL')->afterLast('_')->value(); - if (is_numeric($number)) { - $number = (int) $number - 1; - } else { - $number = 0; + + if (!$foundEnv) { + EnvironmentVariable::create([ + 'key' => $key, + 'value' => $generatedValue, + 'is_build_time' => false, + 'service_id' => $this->id, + 'is_preview' => false, + ]); } - $fqdn = getFqdnWithoutPort(data_get($fqdns, $number, $fqdns->first())); - $url = Url::fromString($fqdn)->getHost(); - $environments = collect(data_get($service, 'environment')); - $environments = $environments->map(function ($envValue) use ($value, $url) { - $envValue = Str::of($envValue)->replace($value, $url); - return $envValue->value(); - }); - $service['environment'] = $environments->toArray(); } + } else { + if ($value->contains(':-')) { + $key = $value->before(':'); + $defaultValue = $value->after(':-'); + } else if ($value->contains('-')) { + $key = $value->before('-'); + $defaultValue = $value->after('-'); + } else if ($value->contains(':?')) { + $key = $value->before(':'); + $defaultValue = $value->after(':?'); + } else if ($value->contains('?')) { + $key = $value->before('?'); + $defaultValue = $value->after('?'); + } else { + $key = $value; + $defaultValue = null; + } + EnvironmentVariable::updateOrCreate([ + 'key' => $key, + 'service_id' => $this->id, + ], [ + 'value' => $defaultValue, + 'is_build_time' => false, + 'service_id' => $this->id, + 'is_preview' => false, + ]); } } } // Add labels to the service - $labels = collect(data_get($service, 'labels', [])); - $labels = collect([]); - $labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id)); - if (!$isDatabase) { + $fqdns = collect(data_get($savedService, 'fqdn')); + $defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); + $serviceLabels = $serviceLabels->merge($defaultLabels); + if (!$isDatabase && $fqdns->count() > 0) { if ($fqdns) { - $labels = $labels->merge(fqdnLabelsForTraefik($fqdns, $container_name, true)); + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true)); } } - - data_set($service, 'labels', $labels->toArray()); + data_set($service, 'labels', $serviceLabels->toArray()); data_forget($service, 'is_database'); data_set($service, 'restart', RESTART_MODE); - data_set($service, 'container_name', $container_name); + data_set($service, 'container_name', $containerName); data_forget($service, 'volumes.*.content'); return $service; }); $finalServices = [ 'version' => $dockerComposeVersion, 'services' => $services->toArray(), - 'volumes' => $composeVolumes->toArray(), - 'networks' => $composeNetworks->toArray(), + 'volumes' => $topLevelVolumes->toArray(), + 'networks' => $topLevelNetworks->toArray(), ]; data_forget($yaml, 'services.*.volumes.*.content'); $this->docker_compose_raw = Yaml::dump($yaml, 10, 2); $this->docker_compose = Yaml::dump($finalServices, 10, 2); $this->save(); $this->saveComposeConfigs(); - $shouldBeDefined = collect([ - 'envs' => $envs, - 'volumes' => $volumes, - 'ports' => $ports - ]); - $parsedCompose = collect([ - 'dockerCompose' => $finalServices, - 'shouldBeDefined' => $shouldBeDefined - ]); - return $parsedCompose; + return collect([]); + // $services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew, $configuration) { + // $container_name = "$serviceName-{$this->uuid}"; + // $isDatabase = false; + // $serviceVariables = collect(data_get($service, 'environment', [])); + + // // Add env_file with at least .env to the service + // $envFile = collect(data_get($service, 'env_file', [])); + // if ($envFile->count() > 0) { + // if (!$envFile->contains('.env')) { + // $envFile->push('.env'); + // } + // } else { + // $envFile = collect(['.env']); + // } + // data_set($service, 'env_file', $envFile->toArray()); + + // // Decide if the service is a database + // $image = data_get($service, 'image'); + // if ($image) { + // $imageName = Str::of($image)->before(':'); + // if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { + // $isDatabase = true; + // data_set($service, 'is_database', true); + // } + // } + // if ($isDatabase) { + // $savedService = ServiceDatabase::where([ + // 'name' => $serviceName, + // 'service_id' => $this->id + // ])->first(); + // } else { + // $savedService = ServiceApplication::where([ + // 'name' => $serviceName, + // 'service_id' => $this->id + // ])->first(); + // } + // if ($isNew || is_null($savedService)) { + // if ($isDatabase) { + // $savedService = ServiceDatabase::create([ + // 'name' => $serviceName, + // 'image' => $image, + // 'service_id' => $this->id + // ]); + // } else { + // $savedService = ServiceApplication::create([ + // 'name' => $serviceName, + // 'fqdn' => $this->generateFqdn($serviceVariables, $serviceName, $configuration), + // 'image' => $image, + // 'service_id' => $this->id + // ]); + // } + // if ($configuration->count() > 0) { + // foreach ($configuration as $requiredFqdn) { + // $requiredFqdn = (array)$requiredFqdn; + // $name = data_get($requiredFqdn, 'name'); + // if ($serviceName === $name) { + // $savedService->required_fqdn = true; + // $savedService->save(); + // break; + // } + // } + // } + // } else { + // if ($isDatabase) { + // $savedService = $this->databases()->whereName($serviceName)->first(); + // } else { + // $savedService = $this->applications()->whereName($serviceName)->first(); + // if (data_get($savedService, 'fqdn')) { + // $defaultUsableFqdn = data_get($savedService, 'fqdn', null); + // } else { + // $defaultUsableFqdn = $this->generateFqdn($serviceVariables, $serviceName, $configuration); + // } + // $savedService->fqdn = $defaultUsableFqdn; + // $savedService->save(); + // } + // } + + // $fqdns = data_get($savedService, 'fqdn'); + // if ($fqdns) { + // $fqdns = collect(Str::of($fqdns)->explode(',')); + // } + // // Collect ports + // $servicePorts = collect(data_get($service, 'ports', [])); + // $ports->put($serviceName, $servicePorts); + // $collectedPorts = collect([]); + // if ($servicePorts->count() > 0) { + // foreach ($servicePorts as $sport) { + // if (is_string($sport) || is_numeric($sport)) { + // $collectedPorts->push($sport); + // } + // if (is_array($sport)) { + // $target = data_get($sport, 'target'); + // $published = data_get($sport, 'published'); + // $collectedPorts->push("$target:$published"); + // } + // } + // } + // $savedService->ports = $collectedPorts->implode(','); + // $savedService->save(); + + // // 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)) { + // if (Str::startsWith($volume, './')) { + // $fsPath = Str::before($volume, ':'); + // $volumePath = Str::of($volume)->after(':')->beforeLast(':'); + // LocalFileVolume::updateOrCreate( + // [ + // 'mount_path' => $volumePath, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ], + // [ + // 'fs_path' => $fsPath, + // 'mount_path' => $volumePath, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ] + // ); + // $savedService->saveFileVolumes(); + // continue; + // } + // $volumeName = Str::before($volume, ':'); + // $volumePath = Str::after($volume, ':'); + // } + // if (is_array($volume)) { + // $volumeName = data_get($volume, 'source'); + // $volumePath = data_get($volume, 'target'); + // $volumeContent = data_get($volume, 'content'); + // if (Str::startsWith($volumeName, './')) { + // $payload = [ + // 'fs_path' => $volumeName, + // 'mount_path' => $volumePath, + + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ]; + // if ($volumeContent) { + // $payload['content'] = $volumeContent; + // } + // LocalFileVolume::updateOrCreate( + // [ + // 'mount_path' => $volumePath, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ], + // $payload + // ); + // if ($volumeContent) { + // $volume = data_forget($volume, 'content'); + // } + // $savedService->saveFileVolumes(); + // continue; + // } + // } + + // $volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) { + // return $key == $volumeName; + // }); + // if (!$volumeExists) { + // if (Str::startsWith($volumeName, '/')) { + // $volumes->put($volumeName, $volumePath); + // LocalPersistentVolume::updateOrCreate( + // [ + // 'mount_path' => $volumePath, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ], + // [ + // 'name' => Str::slug($volumeName, '-'), + // 'mount_path' => $volumePath, + // 'host_path' => $volumeName, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ] + // ); + // } else { + // $composeVolumes->put($volumeName, null); + // LocalPersistentVolume::updateOrCreate( + // [ + // 'name' => $volumeName, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ], + // [ + // 'name' => $volumeName, + // 'mount_path' => $volumePath, + // 'host_path' => null, + // 'resource_id' => $savedService->id, + // 'resource_type' => get_class($savedService) + // ] + // ); + // } + // } + // } + // } + + // // Collect and add networks + // $serviceNetworks = collect(data_get($service, 'networks', [])); + // if ($serviceNetworks->count() > 0) { + // foreach ($serviceNetworks as $networkName => $networkDetails) { + // $networkExists = $composeNetworks->contains(function ($value, $key) use ($networkName) { + // return $value == $networkName || $key == $networkName; + // }); + // if (!$networkExists) { + // $composeNetworks->put($networkDetails, null); + // } + // } + // } + // // Add Coolify specific networks + // $definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) { + // return $value == $definedNetwork; + // }); + // if (!$definedNetworkExists) { + // $composeNetworks->put($definedNetwork, [ + // 'name' => $definedNetwork, + // 'external' => false + // ]); + // } + // $networks = $serviceNetworks->toArray(); + // $networks = array_merge($networks, [$definedNetwork]); + // data_set($service, 'networks', $networks); + + + + // // Get variables from the service + // foreach ($serviceVariables as $variable) { + // $value = Str::after($variable, '='); + // // if (!Str::of($val)->contains($value)) { + // // EnvironmentVariable::updateOrCreate([ + // // 'key' => $variable, + // // 'service_id' => $this->id, + // // ], [ + // // 'value' => $val, + // // 'is_build_time' => false, + // // 'service_id' => $this->id, + // // 'is_preview' => false, + // // ]); + // // continue; + // // } + // if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { + // $value = Str::of(replaceVariables(Str::of($value))); + // $nakedName = $nakedValue = null; + // if ($value->contains(':')) { + // $nakedName = $value->before(':'); + // $nakedValue = $value->after(':'); + // } else if ($value->contains('-')) { + // $nakedName = $value->before('-'); + // $nakedValue = $value->after('-'); + // } else if ($value->contains('+')) { + // $nakedName = $value->before('+'); + // $nakedValue = $value->after('+'); + // } else { + // $nakedName = $value; + // } + // if (isset($nakedName)) { + // if (isset($nakedValue)) { + // if ($nakedValue->startsWith('-')) { + // $nakedValue = Str::of($nakedValue)->after('-'); + // } + // if ($nakedValue->startsWith('+')) { + // $nakedValue = Str::of($nakedValue)->after('+'); + // } + // if (!$envs->has($nakedName->value())) { + // $envs->put($nakedName->value(), $nakedValue->value()); + // EnvironmentVariable::updateOrCreate([ + // 'key' => $nakedName->value(), + // 'service_id' => $this->id, + // ], [ + // 'value' => $nakedValue->value(), + // 'is_build_time' => false, + // 'service_id' => $this->id, + // 'is_preview' => false, + // ]); + // } + // } else { + // if (!$envs->has($nakedName->value())) { + // $envs->put($nakedName->value(), null); + // $envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists(); + // if (!$envExists) { + // EnvironmentVariable::create([ + // 'key' => $nakedName->value(), + // 'value' => null, + // 'service_id' => $this->id, + // 'is_build_time' => false, + // 'is_preview' => false, + // ]); + // } + // } + // } + // } + // } else { + // $variableName = Str::of(replaceVariables(Str::of($value))); + // $generatedValue = null; + // if ($variableName->startsWith('SERVICE_USER')) { + // $variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); + // if (!$variableDefined) { + // $generatedValue = Str::random(10); + // } else { + // $generatedValue = $variableDefined->value; + // } + // if (!$envs->has($variableName->value())) { + // $envs->put($variableName->value(), $generatedValue); + // EnvironmentVariable::updateOrCreate([ + // 'key' => $variableName->value(), + // 'service_id' => $this->id, + // ], [ + // 'value' => $generatedValue, + // 'is_build_time' => false, + // 'service_id' => $this->id, + // 'is_preview' => false, + // ]); + // } + // } else if ($variableName->startsWith('SERVICE_PASSWORD')) { + // $variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); + // if (!$variableDefined) { + // if ($variableName->startsWith('SERVICE_PASSWORD64')) { + // $generatedValue = Str::password(length: 64, symbols: false); + // } else { + // $generatedValue = Str::password(symbols: false); + // } + // } else { + // $generatedValue = $variableDefined->value; + // } + // if (!$envs->has($variableName->value())) { + // $envs->put($variableName->value(), $generatedValue); + // EnvironmentVariable::updateOrCreate([ + // 'key' => $variableName->value(), + // 'service_id' => $this->id, + // ], [ + // 'value' => $generatedValue, + // 'is_build_time' => false, + // 'service_id' => $this->id, + // 'is_preview' => false, + // ]); + // } + // } else if ($variableName->startsWith('SERVICE_BASE64')) { + // $variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); + // $length = Str::of($variableName)->after('SERVICE_BASE64_')->beforeLast('_')->value(); + // if (is_numeric($length)) { + // $length = (int) $length; + // } else { + // $length = 1; + // } + // if (!$variableDefined) { + // $generatedValue = base64_encode(Str::password(length: $length, symbols: false)); + // } else { + // $generatedValue = $variableDefined->value; + // } + // if (!$envs->has($variableName->value())) { + // $envs->put($variableName->value(), $generatedValue); + // EnvironmentVariable::updateOrCreate([ + // 'key' => $variableName->value(), + // 'service_id' => $this->id, + // ], [ + // 'value' => $generatedValue, + // 'is_build_time' => false, + // 'service_id' => $this->id, + // 'is_preview' => false, + // ]); + // } + // } else if ($variableName->startsWith('SERVICE_FQDN')) { + // if ($fqdns) { + // $number = Str::of($variableName)->after('SERVICE_FQDN')->afterLast('_')->value(); + // if (is_numeric($number)) { + // $number = (int) $number - 1; + // } else { + // $number = 0; + // } + // $fqdn = getFqdnWithoutPort(data_get($fqdns, $number, $fqdns->first())); + // $environments = collect(data_get($service, 'environment')); + // $environments = $environments->map(function ($envValue) use ($value, $fqdn) { + // $envValue = Str::of($envValue)->replace($value, $fqdn); + // return $envValue->value(); + // }); + // $service['environment'] = $environments->toArray(); + // } + // } else if ($variableName->startsWith('SERVICE_URL')) { + // if ($fqdns) { + // $number = Str::of($variableName)->after('SERVICE_URL')->afterLast('_')->value(); + // if (is_numeric($number)) { + // $number = (int) $number - 1; + // } else { + // $number = 0; + // } + // $fqdn = getFqdnWithoutPort(data_get($fqdns, $number, $fqdns->first())); + // $url = Url::fromString($fqdn)->getHost(); + // $environments = collect(data_get($service, 'environment')); + // $environments = $environments->map(function ($envValue) use ($value, $url) { + // $envValue = Str::of($envValue)->replace($value, $url); + // return $envValue->value(); + // }); + // $service['environment'] = $environments->toArray(); + // } + // } + // } + // } + + // // Add labels to the service + // $labels = collect(data_get($service, 'labels', [])); + // $labels = collect([]); + // $labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id)); + // if (!$isDatabase) { + // if ($fqdns) { + // $labels = $labels->merge(fqdnLabelsForTraefik($fqdns, $container_name, true)); + // } + // } + + + // data_set($service, 'labels', $labels->toArray()); + // data_forget($service, 'is_database'); + // data_set($service, 'restart', RESTART_MODE); + // data_set($service, 'container_name', $container_name); + // data_forget($service, 'volumes.*.content'); + // return $service; + // }); + // $finalServices = [ + // 'version' => $dockerComposeVersion, + // 'services' => $services->toArray(), + // 'volumes' => $composeVolumes->toArray(), + // 'networks' => $composeNetworks->toArray(), + // ]; + // data_forget($yaml, 'services.*.volumes.*.content'); + // $this->docker_compose_raw = Yaml::dump($yaml, 10, 2); + // $this->docker_compose = Yaml::dump($finalServices, 10, 2); + // $this->save(); + // $this->saveComposeConfigs(); + // $shouldBeDefined = collect([ + // 'envs' => $envs, + // 'volumes' => $volumes, + // 'ports' => $ports + // ]); + // $parsedCompose = collect([ + // 'dockerCompose' => $finalServices, + // 'shouldBeDefined' => $shouldBeDefined + // ]); + // return $parsedCompose; } else { return collect([]); } diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 91bfc7d78..125c6e51d 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -30,4 +30,14 @@ public function saveFileVolumes() { saveFileVolumesHelper($this); } + public function configurationRequired() { + $required = false; + foreach($this->fileStorages as $fileStorage) { + if (!$fileStorage->is_directory && $fileStorage->content == null) { + $required = true; + break; + } + } + return $required; + } } diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 09b216219..c73d641f2 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -30,4 +30,14 @@ public function saveFileVolumes() { saveFileVolumesHelper($this); } + public function configurationRequired() { + $required = false; + foreach($this->fileStorages as $fileStorage) { + if (!$fileStorage->is_directory && $fileStorage->content == null) { + $required = true; + break; + } + } + return $required; + } } diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 830c17b39..4e6450cab 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -1,5 +1,6 @@ name)->upper(); + $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + if ($generatedEnv){ + $generatedEnv->value = $resource->fqdn; + $generatedEnv->save(); + } + + // // Update URL + // $variableName = "SERVICE_URL_" . Str::of($resource->name)->upper(); + // $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + // if ($generatedEnv){ + // $generatedEnv->value = $resource->url; + // $generatedEnv->save(); + // } + $dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2); $resource->service->docker_compose_raw = $dockerComposeRaw; $resource->service->save(); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 803ff7062..a1610cb2f 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -11,11 +11,13 @@ use Illuminate\Database\QueryException; use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; +use Illuminate\Support\Stringable; use Nubs\RandomNameGenerator\All; use Poliander\Cron\CronExpression; use Visus\Cuid2\Cuid2; @@ -385,3 +387,9 @@ function parseEnvFormatToArray($env_file_contents) } return $env_array; } + +function data_get_str($data, $key, $default = null): Stringable +{ + $str = data_get($data, $key, $default) ?? $default; + return Str::of($str); +} diff --git a/examples/docker-compose-weird.yaml b/examples/docker-compose-weird.yaml new file mode 100644 index 000000000..961007a9f --- /dev/null +++ b/examples/docker-compose-weird.yaml @@ -0,0 +1,76 @@ +services: + ghost: + image: ghost:5 + volumes: + - ~/configs:/etc/configs/:ro + - ./var/lib/ghost/content:/tmp/ghost2/content:ro + - /var/lib/ghost/content:/tmp/ghost/content:rw + - ghost-content-data:/var/lib/ghost/content + - type: volume + source: mydata + target: /data + volume: + nocopy: true + - type: bind + source: ./var/lib/ghost/data + target: /data + - type: bind + source: /tmp + target: /tmp + labels: + - "test.label=true" + ports: + - "3000" + - "3000-3005" + - "8000:8000" + - "9090-9091:8080-8081" + - "49100:22" + - "127.0.0.1:8001:8001" + - "127.0.0.1:5000-5010:5000-5010" + - "127.0.0.1::5000" + - "6060:6060/udp" + - "12400-12500:1240" + - target: 80 + published: 8080 + protocol: tcp + mode: host + networks: + - some-network + - other-network + environment: + - database__client=${DATABASE_CLIENT:-mysql} + - database__connection__database=${MYSQL_DATABASE:-ghost} + - database__connection__host=${DATABASE_CONNECTION_HOST:-mysql} + - test=${TEST:?true} + - url=$SERVICE_FQDN_GHOST + - database__connection__user=$SERVICE_USER_MYSQL + - database__connection__password=$SERVICE_PASSWORD_MYSQL + depends_on: + - mysql + mysql: + image: mysql:8.0 + volumes: + - ghost-mysql-data:/var/lib/mysql + environment: + - MYSQL_USER=${SERVICE_USER_MYSQL} + - MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL} + - MYSQL_DATABASE=$MYSQL_DATABASE + - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT} + - SESSION_SECRET + minio: + image: minio/minio + environment: + RACK_ENV: development + A: $A + SHOW: ${SHOW} + SHOW1: ${SHOW2-show1} + SHOW2: ${SHOW3:-show2} + SHOW3: ${SHOW4?show3} + SHOW4: ${SHOW5:?show4} + SHOW5: ${SERVICE_USER_MINIO} + SHOW6: ${SERVICE_PASSWORD_MINIO} + SHOW7: ${SERVICE_PASSWORD_64_MINIO} + SHOW8: ${SERVICE_BASE64_64_MINIO} + SHOW9: ${SERVICE_BASE64_128_MINIO} + SHOW10: ${SERVICE_BASE64_MINIO} + SHOW11: diff --git a/resources/views/livewire/project/new/docker-compose.blade.php b/resources/views/livewire/project/new/docker-compose.blade.php index 2ec301b6b..404ef4498 100644 --- a/resources/views/livewire/project/new/docker-compose.blade.php +++ b/resources/views/livewire/project/new/docker-compose.blade.php @@ -44,6 +44,6 @@ - MYSQL_DATABASE=${MYSQL_DATABASE} - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT} '> - + {{-- --}} diff --git a/resources/views/livewire/project/service/application.blade.php b/resources/views/livewire/project/service/application.blade.php index d9c0a5f30..bf3501496 100644 --- a/resources/views/livewire/project/service/application.blade.php +++ b/resources/views/livewire/project/service/application.blade.php @@ -36,12 +36,4 @@ id="application.exclude_from_status"> - @if ($fileStorages->count() > 0) -

Mounted Files (binds)

-
- @foreach ($fileStorages as $fileStorage) - - @endforeach -
- @endif diff --git a/resources/views/livewire/project/service/database.blade.php b/resources/views/livewire/project/service/database.blade.php index 6bc2c06dd..673d85268 100644 --- a/resources/views/livewire/project/service/database.blade.php +++ b/resources/views/livewire/project/service/database.blade.php @@ -25,12 +25,4 @@ id="database.exclude_from_status"> - @if ($fileStorages->count() > 0) -

Mounted Files (binds)

-
- @foreach ($fileStorages as $fileStorage) - - @endforeach -
- @endif diff --git a/resources/views/livewire/project/service/file-storage.blade.php b/resources/views/livewire/project/service/file-storage.blade.php index ab90058bf..843b75b0e 100644 --- a/resources/views/livewire/project/service/file-storage.blade.php +++ b/resources/views/livewire/project/service/file-storage.blade.php @@ -1,8 +1,8 @@
{{ $fileStorage->mount_path }} - @if(is_null($fileStorage->content) && !$fileStorage->is_directory) - (empty) + @if (is_null($fileStorage->content) && !$fileStorage->is_directory) + (required) @endif
@@ -11,11 +11,13 @@
- - @if (!$fileStorage->is_directory) + + @if ($fileStorage->is_directory) + + @else
- +
diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index d19e492a3..926a29130 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -6,8 +6,6 @@ Service Stack - Compose File Environment @@ -18,10 +16,22 @@
-
+
-

Configuration

+
+

Service Stack

+
Configuration
+
Save + Refresh Status +
+ Show Deployable + Compose +
+
+ Show Source + Compose +
@@ -29,12 +39,26 @@ placeholder="My super wordpress site" />
+
+ + +
+
+ + +
-
-

Service Stack

- Refresh -
-
-
-
-
-

Docker Compose

-
- Show Deployable - Save -
-
- Show Source - Save -
-
- -
- - -
-
- - -
-
diff --git a/resources/views/livewire/project/service/show.blade.php b/resources/views/livewire/project/service/show.blade.php index eba246615..3a9145711 100644 --- a/resources/views/livewire/project/service/show.blade.php +++ b/resources/views/livewire/project/service/show.blade.php @@ -10,6 +10,9 @@ @click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General Storages + @if ($serviceApplication?->configurationRequired() || $serviceDatabase?->configurationRequired()) + (?) + @endif
@@ -19,14 +22,31 @@
+ @if ($serviceApplication->fileStorages()->get()->count() > 0) +

Mounted Files (binds)

+
+ @foreach ($serviceApplication->fileStorages()->get() as $fileStorage) + + @endforeach +
+ @endif
@endisset @isset($serviceDatabase)
+
+ @if ($serviceDatabase->fileStorages()->get()->count() > 0) +

Mounted Files (binds)

+
+ @foreach ($serviceDatabase->fileStorages()->get() as $fileStorage) + + @endforeach +
+ @endif
@endisset
diff --git a/resources/views/livewire/project/shared/storages/show.blade.php b/resources/views/livewire/project/shared/storages/show.blade.php index 3d60c27ae..578ce1607 100644 --- a/resources/views/livewire/project/shared/storages/show.blade.php +++ b/resources/views/livewire/project/shared/storages/show.blade.php @@ -6,10 +6,10 @@ reversible.
Please think again.

- @if ($isReadOnly) + @once ($isReadOnly) Please modify storage layout in your Docker Compose file. - @endif + @endonce
@if ($isReadOnly) diff --git a/tests/Feature/ParseServiceTest.php b/tests/Feature/ParseServiceTest.php new file mode 100644 index 000000000..a48119799 --- /dev/null +++ b/tests/Feature/ParseServiceTest.php @@ -0,0 +1,7 @@ +toBe(3); + });