fix(0.32.1): URL sœurs dracodev dérivées de l'environnement de l'agent
CI / test (push) Successful in 2m58s

This commit is contained in:
Spark
2026-10-25 23:49:33 +00:00
parent 4a97e0d0dc
commit 65c467bc90
4 changed files with 237 additions and 16 deletions
+32
View File
@@ -1,5 +1,37 @@
# Changelog
## 0.32.1 — 2026-10-25
### Corrigé — URL sœurs de la barre dérivées de l'environnement de l'agent
Sur la dev, le menu inter-apps pointait vers les noms **prod**
des apps sœurs : seul `AGENT_EVENEMENTS_URL` y est posé (hôte
`agent-dev.dracodev.net`), et chaque URL sœur retombait sur son
défaut officiel faute de variable explicite — il aurait fallu
déclarer quatre variables de plus dans le `.env` dev.
- **Résolution des URL sœurs** dans `app/config.py` (les
gabarits et le JS de la barre consomment `draco_urls`
inchangé), dans l'ordre : 1) la variable explicite —
`DRACO_SSO_URL` (ObsiGate), `DRACO_FLOWDECK_URL`,
`DRACO_TIKTAK_URL`, `KANBANTE_PUBLIC_URL` (Kanbante
elle-même) — prioritaire, contrat inchangé ; 2) **dérivation
d'environnement** depuis l'origine de l'agent telle que
l'app la connaît déjà (`AGENT_EVENEMENTS_URL`) : si son
hôte est de la forme `agent<SUFFIXE>.dracodev.net`
(suffixe vide ou, typiquement, `-dev`), l'URL sœur devient
`https://<slug><SUFFIXE>.dracodev.net` (slugs : `og`,
`flowdeck`, `tiktak`, `kanbante`) ; hôte hors motif (autre
domaine, LAN, IP) = **aucune dérivation**, jamais de lien
deviné ; 3) le défaut officiel (prod), comme avant.
- L'URL Gitea ne change pas.
- Tests : avec le seul `AGENT_EVENEMENTS_URL` en `-dev`, les
quatre URL sœurs servies sont en `-dev` ; une URL explicite
gagne sur la dérivation ; hôte d'agent hors motif et aucun
environnement posé restent aux défauts officiels
(non-régression).
## 0.32.0 — 2026-10-25
### Refactor — noms publics dracodev par configuration (dev/prod)
+93 -15
View File
@@ -107,6 +107,10 @@ Environment variables:
receiving this app's signed events.
Default: derived from AGENT_URL
(``<AGENT_URL>/api/v1/evenements``).
Son hôte sert aussi de source à la
dérivation d'environnement des URL
sœurs ci-dessous (voir
``_url_soeur_draco``).
KANBANTE_PUBLIC_URL This app's own public base URL
(barre commune self-link, SSO
``return_to``). Default (official)
@@ -119,6 +123,20 @@ Environment variables:
https://tiktak.dracodev.net.
ObsiGate's bar link reuses DRACO_SSO_URL
(the SSO emitter IS ObsiGate).
Résolution d'une URL sœur, dans
l'ordre : 1) la variable explicite
ci-dessus (prioritaire, contrat
inchangé) ; 2) dérivation depuis
l'hôte de l'agent connu par
AGENT_EVENEMENTS_URL — si cet hôte
est ``agent<SUFFIXE>.dracodev.net``
(p. ex. ``agent-dev``), l'URL sœur
devient ``https://<slug><SUFFIXE>.
dracodev.net`` (slugs : og, flowdeck,
tiktak, kanbante) ; hôte hors motif
(autre domaine, LAN, IP) = aucune
dérivation, jamais de lien deviné ;
3) le défaut officiel (prod).
No public dracodev name is hard-coded
outside these defaults: dev/prod differ
only by environment, never by code.
@@ -130,12 +148,14 @@ Environment variables:
"""
import os
import re
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse
BASE_DIR = Path(__file__).resolve().parent.parent
APP_VERSION = "0.32.0"
APP_VERSION = "0.32.1"
def _env_bool(name: str, default: bool) -> bool:
@@ -171,6 +191,48 @@ def _env_wip_limits(name: str) -> dict[str, int]:
return limits
# ── Dérivation d'environnement des URL sœurs dracodev (0.32.1) ──
# Sur la dev, seul AGENT_EVENEMENTS_URL est posé (hôte
# agent-dev.dracodev.net) : les URL sœurs retombaient donc sur leurs
# défauts prod dans la barre commune. La résolution ci-dessous
# évite de devoir déclarer quatre variables de plus.
_MOTIF_HOTE_AGENT = re.compile(r"agent([a-z0-9-]*)\.dracodev\.net")
def _suffixe_environnement_agent(agent_evenements_url: str) -> str | None:
"""Suffixe d'environnement lu sur l'hôte de l'agent.
Retourne le suffixe (``""`` pour le nom officiel, ``"-dev"``
pour la dev) si l'hôte de ``agent_evenements_url`` est de la
forme ``agent<SUFFIXE>.dracodev.net`` ; ``None`` hors motif
(autre domaine, LAN, IP…) — dans ce cas, aucune dérivation.
"""
try:
hote = urlparse(agent_evenements_url).hostname or ""
except ValueError:
return None
motif = _MOTIF_HOTE_AGENT.fullmatch(hote)
return motif.group(1) if motif else None
def _url_soeur_draco(
nom_env: str, slug: str, defaut: str, suffixe: str | None
) -> str:
"""Résout l'URL publique d'une app sœur de la barre commune.
1) ``nom_env`` explicite — prioritaire, contrat inchangé ;
2) dérivation d'environnement : ``https://<slug><suffixe>.
dracodev.net`` quand l'hôte de l'agent suit le motif ;
3) défaut officiel (prod), comme avant.
"""
explicite = os.getenv(nom_env)
if explicite:
return explicite.rstrip("/")
if suffixe is not None:
return f"https://{slug}{suffixe}.dracodev.net"
return defaut
@dataclass(frozen=True)
class Settings:
gitea_url: str = "https://git.dracodev.net"
@@ -234,6 +296,14 @@ def _env_settings() -> Settings:
agent_url = os.getenv(
"AGENT_URL", "https://agent.dracodev.net"
).rstrip("/")
agent_evenements_url = (
os.getenv("AGENT_EVENEMENTS_URL")
or f"{agent_url}/api/v1/evenements"
).rstrip("/")
# Origine de l'agent telle que l'app la connaît déjà : c'est
# elle qui porte le suffixe d'environnement (-dev…) repris
# par les URL sœurs dépourvues de variable explicite.
suffixe_agent = _suffixe_environnement_agent(agent_evenements_url)
return Settings(
gitea_url=os.getenv("GITEA_URL", "https://git.dracodev.net").rstrip("/"),
gitea_token=os.getenv("GITEA_TOKEN", ""),
@@ -276,22 +346,30 @@ def _env_settings() -> Settings:
draco_secret=os.getenv("DRACO_SESSION_SECRET", ""),
draco_secret_prev=os.getenv("DRACO_SESSION_SECRET_PREV", ""),
sso_machine_key=os.getenv("DRACO_SSO_MACHINE_KEY", ""),
sso_url=os.getenv("DRACO_SSO_URL", "https://og.dracodev.net").rstrip("/"),
public_url=os.getenv(
"KANBANTE_PUBLIC_URL", "https://kanbante.dracodev.net"
).rstrip("/"),
flowdeck_url=os.getenv(
"DRACO_FLOWDECK_URL", "https://flowdeck.dracodev.net"
).rstrip("/"),
tiktak_url=os.getenv(
"DRACO_TIKTAK_URL", "https://tiktak.dracodev.net"
).rstrip("/"),
sso_url=_url_soeur_draco(
"DRACO_SSO_URL", "og", "https://og.dracodev.net", suffixe_agent
),
public_url=_url_soeur_draco(
"KANBANTE_PUBLIC_URL",
"kanbante",
"https://kanbante.dracodev.net",
suffixe_agent,
),
flowdeck_url=_url_soeur_draco(
"DRACO_FLOWDECK_URL",
"flowdeck",
"https://flowdeck.dracodev.net",
suffixe_agent,
),
tiktak_url=_url_soeur_draco(
"DRACO_TIKTAK_URL",
"tiktak",
"https://tiktak.dracodev.net",
suffixe_agent,
),
agent_url=agent_url,
archive_suggest_days=_env_int("KANBANTE_ARCHIVE_SUGGEST_DAYS", 180),
agent_evenements_url=(
os.getenv("AGENT_EVENEMENTS_URL")
or f"{agent_url}/api/v1/evenements"
).rstrip("/"),
agent_evenements_url=agent_evenements_url,
agent_evenements_cle=os.getenv("AGENT_EVENEMENTS_CLE", ""),
)
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "kanbante"
version = "0.32.0"
version = "0.32.1"
description = "Kanban project manager integrated with Gitea"
requires-python = ">=3.11"
dependencies = [
+111
View File
@@ -71,6 +71,117 @@ def test_config_agent_evenements_derive_de_agent_url(monkeypatch):
)
# ── Dérivation d'environnement des URL sœurs (0.32.1) ──
# Sur la dev, seul AGENT_EVENEMENTS_URL est posé : les URL sœurs
# doivent suivre son suffixe (-dev) sans quatre variables de plus.
_ENV_URLS_SOEURS = (
"DRACO_SSO_URL", # ObsiGate (émettrice SSO)
"DRACO_FLOWDECK_URL",
"DRACO_TIKTAK_URL",
"KANBANTE_PUBLIC_URL", # Kanbante elle-même
"AGENT_URL",
)
_DEFAUTS_OFFICIELS = (
"https://og.dracodev.net",
"https://flowdeck.dracodev.net",
"https://tiktak.dracodev.net",
"https://kanbante.dracodev.net",
)
_URLS_DEV = (
"https://og-dev.dracodev.net",
"https://flowdeck-dev.dracodev.net",
"https://tiktak-dev.dracodev.net",
"https://kanbante-dev.dracodev.net",
)
def _nettoyer_env_soeurs(monkeypatch):
for nom in _ENV_URLS_SOEURS:
monkeypatch.delenv(nom, raising=False)
def _urls_soeurs(settings):
return (
settings.sso_url,
settings.flowdeck_url,
settings.tiktak_url,
settings.public_url,
)
def test_config_urls_soeurs_derivees_du_seul_agent_evenements(monkeypatch):
from app.config import _env_settings
_nettoyer_env_soeurs(monkeypatch)
monkeypatch.setenv(
"AGENT_EVENEMENTS_URL",
"https://agent-dev.dracodev.net/api/v1/evenements",
)
assert _urls_soeurs(_env_settings()) == _URLS_DEV
def test_barre_urls_soeurs_derivees_du_seul_agent_evenements(
monkeypatch, fake_gitea
):
from fastapi.testclient import TestClient
import app.main as main
_nettoyer_env_soeurs(monkeypatch)
monkeypatch.setenv(
"AGENT_EVENEMENTS_URL",
"https://agent-dev.dracodev.net/api/v1/evenements",
)
with TestClient(main.app) as client:
r = client.get("/")
assert r.status_code == 200
for url in _URLS_DEV:
assert f"{url}/" in r.text
def test_config_url_soeur_explicite_gagne_sur_la_derivation(monkeypatch):
from app.config import _env_settings
_nettoyer_env_soeurs(monkeypatch)
monkeypatch.setenv(
"AGENT_EVENEMENTS_URL",
"https://agent-dev.dracodev.net/api/v1/evenements",
)
monkeypatch.setenv(
"DRACO_FLOWDECK_URL", "https://flowdeck.example.test"
)
s = _env_settings()
assert s.flowdeck_url == "https://flowdeck.example.test"
# Les autres sœurs restent dérivées de l'environnement.
assert s.sso_url == "https://og-dev.dracodev.net"
assert s.tiktak_url == "https://tiktak-dev.dracodev.net"
assert s.public_url == "https://kanbante-dev.dracodev.net"
def test_config_hote_agent_hors_motif_reste_aux_defauts(monkeypatch):
from app.config import _env_settings
_nettoyer_env_soeurs(monkeypatch)
for url_agent in (
"https://agent.example.com/api/v1/evenements",
"https://agent-dev.autre-domaine.net/api/v1/evenements",
"http://192.168.30.101:8790/api/v1/evenements",
"http://127.0.0.1:8790/api/v1/evenements",
):
monkeypatch.setenv("AGENT_EVENEMENTS_URL", url_agent)
assert _urls_soeurs(_env_settings()) == _DEFAUTS_OFFICIELS
def test_config_aucun_env_reste_aux_defauts_officiels(monkeypatch):
from app.config import _env_settings
_nettoyer_env_soeurs(monkeypatch)
monkeypatch.delenv("AGENT_EVENEMENTS_URL", raising=False)
assert _urls_soeurs(_env_settings()) == _DEFAUTS_OFFICIELS
def test_statiques_de_la_barre_servis(client):
assert client.get("/static/css/draco-barre.css").status_code == 200
assert client.get("/static/js/draco-barre.js").status_code == 200