chore: changed migrations, created registries, initial progress

This commit is contained in:
David Buday 2024-11-11 10:26:24 +01:00
parent 9d08eed7f6
commit 3cd185aa78
8 changed files with 152 additions and 88 deletions

View file

@ -2477,11 +2477,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function handleRegistryAuth()
{
$username = $this->application->docker_registry_username;
$registry = $this->application->docker_registry_url ?: 'docker.io';
$token = escapeshellarg($this->application->docker_registry_token);
//$registry = $this->$application->registry; ??
//$token = escapeshellarg($registry->token); ...
$token = escapeshellarg('test');
$url = escapeshellarg('test');
$username = escapeshellarg('test');
$this->application_deployment_queue->addLogEntry('Attempting to log into registry...');
$command = "echo {{secrets.token}} | docker login {$registry} -u {$username} --password-stdin";
$command = "echo {{secrets.token}} | docker login {$url} -u {$username} --password-stdin";
$this->execute_remote_command(
[

View file

@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application;
use App\Actions\Application\GenerateConfig;
use App\Models\Application;
use App\Models\Registry;
use Illuminate\Support\Collection;
use Livewire\Component;
use Spatie\Url\Url;
@ -72,9 +73,7 @@ class General extends Component
'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable',
'application.docker_use_custom_registry' => 'boolean',
'application.docker_registry_url' => 'nullable',
'application.docker_registry_username' => 'nullable|required_if:application.docker_use_custom_registry,true',
'application.docker_registry_token' => 'nullable|required_if:application.docker_use_custom_registry,true',
'application.docker_registry_id' => 'nullable|required_if:application.docker_use_custom_registry,true',
'application.dockerfile_location' => 'nullable',
'application.docker_compose_location' => 'nullable',
'application.docker_compose' => 'nullable',
@ -116,10 +115,8 @@ class General extends Component
'application.dockerfile' => 'Dockerfile',
'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.docker_use_custom_registry' => 'Docker use custom registry',
'application.docker_registry_url' => 'Registry URL',
'application.docker_registry_username' => 'Registry Username',
'application.docker_registry_token' => 'Registry Token',
'application.docker_use_custom_registry' => 'Use private registry',
'application.docker_registry_id' => 'Registry',
'application.dockerfile_location' => 'Dockerfile location',
'application.docker_compose_location' => 'Docker compose location',
'application.docker_compose' => 'Docker compose',
@ -322,7 +319,7 @@ class General extends Component
public function set_redirect()
{
try {
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
$has_www = collect($this->application->fqdns)->filter(fn($fqdn) => str($fqdn)->contains('www.'))->count();
if ($has_www === 0 && $this->application->redirect === 'www') {
$this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');
@ -381,6 +378,7 @@ class General extends Component
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
'application.docker_registry_id' => 'required_if:application.docker_use_custom_registry,true',
]);
}
@ -439,7 +437,14 @@ class General extends Component
echo $config;
}, $fileName, [
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename='.$fileName,
'Content-Disposition' => 'attachment; filename=' . $fileName,
]);
}
public function render()
{
return view('livewire.project.application.general', [
'registries' => Registry::all(),
]);
}
}

View file

@ -3,6 +3,7 @@
namespace App\Livewire\Project\New;
use App\Models\Application;
use App\Models\Registry;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
@ -12,18 +13,14 @@ use Visus\Cuid2\Cuid2;
class DockerImage extends Component
{
public string $dockerImage = '';
public ?string $registryUsername = null;
public ?string $registryToken = null;
public ?string $registryUrl = 'docker.io';
public bool $useCustomRegistry = false;
public ?int $selectedRegistry = null;
public array $parameters;
public array $query;
protected $rules = [
'dockerImage' => 'required|string',
'registryUsername' => 'required_if:useCustomRegistry,true|string|nullable',
'registryToken' => 'required_if:useCustomRegistry,true|string|nullable',
'registryUrl' => 'nullable|string',
'selectedRegistry' => 'nullable|required_if:useCustomRegistry,true',
'useCustomRegistry' => 'boolean'
];
@ -31,27 +28,15 @@ class DockerImage extends Component
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->registryUrl = 'docker.io';
}
public function submit()
{
$this->validate([
'dockerImage' => 'required',
'registryUsername' => 'required_if:useCustomRegistry,true',
'registryToken' => 'required_if:useCustomRegistry,true',
]);
// Only save registry settings if useCustomRegistry is true
if (!$this->useCustomRegistry) {
$this->registryUsername = null;
$this->registryToken = null;
$this->registryUrl = 'docker.io';
}
$this->validate(['dockerImage' => 'required',]);
$image = str($this->dockerImage)->before(':');
$tag = str($this->dockerImage)->contains(':') ?
str($this->dockerImage)->after(':') :
$tag = str($this->dockerImage)->contains(':') ?
str($this->dockerImage)->after(':') :
'latest';
$destination_uuid = $this->query['destination'];
@ -68,7 +53,7 @@ class DockerImage extends Component
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$application = Application::create([
'name' => 'docker-image-'.new Cuid2,
'name' => 'docker-image-' . new Cuid2,
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',
@ -76,19 +61,17 @@ class DockerImage extends Component
'ports_exposes' => 80,
'docker_registry_image_name' => $image,
'docker_registry_image_tag' => $tag,
'docker_use_custom_registry' => $this->useCustomRegistry,
'docker_registry_id' => $this->selectedRegistry,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
'docker_use_custom_registry' => $this->useCustomRegistry,
'docker_registry_url' => $this->registryUrl,
'docker_registry_username' => $this->registryUsername,
'docker_registry_token' => $this->registryToken,
]);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'docker-image-'.$application->uuid,
'name' => 'docker-image-' . $application->uuid,
'fqdn' => $fqdn,
]);
@ -101,6 +84,8 @@ class DockerImage extends Component
public function render()
{
return view('livewire.project.new.docker-image');
return view('livewire.project.new.docker-image', [
'registries' => Registry::all()
]);
}
}

View file

@ -36,9 +36,7 @@ use Visus\Cuid2\Cuid2;
'docker_registry_image_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image name.'],
'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image tag.'],
'docker_use_custom_registry' => ['type' => 'boolean', 'description' => 'Use custom registry.'],
'docker_registry_url' => ['type' => 'string', 'nullable' => true, 'description' => 'Registry URL.'],
'docker_registry_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Registry username.'],
'docker_registry_token' => ['type' => 'string', 'nullable' => true, 'description' => 'Registry token.'],
'docker_registry_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'Docker registry identifier.'],
'build_pack' => ['type' => 'string', 'description' => 'Build pack.', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose']],
'static_image' => ['type' => 'string', 'description' => 'Static image used when static site is deployed.'],
'install_command' => ['type' => 'string', 'description' => 'Install command.'],
@ -113,12 +111,10 @@ class Application extends BaseModel
protected $guarded = [];
protected $casts = [
'docker_registry_token' => 'encrypted',
];
protected $appends = ['server_status'];
protected $with = ['registry'];
protected static function booted()
{
static::saving(function ($application) {
@ -243,7 +239,7 @@ class Application extends BaseModel
$server = data_get($this, 'destination.server');
$workdir = $this->workdir();
if (str($workdir)->endsWith($this->uuid)) {
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
instant_remote_process(['rm -rf ' . $this->workdir()], $server, false);
}
}
@ -377,7 +373,7 @@ class Application extends BaseModel
public function publishDirectory(): Attribute
{
return Attribute::make(
set: fn ($value) => $value ? '/'.ltrim($value, '/') : null,
set: fn($value) => $value ? '/' . ltrim($value, '/') : null,
);
}
@ -459,7 +455,7 @@ class Application extends BaseModel
$git_repository = str_replace('.git', '', $this->git_repository);
$url = Url::fromString($git_repository);
$url = $url->withUserInfo('');
$url = $url->withPath($url->getPath().'/commits/'.$link);
$url = $url->withPath($url->getPath() . '/commits/' . $link);
return $url->__toString();
}
@ -512,21 +508,21 @@ class Application extends BaseModel
public function baseDirectory(): Attribute
{
return Attribute::make(
set: fn ($value) => '/'.ltrim($value, '/'),
set: fn($value) => '/' . ltrim($value, '/'),
);
}
public function portsMappings(): Attribute
{
return Attribute::make(
set: fn ($value) => $value === '' ? null : $value,
set: fn($value) => $value === '' ? null : $value,
);
}
public function portsMappingsArray(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->ports_mappings)
get: fn() => is_null($this->ports_mappings)
? []
: explode(',', $this->ports_mappings),
@ -643,7 +639,7 @@ class Application extends BaseModel
public function portsExposesArray(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->ports_exposes)
get: fn() => is_null($this->ports_exposes)
? []
: explode(',', $this->ports_exposes)
);
@ -860,7 +856,7 @@ class Application extends BaseModel
public function workdir()
{
return application_configuration_dir()."/{$this->uuid}";
return application_configuration_dir() . "/{$this->uuid}";
}
public function isLogDrainEnabled()
@ -870,7 +866,7 @@ class Application extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect;
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build . $this->redirect;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
} else {
@ -924,7 +920,7 @@ class Application extends BaseModel
public function dirOnServer()
{
return application_configuration_dir()."/{$this->uuid}";
return application_configuration_dir() . "/{$this->uuid}";
}
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
@ -1048,7 +1044,7 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
@ -1056,14 +1052,14 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} elseif ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
}
}
@ -1092,7 +1088,7 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
@ -1100,14 +1096,14 @@ class Application extends BaseModel
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && " . $this->buildGitCheckoutCommand($pr_branch_name);
} elseif ($git_type === 'bitbucket') {
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
} else {
$commands->push("echo 'Checking out $branch'");
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" " . $this->buildGitCheckoutCommand($commit);
}
}
@ -1160,7 +1156,7 @@ class Application extends BaseModel
}
if ($source->startsWith('.')) {
$source = $source->after('.');
$source = $workdir.$source;
$source = $workdir . $source;
}
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
}
@ -1171,7 +1167,7 @@ class Application extends BaseModel
$labels->push('coolify.managed=true');
}
if (! $labels->contains('coolify.applicationId')) {
$labels->push('coolify.applicationId='.$this->id);
$labels->push('coolify.applicationId=' . $this->id);
}
if (! $labels->contains('coolify.type')) {
$labels->push('coolify.type=application');
@ -1291,7 +1287,7 @@ class Application extends BaseModel
public function fqdns(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->fqdn)
get: fn() => is_null($this->fqdn)
? []
: explode(',', $this->fqdn),
);
@ -1352,10 +1348,10 @@ class Application extends BaseModel
continue;
}
if (isset($healthcheckCommand) && str_contains($trimmedLine, '\\')) {
$healthcheckCommand .= ' '.trim($trimmedLine, '\\ ');
$healthcheckCommand .= ' ' . trim($trimmedLine, '\\ ');
}
if (isset($healthcheckCommand) && ! str_contains($trimmedLine, '\\') && ! empty($healthcheckCommand)) {
$healthcheckCommand .= ' '.$trimmedLine;
$healthcheckCommand .= ' ' . $trimmedLine;
break;
}
}
@ -1537,4 +1533,9 @@ class Application extends BaseModel
throw new \Exception('Failed to update application settings');
}
}
public function registry()
{
return $this->belongsTo(Registry::class);
}
}

39
app/Models/Registry.php Normal file
View file

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Registry extends Model
{
protected $fillable = [
'name',
'type',
'url',
'username',
'token',
'is_default'
];
protected $casts = [
'is_default' => 'boolean',
'token' => 'encrypted',
];
public static function getTypes(): array
{
return [
'docker_hub' => 'Docker Hub',
'gcr' => 'Google Container Registry',
'ghcr' => 'GitHub Container Registry',
'quay' => 'Quay.io',
'custom' => 'Custom Registry'
];
}
public function applications(): HasMany
{
return $this->hasMany(Application::class);
}
}

View file

@ -8,9 +8,7 @@ return new class extends Migration {
public function up(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->string('docker_registry_username')->nullable();
$table->text('docker_registry_token')->nullable();
$table->string('docker_registry_url')->nullable();
$table->foreignId('docker_registry_id')->nullable();
$table->boolean('docker_use_custom_registry')->default(false);
});
}
@ -18,10 +16,8 @@ return new class extends Migration {
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('docker_registry_username');
$table->dropColumn('docker_registry_token');
$table->dropColumn('docker_registry_url');
$table->dropColumn('docker_registry_id');
$table->dropColumn('docker_use_custom_registry');
});
}
};
};

View file

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('docker_registries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('type'); // docker_hub, gcr, ghcr, quay, custom
$table->string('url')->nullable();
$table->string('username')->nullable();
$table->text('token')->nullable();
$table->boolean('is_default')->default(false);
$table->timestamps();
});
// Add foreign key constraint
Schema::table('applications', function (Blueprint $table) {
$table->foreign('docker_registry_id')
->references('id')
->on('docker_registries')
->nullOnDelete();
});
}
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropForeign(['docker_registry_id']);
});
Schema::dropIfExists('docker_registries');
}
};

View file

@ -137,20 +137,18 @@
@if ($application->build_pack === 'dockerimage')
<div class="pt-4 w-fit">
<x-forms.checkbox id="application.docker_use_custom_registry"
wire:model.live="application.docker_use_custom_registry"
helper="If enabled, you can specify a custom registry URL, username, and token/password."
label="Use Custom Registry Settings" />
<x-forms.checkbox wire:model.live="application.docker_use_custom_registry"
id="application.docker_use_custom_registry"
helper="Select a registry to pull the image from." label="Use Private Registry" />
</div>
@if ($application->docker_use_custom_registry)
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input id="application.docker_registry_url" label="Registry URL"
placeholder="docker.io" />
<x-forms.input id="application.docker_registry_username" label="Registry Username"
required placeholder="Username for private registry" />
<x-forms.input type="password" id="application.docker_registry_token"
label="Registry Token" placeholder="Token for private registry" />
<div class="pt-4">
<x-forms.select id="application.docker_registry_id" label="Select Registry">
@foreach ($registries as $registry)
<option value="{{ $registry['id'] }}">{{ $registry['name'] }}</option>
@endforeach
</x-forms.select>
</div>
@endif
@endif