Merge branch 'next' into auto-cleanup-improvements

This commit is contained in:
Andras Bacsai
2024-08-26 11:13:40 +02:00
committed by GitHub
116 changed files with 3666 additions and 1157 deletions

View File

@@ -3,6 +3,8 @@
namespace App\Models;
use App\Enums\ApplicationDeploymentStatus;
use App\Enums\ProxyTypes;
use App\Jobs\ServerFilesFromServerJob;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -412,23 +414,6 @@ class Application extends BaseModel
);
}
public function dockerComposePrLocation(): Attribute
{
return Attribute::make(
set: function ($value) {
if (is_null($value) || $value === '') {
return '/docker-compose.yaml';
} else {
if ($value !== '/') {
return Str::start(Str::replaceEnd('/', '', $value), '/');
}
return Str::start($value, '/');
}
}
);
}
public function baseDirectory(): Attribute
{
return Attribute::make(
@@ -479,12 +464,12 @@ class Application extends BaseModel
$main_server_status = $this->destination->server->isFunctional();
foreach ($additional_servers_status as $status) {
$server_status = str($status)->before(':')->value();
if ($main_server_status !== $server_status) {
if ($server_status !== 'running') {
return false;
}
}
return true;
return $main_server_status;
}
}
);
@@ -1040,7 +1025,7 @@ class Application extends BaseModel
}
}
public function parseRawCompose()
public function oldRawParser()
{
try {
$yaml = Yaml::parse($this->docker_compose_raw);
@@ -1100,8 +1085,550 @@ class Application extends BaseModel
instant_remote_process($commands, $this->destination->server, false);
}
public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
public function newParser(int $pull_request_id = 0, ?int $preview_id = null)
{
$pullRequestId = $pull_request_id;
$isPullRequest = $pullRequestId == 0 ? false : true;
$uuid = data_get($this, 'uuid');
$server = data_get($this, 'destination.server');
$compose = data_get($this, 'docker_compose_raw');
try {
$yaml = Yaml::parse($compose);
} catch (\Exception $e) {
return;
}
$services = data_get($yaml, 'services', collect([]));
$topLevel = collect([
'volumes' => collect(data_get($yaml, 'volumes', [])),
'networks' => collect(data_get($yaml, 'networks', [])),
'configs' => collect(data_get($yaml, 'configs', [])),
'secrets' => collect(data_get($yaml, 'secrets', [])),
]);
// If there are predefined volumes, make sure they are not null
if ($topLevel->get('volumes')->count() > 0) {
$temp = collect([]);
foreach ($topLevel['volumes'] as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$temp->put($volumeName, $volume);
}
$topLevel['volumes'] = $temp;
}
// Get the base docker network
$baseNetwork = collect([$uuid]);
if ($isPullRequest) {
$baseNetwork = collect(["{$uuid}-{$pullRequestId}"]);
}
$parsedServices = collect([]);
$fileStorages = $this->fileStorages();
// Let's loop through the services
foreach ($services as $serviceName => $service) {
$image = data_get_str($service, 'image');
$restart = data_get_str($service, 'restart', RESTART_MODE);
$logging = data_get($service, 'logging');
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
$logging = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
$volumes = collect(data_get($service, 'volumes', []));
$networks = collect(data_get($service, 'networks', []));
$depends_on = collect(data_get($service, 'depends_on', []));
$labels = collect(data_get($service, 'labels', []));
$environment = collect(data_get($service, 'environment', []));
$buildArgs = collect(data_get($service, 'build.args', []));
$environment = $environment->merge($buildArgs);
$baseName = generateApplicationContainerName(
application: $this,
pull_request_id: $pullRequestId
);
$containerName = "$serviceName-$baseName";
$volumesParsed = collect([]);
if ($volumes->count() > 0) {
foreach ($volumes as $index => $volume) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$foundConfig = $fileStorages->whereMountPath($target)->first();
if (sourceIsLocal($source)) {
$type = str('bind');
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
$isDirectory = true;
}
} else {
$type = str('volume');
}
} elseif (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
$foundConfig = $fileStorages->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
$isDirectory = true;
}
}
}
if ($type->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
$source = replaceLocalSource($source, $mainDirectory);
if ($isPullRequest) {
$source = $source."-pr-$pullRequestId";
}
if (
! $this?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git
) {
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $this->id,
'resource_type' => get_class($this),
]
);
}
$volume = "$source:$target";
} elseif ($type->value() === 'volume') {
if ($topLevel->get('volumes')->has($source->value())) {
$temp = $topLevel->get('volumes')->get($source->value());
if (data_get($temp, 'driver_opts.type') === 'cifs') {
return $volume;
}
if (data_get($temp, 'driver_opts.type') === 'nfs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$uuid}_{$slugWithoutUuid}";
if ($isPullRequest) {
$name = "{$name}-pr-$pullRequestId";
}
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} elseif (is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevel->get('volumes')->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
]
);
}
dispatch(new ServerFilesFromServerJob($this));
$volumesParsed->put($index, $volume);
}
}
if ($depends_on?->count() > 0) {
if ($isPullRequest) {
$newDependsOn = collect([]);
$depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) {
if (is_numeric($condition)) {
$dependency = "$dependency-pr-$pullRequestId";
$newDependsOn->put($condition, $dependency);
} else {
$condition = "$condition-pr-$pullRequestId";
$newDependsOn->put($condition, $dependency);
}
});
$depends_on = $newDependsOn;
}
}
if ($topLevel->get('networks')?->count() > 0) {
foreach ($topLevel->get('networks') as $networkName => $network) {
if ($networkName === 'default') {
continue;
}
// ignore aliases
if ($network['aliases'] ?? false) {
continue;
}
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$networks->put($networkName, null);
}
}
}
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
return $value == $baseNetwork;
});
if (! $baseNetworkExists) {
foreach ($baseNetwork as $network) {
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
}
$networks_temp = collect();
foreach ($networks as $key => $network) {
if (gettype($network) === 'string') {
// networks:
// - appwrite
$networks_temp->put($network, null);
} elseif (gettype($network) === 'array') {
// networks:
// default:
// ipv4_address: 192.168.203.254
$networks_temp->put($key, $network);
}
}
foreach ($baseNetwork as $key => $network) {
$networks_temp->put($network, null);
}
if (data_get($this, 'settings.connect_to_docker_network')) {
$network = $this->destination->network;
$networks_temp->put($network, null);
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
foreach ($environment as $key => $value) {
if (is_numeric($key)) {
if (is_array($value)) {
// - SESSION_SECRET: 123
// - SESSION_SECRET:
$key = str(collect($value)->keys()->first());
$value = str(collect($value)->values()->first());
} else {
$variable = str($value);
if ($variable->contains('=')) {
// - SESSION_SECRET=123
// - SESSION_SECRET=
$key = $variable->before('=');
$value = $variable->after('=');
} else {
// - SESSION_SECRET
$key = $variable;
$value = null;
}
}
} else {
// SESSION_SECRET: 123
// SESSION_SECRET:
$key = str($key);
$value = str($value);
}
// Auto generate FQDN and URL
if ($key->startsWith('SERVICE_FQDN') || $value->startsWith('$SERVICE_FQDN') || $value->startsWith('${SERVICE_FQDN')) {
if ($value->contains('SERVICE_FQDN')) {
$key = str(replaceVariables($value));
$value = null;
}
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
$fqdn = generateFqdn($server, "{$name->value()}-{$uuid}");
$url = str($fqdn)->replace('http://', '')->replace('https://', '')->replace('www.', '');
$keyUrl = $key->replace('SERVICE_FQDN', 'SERVICE_URL');
// TODO: is this needed?
// if (substr_count($key->value(), '_') === 3) {
// // SERVICE_FQDN_UMAMI_1000
// $port = $key->afterLast('_');
// } else {
// // SERVICE_FQDN_UMAMI
// $port = null;
// }
// if ($port) {
// $fqdn = "$fqdn:$port";
// }
if ($value && get_class($value) === 'Illuminate\Support\Stringable') {
$path = $value->value();
$fqdn = "$fqdn$path";
}
// ray([
// 'key' => $key,
// 'value' => $fqdn,
// ]);
// ray($this->environment_variables()->where('key', $key)->where('application_id', $this->id)->first());
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
'key' => $key,
'application_id' => $this->id,
], [
'value' => $fqdn,
'is_build_time' => false,
'is_preview' => false,
]);
$this->environment_variables()->where('key', $keyUrl)->where('application_id', $this->id)->firstOrCreate([
'key' => $keyUrl,
'application_id' => $this->id,
], [
'value' => $url,
'is_build_time' => false,
'is_preview' => false,
]);
} elseif ($value->startsWith('$')) {
// If the value is a variable then we will add it to Coolify's DB
// ${VARIABLE} will be VARIABLE instead
$value = str(replaceVariables($value));
if ($value->contains(':-')) {
$key = $value->before(':');
$defaultValue = $value->after(':-');
} elseif ($value->contains('-')) {
$key = $value->before('-');
$defaultValue = $value->after('-');
} elseif ($value->contains(':?')) {
$key = $value->before(':');
$defaultValue = $value->after(':?');
} elseif ($value->contains('?')) {
$key = $value->before('?');
$defaultValue = $value->after('?');
} else {
$key = $value;
$defaultValue = null;
}
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
'key' => $key,
'application_id' => $this->id,
'is_preview' => false,
], [
'value' => $defaultValue,
'is_build_time' => false,
'is_preview' => false,
]);
}
}
$branch = $this->git_branch;
if ($pullRequestId !== 0) {
$branch = "pull/{$pullRequestId}/head";
}
if ($this->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$environment->put('COOLIFY_BRANCH', $branch);
}
if ($this->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$environment->put('COOLIFY_CONTAINER_NAME', $containerName);
}
// Remove SERVICE_FQDN and SERVICE_URL from environment
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN') && ! str($key)->startsWith('SERVICE_URL');
});
// Labels
$fqdns = collect([]);
$domains = collect(json_decode($this->docker_compose_domains)) ?? collect([]);
if ($domains->count() !== 0) {
$fqdns = data_get($domains, "$serviceName.domain");
if (! $fqdns) {
$fqdns = collect([]);
} else {
$fqdns = str($fqdns)->explode(',');
if ($isPullRequest) {
$preview = $this->previews()->find($preview_id);
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
if ($docker_compose_domains->count() > 0) {
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
if ($found_fqdn) {
$fqdns = collect($found_fqdn);
} else {
$fqdns = collect([]);
}
} else {
$fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pullRequestId);
$url = Url::fromString($fqdn);
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
return $preview_fqdn;
});
}
}
$shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
case ProxyTypes::TRAEFIK->value:
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $this->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
break;
case ProxyTypes::CADDY->value:
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
break;
}
} else {
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $this->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
}
}
}
$defaultLabels = defaultLabels(
id: $this->id,
name: $containerName,
pull_request_id: $pullRequestId,
type: 'application');
$labels = $labels->merge($defaultLabels);
if ($labels->count() > 0 && $this->settings->is_container_label_escape_enabled) {
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$payload = collect($service)->merge([
'restart' => $restart->value(),
'container_name' => $containerName,
'volumes' => $volumesParsed,
'networks' => $networks_temp,
'labels' => $labels,
'environment' => $environment,
]);
if ($logging) {
$payload['logging'] = $logging;
}
if ($depends_on->count() > 0) {
$payload['depends_on'] = $depends_on;
}
if ($isPullRequest) {
$serviceName = "{$serviceName}-pr-{$pullRequestId}";
}
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
return array_search($key, $customOrder);
});
$this->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
data_forget($this, 'environment_variables');
data_forget($this, 'environment_variables_preview');
$this->save();
return $topLevel;
}
public function oldParser(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->compose_parsing_version === '3') {
return $this->newParser($pull_request_id, $preview_id);
} else
if ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
} else {
@@ -1154,7 +1681,7 @@ class Application extends BaseModel
if ($composeFileContent) {
$this->docker_compose_raw = $composeFileContent;
$this->save();
$parsedServices = $this->parseCompose();
$parsedServices = $this->oldParser();
if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();

View File

@@ -14,7 +14,7 @@ class ApplicationPreview extends BaseModel
static::deleting(function ($preview) {
if ($preview->application->build_pack === 'dockercompose') {
$server = $preview->application->destination->server;
$composeFile = $preview->application->parseCompose(pull_request_id: $preview->pull_request_id);
$composeFile = $preview->application->oldParser(pull_request_id: $preview->pull_request_id);
$volumes = data_get($composeFile, 'volumes');
$networks = data_get($composeFile, 'networks');
$networkKeys = collect($networks)->keys();

View File

@@ -6,7 +6,6 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
#[OA\Schema(
@@ -97,8 +96,22 @@ class EnvironmentVariable extends Model
$resource = Application::find($this->application_id);
} elseif ($this->service_id) {
$resource = Service::find($this->service_id);
} elseif ($this->database_id) {
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
} elseif ($this->standalone_postgresql_id) {
$resource = StandalonePostgresql::find($this->standalone_postgresql_id);
} elseif ($this->standalone_redis_id) {
$resource = StandaloneRedis::find($this->standalone_redis_id);
} elseif ($this->standalone_mongodb_id) {
$resource = StandaloneMongodb::find($this->standalone_mongodb_id);
} elseif ($this->standalone_mysql_id) {
$resource = StandaloneMysql::find($this->standalone_mysql_id);
} elseif ($this->standalone_mariadb_id) {
$resource = StandaloneMariadb::find($this->standalone_mariadb_id);
} elseif ($this->standalone_keydb_id) {
$resource = StandaloneKeydb::find($this->standalone_keydb_id);
} elseif ($this->standalone_dragonfly_id) {
$resource = StandaloneDragonfly::find($this->standalone_dragonfly_id);
} elseif ($this->standalone_clickhouse_id) {
$resource = StandaloneClickhouse::find($this->standalone_clickhouse_id);
}
return $resource;
@@ -122,63 +135,6 @@ class EnvironmentVariable extends Model
);
}
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (! $this->application_id) {
return true;
}
$found_in_compose = false;
$found_in_args = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (! $compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
$args = collect(data_get($service, 'build.args'));
if ($environments->isEmpty() && $args->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
$found_in_args = $args->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_args) {
break;
}
}
return $found_in_compose || $found_in_args;
}
);
}
protected function isShared(): Attribute
{
return Attribute::make(
@@ -201,8 +157,10 @@ class EnvironmentVariable extends Model
$environment_variable = trim($environment_variable);
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
if ($sharedEnvsFound->isEmpty()) {
return $environment_variable;
}
foreach ($sharedEnvsFound as $sharedEnv) {
$type = str($sharedEnv)->match('/(.*?)\./');
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {

View File

@@ -24,8 +24,9 @@ class LocalFileVolume extends BaseModel
return $this->morphTo('resource');
}
public function deleteStorageOnServer()
public function loadStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
@@ -35,17 +36,46 @@ class LocalFileVolume extends BaseModel
$server = $this->resource->destination->server;
}
$commands = collect([]);
$fs_path = data_get($this, 'fs_path');
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
ray($isFile, $isDir);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
if ($isFile === 'OK') {
$content = instant_remote_process(["cat $path"], $server, false);
$this->content = $content;
$this->is_directory = false;
$this->save();
}
}
public function deleteStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([]);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($path && $path != '/' && $path != '.' && $path != '..') {
if ($isFile === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
} elseif ($isDir === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
$commands->push("rmdir $path > /dev/null 2>&1 || true");
}
}
if ($commands->count() > 0) {
@@ -55,6 +85,7 @@ class LocalFileVolume extends BaseModel
public function saveStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
@@ -74,30 +105,36 @@ class LocalFileVolume extends BaseModel
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
}
}
$fileVolume = $this;
$path = str(data_get($fileVolume, 'fs_path'));
$content = data_get($fileVolume, 'content');
$path = data_get_str($this, 'fs_path');
$content = data_get($this, 'content');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) {
if ($isFile == 'OK' && $this->is_directory) {
$content = instant_remote_process(["cat $path"], $server, false);
$fileVolume->is_directory = false;
$fileVolume->content = $content;
$fileVolume->save();
$this->is_directory = false;
$this->content = $content;
$this->save();
FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
$fileVolume->is_directory = true;
$fileVolume->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $this->is_directory) {
if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
$this->is_directory = true;
$this->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
}
instant_remote_process([
"rm -fr $path",
"touch $path",
], $server, false);
FileStorageChanged::dispatch(data_get($server, 'team_id'));
}
if ($isDir == 'NOK' && ! $fileVolume->is_directory) {
$chmod = data_get($fileVolume, 'chmod');
$chown = data_get($fileVolume, 'chown');
if ($isDir == 'NOK' && ! $this->is_directory) {
$chmod = data_get($this, 'chmod');
$chown = data_get($this, 'chown');
if ($content) {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
@@ -111,7 +148,7 @@ class LocalFileVolume extends BaseModel
if ($chmod) {
$commands->push("chmod $chmod $path");
}
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) {
} elseif ($isDir == 'NOK' && $this->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
}

View File

@@ -26,6 +26,6 @@ class ScheduledTask extends BaseModel
public function executions(): HasMany
{
return $this->hasMany(ScheduledTaskExecution::class);
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
}
}

View File

@@ -649,7 +649,7 @@ $schema://$host {
}
}
public function getDiskUsage()
public function getDiskUsage(): ?string
{
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
}

View File

@@ -2,11 +2,13 @@
namespace App\Models;
use App\Enums\ProxyTypes;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
@@ -23,6 +25,7 @@ use Symfony\Component\Yaml\Yaml;
'description' => ['type' => 'string', 'description' => 'The description of the service.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
'destination_type' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
@@ -205,6 +208,41 @@ class Service extends BaseModel
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
switch ($image) {
case str($image)?->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_RABBITMQ')->first();
if ($host_port) {
$data = $data->merge([
'Host Port Binding' => [
'key' => data_get($host_port, 'key'),
'value' => data_get($host_port, 'value'),
'rules' => 'required',
],
]);
}
if ($username) {
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
}
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('RabbitMQ', $data->toArray());
break;
case str($image)?->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
@@ -504,6 +542,9 @@ class Service extends BaseModel
default:
$data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
// Chaskiq
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) {
$data = $data->merge([
@@ -525,6 +566,15 @@ class Service extends BaseModel
],
]);
}
if ($admin_email) {
$data = $data->merge([
'Email' => [
'key' => 'ADMIN_EMAIL',
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
]);
}
$fields->put('Admin', $data->toArray());
break;
case str($image)?->contains('vaultwarden'):