ServiceNowCMDBEnrichAsset
This script resolves the owner and related configuration items of a network adapter by searching the ServiceNow CMDB network adapter table using a specified IP address.
python · ServiceNow
Source
from dataclasses import dataclass, asdict from typing import Any import json import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 SERVICENOW_CMDB_RECORD_GET_BY_ID = "servicenow-cmdb-record-get-by-id" CMDB_CI_NETWORK_ADAPTER = "cmdb_ci_network_adapter" DEFAULT_INSTANCE_URL_SUFFIX = "/api/now/table/sys_user_group/global" @dataclass class NetworkAdapter: """Represents a network adapter from ServiceNow CMDB.""" sys_id: str name: str | None = None ip: str | None = None owner: str = "" owner_id: str = "" related_configuration_item_name: str | None = None related_configuration_item_id: str | None = None instance_url: str = "" url: str = "" def to_dict(self) -> dict[str, Any]: return asdict(self) @dataclass class ConfigurationItem: """Represents a related configuration item from ServiceNow CMDB.""" sys_id: str name: str | None = None ip: str | None = None owner: str | None = None owner_id: str | None = None hostname: str | None = None os: str | None = None os_version: str | None = None ci_class: str = "" use: str | None = None url: str = "" def to_dict(self) -> dict[str, Any]: return asdict(self) def get_command_results(command: str, args: dict[str, Any]) -> Union[dict[str, Any], list]: """Execute a Demisto command and return the parsed result. Args: command (str): The Demisto command to execute. args (dict[str, Any]): The arguments to pass to the command. Returns: Union[dict[str, Any], list]: The parsed result of the command, or an empty dict if no valid result is found. Raises: Exception: If the command execution returns an error. """ results = demisto.executeCommand(command, args) if not results or not isinstance(results, list): return {} result = results[0] if not isinstance(result, dict): return {} if result.get("Type") == EntryType.ERROR: raise Exception(result.get("Contents", "Unknown error occurred.")) contents = result.get("Contents") if isinstance(contents, dict): return contents.get("result", {}) return {} def build_servicenow_url(instance_url: str, table: str, sys_id: str) -> str: """Build ServiceNow navigation URL.""" return f"{instance_url}/nav_to.do?uri={table}.do?sys_id={sys_id}" if instance_url else "" def get_network_adapter(sid: str) -> NetworkAdapter: """Fetch and parse network adapter details from ServiceNow.""" result = get_command_results(SERVICENOW_CMDB_RECORD_GET_BY_ID, {"class": CMDB_CI_NETWORK_ADAPTER, "sys_id": sid}) if not isinstance(result, dict): result = {} attributes = result.get("attributes", {}) sys_domain = attributes.get("sys_domain", {}) instance_url = sys_domain.get("link", "").replace(DEFAULT_INSTANCE_URL_SUFFIX, "") assigned_to = attributes.get("assigned_to", {}) if isinstance(attributes.get("assigned_to"), dict) else {} cmdb_ci = attributes.get("cmdb_ci", {}) if isinstance(attributes.get("cmdb_ci"), dict) else {} return NetworkAdapter( sys_id=sid, name=attributes.get("name"), ip=attributes.get("ip_address"), owner=assigned_to.get("display_value", ""), owner_id=assigned_to.get("value", ""), related_configuration_item_name=cmdb_ci.get("display_value"), related_configuration_item_id=cmdb_ci.get("value"), instance_url=instance_url, url=build_servicenow_url(instance_url=instance_url, table="cmdb_ci_network_adapter", sys_id=sid) if instance_url else "", ) def get_related_configuration_item(sid: str, instance_url: str) -> ConfigurationItem: """Fetch and parse related configuration item details from ServiceNow.""" # First get the CI class result = get_command_results(SERVICENOW_CMDB_RECORD_GET_BY_ID, {"class": "cmdb_ci", "sys_id": sid}) if not isinstance(result, dict): result = {} ci_class = result.get("attributes", {}).get("sys_class_name", "") if not ci_class: return ConfigurationItem( sys_id=sid, url=build_servicenow_url(instance_url=instance_url, table="cmdb_ci", sys_id=sid) if instance_url else "" ) # Get full CI details with the specific class result = get_command_results(SERVICENOW_CMDB_RECORD_GET_BY_ID, {"class": ci_class, "sys_id": sid}) if not isinstance(result, dict): result = {} attributes = result.get("attributes", {}) assigned_to = attributes.get("assigned_to", {}) if isinstance(attributes.get("assigned_to"), dict) else {} return ConfigurationItem( sys_id=sid, name=attributes.get("name"), ip=attributes.get("ip_address"), owner=assigned_to.get("display_value"), owner_id=assigned_to.get("value"), hostname=attributes.get("host_name"), os=attributes.get("os"), os_version=attributes.get("os_version"), ci_class=ci_class, use=attributes.get("used_for"), url=build_servicenow_url(instance_url=instance_url, table=ci_class, sys_id=sid) if instance_url else "", ) def main(): """ Main function to resolve network adapters and related configuration items from ServiceNow CMDB. This script queries ServiceNow CMDB for network adapters matching the provided IP address, then retrieves related configuration items and returns enriched information. """ try: args = demisto.args() ip_address = args.get("ip_address") if not ip_address: raise ValueError("IP address is required") # Get network adapters for the given IP result = get_command_results( "servicenow-cmdb-records-list", {"class": CMDB_CI_NETWORK_ADAPTER, "query": f"ip_address={ip_address}"} ) if not isinstance(result, list): result = [] network_adapters = [get_network_adapter(item["sys_id"]) for item in result if item.get("sys_id")] # Get related configuration items related_items = [] for adapter in network_adapters: if adapter.related_configuration_item_id and adapter.instance_url: related_item = get_related_configuration_item(adapter.related_configuration_item_id, adapter.instance_url) related_items.append(related_item) outputs = { "summary": ( f"Found {len(network_adapters)} related network adapters and " f"{len(related_items)} related configuration items in ServiceNow for ip {ip_address}." ), "network_adapters": [adapter.to_dict() for adapter in network_adapters], "related_configuration_items": [item.to_dict() for item in related_items], } # Prepare human-readable output human_readable = ( f"### {outputs['summary']}\n" f"```json\n{json.dumps(outputs['related_configuration_items'], indent=2, default=str)}\n```" ) return_results( CommandResults( outputs=outputs, outputs_prefix="ServiceNowCMDBAssetEnrichment", readable_output=human_readable, raw_response=outputs, ) ) except Exception as e: error_msg = f"Failed to execute ResolveNetworkAdapters: {str(e)}" return_error(error_msg) if __name__ in ["__main__", "__builtin__", "builtins"]: main()