chore: add registry auth to docker image apps

This commit is contained in:
David Buday 2024-11-08 13:31:30 +01:00
parent 013bea0495
commit b57a439b76
4 changed files with 116 additions and 25 deletions

View file

@ -381,17 +381,35 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function deploy_dockerimage_buildpack()
{
$this->dockerImage = $this->application->docker_registry_image_name;
if (str($this->application->docker_registry_image_tag)->isEmpty()) {
$this->dockerImageTag = 'latest';
} else {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
try {
$didLogin = $this->handleRegistryAuth();
// Pull the image
$this->execute_remote_command([
"docker pull {$this->application->image}",
]);
// Logout if we logged in
if ($didLogin) {
$this->application_deployment_queue->addLogEntry('Logging out from registry...');
$this->execute_remote_command([
'docker logout',
'hidden' => true
]);
}
// Continue with the rest of the deployment...
} catch (Exception $e) {
// Make sure to logout even if pull fails
if ($didLogin ?? false) {
$this->execute_remote_command([
'docker logout',
'hidden' => true
]);
}
$this->application_deployment_queue->addLogEntry('Deployment error: ' . $e->getMessage(), 'stderr');
throw $e;
}
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.");
$this->generate_image_names();
$this->prepare_builder_image();
$this->generate_compose_file();
$this->rolling_update();
}
private function deploy_docker_compose_buildpack()
@ -2444,4 +2462,29 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
}
}
private function handleRegistryAuth()
{
if ($this->application->registry_username && $this->application->registry_token) {
try {
$username = escapeshellarg($this->application->registry_username);
$token = escapeshellarg(decrypt($this->application->registry_token));
$this->application_deployment_queue->addLogEntry('Attempting to log into registry...');
$command = "echo {$token} | docker login -u {$username} --password-stdin > /dev/null";
$this->execute_remote_command([
$command,
'hidden' => true,
]);
return true;
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry('Registry authentication error: ' . $e->getMessage(), 'stderr');
throw $e;
}
}
return false;
}
}

View file

@ -12,11 +12,17 @@ use Visus\Cuid2\Cuid2;
class DockerImage extends Component
{
public string $dockerImage = '';
public ?string $registryUsername = null;
public ?string $registryToken = null;
public array $parameters;
public array $query;
protected $rules = [
'dockerImage' => 'required|string',
'registryUsername' => 'nullable|string',
'registryToken' => 'nullable|string',
];
public function mount()
{
$this->parameters = get_route_parameters();
@ -27,25 +33,30 @@ class DockerImage extends Component
{
$this->validate([
'dockerImage' => 'required',
'registryUsername' => 'required_with:registryToken',
'registryToken' => 'required_with:registryUsername',
]);
$image = str($this->dockerImage)->before(':');
if (str($this->dockerImage)->contains(':')) {
$tag = str($this->dockerImage)->after(':');
} else {
$tag = 'latest';
}
$tag = str($this->dockerImage)->contains(':') ?
str($this->dockerImage)->after(':') :
'latest';
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (! $destination) {
throw new \Exception('Destination not found. What?!');
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first()
?? SwarmDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
throw new \Exception('Destination not found.');
}
$destination_class = $destination->getMorphClass();
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$environment = $project->load(['environments'])
->environments
->where('name', $this->parameters['environment_name'])
->first();
$application = Application::create([
'name' => 'docker-image-'.new Cuid2,
'repository_project_id' => 0,
@ -59,6 +70,8 @@ class DockerImage extends Component
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
'registry_username' => $this->registryUsername,
'registry_token' => $this->registryToken ? encrypt($this->registryToken) : null,
]);
$fqdn = generateFqdn($destination->server, $application->uuid);

View file

@ -0,0 +1,23 @@
<?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::table('applications', function (Blueprint $table) {
$table->string('registry_username')->nullable();
$table->text('registry_token')->nullable();
});
}
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('registry_username');
$table->dropColumn('registry_token');
});
}
};

View file

@ -6,6 +6,18 @@
<h2>Docker Image</h2>
<x-forms.button type="submit">Save</x-forms.button>
</div>
<x-forms.input rows="20" id="dockerImage" placeholder="nginx:latest"></x-forms.textarea>
<x-forms.input required id="dockerImage" label="Image" placeholder="nginx:latest" />
<h3 class="pt-4">Registry Authentication</h3>
<div class="flex flex-col gap-4">
<x-forms.input id="registryUsername" required="registryToken" label="Registry Username"
wire:model="registryUsername" placeholder="Username for private registry"
helper="Leave empty for public images or server credentials" />
<x-forms.input type="password" required="registryUsername" id="registryToken"
label="Registry Token/Password" wire:model="registryToken"
placeholder="Token or password for private registry"
helper="Leave empty for public images or server credentials" />
</div>
</form>
</div>