GetIdsFromCustomContent

Extract custom content IDs from custom content bundle file and exclude IDs as specified.

python · XSOAR CI/CD

Source

import json
import os
import tarfile
import tempfile
import traceback
from collections import defaultdict
from pathlib import Path
from typing import Any

import demistomock as demisto
from CommonServerPython import *
from demisto_sdk.commands.common.tools import _get_file_id, find_type, get_file, get_file_displayed_name

from CommonServerUserPython import *


def update_file_prefix(file_name: str) -> str:
    """
    Custom content scripts are prefixed with automation instead of script.
    Removing the "playbook-" prefix from files name.
    """
    if file_name.startswith("playbook-"):
        return file_name[len("playbook-") :]
    if file_name.startswith("automation-"):
        return file_name.replace("automation-", "script-")
    return file_name


def get_content_details(tar_file_handler: Any, member_file: Any) -> tuple[str, dict]:
    """Get content id from tar member file.

    Args:
        tar_file_handler: Tarfile open handler that contains the member file to inspect.
        member_file: The member file in the tar file to inspect.

    Return:
        (entity, file_id_name) of the member file.
    """
    file_type_str = ""
    file_id = None
    with tempfile.TemporaryDirectory() as tmp_dir_name:
        file_name = update_file_prefix(member_file.name.strip("/"))
        file_path = os.path.join(tmp_dir_name, file_name)
        with open(file_path, "w") as file_desc:
            if extracted_file := tar_file_handler.extractfile(member_file):
                file_desc.write(extracted_file.read().decode("utf-8"))
            else:
                raise Exception(f"Could not extract file {file_name} from tar: {file_path}")

        if not os.path.isfile(file_path):
            raise Exception(f"Could not create file {file_path}")

        file_type = find_type(path=file_path)
        file_type_str = file_type.value if file_type else ""
        if file_type_str == "automation":
            file_type_str = "script"

        file_type_str = "list" if not file_type_str and file_name.startswith("list-") else file_type_str
        file_dict = get_file(file_path, Path(file_name).suffix[1:])
        file_id = _get_file_id(file_type_str, file_dict)
        file_id = file_id if file_id else file_dict.get("id")
        file_name = get_file_displayed_name(file_path)

    file_id_name = {"id": file_id, "name": file_name}
    return file_type_str, file_id_name


def get_custom_content_ids(file_entry_id: Any) -> dict:
    """Get custom content ids from custom content bundle.

    Args:
         file_entry_id (str): The entry id of the custom content zip file.

    Return:
        A dict of custom content ids.
    """
    custom_content_ids: defaultdict[Any, list] = defaultdict(list)
    get_file_path_res = demisto.getFilePath(file_entry_id)
    custom_content_file_path = get_file_path_res.get("path")
    if not custom_content_file_path:
        raise ValueError(f"Could not find file path for entry id {file_entry_id}")
    custom_content_tar_file = tarfile.open(custom_content_file_path)
    custom_content_members = custom_content_tar_file.getmembers()

    for custom_content_member in custom_content_members:
        entity, entity_id_name = get_content_details(custom_content_tar_file, custom_content_member)
        if entity and entity_id_name.get("id"):
            custom_content_ids[entity].append(entity_id_name)
        else:
            raise Exception(f"Could not parse content type and id from file name {custom_content_member.name}")

    return custom_content_ids


""" COMMAND FUNCTION """


def filter_lists(include: list, exclude: list) -> list:
    return [item for item in include if item.get("id") not in exclude]


def get_included_ids_command(args: dict[str, Any]) -> CommandResults:
    """Get included ids from installed custom content unless id is excluded.

    Args:
        exclude_ids_list (List[Dict[str:List[str]]]): A list of dicts, each specifies entity ids to exclude.
            (example: [{'integration': ['HelloWorld', 'MyIntegration']}, {'script': ['say_hello']}]
        file_entry_id (str): The entry id of the custom content zip file.

    Return:
        CommandResults Outputs with included ids dict and excluded ids dict, ready to pass to DeleteContent script.
    """
    if (excluded_ids_dicts := args.get("exclude_ids_list", [])) and not isinstance(excluded_ids_dicts, list):
        try:
            excluded_ids_dicts = json.loads(str(args.get("exclude_ids_list")))
        except json.JSONDecodeError as err:
            raise ValueError(f"Failed decoding excluded_ids_list as json: {err!s}")

    custom_content_ids = get_custom_content_ids(file_entry_id=args.get("file_entry_id"))

    included_custom_ids_names = {}
    excluded_ids: defaultdict[Any, list] = defaultdict(list)
    if excluded_ids_dicts:
        # Merge exclusion dicts
        for excluded_ids_dict in excluded_ids_dicts:
            for excluded_entity in excluded_ids_dict:
                excluded_ids[excluded_entity] += excluded_ids_dict.get(excluded_entity, [])

        # Exclude what is relevant
        for custom_entity in custom_content_ids:
            included_custom_ids_names[custom_entity] = filter_lists(
                include=custom_content_ids.get(custom_entity, []), exclude=excluded_ids.get(custom_entity, [])
            )

        # Remove included entities from excluded dict
        for entity in included_custom_ids_names:
            if entity in excluded_ids:
                excluded_ids.pop(entity)

    else:
        included_custom_ids_names = custom_content_ids

    included_custom_ids = {key: [value["id"] for value in lst] for key, lst in included_custom_ids_names.items() if lst}
    included_custom_name = {key: [value["name"] for value in lst] for key, lst in included_custom_ids_names.items() if lst}
    return CommandResults(
        outputs_prefix="GetIdsFromCustomContent",
        outputs_key_field="",
        outputs={
            "included_ids": included_custom_ids,
            "excluded_ids": {key: value for key, value in excluded_ids.items() if value},
        },
        readable_output=tableToMarkdown("Included ids", included_custom_name) + tableToMarkdown("Excluded ids", excluded_ids),
    )


def main():  # pragma: no cover
    try:
        return_results(get_included_ids_command(demisto.args()))
    except Exception as ex:
        return_error(f"Failed to execute GetIdsFromCustomContent. Error: {ex!s}\n Traceback: {traceback.format_exc()}")


""" ENTRY POINT """


if __name__ in ("__main__", "__builtin__", "builtins"):  # pragma: no cover
    main()

README

Extract custom content IDs from custom content bundle file and exclude IDs as specified.

Script Data


Name Description
Script Type python3
Cortex XSOAR Version 6.8.0

Inputs


Argument Name Description
exclude_ids_list List of dictionaries of IDs to exclude in a JSON format (e.g., [{“job”: [“job1”, “job2”], “pack”: [“pack1”]}, {“job”: [“job3”]}]
file_entry_id The entry ID of the custom content tar file.

Outputs


Path Description Type
GetIdsFromCustomContent.included_ids Dictionary of IDs of custom content excluding the ones specified. Unknown
GetIdsFromCustomContent.excluded_ids Dictionary of IDs of custom content excluding the ones specified. Unknown