coolify/app/Models/Service.php

364 lines
17 KiB
PHP
Raw Normal View History

2023-09-20 15:42:41 +02:00
<?php
namespace App\Models;
2023-09-21 17:48:31 +02:00
use App\Enums\ProxyTypes;
2023-09-20 15:42:41 +02:00
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Str;
class Service extends BaseModel
{
use HasFactory;
protected $guarded = [];
public function persistentStorages()
{
return $this->morphMany(LocalPersistentVolume::class, 'resource');
}
public function portsMappingsArray(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->ports_mappings)
? []
: explode(',', $this->ports_mappings),
);
}
public function portsExposesArray(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->ports_exposes)
? []
: explode(',', $this->ports_exposes)
);
}
public function applications()
{
return $this->hasMany(ServiceApplication::class);
}
public function databases()
{
return $this->hasMany(ServiceDatabase::class);
}
2023-09-21 17:48:31 +02:00
public function environment()
{
return $this->belongsTo(Environment::class);
}
public function server() {
return $this->belongsTo(Server::class);
}
public function byName(string $name)
{
$app = $this->applications()->whereName($name)->first();
if ($app) {
return $app;
}
$db = $this->databases()->whereName($name)->first();
if ($db) {
return $db;
}
return null;
}
2023-09-20 15:42:41 +02:00
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
}
2023-09-21 17:48:31 +02:00
public function parse(bool $isNew = false): Collection
2023-09-20 15:42:41 +02:00
{
2023-09-21 17:48:31 +02:00
// ray()->clearAll();
ray('Service parse');
2023-09-20 15:42:41 +02:00
if ($this->docker_compose_raw) {
$yaml = Yaml::parse($this->docker_compose_raw);
$composeVolumes = collect(data_get($yaml, 'volumes', []));
$composeNetworks = collect(data_get($yaml, 'networks', []));
2023-09-21 17:48:31 +02:00
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
2023-09-20 15:42:41 +02:00
$services = data_get($yaml, 'services');
2023-09-21 17:48:31 +02:00
$definedNetwork = $this->uuid;
2023-09-20 15:42:41 +02:00
$volumes = collect([]);
$envs = collect([]);
$ports = collect([]);
2023-09-21 17:48:31 +02:00
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) {
$container_name = "$serviceName-{$this->uuid}";
2023-09-20 15:42:41 +02:00
$isDatabase = false;
// 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);
}
}
2023-09-21 17:48:31 +02:00
if ($isNew) {
2023-09-20 15:42:41 +02:00
if ($isDatabase) {
$savedService = ServiceDatabase::create([
'name' => $serviceName,
'service_id' => $this->id
]);
} else {
2023-09-21 17:48:31 +02:00
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
if (isDev()) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
}
2023-09-20 15:42:41 +02:00
$savedService = ServiceApplication::create([
'name' => $serviceName,
2023-09-21 17:48:31 +02:00
'fqdn' => $defaultUsableFqdn,
2023-09-20 15:42:41 +02:00
'service_id' => $this->id
]);
}
2023-09-21 17:48:31 +02:00
} else {
if ($isDatabase) {
$savedService = $this->databases()->whereName($serviceName)->first();
} else {
$savedService = $this->applications()->whereName($serviceName)->first();
}
2023-09-20 15:42:41 +02:00
}
2023-09-21 17:48:31 +02:00
$fqdn = data_get($savedService, 'fqdn');
2023-09-20 15:42:41 +02:00
// Collect ports
$servicePorts = collect(data_get($service, 'ports', []));
$ports->put($serviceName, $servicePorts);
2023-09-21 17:48:31 +02:00
if ($isNew) {
$ports = collect([]);
if ($servicePorts->count() > 0) {
foreach ($servicePorts as $sport) {
if (is_string($sport)) {
$ports->push($sport);
}
if (is_array($sport)) {
$target = data_get($sport, 'target');
$published = data_get($sport, 'published');
$ports->push("$target:$published");
}
}
}
$savedService->ports_exposes = $ports->implode(',');
2023-09-20 15:42:41 +02:00
$savedService->save();
}
// Collect volumes
$serviceVolumes = collect(data_get($service, 'volumes', []));
if ($serviceVolumes->count() > 0) {
foreach ($serviceVolumes as $volume) {
if (is_string($volume)) {
$volumeName = Str::before($volume, ':');
$volumePath = Str::after($volume, ':');
}
if (is_array($volume)) {
$volumeName = data_get($volume, 'source');
$volumePath = data_get($volume, 'target');
}
$volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) {
return $key == $volumeName;
});
if (!$volumeExists) {
if (!Str::startsWith($volumeName, '/')) {
$composeVolumes->put($volumeName, null);
}
$volumes->put($volumeName, $volumePath);
2023-09-21 17:48:31 +02:00
if ($isNew) {
2023-09-20 15:42:41 +02:00
LocalPersistentVolume::create([
'name' => $volumeName,
'mount_path' => $volumePath,
'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) {
2023-09-21 17:48:31 +02:00
$composeNetworks->put($networkDetails, null);
2023-09-20 15:42:41 +02:00
}
}
}
// Add Coolify specific networks
$definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (!$definedNetworkExists) {
$composeNetworks->put($definedNetwork, [
2023-09-21 17:48:31 +02:00
'name' => $definedNetwork,
'external' => false
2023-09-20 15:42:41 +02:00
]);
}
2023-09-21 17:48:31 +02:00
$networks = $serviceNetworks->toArray();
$networks = array_merge($networks, [$definedNetwork]);
data_set($service, 'networks', $networks);
2023-09-20 15:42:41 +02:00
2023-09-21 17:48:31 +02:00
// Get variables from the service
2023-09-20 15:42:41 +02:00
$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, '$')) {
$value = Str::of(replaceVariables(Str::of($value)));
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());
2023-09-21 17:48:31 +02:00
if ($isNew) {
2023-09-20 15:42:41 +02:00
EnvironmentVariable::create([
'key' => $nakedName->value(),
'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);
2023-09-21 17:48:31 +02:00
if ($isNew) {
2023-09-20 15:42:41 +02:00
EnvironmentVariable::create([
'key' => $nakedName->value(),
'value' => null,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
}
}
}
}
} else {
2023-09-21 17:48:31 +02:00
$variableName = Str::of(replaceVariables(Str::of($value)));
2023-09-20 15:42:41 +02:00
$generatedValue = null;
2023-09-21 17:48:31 +02:00
if ($variableName->startsWith('SERVICE_USER')) {
2023-09-20 15:42:41 +02:00
$generatedValue = Str::random(10);
2023-09-21 17:48:31 +02:00
if ($isNew) {
if (!$envs->has($variableName->value())) {
$envs->put($variableName->value(), $generatedValue);
2023-09-20 15:42:41 +02:00
EnvironmentVariable::create([
2023-09-21 17:48:31 +02:00
'key' => $variableName->value(),
2023-09-20 15:42:41 +02:00
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
}
}
2023-09-21 17:48:31 +02:00
} else if ($variableName->startsWith('SERVICE_PASSWORD')) {
2023-09-20 15:42:41 +02:00
$generatedValue = Str::password(symbols: false);
2023-09-21 17:48:31 +02:00
if ($isNew) {
if (!$envs->has($variableName->value())) {
$envs->put($variableName->value(), $generatedValue);
2023-09-20 15:42:41 +02:00
EnvironmentVariable::create([
2023-09-21 17:48:31 +02:00
'key' => $variableName->value(),
2023-09-20 15:42:41 +02:00
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
}
}
2023-09-21 17:48:31 +02:00
} else if ($variableName->startsWith('SERVICE_FQDN')) {
if ($fqdn) {
$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 ($fqdn) {
$url = Str::of($fqdn)->after('https://')->before('/');
$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();
}
2023-09-20 15:42:41 +02:00
}
}
}
2023-09-21 17:48:31 +02:00
if ($this->server->proxyType() === ProxyTypes::TRAEFIK_V2->value) {
$labels = collect(data_get($service, 'labels', []));
$labels = collect([]);
$labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service'));
if (!$isDatabase) {
if ($fqdn) {
$labels = $labels->merge(fqdnLabelsForTraefik($fqdn, $container_name, true));
}
}
data_set($service, 'labels', $labels->toArray());
}
2023-09-20 15:42:41 +02:00
data_forget($service, 'is_database');
2023-09-21 17:48:31 +02:00
data_set($service, 'restart', RESTART_MODE);
data_set($service, 'container_name', $container_name);
2023-09-20 15:42:41 +02:00
data_forget($service, 'documentation');
return $service;
});
2023-09-21 17:48:31 +02:00
// $services = $services->map(function ($service, $serviceName) {
// $dependsOn = collect(data_get($service, 'depends_on', []));
// $dependsOn = $dependsOn->map(function ($value) {
// return "$value-{$this->uuid}";
// });
// data_set($service, 'depends_on', $dependsOn->toArray());
// return $service;
// });
// $renamedServices = collect([]);
// collect($services)->map(function ($service, $serviceName) use ($renamedServices) {
// $newServiceName = "$serviceName-$this->uuid";
// $renamedServices->put($newServiceName, $service);
// });
2023-09-20 15:42:41 +02:00
2023-09-21 17:48:31 +02:00
$finalServices = [
'version' => $dockerComposeVersion,
'services' => $services->toArray(),
'volumes' => $composeVolumes->toArray(),
'networks' => $composeNetworks->toArray(),
];
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->save();
2023-09-20 15:42:41 +02:00
$shouldBeDefined = collect([
'envs' => $envs,
'volumes' => $volumes,
'ports' => $ports
]);
$parsedCompose = collect([
2023-09-21 17:48:31 +02:00
'dockerCompose' => $finalServices,
2023-09-20 15:42:41 +02:00
'shouldBeDefined' => $shouldBeDefined
]);
return $parsedCompose;
} else {
return collect([]);
}
}
}