puh, fixes

This commit is contained in:
Andras Bacsai
2023-09-26 14:45:52 +02:00
parent 03c9793d11
commit fabb97330a
32 changed files with 386 additions and 127 deletions

View File

@@ -12,6 +12,7 @@ class StartService
{
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
$commands[] = "echo '####### Pulling images.'";
$commands[] = "docker compose pull";

View File

@@ -69,7 +69,12 @@ class ProjectController extends Controller
if ($type->startsWith('one-click-service-')) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
$oneClickDotEnvs = collect(data_get($services, "$oneClickServiceName.envs", []));
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
$oneClickRequiredFqdn = data_get($services, "$oneClickServiceName.generateFqdn", []);
$oneClickRequiredFqdn = collect($oneClickRequiredFqdn);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
}
if ($oneClickService) {
$service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10),
@@ -77,18 +82,37 @@ class ProjectController extends Controller
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
if ($oneClickDotEnvs->count() > 0) {
$oneClickDotEnvs->each(function ($value, $key) use ($service) {
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs && $oneClickDotEnvs->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = Str::before($value, '=');
$value = Str::of(Str::after($value, '='));
if ($value->contains('SERVICE_USER')) {
$value = Str::of(Str::random(10));
}
if ($value->contains('SERVICE_PASSWORD')) {
$value = Str::of(Str::password(symbols: false));
}
if ($value->contains('SERVICE_BASE64')) {
$length = Str::of($value)->after('SERVICE_BASE64_')->beforeLast('_')->value();
if (is_numeric($length)) {
$length = (int) $length;
} else {
$length = 1;
}
$value = Str::of(base64_encode(Str::password(length: $length, symbols: false)));
}
EnvironmentVariable::create([
'key' => $key,
'value' => $value,
'value' => $value->value(),
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);
$service->parse(isNew: true, requiredFqdns: $oneClickRequiredFqdn);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Project\New;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Service;
use Livewire\Component;
@@ -11,6 +12,7 @@ use Symfony\Component\Yaml\Yaml;
class DockerCompose extends Component
{
public string $dockerComposeRaw = '';
public string $envFile = '';
public array $parameters;
public array $query;
public function mount()
@@ -45,13 +47,22 @@ class DockerCompose extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$service = Service::create([
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) {
EnvironmentVariable::create([
'key' => $key,
'value' => $variable,
'is_build_time' => false,
'is_preview' => false,
'service_id' => $service->id,
]);
}
$service->name = "service-$service->uuid";
$service->parse(isNew: true);
@@ -64,6 +75,5 @@ class DockerCompose extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -29,6 +29,7 @@ class Select extends Component
protected $queryString = [
'server',
];
public function mount()
{
$this->parameters = get_route_parameters();
@@ -46,6 +47,7 @@ class Select extends Component
// return handleError($e, $this);
// }
// }
public function loadThings()
{
$this->loadServices();
@@ -77,7 +79,6 @@ class Select extends Component
});
}
$this->services = $cached;
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);

View File

@@ -3,26 +3,29 @@
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 = null;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [
'application.human_name' => 'nullable',
'application.description' => 'nullable',
'application.fqdn' => 'nullable',
'application.image_tag' => 'required',
'application.ignore_from_status' => 'required|boolean',
'application.image' => 'required',
'application.exclude_from_status' => 'required|boolean',
'application.required_fqdn' => 'required|boolean',
];
public function render()
{
return view('livewire.project.service.application');
}
public function instantSave() {
public function instantSave()
{
$this->submit();
}
public function refreshFileStorages()
@@ -42,6 +45,7 @@ class Application extends Component
public function mount()
{
$this->parameters = get_route_parameters();
$this->fileStorages = collect();
$this->refreshFileStorages();
}
public function submit()
@@ -49,6 +53,7 @@ class Application extends Component
try {
$this->validate();
$this->application->save();
switchImage($this->application);
$this->emit('success', 'Application saved successfully.');
} catch (\Throwable $e) {
ray($e);

View File

@@ -8,25 +8,34 @@ use Livewire\Component;
class Database extends Component
{
public ServiceDatabase $database;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [
'database.human_name' => 'nullable',
'database.description' => 'nullable',
'database.image_tag' => 'required',
'database.ignore_from_status' => 'required|boolean',
'database.image' => 'required',
'database.exclude_from_status' => 'required|boolean',
];
public function render()
{
return view('livewire.project.service.database');
}
public function mount() {
$this->refreshFileStorages();
}
public function instantSave() {
$this->submit();
}
public function refreshFileStorages()
{
$this->fileStorages = $this->database->fileStorages()->get();
}
public function submit()
{
try {
$this->validate();
$this->database->save();
switchImage($this->database);
$this->emit('success', 'Database saved successfully.');
} catch (\Throwable $e) {
ray($e);

View File

@@ -15,6 +15,7 @@ class FileStorage extends Component
public string $fs_path;
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable',
@@ -39,6 +40,9 @@ class FileStorage extends Component
return handleError($e, $this);
}
}
public function instantSave() {
$this->submit();
}
public function render()
{
return view('livewire.project.service.file-storage');

View File

@@ -3,32 +3,57 @@
namespace App\Http\Livewire\Project\Service;
use App\Models\Service;
use App\Models\ServiceApplication;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component;
class Index extends Component
{
use WithRateLimiting;
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
public function manualRefreshStack() {
try {
$this->rateLimit(5);
$this->refreshStack();
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
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;
}
});
});
$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;
}
});
});
$this->emit('success', 'Stack refreshed successfully.');
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
$this->refreshStack();
}
public function render()
{
@@ -43,7 +68,7 @@ class Index extends Component
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
$this->service->saveComposeConfigs();
} catch(\Throwable $e) {
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

View File

@@ -39,5 +39,6 @@ class Navbar extends Component
{
StopService::run($this->service);
$this->service->refresh();
$this->emit('success', 'Service stopped successfully.');
}
}

View File

@@ -20,6 +20,7 @@ class Danger extends Component
public function delete()
{
// Should be queued
try {
if ($this->resource->type() === 'service') {
$server = $this->resource->server;

View File

@@ -9,13 +9,13 @@ class Add extends Component
public $parameters;
public bool $is_preview = false;
public string $key;
public string $value;
public ?string $value = null;
public bool $is_build_time = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
'key' => 'required|string',
'value' => 'required|string',
'value' => 'nullable',
'is_build_time' => 'required|boolean',
];
protected $validationAttributes = [
@@ -32,6 +32,7 @@ class Add extends Component
public function submit()
{
$this->validate();
ray($this->key, $this->value, $this->is_build_time);
$this->emitUp('submit', [
'key' => $this->key,
'value' => $this->value,

View File

@@ -53,11 +53,11 @@ class All extends Component
$this->resource->environment_variables_preview()->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
ray($variables);
$existingVariables = $this->resource->environment_variables();
$this->resource->environment_variables()->delete();
}
foreach ($variables as $key => $variable) {
ray($key, $variable);
$found = $existingVariables->where('key', $key)->first();
if ($found) {
$found->value = $variable;
@@ -110,11 +110,16 @@ class All extends Component
$environment->is_build_time = $data['is_build_time'];
$environment->is_preview = $data['is_preview'];
if ($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if ($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
switch ($this->resource->type()) {
case 'application':
$environment->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
}
$environment->save();
$this->refreshEnvs();

View File

@@ -15,7 +15,7 @@ class Show extends Component
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
];
protected $validationAttributes = [

View File

@@ -151,6 +151,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$subType = data_get($labels, 'coolify.service.subType');
$subId = data_get($labels, 'coolify.service.subId');
$service = $services->where('id', $serviceLabelId)->first();
if (!$service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {

View File

@@ -96,21 +96,47 @@ class Service extends BaseModel
foreach ($envs as $env) {
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
}
if ($envs->count() === 0) {
$commands[] = "touch .env";
}
instant_remote_process($commands, $this->server);
}
private function generateFqdn($serviceVariables, $serviceName)
private function sslip(Server $server)
{
if (isDev()) {
return "127.0.0.1.sslip.io";
}
return "{$server->ip}.sslip.io";
}
private function generateFqdn($serviceVariables, $serviceName, Collection $requiredFqdns)
{
// Add sslip.io to the service
// 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";
$defaultUsableFqdn = null;
$sslip = $this->sslip($this->server);
if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$sslip}";
}
if ($requiredFqdns->count() > 0) {
foreach ($requiredFqdns as $requiredFqdn) {
$requiredFqdn = (array)$requiredFqdn;
$name = data_get($requiredFqdn, 'name');
$path = data_get($requiredFqdn, 'path');
$customFqdn = data_get($requiredFqdn, 'customFqdn');
if ($serviceName === $name) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$sslip}{$path}";
if ($customFqdn) {
$defaultUsableFqdn = "http://$customFqdn-{$this->uuid}.{$sslip}{$path}";
}
}
}
// }
}
return $defaultUsableFqdn ?? null;
}
public function parse(bool $isNew = false): Collection
public function parse(bool $isNew = false, ?Collection $requiredFqdns = null): Collection
{
if (!$requiredFqdns) {
$requiredFqdns = collect([]);
}
ray('parsing');
// ray()->clearAll();
if ($this->docker_compose_raw) {
@@ -130,16 +156,26 @@ class Service extends BaseModel
$envs = collect([]);
$ports = collect([]);
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) {
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew, $requiredFqdns) {
$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(':');
$imageTag = Str::of($image)->after(':') ?? 'latest';
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
$isDatabase = true;
data_set($service, 'is_database', true);
@@ -160,17 +196,29 @@ class Service extends BaseModel
if ($isDatabase) {
$savedService = ServiceDatabase::create([
'name' => $serviceName,
'image_tag' => $imageTag,
'image' => $image,
'service_id' => $this->id
]);
} else {
$savedService = ServiceApplication::create([
'name' => $serviceName,
'fqdn' => $this->generateFqdn($serviceVariables, $serviceName),
'image_tag' => $imageTag,
'fqdn' => $this->generateFqdn($serviceVariables, $serviceName, $requiredFqdns),
'image' => $image,
'service_id' => $this->id
]);
}
if ($requiredFqdns->count() > 0) {
$found = false;
foreach ($requiredFqdns 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();
@@ -179,14 +227,13 @@ class Service extends BaseModel
if (data_get($savedService, 'fqdn')) {
$defaultUsableFqdn = data_get($savedService, 'fqdn', null);
} else {
$defaultUsableFqdn = $this->generateFqdn($serviceVariables, $serviceName);
$defaultUsableFqdn = $this->generateFqdn($serviceVariables, $serviceName, $requiredFqdns);
}
$savedService->fqdn = $defaultUsableFqdn;
$savedService->save();
}
}
// Set image tag
$fqdns = data_get($savedService, 'fqdn');
if ($fqdns) {
$fqdns = collect(Str::of($fqdns)->explode(','));
@@ -209,6 +256,7 @@ class Service extends BaseModel
}
$savedService->ports = $collectedPorts->implode(',');
$savedService->save();
// Collect volumes
$serviceVolumes = collect(data_get($service, 'volumes', []));
if ($serviceVolumes->count() > 0) {
@@ -341,6 +389,7 @@ class Service extends BaseModel
$value = Str::after($variable, '=');
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(':');
@@ -464,6 +513,7 @@ class Service extends BaseModel
$number = 0;
}
$fqdn = getFqdnWithoutPort(data_get($fqdns, $number, $fqdns->first()));
ray($fqdns);
$environments = collect(data_get($service, 'environment'));
$environments = $environments->map(function ($envValue) use ($value, $fqdn) {
$envValue = Str::of($envValue)->replace($value, $fqdn);
@@ -491,6 +541,7 @@ class Service extends BaseModel
}
}
}
// Add labels to the service
$labels = collect(data_get($service, 'labels', []));
$labels = collect([]);

View File

@@ -18,7 +18,6 @@ class ServiceApplication extends BaseModel
{
$services = Cache::get('services', []);
$service = data_get($services, $this->name, []);
ray($this->name);
return data_get($service, 'documentation', 'https://coolify.io/docs');
}
public function service()