Paillasse/portail/app/Services/OpenStack/HeatClient.php
2026-10-12 21:32:52 +02:00

100 lines
3.1 KiB
PHP

<?php
namespace App\Services\OpenStack;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use RuntimeException;
/**
* Juste ce qu'il faut pour créer, suivre et supprimer une stack Heat
* (étape 10) — pas de client générique, le besoin réel de Syllabus est
* étroit (un lab = une stack, pour la durée d'une session).
*/
class HeatClient
{
public function __construct(private readonly KeystoneAuth $auth) {}
/**
* @param array<string, mixed> $template
* @return array{id: string}
*/
public function creerStack(string $nom, array $template): array
{
$reponse = $this->requete()->post($this->url('/stacks'), [
'stack_name' => $nom,
'disable_rollback' => true,
'template' => $template,
]);
if ($reponse->failed()) {
throw new RuntimeException("Heat a refusé la création de la stack {$nom} : ".$reponse->body());
}
return ['id' => $reponse->json('stack.id')];
}
/**
* @return array{statut: string, raison: ?string, sorties: array<string, mixed>}
*/
public function etatStack(string $nom, string $id): array
{
$reponse = $this->requete()->get($this->url("/stacks/{$nom}/{$id}"));
if ($reponse->failed()) {
throw new RuntimeException("Impossible de lire l'état de la stack {$nom} : ".$reponse->body());
}
$sorties = [];
foreach ($reponse->json('stack.outputs') ?? [] as $sortie) {
$sorties[$sortie['output_key']] = $sortie['output_value'];
}
return [
'statut' => $reponse->json('stack.stack_status'),
'raison' => $reponse->json('stack.stack_status_reason'),
'sorties' => $sorties,
];
}
/**
* Toutes les stacks du projet (quelques dizaines au plus : pas de pagination).
*
* @return array<int, array{nom: string, id: string, statut: string, cree_a: string}>
*/
public function listerStacks(): array
{
$reponse = $this->requete()->get($this->url('/stacks'));
if ($reponse->failed()) {
throw new RuntimeException('Impossible de lister les stacks : '.$reponse->body());
}
return array_map(fn (array $s) => [
'nom' => $s['stack_name'],
'id' => $s['id'],
'statut' => $s['stack_status'],
'cree_a' => $s['creation_time'],
], $reponse->json('stacks') ?? []);
}
public function supprimerStack(string $nom, string $id): void
{
$reponse = $this->requete()->delete($this->url("/stacks/{$nom}/{$id}"));
// 404 : déjà supprimée (idempotent, pas une erreur pour nous).
if ($reponse->failed() && $reponse->status() !== 404) {
throw new RuntimeException("Heat a refusé la suppression de la stack {$nom} : ".$reponse->body());
}
}
private function requete(): PendingRequest
{
return Http::withHeaders(['X-Auth-Token' => $this->auth->jeton()]);
}
private function url(string $chemin): string
{
return config('openstack.heat_url').'/'.$this->auth->projetId().$chemin;
}
}