Files
keystone/app/Actions/Environments/AttachManagedService.php

153 lines
5.1 KiB
PHP

<?php
namespace App\Actions\Environments;
use App\Drivers\Concerns\SupportsSlices;
use App\Enums\EnvironmentAttachmentRole;
use App\Enums\EnvironmentVariableSource;
use App\Enums\OperationKind;
use App\Enums\OperationStatus;
use App\Enums\ServiceType;
use App\Models\Environment;
use App\Models\EnvironmentAttachment;
use App\Models\Service;
use App\Models\ServiceSlice;
use Illuminate\Support\Str;
use InvalidArgumentException;
class AttachManagedService
{
public function execute(
Environment $environment,
Service $service,
EnvironmentAttachmentRole $role,
?string $name = null,
?string $envPrefix = null,
bool $isPrimary = true,
): EnvironmentAttachment {
$slice = $this->createDefaultSlice($environment, $service, $role, $name);
$attachment = $environment->attachments()->create([
'service_id' => $service->id,
'service_slice_id' => $slice?->id,
'role' => $role,
'env_prefix' => $envPrefix,
'is_primary' => $isPrimary,
]);
$this->syncManagedVariables($environment, $service, $slice, $envPrefix, $role);
$this->createSliceProvisionOperation($service, $slice);
return $attachment;
}
private function createDefaultSlice(
Environment $environment,
Service $service,
EnvironmentAttachmentRole $role,
?string $name,
): ?ServiceSlice {
return match ($service->type) {
ServiceType::POSTGRES => $service->slices()->firstOrCreate([
'environment_id' => $environment->id,
'type' => 'database_user',
'name' => $name ?? $this->sliceName($environment),
], [
'status' => 'pending',
'config' => [],
'credentials' => [
'database' => $name ?? $this->sliceName($environment),
'username' => $name ?? $this->sliceName($environment),
'password' => Str::password(32),
],
]),
ServiceType::VALKEY => $service->slices()->firstOrCreate([
'environment_id' => $environment->id,
'type' => 'logical_database',
'name' => $name ?? $this->sliceName($environment),
], [
'status' => 'pending',
'config' => [
'database' => $this->nextValkeyDatabase($service),
],
]),
ServiceType::CADDY => $service->slices()->firstOrCreate([
'environment_id' => $environment->id,
'type' => 'route',
'name' => $name ?? $environment->name,
], [
'status' => 'pending',
'config' => [],
]),
default => $role === EnvironmentAttachmentRole::CUSTOM ? null : throw new InvalidArgumentException("Service [{$service->type->value}] does not support managed attachments."),
};
}
private function syncManagedVariables(Environment $environment, Service $service, ?ServiceSlice $slice, ?string $envPrefix, EnvironmentAttachmentRole $role): void
{
if (! $slice) {
return;
}
$driver = $service->driver();
if (! $driver instanceof SupportsSlices) {
return;
}
foreach ($driver->environmentExportsForSlice($slice, $role) as $key => $value) {
$environment->variables()->updateOrCreate([
'key' => $this->variableKey($key, $envPrefix),
], [
'value' => $value,
'source' => EnvironmentVariableSource::MANAGED_ATTACHMENT,
'service_slice_id' => $slice->id,
'overridable' => false,
]);
}
}
private function createSliceProvisionOperation(Service $service, ?ServiceSlice $slice): void
{
if (! $slice || ! $slice->wasRecentlyCreated) {
return;
}
$driver = $service->driver();
if (! $driver instanceof SupportsSlices) {
return;
}
$operation = $slice->operations()->create([
'kind' => OperationKind::SLICE_PROVISION,
'status' => OperationStatus::PENDING,
]);
$operation->steps()->create([
'name' => 'Provision '.$service->type->value.' slice',
'order' => 1,
'status' => OperationStatus::PENDING,
'script' => $driver->provisionSliceScript($slice),
]);
}
private function nextValkeyDatabase(Service $service): int
{
return ((int) $service->slices()
->where('type', 'logical_database')
->get()
->max(fn (ServiceSlice $slice): int => (int) ($slice->config['database'] ?? 0))) + 1;
}
private function sliceName(Environment $environment): string
{
return str($environment->application->name.' '.$environment->name)->slug('_')->value();
}
private function variableKey(string $key, ?string $envPrefix): string
{
return $envPrefix ? $envPrefix.'_'.$key : $key;
}
}