0f1a6d865d
Applications can now declare a `credentials:` list in the YAML manifest.
Each entry holds a static `client_id` (and optional `secret`) that is
provisioned via POST /applications/{id}/extcredentials when the application
is first created. Existing applications are never modified.
Also adds CHANGELOG.md and ROADMAP.md for the v0.1.0 initial release.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
184 lines
7.7 KiB
Python
184 lines
7.7 KiB
Python
import logging
|
|
|
|
from ._colors import _c
|
|
from .client import AxwayClient
|
|
from .models import Action, EnvConfig, Manifest, NormResult
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def _applies_to(target_envs: list[str], env_name: str) -> bool:
|
|
return not target_envs or env_name in target_envs
|
|
|
|
|
|
def normalize_env(env: EnvConfig, manifest: Manifest, dry_run: bool) -> NormResult:
|
|
result = NormResult()
|
|
tag = f"[{env.name}]"
|
|
|
|
log.info(f"{tag} Connexion à {env.url} …")
|
|
client = AxwayClient(env)
|
|
|
|
# ── 1. Organisations ──────────────────────────────────────────────────────
|
|
try:
|
|
existing_orgs = client.list_orgs()
|
|
except Exception as e:
|
|
result.errors.append(f"{tag} Impossible de lister les organisations : {e}")
|
|
return result
|
|
|
|
for org_spec in manifest.organizations:
|
|
if not _applies_to(org_spec.environments, env.name):
|
|
continue
|
|
|
|
action = Action(
|
|
kind="create_org",
|
|
env_name=env.name,
|
|
description=f"Créer organisation « {org_spec.name} »",
|
|
detail={"org": org_spec.name},
|
|
)
|
|
|
|
if org_spec.name in existing_orgs:
|
|
log.debug(f"{tag} Org « {org_spec.name} » déjà présente — skip")
|
|
result.actions_skipped.append(action)
|
|
continue
|
|
|
|
log.info(_c("yellow", f"{tag} {'[DRY-RUN] ' if dry_run else ''}Création org « {org_spec.name} »"))
|
|
if not dry_run:
|
|
try:
|
|
created = client.create_org(org_spec)
|
|
existing_orgs[org_spec.name] = created
|
|
log.info(_c("green", f"{tag} ✅ Org « {org_spec.name} » créée (id={created.get('id')})"))
|
|
except Exception as e:
|
|
msg = f"{tag} ❌ Échec création org « {org_spec.name} » : {e}"
|
|
log.error(msg)
|
|
result.errors.append(msg)
|
|
continue
|
|
else:
|
|
# Placeholder so apps referencing this org are processed in dry-run
|
|
existing_orgs[org_spec.name] = {"id": "", "name": org_spec.name}
|
|
result.actions_done.append(action)
|
|
|
|
# ── 2. Applications ───────────────────────────────────────────────────────
|
|
try:
|
|
existing_apps = client.list_apps()
|
|
except Exception as e:
|
|
result.errors.append(f"{tag} Impossible de lister les applications : {e}")
|
|
return result
|
|
|
|
for app_spec in manifest.applications:
|
|
if not _applies_to(app_spec.environments, env.name):
|
|
continue
|
|
|
|
key = (app_spec.org_name, app_spec.name)
|
|
|
|
action_app = Action(
|
|
kind="create_app",
|
|
env_name=env.name,
|
|
description=f"Créer application « {app_spec.name} » dans org « {app_spec.org_name} »",
|
|
detail={"org": app_spec.org_name, "app": app_spec.name},
|
|
)
|
|
|
|
raw_org = existing_orgs.get(app_spec.org_name)
|
|
if raw_org is None:
|
|
msg = (
|
|
f"{tag} ❌ Organisation « {app_spec.org_name} » introuvable dans l'API Manager "
|
|
f"— application « {app_spec.name} » ignorée. "
|
|
f"Créez l'organisation manuellement dans l'API Manager."
|
|
)
|
|
log.error(msg)
|
|
result.errors.append(msg)
|
|
continue
|
|
|
|
org_id = raw_org["id"]
|
|
|
|
if key not in existing_apps:
|
|
log.info(_c("yellow", f"{tag} {'[DRY-RUN] ' if dry_run else ''}Création app « {app_spec.name} » (org={app_spec.org_name})"))
|
|
if not dry_run:
|
|
try:
|
|
created_app = client.create_app(app_spec, org_id)
|
|
existing_apps[key] = created_app
|
|
log.info(_c("green", f"{tag} ✅ App « {app_spec.name} » créée (id={created_app.get('id')})"))
|
|
except Exception as e:
|
|
msg = f"{tag} ❌ Échec création app « {app_spec.name} » : {e}"
|
|
log.error(msg)
|
|
result.errors.append(msg)
|
|
continue
|
|
|
|
app_id_new = created_app.get("id", "")
|
|
for cred in app_spec.credentials:
|
|
try:
|
|
client.create_app_ext_credential(app_id_new, cred.client_id, cred.secret)
|
|
log.info(_c("green", f"{tag} ✅ External credential « {cred.client_id} » ajouté à « {app_spec.name} »"))
|
|
except Exception as e:
|
|
msg = f"{tag} ❌ Échec ajout external credential à « {app_spec.name} » : {e}"
|
|
log.error(msg)
|
|
result.errors.append(msg)
|
|
result.actions_done.append(action_app)
|
|
else:
|
|
result.actions_skipped.append(action_app)
|
|
|
|
# ── 3. Souscriptions aux APIs ─────────────────────────────────────────
|
|
if not app_spec.apis:
|
|
continue
|
|
|
|
raw_app = existing_apps.get(key)
|
|
if raw_app is None:
|
|
continue # app non créée (dry-run ou erreur)
|
|
|
|
app_id = raw_app.get("id", "")
|
|
|
|
try:
|
|
granted = client.list_app_api_access(app_id)
|
|
granted_ids = {a.get("apiId", a.get("id", "")) for a in granted}
|
|
granted_names = {a.get("apiName", a.get("name", "")) for a in granted}
|
|
except Exception as e:
|
|
log.warning(f"{tag} Impossible de lire les accès API de « {app_spec.name} » : {e}")
|
|
granted_ids = set()
|
|
granted_names = set()
|
|
|
|
for api_access in app_spec.apis:
|
|
action_api = Action(
|
|
kind="grant_api",
|
|
env_name=env.name,
|
|
description=(
|
|
f"Abonner « {app_spec.name} » à l'API « {api_access.api_name} »"
|
|
+ (f" v{api_access.api_version}" if api_access.api_version else "")
|
|
),
|
|
detail={"app": app_spec.name, "api": api_access.api_name, "version": api_access.api_version},
|
|
)
|
|
|
|
if api_access.api_name in granted_names:
|
|
log.debug(f"{tag} API « {api_access.api_name} » déjà accordée à « {app_spec.name} » — skip")
|
|
result.actions_skipped.append(action_api)
|
|
continue
|
|
|
|
if dry_run:
|
|
log.info(_c("yellow", f"{tag} [DRY-RUN] Abonnement « {app_spec.name} » → API « {api_access.api_name} »"))
|
|
result.actions_done.append(action_api)
|
|
continue
|
|
|
|
fe_api = client.find_frontend_api(api_access.api_name, api_access.api_version)
|
|
if fe_api is None:
|
|
msg = (
|
|
f"{tag} ⚠️ API front-end « {api_access.api_name} » introuvable "
|
|
f"— souscription de « {app_spec.name} » ignorée"
|
|
)
|
|
log.warning(msg)
|
|
result.errors.append(msg)
|
|
continue
|
|
|
|
api_id = fe_api["id"]
|
|
if api_id in granted_ids:
|
|
result.actions_skipped.append(action_api)
|
|
continue
|
|
|
|
try:
|
|
client.grant_api_access(app_id, api_id)
|
|
log.info(_c("green", f"{tag} ✅ Accès API « {api_access.api_name} » accordé à « {app_spec.name} »"))
|
|
result.actions_done.append(action_api)
|
|
except Exception as e:
|
|
msg = f"{tag} ❌ Échec souscription « {app_spec.name} » → « {api_access.api_name} » : {e}"
|
|
log.error(msg)
|
|
result.errors.append(msg)
|
|
|
|
return result
|