ajoute le proxy console Go et le relais WebSocket vers nova-serialproxy

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-08 09:35:16 +02:00
parent 30e14b1c54
commit 8742bde807
30 changed files with 1103 additions and 6 deletions

View file

@ -0,0 +1,47 @@
<?php
namespace App\Services\OpenStack;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
/**
* Range l'adresse de la console série dans Redis sous un jeton aléatoire,
* pour 5 minutes (étape 9). Le navigateur ne reçoit que ce jeton, jamais
* l'URL ni le jeton Nova sous-jacent. Format lu par proxy-console/main.go :
* ne pas changer les noms de champs sans mettre à jour les deux côtés.
*/
class JetonConsole
{
private const DUREE_MINUTES = 5;
public function __construct(private readonly NovaClient $nova) {}
public function pourEcriture(string $instanceId): string
{
return $this->creer($instanceId, 'ecriture');
}
public function pourLecture(string $instanceId): string
{
return $this->creer($instanceId, 'lecture');
}
private function creer(string $instanceId, string $role): string
{
$jeton = Str::random(40);
$url = $this->nova->consoleSerielle($instanceId);
Redis::setex(
"console:{$jeton}",
self::DUREE_MINUTES * 60,
json_encode([
'url' => $url,
'instance_id' => $instanceId,
'role' => $role,
]),
);
return $jeton;
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Services\OpenStack;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Juste ce qu'il faut pour demander à Nova l'adresse de la console série
* d'une VM (étape 9). La liaison Laravel ↔ OpenStack complète (Heat, labs)
* est l'étape 10 : on ne construit pas de client générique ici.
*/
class NovaClient
{
public function consoleSerielle(string $instanceId): string
{
$reponse = Http::withHeaders([
'X-Auth-Token' => $this->jetonKeystone(),
'OpenStack-API-Version' => 'compute 2.99',
])
->post(config('openstack.nova_url')."/servers/{$instanceId}/remote-consoles", [
'remote_console' => ['protocol' => 'serial', 'type' => 'serial'],
]);
if ($reponse->failed()) {
throw new RuntimeException("Nova a refusé la demande de console série pour {$instanceId} : ".$reponse->body());
}
return $reponse->json('remote_console.url');
}
/**
* Jeton Keystone mis en cache (un jeton v3 dure 1h côté Keystone, on le
* garde un peu moins longtemps par prudence).
*/
private function jetonKeystone(): string
{
return Cache::remember('openstack.jeton_keystone', now()->addMinutes(50), function () {
$reponse = Http::post(config('openstack.auth_url').'/v3/auth/tokens', [
'auth' => [
'identity' => [
'methods' => ['application_credential'],
'application_credential' => [
'id' => config('openstack.application_credential_id'),
'secret' => config('openstack.application_credential_secret'),
],
],
],
]);
if ($reponse->failed()) {
throw new RuntimeException('Authentification OpenStack refusée : '.$reponse->body());
}
return $reponse->header('X-Subject-Token');
});
}
}