fix(draco): URLs sœurs dérivées de l'environnement de l'agent (7.82.3)
Résolution uniforme des quatre sœurs (ObsiGate, Flowdeck, TikTak, Kanbante) dans app/services/draco_urls.py : explicite prioritaire (contrat inchangé), sinon dérivation depuis l'hôte de l'agent connu via AGENT_EVENEMENTS_URL (agent<SUFFIXE>.dracodev.net → <slug><SUFFIXE>.dracodev.net), sinon défaut officiel. Hors motif (autre domaine, LAN, IP) : aucune dérivation. Gitea inchangé. Tests +4 dans test_draco_urls_devprod.py.
This commit is contained in:
@@ -1,5 +1,34 @@
|
|||||||
# Changelog - FlowDeck
|
# Changelog - FlowDeck
|
||||||
|
|
||||||
|
## v7.82.3 (2026-10-25) — URLs sœurs dérivées de l'environnement de l'agent
|
||||||
|
|
||||||
|
- **Bug corrigé (dev)** : les URL sœurs servies par `draco_urls()`
|
||||||
|
(menu inter-apps, SSO, preset TikTak, références croisées)
|
||||||
|
restaient aux noms **prod** quand le `.env` ne déclarait pas les
|
||||||
|
quatre URL une à une — seul l'agent était en `-dev`, via
|
||||||
|
`AGENT_EVENEMENTS_URL`. Le menu inter-apps de la dev envoyait
|
||||||
|
donc vers la prod.
|
||||||
|
- **Dérivation d'environnement** (`app/services/draco_urls.py`,
|
||||||
|
résolution uniforme des quatre sœurs — ObsiGate, Flowdeck,
|
||||||
|
TikTak, Kanbante) : 1) URL explicite prioritaire, contrat
|
||||||
|
inchangé (`DRACO_SSO_URL`, `TIKTAK_URL`, `KANBANTE_URL`,
|
||||||
|
`FLOWDECK_URL`/`OAUTH_REDIRECT_URI`…) ; 2) sinon, si l'hôte de
|
||||||
|
l'agent tel que l'app le connaît déjà (`AGENT_EVENEMENTS_URL`,
|
||||||
|
via `agent_evenements_url()`) est de la forme
|
||||||
|
`agent<SUFFIXE>.dracodev.net` (SUFFIXE éventuellement vide,
|
||||||
|
typiquement `-dev`), l'URL sœur est dérivée :
|
||||||
|
`https://<slug><SUFFIXE>.dracodev.net` (slugs `og`, `flowdeck`,
|
||||||
|
`tiktak`, `kanbante`) ; hôte hors motif (autre domaine, LAN,
|
||||||
|
IP) = **aucune dérivation**, jamais un lien deviné ; 3) sinon
|
||||||
|
défaut officiel, comme avant. L'URL **Gitea ne change pas** ;
|
||||||
|
les gabarits et le JS de la barre, qui consomment `draco_urls()`,
|
||||||
|
sont inchangés.
|
||||||
|
- Tests : `tests/test_draco_urls_devprod.py` +4 — seul
|
||||||
|
`AGENT_EVENEMENTS_URL=…agent-dev…` posé → sœurs en `-dev` ;
|
||||||
|
explicite gagne sur la dérivation ; hôtes d'agent hors motif
|
||||||
|
→ défauts officiels ; agent officiel (suffixe vide) → sœurs
|
||||||
|
officielles. Non-régression « aucun env » déjà couverte.
|
||||||
|
|
||||||
## v7.82.2 (2026-10-25) — CI test entièrement verte : pages-supports de templates exclues des listes
|
## v7.82.2 (2026-10-25) — CI test entièrement verte : pages-supports de templates exclues des listes
|
||||||
|
|
||||||
Cause racine commune à 7 des 8 échecs restants : la refonte templates
|
Cause racine commune à 7 des 8 échecs restants : la refonte templates
|
||||||
|
|||||||
+1
-1
@@ -216,7 +216,7 @@ def _lire_version() -> str:
|
|||||||
|
|
||||||
|
|
||||||
# Littéral de version synchronisé avec le fichier VERSION :
|
# Littéral de version synchronisé avec le fichier VERSION :
|
||||||
# version="7.82.2"
|
# version="7.82.3"
|
||||||
# Patron historique des bumps (voir scripts/bump_version.py, qui cherche
|
# Patron historique des bumps (voir scripts/bump_version.py, qui cherche
|
||||||
# ce littéral) et contrôle de tests/test_pwa_offline.py. La version
|
# ce littéral) et contrôle de tests/test_pwa_offline.py. La version
|
||||||
# runtime reste lue dynamiquement depuis VERSION par _lire_version()
|
# runtime reste lue dynamiquement depuis VERSION par _lire_version()
|
||||||
|
|||||||
@@ -18,11 +18,26 @@ Priorité de résolution, à chaque appel (les tests et les modules qui
|
|||||||
lisaient déjà ``os.environ`` au moment de l'appel gardent ce
|
lisaient déjà ``os.environ`` au moment de l'appel gardent ce
|
||||||
comportement) : variable d'environnement > ``app.config.settings`` >
|
comportement) : variable d'environnement > ``app.config.settings`` >
|
||||||
défaut officiel. Aucun défaut ne contient ``-dev``.
|
défaut officiel. Aucun défaut ne contient ``-dev``.
|
||||||
|
|
||||||
|
**Dérivation d'environnement (v7.82.3)** — pour les quatre URL
|
||||||
|
sœurs (ObsiGate, Flowdeck, TikTak, Kanbante), entre la configuration
|
||||||
|
explicite et le défaut officiel : si l'origine de l'agent telle que
|
||||||
|
l'app la connaît déjà (``AGENT_EVENEMENTS_URL``, via
|
||||||
|
``agent_evenements_url()``) a un hôte de la forme
|
||||||
|
``agent<SUFFIXE>.dracodev.net`` (SUFFIXE éventuellement vide,
|
||||||
|
typiquement ``-dev``), l'URL sœur est dérivée :
|
||||||
|
``https://<slug><SUFFIXE>.dracodev.net`` (slugs : ``og``,
|
||||||
|
``flowdeck``, ``tiktak``, ``kanbante``). Ainsi une installation dev
|
||||||
|
qui ne déclare que l'agent pointe ses liens inter-apps en ``-dev``
|
||||||
|
sans quatre variables de plus. Si l'hôte de l'agent ne correspond
|
||||||
|
pas au motif (autre domaine, LAN, IP), **aucune dérivation** : on
|
||||||
|
ne fabrique jamais un lien deviné. L'URL Gitea ne change pas.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
@@ -51,9 +66,70 @@ def _resoudre(env_name: str, settings_attr: str, app: str) -> str:
|
|||||||
return OFFICIELLES[app]
|
return OFFICIELLES[app]
|
||||||
|
|
||||||
|
|
||||||
|
# Slugs d'hôte des applications sœurs (dérivation d'environnement).
|
||||||
|
_SLUGS_SOEURS = {
|
||||||
|
"obsigate": "og",
|
||||||
|
"flowdeck": "flowdeck",
|
||||||
|
"tiktak": "tiktak",
|
||||||
|
"kanbante": "kanbante",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hôte de l'agent : agent<SUFFIXE>.dracodev.net (SUFFIXE = ``-dev``…).
|
||||||
|
_MOTIF_HOTE_AGENT = re.compile(r"^agent(-[a-z0-9-]+)?\.dracodev\.net$")
|
||||||
|
|
||||||
|
|
||||||
|
def _suffixe_environnement() -> str | None:
|
||||||
|
"""Suffixe d'environnement porté par l'hôte de l'agent, ou None.
|
||||||
|
|
||||||
|
Source : l'origine de l'agent telle que l'app la connaît déjà —
|
||||||
|
``agent_evenements_url()`` (``AGENT_EVENEMENTS_URL`` explicite,
|
||||||
|
sinon dérivée de la base de l'agent). Retourne ``""`` pour
|
||||||
|
l'hôte officiel, ``"-dev"`` pour ``agent-dev.dracodev.net``, et
|
||||||
|
None si l'hôte ne correspond pas au motif (aucune dérivation).
|
||||||
|
"""
|
||||||
|
hote_agent = (urlparse(agent_evenements_url()).hostname or "").lower()
|
||||||
|
m = _MOTIF_HOTE_AGENT.match(hote_agent)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
return m.group(1) or ""
|
||||||
|
|
||||||
|
|
||||||
|
def _url_derivee(app: str) -> str:
|
||||||
|
"""URL sœur dérivée de l'environnement de l'agent ("" si aucune)."""
|
||||||
|
slug = _SLUGS_SOEURS.get(app)
|
||||||
|
if not slug:
|
||||||
|
return ""
|
||||||
|
suffixe = _suffixe_environnement()
|
||||||
|
if suffixe is None:
|
||||||
|
return ""
|
||||||
|
return f"https://{slug}{suffixe}.dracodev.net"
|
||||||
|
|
||||||
|
|
||||||
|
def _resoudre_soeur(env_name: str, settings_attr: str, app: str) -> str:
|
||||||
|
"""Résolution d'une URL sœur : explicite > dérivation > officiel.
|
||||||
|
|
||||||
|
La valeur ``settings`` égale au nom officiel est le *défaut* de
|
||||||
|
``app/config.py``, pas un choix explicite : elle ne doit pas
|
||||||
|
bloquer la dérivation d'environnement. Une valeur settings
|
||||||
|
différente (dev, LAN, domaine propre) reste prioritaire.
|
||||||
|
"""
|
||||||
|
env = _nettoyer(os.environ.get(env_name, ""))
|
||||||
|
if env:
|
||||||
|
return env
|
||||||
|
cfg = _nettoyer(getattr(settings, settings_attr, "") or "")
|
||||||
|
if cfg and cfg != OFFICIELLES[app]:
|
||||||
|
return cfg
|
||||||
|
derivee = _url_derivee(app)
|
||||||
|
if derivee:
|
||||||
|
return derivee
|
||||||
|
if cfg:
|
||||||
|
return cfg
|
||||||
|
return OFFICIELLES[app]
|
||||||
|
|
||||||
|
|
||||||
def obsigate_url() -> str:
|
def obsigate_url() -> str:
|
||||||
"""ObsiGate — aussi l'émettrice SSO (DRACO_SSO_URL, déjà en service)."""
|
"""ObsiGate — aussi l'émettrice SSO (DRACO_SSO_URL, déjà en service)."""
|
||||||
return _resoudre("DRACO_SSO_URL", "draco_sso_url", "obsigate")
|
return _resoudre_soeur("DRACO_SSO_URL", "draco_sso_url", "obsigate")
|
||||||
|
|
||||||
|
|
||||||
def agent_url() -> str:
|
def agent_url() -> str:
|
||||||
@@ -62,11 +138,11 @@ def agent_url() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def tiktak_url() -> str:
|
def tiktak_url() -> str:
|
||||||
return _resoudre("TIKTAK_URL", "tiktak_url", "tiktak")
|
return _resoudre_soeur("TIKTAK_URL", "tiktak_url", "tiktak")
|
||||||
|
|
||||||
|
|
||||||
def kanbante_url() -> str:
|
def kanbante_url() -> str:
|
||||||
return _resoudre("KANBANTE_URL", "kanbante_url", "kanbante")
|
return _resoudre_soeur("KANBANTE_URL", "kanbante_url", "kanbante")
|
||||||
|
|
||||||
|
|
||||||
def gitea_url() -> str:
|
def gitea_url() -> str:
|
||||||
@@ -98,6 +174,9 @@ def flowdeck_url() -> str:
|
|||||||
base = _nettoyer(getattr(settings, "app_base_url", "") or "")
|
base = _nettoyer(getattr(settings, "app_base_url", "") or "")
|
||||||
if base and "localhost" not in base and "127.0.0.1" not in base:
|
if base and "localhost" not in base and "127.0.0.1" not in base:
|
||||||
return base
|
return base
|
||||||
|
derivee = _url_derivee("flowdeck")
|
||||||
|
if derivee:
|
||||||
|
return derivee
|
||||||
return OFFICIELLES["flowdeck"]
|
return OFFICIELLES["flowdeck"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,72 @@ def test_preset_tiktak_depuis_config(monkeypatch):
|
|||||||
"https://tiktak-dev.dracodev.net/mcp"
|
"https://tiktak-dev.dracodev.net/mcp"
|
||||||
|
|
||||||
|
|
||||||
|
def test_derivation_dev_depuis_agent_evenements_seul(monkeypatch):
|
||||||
|
# Cas réel de la dev : seul AGENT_EVENEMENTS_URL est posé —
|
||||||
|
# les quatre URLs sœurs servies suivent en ``-dev``, sans
|
||||||
|
# déclarer quatre variables de plus. Gitea ne change pas.
|
||||||
|
monkeypatch.setenv(
|
||||||
|
"AGENT_EVENEMENTS_URL",
|
||||||
|
"https://agent-dev.dracodev.net/api/v1/evenements")
|
||||||
|
urls = draco_urls.toutes()
|
||||||
|
assert urls["obsigate"] == "https://og-dev.dracodev.net"
|
||||||
|
assert urls["flowdeck"] == "https://flowdeck-dev.dracodev.net"
|
||||||
|
assert urls["tiktak"] == "https://tiktak-dev.dracodev.net"
|
||||||
|
assert urls["kanbante"] == "https://kanbante-dev.dracodev.net"
|
||||||
|
assert urls["gitea"] == "https://git.dracodev.net"
|
||||||
|
# Les accesseurs individuels partagent la même résolution.
|
||||||
|
assert draco_urls.obsigate_url() == "https://og-dev.dracodev.net"
|
||||||
|
assert draco_urls.flowdeck_url() == "https://flowdeck-dev.dracodev.net"
|
||||||
|
assert draco_urls.tiktak_url() == "https://tiktak-dev.dracodev.net"
|
||||||
|
assert draco_urls.kanbante_url() == "https://kanbante-dev.dracodev.net"
|
||||||
|
|
||||||
|
|
||||||
|
def test_derivation_explicite_gagne_sur_derivation(monkeypatch):
|
||||||
|
monkeypatch.setenv(
|
||||||
|
"AGENT_EVENEMENTS_URL",
|
||||||
|
"https://agent-dev.dracodev.net/api/v1/evenements")
|
||||||
|
# Explicites (contrat inchangé) : elles priment sur la dérivation,
|
||||||
|
# y compris quand elles pointent le nom officiel.
|
||||||
|
monkeypatch.setenv("DRACO_SSO_URL", "https://og.dracodev.net")
|
||||||
|
monkeypatch.setenv("TIKTAK_URL", "https://tiktak.example.net")
|
||||||
|
urls = draco_urls.toutes()
|
||||||
|
assert urls["obsigate"] == "https://og.dracodev.net"
|
||||||
|
assert urls["tiktak"] == "https://tiktak.example.net"
|
||||||
|
# Non déclarées : dérivées.
|
||||||
|
assert urls["flowdeck"] == "https://flowdeck-dev.dracodev.net"
|
||||||
|
assert urls["kanbante"] == "https://kanbante-dev.dracodev.net"
|
||||||
|
|
||||||
|
|
||||||
|
def test_derivation_hote_agent_hors_motif_defauts_officiels(monkeypatch):
|
||||||
|
# Autre domaine, LAN, IP : aucune dérivation, jamais un lien deviné.
|
||||||
|
for url_agent in (
|
||||||
|
"https://agent.example.com/api/v1/evenements",
|
||||||
|
"https://agent-dev.example.net/api/v1/evenements",
|
||||||
|
"https://monagent-dev.dracodev.net/api/v1/evenements",
|
||||||
|
"https://agent-dev.dracodev.net.evil.com/api/v1/evenements",
|
||||||
|
"http://192.168.30.101:8790/api/v1/evenements",
|
||||||
|
"http://127.0.0.1:8790/api/v1/evenements",
|
||||||
|
"http://agent-dev.local:8790/api/v1/evenements",
|
||||||
|
):
|
||||||
|
monkeypatch.setenv("AGENT_EVENEMENTS_URL", url_agent)
|
||||||
|
urls = draco_urls.toutes()
|
||||||
|
assert urls["obsigate"] == "https://og.dracodev.net", url_agent
|
||||||
|
assert urls["flowdeck"] == "https://flowdeck.dracodev.net", url_agent
|
||||||
|
assert urls["tiktak"] == "https://tiktak.dracodev.net", url_agent
|
||||||
|
assert urls["kanbante"] == "https://kanbante.dracodev.net", url_agent
|
||||||
|
|
||||||
|
|
||||||
|
def test_derivation_agent_officiel_suffixe_vide(monkeypatch):
|
||||||
|
# Hôte agent officiel : suffixe vide, les sœurs restent officielles.
|
||||||
|
monkeypatch.setenv(
|
||||||
|
"AGENT_EVENEMENTS_URL", "https://agent.dracodev.net/api/v1/evenements")
|
||||||
|
urls = draco_urls.toutes()
|
||||||
|
assert urls["obsigate"] == "https://og.dracodev.net"
|
||||||
|
assert urls["flowdeck"] == "https://flowdeck.dracodev.net"
|
||||||
|
assert urls["tiktak"] == "https://tiktak.dracodev.net"
|
||||||
|
assert urls["kanbante"] == "https://kanbante.dracodev.net"
|
||||||
|
|
||||||
|
|
||||||
def test_bouton_sso_login_depuis_config(monkeypatch):
|
def test_bouton_sso_login_depuis_config(monkeypatch):
|
||||||
from app.routers.auth import _sso_login_url
|
from app.routers.auth import _sso_login_url
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user