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; } }