Add managed registry provisioning, pruning, and readiness tracking

This commit is contained in:
2026-06-08 20:44:16 +01:00
parent 5b977c1f41
commit 3a851db08f
52 changed files with 2706 additions and 116 deletions

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use App\Actions\Registries\CreateManagedRegistrySmokeCheckOperation;
use App\Enums\RegistryType;
use App\Models\Registry;
use App\Models\Server;
use Illuminate\Console\Command;
class CheckManagedRegistry extends Command
{
protected $signature = 'keystone:managed-registry:check
{registry : Managed registry id}
{--build-server= : Build server id, defaults to the registry control server}
{--runtime-server=* : Runtime server id to pull the smoke image}
{--dispatch : Dispatch the first operation step immediately}';
protected $description = 'Create managed registry HTTPS/auth/push/pull smoke-check operations.';
public function handle(CreateManagedRegistrySmokeCheckOperation $operations): int
{
$registry = Registry::query()
->where('type', RegistryType::MANAGED->value)
->findOrFail((int) $this->argument('registry'));
$buildServer = $this->option('build-server')
? Server::query()
->where('organisation_id', $registry->organisation_id)
->findOrFail((int) $this->option('build-server'))
: null;
$runtimeServers = collect($this->option('runtime-server'))
->map(fn (string $serverId): Server => Server::query()
->where('organisation_id', $registry->organisation_id)
->findOrFail((int) $serverId));
$operation = $operations->execute($registry, $buildServer, $runtimeServers);
$this->info("Created registry smoke-check operation {$operation->id}.");
if ($this->option('dispatch')) {
$operation->steps()->orderBy('order')->first()?->dispatchJob();
$this->info('Dispatched registry smoke-check operation.');
}
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Console\Commands;
use App\Actions\Registries\CreateManagedRegistryProvisionOperation;
use App\Models\Organisation;
use App\Models\Server;
use App\Services\Registries\ManagedRegistryHealth;
use App\Services\Registries\ManagedRegistryProvisioner;
use Illuminate\Console\Command;
class ProvisionManagedRegistry extends Command
{
protected $signature = 'keystone:managed-registry:provision
{organisation : Organisation id or slug}
{--url= : HTTPS registry hostname}
{--control-server= : Control/build server id}
{--storage-path= : Registry storage path}
{--retention= : Successful artifacts to retain per environment}
{--create-operation : Create the remote registry install/proxy operation}
{--dispatch : Dispatch the first operation step immediately}
{--mark-healthy : Mark the persisted registry ready after configuration validation}';
protected $description = 'Persist and optionally install a first-party managed Docker registry.';
public function handle(ManagedRegistryProvisioner $provisioner, ManagedRegistryHealth $health, CreateManagedRegistryProvisionOperation $operations): int
{
$organisationKey = (string) $this->argument('organisation');
$organisation = Organisation::query()
->where('id', $organisationKey)
->orWhere('slug', $organisationKey)
->firstOrFail();
$url = (string) ($this->option('url') ?: config('keystone.managed_registry.url'));
if ($url === '') {
$this->error('Provide --url or KEYSTONE_MANAGED_REGISTRY_URL.');
return self::FAILURE;
}
$controlServer = $this->option('control-server')
? Server::query()
->where('organisation_id', $organisation->id)
->findOrFail((int) $this->option('control-server'))
: null;
$registry = $provisioner->provision(
organisation: $organisation,
url: $url,
controlServer: $controlServer,
storagePath: $this->option('storage-path') ? (string) $this->option('storage-path') : null,
retention: $this->option('retention') ? (int) $this->option('retention') : null,
);
$blocker = $health->readinessBlocker($registry);
if ($this->option('mark-healthy') && $blocker !== null && $blocker !== 'Managed registry has not passed readiness checks.') {
$this->error($blocker);
return self::FAILURE;
}
if ($this->option('mark-healthy')) {
$registry->markHealthy('Marked ready by provisioning command.');
$blocker = null;
}
$this->info("Managed registry {$registry->url} persisted for {$organisation->name}.");
if ($blocker !== null) {
$this->warn($blocker);
}
if ($this->option('create-operation')) {
$operation = $operations->execute($registry);
$this->info("Created registry provision operation {$operation->id}.");
if ($this->option('dispatch')) {
$operation->steps()->orderBy('order')->first()?->dispatchJob();
$this->info('Dispatched registry provision operation.');
}
}
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Console\Commands;
use App\Actions\Registries\CreateManagedRegistryMaintenanceOperation;
use App\Enums\RegistryType;
use App\Models\Registry;
use Illuminate\Console\Command;
class PruneManagedRegistry extends Command
{
protected $signature = 'keystone:managed-registry:prune
{registry? : Managed registry id}
{--dispatch : Dispatch the first operation step immediately}';
protected $description = 'Create managed registry manifest deletion and garbage-collection operations.';
public function handle(CreateManagedRegistryMaintenanceOperation $operations): int
{
$registries = Registry::query()
->where('type', RegistryType::MANAGED->value)
->when($this->argument('registry'), fn ($query) => $query->whereKey((int) $this->argument('registry')))
->get();
$count = 0;
foreach ($registries as $registry) {
$operation = $operations->execute($registry);
$count++;
if ($this->option('dispatch')) {
$operation->steps()->orderBy('order')->first()?->dispatchJob();
}
}
$this->info("Created {$count} managed registry maintenance operation(s).");
return self::SUCCESS;
}
}