*/ private const DATABASE_MODELS = [ 'StandalonePostgresql' => StandalonePostgresql::class, 'StandaloneMysql' => StandaloneMysql::class, 'StandaloneMariadb' => StandaloneMariadb::class, 'StandaloneMongodb' => StandaloneMongodb::class, 'StandaloneRedis' => StandaloneRedis::class, 'StandaloneKeydb' => StandaloneKeydb::class, 'StandaloneDragonfly' => StandaloneDragonfly::class, 'StandaloneClickhouse' => StandaloneClickhouse::class, ]; /** @var array */ private array $privateKeyMap = []; /** @var array */ private array $githubAppMap = []; /** @var array */ private array $gitlabAppMap = []; /** @var array */ private array $s3StorageMap = []; /** @var array */ private array $cloudTokenMap = []; /** @var array */ private array $volumeMap = []; /** @var array */ private array $applicationMap = []; /** @var array */ private array $serviceMap = []; /** @var array */ private array $databaseMap = []; /** * @param array $bundle * @return array{ * dry_run: bool, * warnings: list, * server_uuid: string|null, * private_key_uuid: string|null, * created: array, * preserved_uuids: bool, * export_id: string|null, * claimed: bool, * claim: array|null * } */ public function import( array $bundle, int $teamId, bool $dryRun = false, bool $preserveUuids = true, bool $adoptMode = true, bool $claim = true, bool $writeRemote = false, bool $rebindSentinel = true, ): array { ServerTransferBundle::assertValid($bundle); $validation = ServerTransferBundle::validate($bundle); $warnings = $validation['warnings']; if (is_array(data_get($bundle, 'warnings'))) { $warnings = array_values(array_unique(array_merge($warnings, $bundle['warnings']))); } $serverUuid = (string) data_get($bundle, 'server.uuid'); $serverIp = (string) data_get($bundle, 'server.ip'); $uuidConflict = Server::withTrashed()->where('uuid', $serverUuid)->exists() && $preserveUuids; $existingIp = Server::where('ip', $serverIp)->first(); $ipConflict = $existingIp !== null; if (! $dryRun && $uuidConflict) { throw ValidationException::withMessages([ 'server.uuid' => ["A server with UUID {$serverUuid} already exists on this instance. Delete/transfer it first, or import with preserve_uuids=false."], ]); } if (! $dryRun && $ipConflict) { throw ValidationException::withMessages([ 'server.ip' => ["A server with IP/domain {$serverIp} already exists (uuid={$existingIp->uuid}). Complete handoff on the source instance first."], ]); } if ($uuidConflict) { $warnings[] = "A server with UUID {$serverUuid} already exists on this instance."; } if ($ipConflict) { $warnings[] = "A server with IP/domain {$serverIp} already exists (uuid={$existingIp->uuid})."; } $created = [ 'projects' => 0, 'environments' => 0, 'applications' => 0, 'databases' => 0, 'services' => 0, 'destinations' => 0, 'private_keys' => 0, 'github_apps' => 0, 'gitlab_apps' => 0, 's3_storages' => 0, 'cloud_provider_tokens' => 0, 'ssl_certificates' => 0, 'volume_backups' => 0, 'previews' => 0, ]; if ($dryRun) { foreach (data_get($bundle, 'projects', []) as $project) { $created['projects']++; foreach (data_get($project, 'environments', []) as $environment) { $created['environments']++; $created['applications'] += count(data_get($environment, 'applications', [])); $created['databases'] += count(data_get($environment, 'databases', [])); $created['services'] += count(data_get($environment, 'services', [])); foreach (data_get($environment, 'applications', []) as $app) { $created['previews'] += count(data_get($app, 'previews', [])); } } } $created['destinations'] = count(data_get($bundle, 'destinations', [])); $created['private_keys'] = max( count(data_get($bundle, 'private_keys', [])), data_get($bundle, 'private_key') ? 1 : 0 ); $created['github_apps'] = count(data_get($bundle, 'github_apps', [])); $created['gitlab_apps'] = count(data_get($bundle, 'gitlab_apps', [])); $created['s3_storages'] = count(data_get($bundle, 's3_storages', [])); $created['cloud_provider_tokens'] = count(data_get($bundle, 'cloud_provider_tokens', [])); $created['ssl_certificates'] = count(data_get($bundle, 'ssl_certificates', [])); $created['volume_backups'] = count(data_get($bundle, 'volume_backups', [])); // Append transfer hints for the target instance FQDN. $targetUrl = rtrim((string) (instanceSettings()->fqdn ?: config('app.url')), '/'); if (count(data_get($bundle, 'github_apps', [])) > 0) { $warnings[] = "After import, set GitHub App webhook URL to {$targetUrl}/webhooks/source/github/events (and update setup/callback URLs in GitHub if needed) so automations work on this instance."; } if (count(data_get($bundle, 'gitlab_apps', [])) > 0) { $warnings[] = "After import, set GitLab webhook URL to {$targetUrl}/webhooks/source/gitlab/events so automations work on this instance."; } return [ 'dry_run' => true, 'warnings' => $warnings, 'server_uuid' => $preserveUuids ? $serverUuid : null, 'private_key_uuid' => data_get($bundle, 'private_key.uuid'), 'created' => $created, 'preserved_uuids' => $preserveUuids, 'export_id' => data_get($bundle, 'export_id'), 'claimed' => false, 'claim' => null, ]; } $result = DB::transaction(function () use ($bundle, $teamId, $preserveUuids, $adoptMode, $warnings, &$created) { $this->privateKeyMap = []; $this->githubAppMap = []; $this->gitlabAppMap = []; $this->s3StorageMap = []; $this->cloudTokenMap = []; $this->volumeMap = []; $this->applicationMap = []; $this->serviceMap = []; $this->databaseMap = []; // Import shared dependencies first $keyPayloads = data_get($bundle, 'private_keys', []); if ($keyPayloads === [] && data_get($bundle, 'private_key')) { $keyPayloads = [data_get($bundle, 'private_key')]; } foreach ($keyPayloads as $keyPayload) { $key = $this->importPrivateKey($keyPayload, $teamId, $preserveUuids); $this->privateKeyMap[(string) data_get($keyPayload, 'uuid', $key->uuid)] = $key; $this->privateKeyMap[$key->uuid] = $key; $created['private_keys']++; } foreach (data_get($bundle, 'github_apps', []) as $ghPayload) { // Defensive: never import system-wide GitHub Apps from a bundle. if ((bool) data_get($ghPayload, 'is_system_wide', false)) { continue; } $gh = $this->importGithubApp($ghPayload, $teamId, $preserveUuids); $this->githubAppMap[(string) data_get($ghPayload, 'uuid', $gh->uuid)] = $gh; $this->githubAppMap[$gh->uuid] = $gh; $created['github_apps']++; } foreach (data_get($bundle, 'gitlab_apps', []) as $glPayload) { // Defensive: never import system-wide GitLab Apps from a bundle. if ((bool) data_get($glPayload, 'is_system_wide', false)) { continue; } $gl = $this->importGitlabApp($glPayload, $teamId, $preserveUuids); $this->gitlabAppMap[(string) data_get($glPayload, 'uuid', $gl->uuid)] = $gl; $this->gitlabAppMap[$gl->uuid] = $gl; $created['gitlab_apps']++; } foreach (data_get($bundle, 's3_storages', []) as $s3Payload) { $s3 = $this->importS3Storage($s3Payload, $teamId, $preserveUuids); $this->s3StorageMap[(string) data_get($s3Payload, 'uuid', $s3->uuid)] = $s3; $this->s3StorageMap[$s3->uuid] = $s3; $created['s3_storages']++; } foreach (data_get($bundle, 'cloud_provider_tokens', []) as $tokenPayload) { $token = $this->importCloudProviderToken($tokenPayload, $teamId, $preserveUuids); $this->cloudTokenMap[(string) data_get($tokenPayload, 'uuid', $token->uuid)] = $token; $this->cloudTokenMap[$token->uuid] = $token; $created['cloud_provider_tokens']++; } $serverKeyUuid = (string) data_get($bundle, 'private_key.uuid', ''); $privateKey = $this->privateKeyMap[$serverKeyUuid] ?? $this->importPrivateKey(data_get($bundle, 'private_key', []), $teamId, $preserveUuids); $server = $this->importServer(data_get($bundle, 'server', []), $privateKey, $teamId, $preserveUuids, $adoptMode); $destinationMap = $this->importDestinations(data_get($bundle, 'destinations', []), $server, $preserveUuids); $created['destinations'] = count($destinationMap); $this->importServerSharedEnvVars(data_get($bundle, 'shared_environment_variables.server', []), $server, $teamId); foreach (data_get($bundle, 'projects', []) as $projectPayload) { $project = $this->importProject($projectPayload, $teamId, $preserveUuids, $created); foreach (data_get($projectPayload, 'environments', []) as $environmentPayload) { $environment = $this->importEnvironment($environmentPayload, $project, $preserveUuids, $created); foreach (data_get($environmentPayload, 'applications', []) as $appPayload) { $app = $this->importApplication($appPayload, $environment, $destinationMap, $preserveUuids, $adoptMode, $teamId); $this->applicationMap[(string) data_get($appPayload, 'uuid', $app->uuid)] = $app; $created['applications']++; $created['previews'] += count(data_get($appPayload, 'previews', [])); } foreach (data_get($environmentPayload, 'databases', []) as $dbPayload) { $db = $this->importDatabase($dbPayload, $environment, $destinationMap, $preserveUuids, $adoptMode, $teamId); if ($db) { $this->databaseMap[(string) data_get($dbPayload, 'uuid', $db->uuid)] = $db; } $created['databases']++; } foreach (data_get($environmentPayload, 'services', []) as $servicePayload) { $service = $this->importService($servicePayload, $environment, $server, $destinationMap, $preserveUuids, $adoptMode, $teamId); $this->serviceMap[(string) data_get($servicePayload, 'uuid', $service->uuid)] = $service; $created['services']++; } } } $created['ssl_certificates'] = $this->importSslCertificates(data_get($bundle, 'ssl_certificates', []), $server); $created['volume_backups'] = $this->importVolumeBackups(data_get($bundle, 'volume_backups', []), $teamId); $targetUrl = rtrim((string) (instanceSettings()->fqdn ?: config('app.url')), '/'); if ($created['github_apps'] > 0) { $warnings[] = "GitHub Apps imported. Update webhook URL to {$targetUrl}/webhooks/source/github/events (and setup/callback URLs in GitHub) so automations work on this instance."; } if ($created['gitlab_apps'] > 0) { $warnings[] = "GitLab sources imported. Update webhook URL to {$targetUrl}/webhooks/source/gitlab/events so automations work on this instance."; } $metadata = $server->server_metadata ?? []; $metadata['transfer'] = [ 'status' => 'imported', 'export_id' => data_get($bundle, 'export_id'), 'source_instance_url' => data_get($bundle, 'source_instance.url'), 'imported_at' => now()->toIso8601String(), 'adopt_mode' => $adoptMode, ]; $server->server_metadata = $metadata; $server->save(); return [ 'dry_run' => false, 'warnings' => array_values(array_unique($warnings)), 'server_uuid' => $server->uuid, 'private_key_uuid' => $privateKey->uuid, 'created' => $created, 'preserved_uuids' => $preserveUuids, 'export_id' => data_get($bundle, 'export_id'), 'claimed' => false, 'claim' => null, ]; }); // Claim after the import transaction commits so host/SSH work cannot roll back DB rows. if ($claim && filled(data_get($result, 'server_uuid'))) { $server = Server::where('uuid', $result['server_uuid'])->where('team_id', $teamId)->first(); if ($server) { try { $claimResult = app(ServerTransferClaimer::class)->claim( $server, writeRemote: $writeRemote, rebindSentinel: $rebindSentinel, ); $result['claimed'] = true; $result['claim'] = $claimResult; if (! data_get($claimResult, 'claim_written') && $writeRemote) { $result['warnings'][] = 'Server imported and claimed in Coolify, but the remote ownership file was not written (SSH unavailable).'; } } catch (Throwable $e) { $result['claimed'] = false; $result['claim'] = null; $result['warnings'][] = 'Server imported, but automatic claim failed: '.$e->getMessage(); } } } return $result; } /** * @param array $payload */ private function importPrivateKey(array $payload, int $teamId, bool $preserveUuids): PrivateKey { $material = (string) data_get($payload, 'private_key'); if ($material === '') { throw new RuntimeException('Private key material is required.'); } $fingerprint = PrivateKey::generateFingerprint($material) ?? data_get($payload, 'fingerprint'); if ($fingerprint) { $existing = PrivateKey::query()->where('fingerprint', $fingerprint)->first(); if ($existing) { if ((int) $existing->team_id !== $teamId) { throw ValidationException::withMessages([ 'private_key' => ['This SSH private key already exists on another team on this instance.'], ]); } return $existing; } } $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); if (PrivateKey::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $key = new PrivateKey([ 'name' => data_get($payload, 'name') ?: 'Imported transfer key', 'description' => data_get($payload, 'description'), 'private_key' => $material, 'team_id' => $teamId, 'is_git_related' => (bool) data_get($payload, 'is_git_related', false), ]); $key->uuid = $uuid; try { $key->save(); } catch (Throwable $e) { // Retry once without forcing uuid if uniqueness/validation conflicts. $key->uuid = new_public_id(); $key->save(); } return $key->fresh(); } /** * @param array $payload */ private function importServer(array $payload, PrivateKey $privateKey, int $teamId, bool $preserveUuids, bool $adoptMode): Server { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); if (Server::withTrashed()->where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $cloudTokenId = null; $cloudTokenUuid = data_get($payload, 'cloud_provider_token_uuid'); if ($cloudTokenUuid && isset($this->cloudTokenMap[$cloudTokenUuid])) { $cloudTokenId = $this->cloudTokenMap[$cloudTokenUuid]->id; } $server = new Server; $server->forceFill([ 'name' => data_get($payload, 'name') ?: generate_random_name(), 'description' => data_get($payload, 'description'), 'ip' => data_get($payload, 'ip'), 'port' => (int) data_get($payload, 'port', 22), 'user' => data_get($payload, 'user') ?: 'root', 'private_key_id' => $privateKey->id, 'team_id' => $teamId, 'cloud_provider_token_id' => $cloudTokenId, ]); $server->uuid = $uuid; $server->save(); if ($server->settings && data_get($payload, 'is_build_server')) { $server->settings->is_build_server = true; $server->settings->save(); } $proxy = data_get($payload, 'proxy', []); if (is_array($proxy) && $proxy !== []) { foreach ($proxy as $key => $value) { $server->proxy->set($key, $value); } $server->save(); } $settingsPayload = data_get($payload, 'settings', []); if (is_array($settingsPayload) && $server->settings) { $safe = collect($settingsPayload) ->except([ 'id', 'server_id', 'created_at', 'updated_at', 'is_reachable', 'is_usable', 'force_disabled', 'sentinel_token', 'sentinel_custom_url', ]) ->all(); $server->settings->fill($safe); // Imported servers start not validated; claim will rebind sentinel. $server->settings->is_reachable = false; $server->settings->is_usable = false; $server->settings->force_disabled = false; $server->settings->is_sentinel_enabled = false; $server->settings->save(); } if ($adoptMode) { // Do not trigger validation/install automatically; operator claims next. $server->settings->is_reachable = false; $server->settings->is_usable = false; $server->settings->save(); } return $server->fresh(['settings', 'standaloneDockers', 'swarmDockers']); } /** * @param list> $destinations * @return array keyed by original uuid */ private function importDestinations(array $destinations, Server $server, bool $preserveUuids): array { $map = []; $existingStandalone = $server->standaloneDockers->values(); $existingSwarm = $server->swarmDockers->values(); $standaloneIndex = 0; $swarmIndex = 0; if ($destinations === []) { $first = $existingStandalone->first() ?? $existingSwarm->first(); if ($first) { $map[$first->uuid] = $first; } return $map; } foreach ($destinations as $destination) { $type = data_get($destination, 'type', 'standalone'); $originalUuid = (string) data_get($destination, 'uuid'); $uuid = $preserveUuids && $originalUuid !== '' ? $originalUuid : new_public_id(); if ($type === 'swarm') { $model = $existingSwarm->get($swarmIndex); $swarmIndex++; if ($model) { $model->forceFill([ 'uuid' => SwarmDocker::where('uuid', $uuid)->where('id', '!=', $model->id)->exists() ? $model->uuid : $uuid, 'name' => data_get($destination, 'name') ?: $model->name, 'network' => data_get($destination, 'network') ?: $model->network, ])->saveQuietly(); } else { $model = new SwarmDocker; $model->forceFill([ 'uuid' => SwarmDocker::where('uuid', $uuid)->exists() ? new_public_id() : $uuid, 'name' => data_get($destination, 'name') ?: 'coolify-overlay', 'network' => data_get($destination, 'network') ?: 'coolify-overlay', 'server_id' => $server->id, ])->saveQuietly(); } } else { $model = $existingStandalone->get($standaloneIndex); $standaloneIndex++; if ($model) { $targetUuid = StandaloneDocker::where('uuid', $uuid)->where('id', '!=', $model->id)->exists() ? $model->uuid : $uuid; $model->forceFill([ 'uuid' => $targetUuid, 'name' => data_get($destination, 'name') ?: $model->name, 'network' => data_get($destination, 'network') ?: $model->network, ])->saveQuietly(); } else { $model = new StandaloneDocker; $model->forceFill([ 'uuid' => StandaloneDocker::where('uuid', $uuid)->exists() ? new_public_id() : $uuid, 'name' => data_get($destination, 'name') ?: 'coolify', 'network' => data_get($destination, 'network') ?: 'coolify', 'server_id' => $server->id, ])->saveQuietly(); } } $map[$originalUuid !== '' ? $originalUuid : $model->uuid] = $model->fresh(); } return $map; } /** * @param list> $variables */ private function importServerSharedEnvVars(array $variables, Server $server, int $teamId): void { foreach ($variables as $variable) { SharedEnvironmentVariable::create([ 'key' => data_get($variable, 'key'), 'value' => data_get($variable, 'value'), 'is_multiline' => (bool) data_get($variable, 'is_multiline', false), 'is_literal' => (bool) data_get($variable, 'is_literal', false), 'is_shown_once' => (bool) data_get($variable, 'is_shown_once', false), 'comment' => data_get($variable, 'comment'), 'type' => 'server', 'server_id' => $server->id, 'team_id' => $teamId, ]); } } /** * @param array $payload * @param array $created */ private function importProject(array $payload, int $teamId, bool $preserveUuids, array &$created): Project { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); $existing = Project::where('uuid', $uuid)->where('team_id', $teamId)->first(); if ($existing) { $this->importProjectSharedEnvVars(data_get($payload, 'shared_environment_variables', []), $existing, $teamId); return $existing; } if (Project::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $project = Project::create([ 'uuid' => $uuid, 'name' => data_get($payload, 'name') ?: generate_random_name(), 'description' => data_get($payload, 'description'), 'team_id' => $teamId, ]); $created['projects']++; $this->importProjectSharedEnvVars(data_get($payload, 'shared_environment_variables', []), $project, $teamId); return $project->fresh(['environments']); } /** * @param list> $variables */ private function importProjectSharedEnvVars(array $variables, Project $project, int $teamId): void { foreach ($variables as $variable) { $exists = SharedEnvironmentVariable::query() ->where('type', 'project') ->where('project_id', $project->id) ->where('key', data_get($variable, 'key')) ->exists(); if ($exists) { continue; } SharedEnvironmentVariable::create([ 'key' => data_get($variable, 'key'), 'value' => data_get($variable, 'value'), 'is_multiline' => (bool) data_get($variable, 'is_multiline', false), 'is_literal' => (bool) data_get($variable, 'is_literal', false), 'is_shown_once' => (bool) data_get($variable, 'is_shown_once', false), 'comment' => data_get($variable, 'comment'), 'type' => 'project', 'project_id' => $project->id, 'team_id' => $teamId, ]); } } /** * @param array $payload * @param array $created */ private function importEnvironment(array $payload, Project $project, bool $preserveUuids, array &$created): Environment { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); $byUuid = Environment::where('uuid', $uuid)->where('project_id', $project->id)->first(); if ($byUuid) { $this->importEnvironmentSharedEnvVars(data_get($payload, 'shared_environment_variables', []), $byUuid, $project->team_id); return $byUuid; } $name = (string) data_get($payload, 'name', 'production'); $byName = $project->environments()->where('name', $name)->first(); if ($byName) { if ($preserveUuids && $uuid !== '' && ! Environment::where('uuid', $uuid)->exists()) { $byName->uuid = $uuid; $byName->save(); } if (filled(data_get($payload, 'description'))) { $byName->description = data_get($payload, 'description'); $byName->save(); } $this->importEnvironmentSharedEnvVars(data_get($payload, 'shared_environment_variables', []), $byName, $project->team_id); return $byName->fresh(); } if (Environment::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $environment = Environment::create([ 'uuid' => $uuid, 'name' => $name, 'description' => data_get($payload, 'description'), 'project_id' => $project->id, ]); $created['environments']++; $this->importEnvironmentSharedEnvVars(data_get($payload, 'shared_environment_variables', []), $environment, $project->team_id); return $environment; } /** * @param list> $variables */ private function importEnvironmentSharedEnvVars(array $variables, Environment $environment, int $teamId): void { foreach ($variables as $variable) { $exists = SharedEnvironmentVariable::query() ->where('type', 'environment') ->where('environment_id', $environment->id) ->where('key', data_get($variable, 'key')) ->exists(); if ($exists) { continue; } SharedEnvironmentVariable::create([ 'key' => data_get($variable, 'key'), 'value' => data_get($variable, 'value'), 'is_multiline' => (bool) data_get($variable, 'is_multiline', false), 'is_literal' => (bool) data_get($variable, 'is_literal', false), 'is_shown_once' => (bool) data_get($variable, 'is_shown_once', false), 'comment' => data_get($variable, 'comment'), 'type' => 'environment', 'environment_id' => $environment->id, 'team_id' => $teamId, ]); } } /** * @param array $payload * @param array $destinationMap */ private function importApplication( array $payload, Environment $environment, array $destinationMap, bool $preserveUuids, bool $adoptMode, int $teamId, ): Application { $destination = $this->resolveDestination(data_get($payload, 'destination_uuid'), $destinationMap); $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); if (Application::withTrashed()->where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $attributes = (array) data_get($payload, 'attributes', []); unset($attributes['id'], $attributes['environment_id'], $attributes['destination_id'], $attributes['destination_type'], $attributes['source_id'], $attributes['source_type'], $attributes['private_key_id']); $attributes = $this->onlyExistingColumns(Application::class, $attributes); $attributes['uuid'] = $uuid; $attributes['environment_id'] = $environment->id; $attributes['destination_id'] = $destination->id; $attributes['destination_type'] = $destination->getMorphClass(); // Re-link git source $source = data_get($payload, 'source'); if (is_array($source) && filled(data_get($source, 'uuid'))) { $sourceUuid = (string) data_get($source, 'uuid'); $sourceType = (string) data_get($source, 'type'); if ($sourceType === 'github_app') { $gh = $this->githubAppMap[$sourceUuid] ?? GithubApp::query()->where('uuid', $sourceUuid)->where('is_system_wide', true)->first() ?? GithubApp::query()->where('uuid', $sourceUuid)->where('team_id', $teamId)->first(); if ($gh) { $attributes['source_type'] = GithubApp::class; $attributes['source_id'] = $gh->id; } } elseif ($sourceType === 'gitlab_app') { $gl = $this->gitlabAppMap[$sourceUuid] ?? GitlabApp::query()->where('uuid', $sourceUuid)->where('is_system_wide', true)->first() ?? GitlabApp::query()->where('uuid', $sourceUuid)->where('team_id', $teamId)->first(); if ($gl) { $attributes['source_type'] = GitlabApp::class; $attributes['source_id'] = $gl->id; } } } // Re-link deploy key $appKeyUuid = data_get($payload, 'private_key_uuid'); if ($appKeyUuid && isset($this->privateKeyMap[$appKeyUuid])) { $attributes['private_key_id'] = $this->privateKeyMap[$appKeyUuid]->id; } if ($adoptMode) { // Keep runtime status if provided so inventory can match, but default exited if empty. $attributes['status'] = data_get($attributes, 'status') ?: 'exited'; } else { $attributes['status'] = 'exited'; } $application = Application::create($attributes); $settings = (array) data_get($payload, 'settings', []); if ($settings !== [] && $application->settings) { $application->settings->fill(collect($settings)->except(['id', 'application_id'])->all()); $application->settings->save(); } $this->importEnvVars(data_get($payload, 'environment_variables', []), $application, false); $this->importEnvVars(data_get($payload, 'environment_variables_preview', []), $application, true); $this->importPersistentStorages(data_get($payload, 'persistent_storages', []), $application); $this->importFileStorages(data_get($payload, 'file_storages', []), $application); $this->importScheduledTasks(data_get($payload, 'scheduled_tasks', []), $application, $teamId); $this->importTags(data_get($payload, 'tags', []), $application, $teamId); $this->importPreviews(data_get($payload, 'previews', []), $application, $preserveUuids, $adoptMode); return $application; } /** * @param array $payload * @param array $destinationMap */ private function importDatabase( array $payload, Environment $environment, array $destinationMap, bool $preserveUuids, bool $adoptMode, int $teamId = 0, ): ?Model { $type = (string) data_get($payload, 'type', 'StandalonePostgresql'); $modelClass = self::DATABASE_MODELS[$type] ?? (is_string(data_get($payload, 'model')) && class_exists((string) data_get($payload, 'model')) ? (string) data_get($payload, 'model') : null); if (! $modelClass || ! class_exists($modelClass)) { throw new RuntimeException("Unsupported database type: {$type}"); } $destination = $this->resolveDestination(data_get($payload, 'destination_uuid'), $destinationMap); $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); if ($modelClass::withTrashed()->where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $attributes = (array) data_get($payload, 'attributes', []); unset($attributes['id'], $attributes['environment_id'], $attributes['destination_id'], $attributes['destination_type']); $attributes = $this->onlyExistingColumns($modelClass, $attributes); $attributes['uuid'] = $uuid; $attributes['environment_id'] = $environment->id; $attributes['destination_id'] = $destination->id; $attributes['destination_type'] = $destination->getMorphClass(); $attributes['status'] = $adoptMode ? (data_get($attributes, 'status') ?: 'exited') : 'exited'; // Avoid auto-created default volumes; we restore exported ones. $database = $modelClass::withoutEvents(function () use ($modelClass, $attributes, $uuid) { $database = new $modelClass; $database->forceFill(collect($attributes)->except(['uuid'])->all()); $database->uuid = $uuid; $database->save(); return $database; }); $this->importEnvVars(data_get($payload, 'environment_variables', []), $database, false); $this->importPersistentStorages(data_get($payload, 'persistent_storages', []), $database); $this->importFileStorages(data_get($payload, 'file_storages', []), $database); if (method_exists($database, 'tags')) { $this->importTags(data_get($payload, 'tags', []), $database, $teamId); } $this->importScheduledBackups(data_get($payload, 'scheduled_backups', []), $database, $teamId); return $database; } /** * @param array $payload * @param array $destinationMap */ private function importService( array $payload, Environment $environment, Server $server, array $destinationMap, bool $preserveUuids, bool $adoptMode, int $teamId = 0, ): Service { $destination = $this->resolveDestination(data_get($payload, 'destination_uuid'), $destinationMap); $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); if (Service::withTrashed()->where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $attributes = (array) data_get($payload, 'attributes', []); unset($attributes['id'], $attributes['environment_id'], $attributes['destination_id'], $attributes['destination_type'], $attributes['server_id']); $attributes = $this->onlyExistingColumns(Service::class, $attributes); $attributes['uuid'] = $uuid; $attributes['environment_id'] = $environment->id; $attributes['server_id'] = $server->id; $attributes['destination_id'] = $destination->id; $attributes['destination_type'] = $destination->getMorphClass(); $service = Service::create($attributes); $this->importEnvVars(data_get($payload, 'environment_variables', []), $service, false); $this->importScheduledTasks(data_get($payload, 'scheduled_tasks', []), $service, $teamId); $this->importTags(data_get($payload, 'tags', []), $service, $teamId); foreach (data_get($payload, 'applications', []) as $appPayload) { $this->importServiceApplication($appPayload, $service, $preserveUuids, $adoptMode); } foreach (data_get($payload, 'databases', []) as $dbPayload) { $this->importServiceDatabase($dbPayload, $service, $preserveUuids, $adoptMode, $teamId); } return $service; } /** * @param array $appPayload */ private function importServiceApplication(array $appPayload, Service $service, bool $preserveUuids, bool $adoptMode): ServiceApplication { $appUuid = $preserveUuids && filled(data_get($appPayload, 'uuid')) ? (string) data_get($appPayload, 'uuid') : new_public_id(); if (ServiceApplication::where('uuid', $appUuid)->exists()) { $appUuid = new_public_id(); } $attributes = (array) data_get($appPayload, 'attributes', []); if ($attributes === []) { // Back-compat with older thin service-application payloads. $attributes = collect($appPayload)->except([ 'uuid', 'environment_variables', 'persistent_storages', 'file_storages', 'attributes', ])->all(); } unset($attributes['id'], $attributes['service_id'], $attributes['created_at'], $attributes['updated_at'], $attributes['deleted_at']); $attributes = $this->onlyExistingColumns(ServiceApplication::class, $attributes); $attributes['service_id'] = $service->id; $attributes['name'] = data_get($attributes, 'name') ?: data_get($appPayload, 'name') ?: 'service-app'; $attributes['status'] = $adoptMode ? (data_get($attributes, 'status') ?: data_get($appPayload, 'status') ?: 'running:unknown') : 'exited:unhealthy'; $serviceApp = new ServiceApplication; $serviceApp->forceFill($attributes); $serviceApp->uuid = $appUuid; $serviceApp->save(); $this->importEnvVars(data_get($appPayload, 'environment_variables', []), $serviceApp, false); $this->importPersistentStorages(data_get($appPayload, 'persistent_storages', []), $serviceApp); $this->importFileStorages(data_get($appPayload, 'file_storages', []), $serviceApp); return $serviceApp; } /** * @param array $dbPayload */ private function importServiceDatabase(array $dbPayload, Service $service, bool $preserveUuids, bool $adoptMode, int $teamId): ServiceDatabase { $dbUuid = $preserveUuids && filled(data_get($dbPayload, 'uuid')) ? (string) data_get($dbPayload, 'uuid') : new_public_id(); if (ServiceDatabase::where('uuid', $dbUuid)->exists()) { $dbUuid = new_public_id(); } $attributes = (array) data_get($dbPayload, 'attributes', []); if ($attributes === []) { $attributes = collect($dbPayload)->except([ 'uuid', 'environment_variables', 'persistent_storages', 'file_storages', 'scheduled_backups', 'attributes', ])->all(); } unset($attributes['id'], $attributes['service_id'], $attributes['created_at'], $attributes['updated_at'], $attributes['deleted_at']); $attributes = $this->onlyExistingColumns(ServiceDatabase::class, $attributes); $attributes['service_id'] = $service->id; $attributes['name'] = data_get($attributes, 'name') ?: data_get($dbPayload, 'name') ?: 'service-db'; $attributes['status'] = $adoptMode ? (data_get($attributes, 'status') ?: data_get($dbPayload, 'status') ?: 'running:unknown') : 'exited:unhealthy'; $serviceDb = new ServiceDatabase; $serviceDb->forceFill($attributes); $serviceDb->uuid = $dbUuid; $serviceDb->save(); $this->importPersistentStorages(data_get($dbPayload, 'persistent_storages', []), $serviceDb); $this->importFileStorages(data_get($dbPayload, 'file_storages', []), $serviceDb); $this->importScheduledBackups(data_get($dbPayload, 'scheduled_backups', []), $serviceDb, $teamId); return $serviceDb; } /** * @param list> $variables */ private function importEnvVars(array $variables, object $resource, bool $isPreview): void { foreach ($variables as $variable) { EnvironmentVariable::withoutEvents(function () use ($variable, $resource, $isPreview) { $uuid = filled(data_get($variable, 'uuid')) ? (string) data_get($variable, 'uuid') : new_public_id(); if (EnvironmentVariable::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $env = new EnvironmentVariable; $env->forceFill([ 'key' => data_get($variable, 'key'), 'value' => data_get($variable, 'value'), 'is_literal' => (bool) data_get($variable, 'is_literal', false), 'is_multiline' => (bool) data_get($variable, 'is_multiline', false), 'is_preview' => $isPreview || (bool) data_get($variable, 'is_preview', false), 'is_runtime' => (bool) data_get($variable, 'is_runtime', true), 'is_buildtime' => (bool) data_get($variable, 'is_buildtime', true), 'is_shown_once' => (bool) data_get($variable, 'is_shown_once', false), 'is_shared' => (bool) data_get($variable, 'is_shared', false), 'is_required' => (bool) data_get($variable, 'is_required', false), 'comment' => data_get($variable, 'comment'), 'order' => data_get($variable, 'order'), 'resourceable_type' => $resource->getMorphClass(), 'resourceable_id' => $resource->id, ]); $env->uuid = $uuid; $env->save(); }); } } /** * @param list> $volumes */ private function importPersistentStorages(array $volumes, object $resource): void { foreach ($volumes as $volume) { $uuid = filled(data_get($volume, 'uuid')) ? (string) data_get($volume, 'uuid') : new_public_id(); if (LocalPersistentVolume::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $volumeModel = new LocalPersistentVolume; $volumeModel->forceFill([ 'name' => data_get($volume, 'name'), 'mount_path' => data_get($volume, 'mount_path'), 'host_path' => data_get($volume, 'host_path'), 'is_preview_suffix_enabled' => (bool) data_get($volume, 'is_preview_suffix_enabled', false), 'resource_type' => $resource->getMorphClass(), 'resource_id' => $resource->id, ]); $volumeModel->uuid = $uuid; $volumeModel->save(); $this->volumeMap[$uuid] = $volumeModel; if (filled(data_get($volume, 'uuid'))) { $this->volumeMap[(string) data_get($volume, 'uuid')] = $volumeModel; } } } /** * @param list> $storages */ private function importFileStorages(array $storages, object $resource): void { foreach ($storages as $storage) { LocalFileVolume::withoutEvents(function () use ($storage, $resource) { $uuid = filled(data_get($storage, 'uuid')) ? (string) data_get($storage, 'uuid') : new_public_id(); if (LocalFileVolume::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } // uuid is not fillable and withoutEvents skips BaseModel's creating hook. $file = new LocalFileVolume; $file->forceFill([ 'fs_path' => data_get($storage, 'fs_path'), 'mount_path' => data_get($storage, 'mount_path'), 'content' => data_get($storage, 'content'), 'is_directory' => (bool) data_get($storage, 'is_directory', false), 'is_host_file' => (bool) data_get($storage, 'is_host_file', false), 'chown' => data_get($storage, 'chown'), 'chmod' => data_get($storage, 'chmod'), 'is_based_on_git' => (bool) data_get($storage, 'is_based_on_git', false), 'is_preview_suffix_enabled' => (bool) data_get($storage, 'is_preview_suffix_enabled', false), 'resource_type' => $resource->getMorphClass(), 'resource_id' => $resource->id, ]); $file->uuid = $uuid; $file->save(); $this->volumeMap[$uuid] = $file; if (filled(data_get($storage, 'uuid'))) { $this->volumeMap[(string) data_get($storage, 'uuid')] = $file; } }); } } /** * @param list> $tasks */ private function importScheduledTasks(array $tasks, Application|Service $resource, int $teamId): void { foreach ($tasks as $task) { $uuid = filled(data_get($task, 'uuid')) ? (string) data_get($task, 'uuid') : new_public_id(); if (ScheduledTask::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $payload = [ 'uuid' => $uuid, 'name' => data_get($task, 'name'), 'command' => data_get($task, 'command'), 'frequency' => data_get($task, 'frequency'), 'container' => data_get($task, 'container'), 'timeout' => data_get($task, 'timeout'), 'enabled' => (bool) data_get($task, 'enabled', true), 'team_id' => $teamId, ]; if ($resource instanceof Application) { $payload['application_id'] = $resource->id; } else { $payload['service_id'] = $resource->id; } ScheduledTask::create($payload); } } /** * @param list $tags */ private function importTags(array $tags, Model $resource, int $teamId): void { if ($tags === [] || ! method_exists($resource, 'tags')) { return; } foreach ($tags as $tagPayload) { $name = strtolower(trim((string) data_get($tagPayload, 'name', ''))); if ($name === '') { continue; } $uuid = filled(data_get($tagPayload, 'uuid')) ? (string) data_get($tagPayload, 'uuid') : new_public_id(); $tag = Tag::query() ->where('team_id', $teamId) ->where('name', $name) ->first(); if (! $tag) { // Prefer preserved uuid when free; otherwise create with a new one. if (Tag::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $tag = new Tag; $tag->forceFill([ 'name' => $name, 'team_id' => $teamId, ]); $tag->uuid = $uuid; $tag->save(); } $resource->tags()->syncWithoutDetaching([$tag->id]); } } /** * @param list> $backups */ private function importScheduledBackups(array $backups, Model $database, int $teamId): void { foreach ($backups as $backup) { $uuid = filled(data_get($backup, 'uuid')) ? (string) data_get($backup, 'uuid') : new_public_id(); if (ScheduledDatabaseBackup::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $s3Id = null; $s3Uuid = data_get($backup, 's3_storage_uuid'); if ($s3Uuid && isset($this->s3StorageMap[$s3Uuid])) { $s3Id = $this->s3StorageMap[$s3Uuid]->id; } ScheduledDatabaseBackup::create([ 'uuid' => $uuid, 'team_id' => $teamId, 'description' => data_get($backup, 'description'), 'enabled' => (bool) data_get($backup, 'enabled', true), 'save_s3' => (bool) data_get($backup, 'save_s3', false) && $s3Id !== null, 'frequency' => data_get($backup, 'frequency'), 'databases_to_backup' => data_get($backup, 'databases_to_backup'), 'dump_all' => (bool) data_get($backup, 'dump_all', false), 'database_backup_retention_amount_locally' => data_get($backup, 'database_backup_retention_amount_locally', 0), 'database_backup_retention_days_locally' => data_get($backup, 'database_backup_retention_days_locally'), 'database_backup_retention_max_storage_locally' => data_get($backup, 'database_backup_retention_max_storage_locally'), 'database_backup_retention_amount_s3' => data_get($backup, 'database_backup_retention_amount_s3'), 'database_backup_retention_days_s3' => data_get($backup, 'database_backup_retention_days_s3'), 'database_backup_retention_max_storage_s3' => data_get($backup, 'database_backup_retention_max_storage_s3'), 'timeout' => data_get($backup, 'timeout'), 'disable_local_backup' => (bool) data_get($backup, 'disable_local_backup', false), 's3_storage_id' => $s3Id, 'database_type' => $database->getMorphClass(), 'database_id' => $database->id, ]); } } /** * @param array $payload */ private function importGithubApp(array $payload, int $teamId, bool $preserveUuids): GithubApp { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); $existing = GithubApp::where('uuid', $uuid)->where('team_id', $teamId)->first(); if ($existing) { return $existing; } if (GithubApp::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $privateKeyId = null; $keyUuid = data_get($payload, 'private_key_uuid'); if ($keyUuid && isset($this->privateKeyMap[$keyUuid])) { $privateKeyId = $this->privateKeyMap[$keyUuid]->id; } $app = new GithubApp; $app->forceFill([ 'team_id' => $teamId, 'private_key_id' => $privateKeyId, 'name' => data_get($payload, 'name') ?: 'Imported GitHub App', 'organization' => data_get($payload, 'organization'), 'api_url' => data_get($payload, 'api_url') ?: 'https://api.github.com', 'html_url' => data_get($payload, 'html_url') ?: 'https://github.com', 'custom_user' => data_get($payload, 'custom_user'), 'custom_port' => data_get($payload, 'custom_port'), 'app_id' => data_get($payload, 'app_id'), 'installation_id' => data_get($payload, 'installation_id'), 'client_id' => data_get($payload, 'client_id'), 'client_secret' => data_get($payload, 'client_secret'), 'webhook_secret' => data_get($payload, 'webhook_secret'), 'is_system_wide' => false, 'is_public' => (bool) data_get($payload, 'is_public', false), 'contents' => data_get($payload, 'contents'), 'metadata' => data_get($payload, 'metadata'), 'pull_requests' => data_get($payload, 'pull_requests'), 'administration' => data_get($payload, 'administration'), ]); $app->uuid = $uuid; $app->save(); return $app; } /** * @param array $payload */ private function importGitlabApp(array $payload, int $teamId, bool $preserveUuids): GitlabApp { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); $existing = GitlabApp::where('uuid', $uuid)->where('team_id', $teamId)->first(); if ($existing) { return $existing; } if (GitlabApp::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $privateKeyId = null; $keyUuid = data_get($payload, 'private_key_uuid'); if ($keyUuid && isset($this->privateKeyMap[$keyUuid])) { $privateKeyId = $this->privateKeyMap[$keyUuid]->id; } $app = new GitlabApp; $app->forceFill([ 'team_id' => $teamId, 'private_key_id' => $privateKeyId, 'name' => data_get($payload, 'name') ?: 'Imported GitLab App', 'organization' => data_get($payload, 'organization'), 'api_url' => data_get($payload, 'api_url'), 'html_url' => data_get($payload, 'html_url'), 'custom_port' => data_get($payload, 'custom_port'), 'custom_user' => data_get($payload, 'custom_user'), 'is_system_wide' => false, 'is_public' => (bool) data_get($payload, 'is_public', false), 'app_id' => data_get($payload, 'app_id'), 'app_secret' => data_get($payload, 'app_secret'), 'oauth_id' => data_get($payload, 'oauth_id'), 'client_id' => data_get($payload, 'client_id'), 'client_secret' => data_get($payload, 'client_secret'), 'access_token' => data_get($payload, 'access_token'), 'refresh_token' => data_get($payload, 'refresh_token'), 'expires_at' => data_get($payload, 'expires_at'), 'redirect_uri' => data_get($payload, 'redirect_uri'), 'group_name' => data_get($payload, 'group_name'), 'public_key' => data_get($payload, 'public_key'), 'webhook_token' => data_get($payload, 'webhook_token'), 'deploy_key_id' => data_get($payload, 'deploy_key_id'), ]); $app->uuid = $uuid; $app->save(); return $app; } /** * @param array $payload */ private function importS3Storage(array $payload, int $teamId, bool $preserveUuids): S3Storage { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); $existing = S3Storage::where('uuid', $uuid)->where('team_id', $teamId)->first(); if ($existing) { return $existing; } // Reuse by endpoint+bucket+key if already present for team $byIdentity = S3Storage::query() ->where('team_id', $teamId) ->where('endpoint', data_get($payload, 'endpoint')) ->where('bucket', data_get($payload, 'bucket')) ->first(); if ($byIdentity) { return $byIdentity; } if (S3Storage::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $storage = new S3Storage; $storage->forceFill([ 'team_id' => $teamId, 'name' => data_get($payload, 'name') ?: 'Imported S3', 'description' => data_get($payload, 'description'), 'region' => data_get($payload, 'region'), 'key' => data_get($payload, 'key'), 'secret' => data_get($payload, 'secret'), 'bucket' => data_get($payload, 'bucket'), 'endpoint' => data_get($payload, 'endpoint'), 'is_usable' => (bool) data_get($payload, 'is_usable', true), ]); $storage->uuid = $uuid; $storage->save(); return $storage; } /** * @param array $payload */ private function importCloudProviderToken(array $payload, int $teamId, bool $preserveUuids): CloudProviderToken { $uuid = $preserveUuids && filled(data_get($payload, 'uuid')) ? (string) data_get($payload, 'uuid') : new_public_id(); $existing = CloudProviderToken::where('uuid', $uuid)->where('team_id', $teamId)->first(); if ($existing) { return $existing; } if (CloudProviderToken::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $token = new CloudProviderToken; $token->forceFill([ 'team_id' => $teamId, 'provider' => data_get($payload, 'provider'), 'token' => data_get($payload, 'token'), 'name' => data_get($payload, 'name') ?: 'Imported cloud token', 'description' => data_get($payload, 'description'), ]); $token->uuid = $uuid; $token->save(); return $token; } /** * @param list> $previews */ private function importPreviews(array $previews, Application $application, bool $preserveUuids, bool $adoptMode): void { foreach ($previews as $previewPayload) { $uuid = $preserveUuids && filled(data_get($previewPayload, 'uuid')) ? (string) data_get($previewPayload, 'uuid') : new_public_id(); $preview = ApplicationPreview::withTrashed()->where('uuid', $uuid)->first(); if (! $preview) { $pullRequestId = data_get($previewPayload, 'pull_request_id'); if ($pullRequestId !== null) { $preview = ApplicationPreview::withTrashed() ->where('application_id', $application->id) ->where('pull_request_id', $pullRequestId) ->first(); } } if ($preview && ApplicationPreview::withTrashed()->where('uuid', $uuid)->where('id', '!=', $preview->id)->exists()) { $uuid = new_public_id(); } elseif (! $preview && ApplicationPreview::withTrashed()->where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $fqdn = data_get($previewPayload, 'fqdn'); // FQDN is globally unique; free it from other rows (including soft-deleted leftovers). if (filled($fqdn)) { ApplicationPreview::withoutEvents(function () use ($fqdn, $preview) { ApplicationPreview::withTrashed() ->where('fqdn', $fqdn) ->when($preview, fn ($q) => $q->where('id', '!=', $preview->id)) ->get() ->each(function (ApplicationPreview $conflict) { $conflict->fqdn = null; $conflict->saveQuietly(); }); }); } $status = $adoptMode ? (data_get($previewPayload, 'status') ?: 'exited') : 'exited'; $attributes = [ 'application_id' => $application->id, 'pull_request_id' => data_get($previewPayload, 'pull_request_id'), 'pull_request_html_url' => data_get($previewPayload, 'pull_request_html_url') ?: '', 'pull_request_issue_comment_id' => data_get($previewPayload, 'pull_request_issue_comment_id'), 'fqdn' => $fqdn, 'status' => $status, 'git_type' => data_get($previewPayload, 'git_type'), 'docker_compose_domains' => data_get($previewPayload, 'docker_compose_domains'), 'docker_registry_image_tag' => data_get($previewPayload, 'docker_registry_image_tag'), ]; if ($preview) { if ($preview->trashed()) { $preview->restore(); } $preview->forceFill($attributes); if ($preserveUuids && filled(data_get($previewPayload, 'uuid')) && $preview->uuid !== $uuid) { if (! ApplicationPreview::withTrashed()->where('uuid', $uuid)->where('id', '!=', $preview->id)->exists()) { $preview->uuid = $uuid; } } $preview->save(); } else { $preview = new ApplicationPreview; $preview->forceFill($attributes); $preview->uuid = $uuid; $preview->save(); } // Avoid duplicating volumes when re-importing the same preview. if ($preview->wasRecentlyCreated || $preview->persistentStorages()->count() === 0) { $this->importPersistentStorages(data_get($previewPayload, 'persistent_storages', []), $preview); } } } /** * @param list> $certificates */ private function importSslCertificates(array $certificates, Server $server): int { $count = 0; foreach ($certificates as $certPayload) { $resourceType = null; $resourceId = null; $kind = data_get($certPayload, 'resource_kind'); $resourceUuid = data_get($certPayload, 'resource_uuid'); if ($kind === 'application' && $resourceUuid && isset($this->applicationMap[$resourceUuid])) { $resourceType = $this->applicationMap[$resourceUuid]->getMorphClass(); $resourceId = $this->applicationMap[$resourceUuid]->id; } elseif ($kind === 'service' && $resourceUuid && isset($this->serviceMap[$resourceUuid])) { $resourceType = $this->serviceMap[$resourceUuid]->getMorphClass(); $resourceId = $this->serviceMap[$resourceUuid]->id; } elseif ($resourceUuid && isset($this->databaseMap[$resourceUuid])) { $db = $this->databaseMap[$resourceUuid]; $resourceType = $db->getMorphClass(); $resourceId = $db->id; } SslCertificate::create([ 'ssl_certificate' => data_get($certPayload, 'ssl_certificate'), 'ssl_private_key' => data_get($certPayload, 'ssl_private_key'), 'configuration_dir' => data_get($certPayload, 'configuration_dir'), 'mount_path' => data_get($certPayload, 'mount_path'), 'common_name' => data_get($certPayload, 'common_name') ?: 'imported', 'subject_alternative_names' => data_get($certPayload, 'subject_alternative_names'), 'valid_until' => data_get($certPayload, 'valid_until') ?: now()->addYear(), 'is_ca_certificate' => (bool) data_get($certPayload, 'is_ca_certificate', false), 'server_id' => $server->id, 'resource_type' => $resourceType, 'resource_id' => $resourceId, ]); $count++; } return $count; } /** * @param list> $backups */ private function importVolumeBackups(array $backups, int $teamId): int { $count = 0; foreach ($backups as $backupPayload) { $volumeUuid = (string) data_get($backupPayload, 'backupable_uuid', ''); $volume = $this->volumeMap[$volumeUuid] ?? null; if (! $volume) { continue; } $uuid = filled(data_get($backupPayload, 'uuid')) ? (string) data_get($backupPayload, 'uuid') : new_public_id(); if (ScheduledVolumeBackup::where('uuid', $uuid)->exists()) { $uuid = new_public_id(); } $s3Id = null; $s3Uuid = data_get($backupPayload, 's3_storage_uuid'); if ($s3Uuid && isset($this->s3StorageMap[$s3Uuid])) { $s3Id = $this->s3StorageMap[$s3Uuid]->id; } ScheduledVolumeBackup::create([ 'uuid' => $uuid, 'backupable_type' => $volume->getMorphClass(), 'backupable_id' => $volume->id, 'team_id' => $teamId, 's3_storage_id' => $s3Id, 'frequency' => data_get($backupPayload, 'frequency'), 'enabled' => (bool) data_get($backupPayload, 'enabled', true), 'save_s3' => (bool) data_get($backupPayload, 'save_s3', false) && $s3Id !== null, 'disable_local_backup' => (bool) data_get($backupPayload, 'disable_local_backup', false), 'stop_during_backup' => (bool) data_get($backupPayload, 'stop_during_backup', false), 'retention_amount_locally' => data_get($backupPayload, 'retention_amount_locally'), 'retention_days_locally' => data_get($backupPayload, 'retention_days_locally'), 'retention_max_storage_locally' => data_get($backupPayload, 'retention_max_storage_locally'), 'retention_amount_s3' => data_get($backupPayload, 'retention_amount_s3'), 'retention_days_s3' => data_get($backupPayload, 'retention_days_s3'), 'retention_max_storage_s3' => data_get($backupPayload, 'retention_max_storage_s3'), 'timeout' => data_get($backupPayload, 'timeout'), ]); $count++; } return $count; } /** * @param array $destinationMap */ private function resolveDestination(?string $uuid, array $destinationMap): StandaloneDocker|SwarmDocker { if ($uuid && isset($destinationMap[$uuid])) { return $destinationMap[$uuid]; } $first = reset($destinationMap); if ($first instanceof StandaloneDocker || $first instanceof SwarmDocker) { return $first; } throw new RuntimeException('No destination available for imported resource.'); } /** * @param class-string $modelClass * @param array $attributes * @return array */ private function onlyExistingColumns(string $modelClass, array $attributes): array { /** @var Model $model */ $model = new $modelClass; $columns = Schema::getColumnListing($model->getTable()); return collect($attributes) ->only($columns) ->all(); } }