"""Upsert the DAX Feishu approval-flow mapping into Helix. This is an ops script for post-definition deployment. Feishu approval definitions remain outside Helix runtime; after the definition is published, run this script inside the backend container with the published approval code. Usage: export HELIX_DAX_FEISHU_APPROVAL_CODE=... /app/.venv/bin/python scripts/upsert_data_access_approval_flow.py --commit Optionally target a specific Feishu integration: /app/.venv/bin/python scripts/upsert_data_access_approval_flow.py \ --integration-slug feishu-primary --approval-code ... --commit """ from __future__ import annotations import argparse import asyncio import os import sys from typing import Any from sqlalchemy import select # Register mapper classes before touching Integration relationships. import app.approval.models # noqa: F401 import app.audit.models # noqa: F401 import app.auth.models # noqa: F401 import app.domains.apikey.models # noqa: F401 import app.domains.asset.models # noqa: F401 import app.domains.cmdb.models # noqa: F401 import app.domains.data_access.models # noqa: F401 import app.domains.email_group.models # noqa: F401 import app.domains.hr.models # noqa: F401 import app.domains.offboarding.models # noqa: F401 import app.domains.permission.models # noqa: F401 import app.domains.provisioning.models # noqa: F401 import app.identity.person_models # noqa: F401 from app.core.database import async_session_factory from app.domains.data_access.approval_mapping import ( data_access_group_grant_flow_mapping, data_access_personal_grant_flow_mapping, data_access_service_account_grant_flow_mapping, ) from app.domains.data_access.constants import ( APPROVAL_FLOW_DATA_ACCESS_GROUP_GRANT, APPROVAL_FLOW_DATA_ACCESS_PERSONAL_GRANT, APPROVAL_FLOW_DATA_ACCESS_SERVICE_ACCOUNT_GRANT, ) from app.integrations.models import Integration, IntegrationApprovalFlow from app.integrations.service import get_primary _SUPPORTED_FLOW_KEYS = ( APPROVAL_FLOW_DATA_ACCESS_GROUP_GRANT, APPROVAL_FLOW_DATA_ACCESS_SERVICE_ACCOUNT_GRANT, APPROVAL_FLOW_DATA_ACCESS_PERSONAL_GRANT, ) def _approval_code(*, arg_value: str | None, flow_key: str) -> str: parameter_env = { APPROVAL_FLOW_DATA_ACCESS_GROUP_GRANT: ("FEISHU_APPROVAL_DATA_ACCESS_GROUP_GRANT_CODE"), APPROVAL_FLOW_DATA_ACCESS_SERVICE_ACCOUNT_GRANT: ( "FEISHU_APPROVAL_DATA_ACCESS_SERVICE_ACCOUNT_GRANT_CODE" ), APPROVAL_FLOW_DATA_ACCESS_PERSONAL_GRANT: ( "FEISHU_APPROVAL_DATA_ACCESS_PERSONAL_GRANT_CODE" ), }[flow_key] value = ( arg_value or os.environ.get("HELIX_DAX_FEISHU_APPROVAL_CODE") or os.environ.get(parameter_env) or "" ) value = value.strip() if not value or value == "REPLACE_WITH_FEISHU_APPROVAL_CODE": print( "ERROR: provide a real Feishu approval code via --approval-code " "or HELIX_DAX_FEISHU_APPROVAL_CODE.", file=sys.stderr, ) sys.exit(2) return value def _flow_mapping(*, flow_key: str, external_code: str) -> dict[str, Any]: if flow_key == APPROVAL_FLOW_DATA_ACCESS_GROUP_GRANT: return data_access_group_grant_flow_mapping(external_code=external_code) if flow_key == APPROVAL_FLOW_DATA_ACCESS_SERVICE_ACCOUNT_GRANT: return data_access_service_account_grant_flow_mapping( external_code=external_code, ) if flow_key == APPROVAL_FLOW_DATA_ACCESS_PERSONAL_GRANT: return data_access_personal_grant_flow_mapping(external_code=external_code) raise ValueError(f"Unsupported DAX approval flow key: {flow_key!r}") async def _load_feishu_integration( *, integration_slug: str | None, ) -> Integration: async with async_session_factory() as db: if integration_slug: row = await db.scalar( select(Integration).where(Integration.slug == integration_slug), ) if row is None: print( f"ERROR: no integration found for slug={integration_slug!r}.", file=sys.stderr, ) sys.exit(3) else: row = await get_primary("chat_approval", session=db) if row.kind != "chat_approval" or row.provider != "feishu": print( "ERROR: DAX approval flow must be attached to a " f"chat_approval/feishu integration, got " f"{row.kind}/{row.provider} ({row.slug}).", file=sys.stderr, ) sys.exit(4) return row async def _run( *, approval_code: str, flow_key: str, integration_slug: str | None, commit: bool, ) -> None: integration = await _load_feishu_integration(integration_slug=integration_slug) mapping = _flow_mapping(flow_key=flow_key, external_code=approval_code) async with async_session_factory() as db: row = await db.scalar( select(IntegrationApprovalFlow).where( IntegrationApprovalFlow.integration_id == integration.id, IntegrationApprovalFlow.flow_key == flow_key, ), ) created = row is None if row is None: row = IntegrationApprovalFlow( integration_id=integration.id, flow_key=mapping["flow_key"], label=mapping["label"], external_code=mapping["external_code"], payload_schema=mapping["payload_schema"], active=True, accept_external_origin=False, ) db.add(row) else: row.label = mapping["label"] row.external_code = mapping["external_code"] row.payload_schema = mapping["payload_schema"] row.active = True row.accept_external_origin = False if commit: await db.commit() action = "created" if created else "updated" print( "OK: " f"{action} {flow_key} " f"on integration {integration.slug} " f"with approval_code={approval_code}", ) else: await db.rollback() action = "create" if created else "update" print( "DRY RUN: would " f"{action} {flow_key} " f"on integration {integration.slug} " f"with approval_code={approval_code}. " "Re-run with --commit to write.", ) def main() -> None: parser = argparse.ArgumentParser( description="Upsert the DAX Feishu approval-flow mapping.", ) parser.add_argument( "--approval-code", help=("Published Feishu approval code. Defaults to HELIX_DAX_FEISHU_APPROVAL_CODE."), ) parser.add_argument( "--integration-slug", help="Specific chat_approval/feishu integration slug. Defaults to primary.", ) parser.add_argument( "--flow-key", choices=_SUPPORTED_FLOW_KEYS, default=APPROVAL_FLOW_DATA_ACCESS_GROUP_GRANT, help=("DAX approval-flow key to upsert. Defaults to the existing Google Group flow."), ) parser.add_argument( "--commit", action="store_true", help="Write changes. Without this flag the script is a dry run.", ) args = parser.parse_args() asyncio.run( _run( approval_code=_approval_code( arg_value=args.approval_code, flow_key=args.flow_key, ), flow_key=args.flow_key, integration_slug=args.integration_slug, commit=args.commit, ), ) if __name__ == "__main__": main()