Initial SFERA platform baseline
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
import re
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir import EdgeKind, NodeKind, SirSnapshot
|
||||
|
||||
|
||||
class IntegrationKind(str, Enum):
|
||||
HTTP_SERVICE = "HTTP_SERVICE"
|
||||
WEB_SERVICE = "WEB_SERVICE"
|
||||
EXCHANGE_PLAN = "EXCHANGE_PLAN"
|
||||
FILE_EXCHANGE = "FILE_EXCHANGE"
|
||||
COM_CONNECTOR = "COM_CONNECTOR"
|
||||
UNKNOWN = "UNKNOWN"
|
||||
|
||||
|
||||
class IntegrationEndpoint(BaseModel):
|
||||
endpoint_id: str
|
||||
name: str
|
||||
kind: IntegrationKind = IntegrationKind.UNKNOWN
|
||||
direction: str = "UNKNOWN"
|
||||
owner: str | None = None
|
||||
attributes: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class IntegrationTopology(BaseModel):
|
||||
project_id: str
|
||||
endpoints: list[IntegrationEndpoint] = Field(default_factory=list)
|
||||
|
||||
def by_kind(self, kind: IntegrationKind) -> list[IntegrationEndpoint]:
|
||||
return [endpoint for endpoint in self.endpoints if endpoint.kind == kind]
|
||||
|
||||
|
||||
_URL_RE = re.compile(r"https?://[^\"'\s;]+", re.IGNORECASE)
|
||||
|
||||
|
||||
def build_integration_topology(snapshot: SirSnapshot) -> IntegrationTopology:
|
||||
endpoints: list[IntegrationEndpoint] = []
|
||||
nodes = {node.lineage_id: node for node in snapshot.nodes}
|
||||
graph_endpoint_lineages = set()
|
||||
for edge in snapshot.edges:
|
||||
if edge.kind != EdgeKind.USES_INTEGRATION or edge.target_lineage not in nodes:
|
||||
continue
|
||||
endpoint_node = nodes[edge.target_lineage]
|
||||
if endpoint_node.kind != NodeKind.INTEGRATION_ENDPOINT:
|
||||
continue
|
||||
owner = nodes.get(edge.source_lineage)
|
||||
graph_endpoint_lineages.add(endpoint_node.lineage_id)
|
||||
endpoints.append(
|
||||
IntegrationEndpoint(
|
||||
endpoint_id=f"integration.{endpoint_node.lineage_id}",
|
||||
name=endpoint_node.name,
|
||||
kind=IntegrationKind(endpoint_node.attributes.get("integration_kind", "UNKNOWN")),
|
||||
direction=str(endpoint_node.attributes.get("direction", "UNKNOWN")),
|
||||
owner=owner.qualified_name if owner is not None else None,
|
||||
attributes=endpoint_node.attributes,
|
||||
)
|
||||
)
|
||||
for node in snapshot.nodes:
|
||||
if node.kind == NodeKind.MODULE and not graph_endpoint_lineages:
|
||||
endpoints.extend(_module_integrations(node))
|
||||
elif node.kind == NodeKind.EXCHANGE_PLAN:
|
||||
endpoints.append(
|
||||
IntegrationEndpoint(
|
||||
endpoint_id=f"integration.{node.lineage_id}",
|
||||
name=node.name,
|
||||
kind=IntegrationKind.EXCHANGE_PLAN,
|
||||
direction="BIDIRECTIONAL",
|
||||
owner=node.qualified_name,
|
||||
attributes={"qualified_name": node.qualified_name},
|
||||
)
|
||||
)
|
||||
return IntegrationTopology(project_id=snapshot.project_id, endpoints=_dedupe_endpoints(endpoints))
|
||||
|
||||
|
||||
def _module_integrations(node) -> list[IntegrationEndpoint]:
|
||||
text = str(node.attributes.get("source_text", ""))
|
||||
if not text:
|
||||
return []
|
||||
endpoints: list[IntegrationEndpoint] = []
|
||||
for index, url in enumerate(_URL_RE.findall(text), start=1):
|
||||
endpoints.append(
|
||||
IntegrationEndpoint(
|
||||
endpoint_id=f"integration.{node.lineage_id}.url.{index}",
|
||||
name=url,
|
||||
kind=IntegrationKind.HTTP_SERVICE,
|
||||
direction="OUTBOUND",
|
||||
owner=node.qualified_name,
|
||||
attributes={"url": url},
|
||||
)
|
||||
)
|
||||
if "HTTPСоединение" in text or "HTTPConnection" in text:
|
||||
endpoints.append(_code_endpoint(node, "HTTPConnection", IntegrationKind.HTTP_SERVICE, "OUTBOUND"))
|
||||
if "WSПрокси" in text or "WSProxy" in text or "WSСсылка" in text:
|
||||
endpoints.append(_code_endpoint(node, "WSProxy", IntegrationKind.WEB_SERVICE, "OUTBOUND"))
|
||||
if "FTPСоединение" in text or "FTPConnection" in text:
|
||||
endpoints.append(_code_endpoint(node, "FTPConnection", IntegrationKind.FILE_EXCHANGE, "OUTBOUND"))
|
||||
if "COMОбъект" in text or "COMObject" in text:
|
||||
endpoints.append(_code_endpoint(node, "COMObject", IntegrationKind.COM_CONNECTOR, "OUTBOUND"))
|
||||
return endpoints
|
||||
|
||||
|
||||
def _code_endpoint(node, name: str, kind: IntegrationKind, direction: str) -> IntegrationEndpoint:
|
||||
return IntegrationEndpoint(
|
||||
endpoint_id=f"integration.{node.lineage_id}.{name.casefold()}",
|
||||
name=name,
|
||||
kind=kind,
|
||||
direction=direction,
|
||||
owner=node.qualified_name,
|
||||
)
|
||||
|
||||
|
||||
def _dedupe_endpoints(endpoints: list[IntegrationEndpoint]) -> list[IntegrationEndpoint]:
|
||||
seen: dict[tuple[str, str, str | None], IntegrationEndpoint] = {}
|
||||
for endpoint in endpoints:
|
||||
seen.setdefault((endpoint.name, endpoint.kind.value, endpoint.owner), endpoint)
|
||||
return sorted(seen.values(), key=lambda endpoint: (endpoint.kind.value, endpoint.name))
|
||||
|
||||
|
||||
__all__ = [
|
||||
"IntegrationEndpoint",
|
||||
"IntegrationKind",
|
||||
"IntegrationTopology",
|
||||
"build_integration_topology",
|
||||
]
|
||||
Reference in New Issue
Block a user