|
4 | 4 | - Label constants for managed-runtime fragment types |
5 | 5 | - Fragment listing by label (MCP, A2A, IAS) |
6 | 6 | - IAS fragment name lookup for auth flows |
| 7 | +- Active integration listing for tenant context |
7 | 8 | """ |
8 | 9 |
|
9 | 10 | import logging |
10 | 11 | from enum import Enum |
| 12 | +from typing import Optional |
11 | 13 |
|
12 | 14 | from sap_cloud_sdk.destination import ( |
13 | 15 | create_fragment_client, |
|
25 | 27 |
|
26 | 28 | _DESTINATION_INSTANCE = "default" |
27 | 29 |
|
| 30 | +# URL mode path segments used by system integration fragments |
| 31 | +_INTEGRATION_URL_MODES = ("mcp", "a2a") |
| 32 | + |
28 | 33 |
|
29 | 34 | class FragmentLabel(str, Enum): |
30 | 35 | """Label values for the sap-managed-runtime-type fragment label key.""" |
@@ -118,3 +123,91 @@ def get_ias_user_fragment_name(tenant_subdomain: str) -> str: |
118 | 123 | f"for tenant '{tenant_subdomain}'" |
119 | 124 | ) |
120 | 125 | return fragments[0].name |
| 126 | + |
| 127 | + |
| 128 | +def list_active_integrations(tenant_subdomain: str) -> list[dict]: |
| 129 | + """List all active backend system integrations for the given tenant. |
| 130 | +
|
| 131 | + Reads Destination Service instance fragments written by the Destinations |
| 132 | + Facilitator during UCL Formation assignment (SPII flow). Each fragment |
| 133 | + represents a connected backend system (e.g. SAP PCE, SAP S/4HANA). |
| 134 | +
|
| 135 | + Extracts integration details from the fragment URL, which always has the form: |
| 136 | + {agw_base_url}/v1/mcp/{ord_id}/{gtid} (MCP integrations) |
| 137 | + {agw_base_url}/v1/a2a/{ord_id}/{gtid} (A2A integrations) |
| 138 | +
|
| 139 | + Args: |
| 140 | + tenant_subdomain: Subscriber tenant subdomain. |
| 141 | +
|
| 142 | + Returns: |
| 143 | + List of dicts, each with keys: |
| 144 | + - global_tenant_id: GTID of the connected partner system. |
| 145 | + - system_type: Application namespace of the partner (e.g. "sap.pce"). |
| 146 | + - integration_dependency: ORD ID of the integration dependency fulfilled. |
| 147 | + Returns empty list if no active integrations exist. |
| 148 | + """ |
| 149 | + client = create_fragment_client( |
| 150 | + instance=_DESTINATION_INSTANCE, |
| 151 | + _telemetry_source=Module.AGENTGATEWAY, |
| 152 | + ) |
| 153 | + fragments = client.list_instance_fragments( |
| 154 | + filter=ListOptions( |
| 155 | + filter_labels=[ |
| 156 | + Label( |
| 157 | + key=LABEL_KEY, |
| 158 | + values=[FragmentLabel.MCP.value, FragmentLabel.A2A.value], |
| 159 | + ) |
| 160 | + ] |
| 161 | + ), |
| 162 | + tenant=tenant_subdomain, |
| 163 | + ) |
| 164 | + |
| 165 | + result = [] |
| 166 | + for fragment in fragments: |
| 167 | + url = fragment.properties.get("URL", "") |
| 168 | + entry = _parse_integration_from_url(url) |
| 169 | + if entry is not None: |
| 170 | + result.append(entry) |
| 171 | + return result |
| 172 | + |
| 173 | + |
| 174 | +def _parse_integration_from_url(url: str) -> Optional[dict]: |
| 175 | + """Extract integration metadata from a system fragment URL. |
| 176 | +
|
| 177 | + Fragment URLs have the form: |
| 178 | + {base}/v1/{mode}/{ord_id}/{gtid} |
| 179 | + where mode is "mcp" or "a2a", ord_id may contain colons and slashes, |
| 180 | + and gtid is the last path segment. |
| 181 | +
|
| 182 | + Args: |
| 183 | + url: The fragment URL property value. |
| 184 | +
|
| 185 | + Returns: |
| 186 | + Dict with global_tenant_id, system_type, integration_dependency, |
| 187 | + or None if the URL does not match the expected pattern. |
| 188 | + """ |
| 189 | + parts = url.rstrip("/").split("/") |
| 190 | + |
| 191 | + mode_idx = None |
| 192 | + for i, part in enumerate(parts): |
| 193 | + if i > 0 and parts[i - 1] == "v1" and part in _INTEGRATION_URL_MODES: |
| 194 | + mode_idx = i |
| 195 | + break |
| 196 | + |
| 197 | + if mode_idx is None or mode_idx + 2 > len(parts) - 1: |
| 198 | + logger.debug("Skipping fragment with unexpected URL pattern: %s", url) |
| 199 | + return None |
| 200 | + |
| 201 | + gtid = parts[-1] |
| 202 | + ord_id = "/".join(parts[mode_idx + 1 : -1]) |
| 203 | + system_type = ord_id.split(":")[0] |
| 204 | + |
| 205 | + if not gtid or not ord_id: |
| 206 | + logger.debug("Skipping fragment with empty gtid or ord_id in URL: %s", url) |
| 207 | + return None |
| 208 | + |
| 209 | + return { |
| 210 | + "global_tenant_id": gtid, |
| 211 | + "system_type": system_type, |
| 212 | + "integration_dependency": ord_id, |
| 213 | + } |
0 commit comments