feat(apim-apps): add external credentials support for new applications

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>
This commit is contained in:
2026-06-29 22:16:48 +02:00
parent 7738a51d16
commit 0f1a6d865d
10 changed files with 251 additions and 3 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
from .client import AxwayClient, _AppendOnlySession
from .engine import normalize_env
from .loader import EXAMPLE_CONFIG, EXAMPLE_MANIFEST, load_config, load_manifest
from .models import Action, ApiAccess, AppSpec, EnvConfig, Manifest, NormResult, OrgSpec
from .models import Action, ApiAccess, AppCredential, AppSpec, EnvConfig, Manifest, NormResult, OrgSpec
from .report import print_report
+6
View File
@@ -98,6 +98,12 @@ class AxwayClient:
"state": "approved",
})
def create_app_ext_credential(self, app_id: str, client_id: str, secret: str = "") -> dict:
payload: dict = {"clientId": client_id, "enabled": True}
if secret:
payload["secret"] = secret
return self._post(f"/applications/{app_id}/extcredentials", payload)
# ── APIs front-end ────────────────────────────────────────────────────────
def list_frontend_apis(self) -> list[dict]:
+10
View File
@@ -102,6 +102,16 @@ def normalize_env(env: EnvConfig, manifest: Manifest, dry_run: bool) -> NormResu
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)
+9 -1
View File
@@ -3,7 +3,7 @@ import textwrap
import yaml
from .models import ApiAccess, AppSpec, EnvConfig, Manifest, OrgSpec
from .models import ApiAccess, AppCredential, AppSpec, EnvConfig, Manifest, OrgSpec
EXAMPLE_MANIFEST = textwrap.dedent("""\
# =============================================================================
@@ -26,6 +26,8 @@ applications:
description: "Application mobile grand public"
email: "mobile-app@example.com"
enabled: true
credentials:
- client_id: "my-external-client-id"
apis:
- name: "Catalogue-Produits"
version: "v2"
@@ -96,6 +98,11 @@ def load_manifest(path: str) -> Manifest:
apis.append(ApiAccess(api_name=api))
else:
apis.append(ApiAccess(api_name=api["name"], api_version=api.get("version", "")))
credentials = [
AppCredential(client_id=c["client_id"], secret=c.get("secret", ""))
for c in a.get("credentials", [])
if c.get("client_id")
]
manifest.applications.append(AppSpec(
name=a["name"],
org_name=a["organization"],
@@ -105,6 +112,7 @@ def load_manifest(path: str) -> Manifest:
enabled=a.get("enabled", True),
apis=apis,
environments=a.get("environments", []),
credentials=credentials,
))
return manifest
+7
View File
@@ -7,6 +7,12 @@ class ApiAccess:
api_version: str = ""
@dataclass
class AppCredential:
client_id: str
secret: str = ""
@dataclass
class AppSpec:
name: str
@@ -17,6 +23,7 @@ class AppSpec:
enabled: bool = True
apis: list["ApiAccess"] = field(default_factory=list)
environments: list[str] = field(default_factory=list)
credentials: list["AppCredential"] = field(default_factory=list)
@dataclass