Implement Keystone environment deployments
This commit is contained in:
152
app/Actions/Environments/AttachManagedService.php
Normal file
152
app/Actions/Environments/AttachManagedService.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user