diff --git a/app/Data/ServerProviders/Location.php b/app/Data/ServerProviders/Location.php index bf89768..b9c9573 100644 --- a/app/Data/ServerProviders/Location.php +++ b/app/Data/ServerProviders/Location.php @@ -9,5 +9,6 @@ class Location public string $name, public string $country, public string $city, + public ?string $networkZone = null, ) {} } diff --git a/app/Data/ServerProviders/Network.php b/app/Data/ServerProviders/Network.php index 102e441..97fb136 100644 --- a/app/Data/ServerProviders/Network.php +++ b/app/Data/ServerProviders/Network.php @@ -8,5 +8,6 @@ class Network public string $id, public string $name, public string $ipRange, + public ?string $networkZone = 'global', ) {} } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 877a5f6..6552af7 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -63,6 +63,7 @@ class ServerController extends Controller 'server_type' => ['required', 'string'], 'location' => ['required', 'string'], 'image' => ['required', 'string'], + 'network_zone' => ['nullable', 'string'], ]); $sudoPassword = Str::random(32); @@ -74,10 +75,19 @@ class ServerController extends Controller return back()->with('error', 'Invalid provider'); } - if (! $network = $provider->networks()->first()) { - // we need a keystone network to create this server + $networkZone = $request->network_zone ?? 'global'; + + // Look for an existing network with the same network_zone + $network = $provider->networks() + ->where('network_zone', $networkZone) + ->first(); + + if (! $network) { + // We need to create a network with the correct network zone + $networkName = "keystone-{$networkZone}"; $createdNetwork = $providerService->createNetwork( - name: 'keystone', + name: $networkName, + networkZone: $networkZone ); $network = $provider->networks()->create([ @@ -86,6 +96,7 @@ class ServerController extends Controller 'type' => NetworkType::EXTERNAL, 'name' => $createdNetwork->name, 'ip_range' => $createdNetwork->ipRange, + 'network_zone' => $networkZone, ]); } diff --git a/app/Http/Integrations/Requests/Hetzner/Networks/CreateNetworkRequest.php b/app/Http/Integrations/Requests/Hetzner/Networks/CreateNetworkRequest.php index a1fec57..ffe6a9f 100644 --- a/app/Http/Integrations/Requests/Hetzner/Networks/CreateNetworkRequest.php +++ b/app/Http/Integrations/Requests/Hetzner/Networks/CreateNetworkRequest.php @@ -15,14 +15,27 @@ class CreateNetworkRequest extends Request implements HasBody public function __construct( protected ?string $name = null, + protected ?string $networkZone = null, ) {} protected function defaultBody(): array { - return [ + $body = [ 'name' => $this->name, 'ip_range' => '10.0.0.0/16', ]; + + if ($this->networkZone) { + $body['subnets'] = [ + [ + 'type' => 'cloud', + 'ip_range' => '10.0.1.0/24', + 'network_zone' => $this->networkZone + ] + ]; + } + + return $body; } public function resolveEndpoint(): string diff --git a/app/Services/ServerProviders/HetznerService.php b/app/Services/ServerProviders/HetznerService.php index 219db8b..43ea35f 100644 --- a/app/Services/ServerProviders/HetznerService.php +++ b/app/Services/ServerProviders/HetznerService.php @@ -111,6 +111,7 @@ class HetznerService extends ServerProviderService name: $location['name'], country: $location['country'], city: $location['city'], + networkZone: $location['network_zone'] ?? null, ); })->values(); } @@ -135,7 +136,7 @@ class HetznerService extends ServerProviderService })->where('osVersion', '!=', 'unknown')->values(); } - public function findNetwork(string $name): ?Network + public function findNetwork(string $name, ?string $networkZone = null): ?Network { $response = $this->connector->send(new GetNetworksRequest( name: $name, @@ -152,16 +153,18 @@ class HetznerService extends ServerProviderService id: $network['id'], name: $network['name'], ipRange: $network['ip_range'], + networkZone: $networkZone ?? 'global', ); } return null; } - public function createNetwork(string $name): Network + public function createNetwork(string $name, ?string $networkZone = null): Network { $response = $this->connector->send(new CreateNetworkRequest( name: $name, + networkZone: $networkZone, )); if ($response->status() !== 201) { @@ -169,6 +172,7 @@ class HetznerService extends ServerProviderService 'response' => $response->json(), 'status' => $response->status(), 'name' => $name, + 'networkZone' => $networkZone, ]); throw new Exception('Failed to create network on Hetzner'); } @@ -177,6 +181,7 @@ class HetznerService extends ServerProviderService id: $response->json('network.id'), name: $response->json('network.name'), ipRange: $response->json('network.ip_range'), + networkZone: $response->json('network.network_zone') ?? $networkZone ?? 'global', ); } } diff --git a/app/Services/ServerProviders/ServerProviderService.php b/app/Services/ServerProviders/ServerProviderService.php index 47efd0a..b4ecac2 100644 --- a/app/Services/ServerProviders/ServerProviderService.php +++ b/app/Services/ServerProviders/ServerProviderService.php @@ -28,7 +28,7 @@ abstract class ServerProviderService abstract public function getImages(): Collection; - abstract public function findNetwork(string $name): ?Network; + abstract public function findNetwork(string $name, ?string $networkZone = null): ?Network; - abstract public function createNetwork(string $name): Network; + abstract public function createNetwork(string $name, ?string $networkZone = null): Network; } diff --git a/database/migrations/2025_04_06_171437_create_networks_table.php b/database/migrations/2025_04_06_171437_create_networks_table.php index 822b654..843495d 100644 --- a/database/migrations/2025_04_06_171437_create_networks_table.php +++ b/database/migrations/2025_04_06_171437_create_networks_table.php @@ -15,6 +15,7 @@ return new class extends Migration $table->foreignIdFor(Organisation::class); $table->foreignIdFor(Provider::class); $table->string('external_id')->nullable(); + $table->string('network_zone')->default('global'); $table->string('type'); $table->string('name'); $table->string('ip_range'); diff --git a/resources/js/pages/servers/Create.vue b/resources/js/pages/servers/Create.vue index 28e7f27..bde12c0 100644 --- a/resources/js/pages/servers/Create.vue +++ b/resources/js/pages/servers/Create.vue @@ -15,23 +15,26 @@ const props = defineProps({ const form = useForm({ provider: null, location: null, + network_zone: null, server_type: null, image: null, }); watch( () => form.provider, - (provider) => { + () => { loadLocations(); }, ); -watch ( +watch( () => form.location, (location) => { + const selectedLoc = props.locations.find((loc) => loc.id === location)?.networkZone; + form.network_zone = selectedLoc; loadServerTypes(); - } -) + }, +); loadLocations(); loadServerTypes(); @@ -75,7 +78,7 @@ function loadServerTypes() { }, { title: 'Create', - } + }, ]" >