MarketplacePackInstaller Deprecated
Deprecated. Use ContentPackInstaller instead.
python · XSOAR CI/CD
Source
import demistomock as demisto from CommonServerPython import * from packaging.version import parse, Version, LegacyVersion SCRIPT_NAME = 'MarketplacePackInstaller' class ContentPackInstaller: """A class that handles all marketplace packs' installation process. """ PACK_ID_VERSION_FORMAT = '{}::{}' def __init__(self, instance_name=''): self.installed_packs: Dict[str, Union[Version, LegacyVersion]] = dict() self.newly_installed_packs: Dict[str, Version] = dict() self.already_on_machine_packs: Dict[str, Union[Version, LegacyVersion]] = dict() self.packs_data: Dict[str, Dict[str, str]] = dict() self.packs_dependencies: Dict[str, Dict[str, Dict[str, str]]] = dict() self.instance_name: str = instance_name self.get_installed_packs() def get_installed_packs(self) -> None: """Gets the current installed packs on the machine. """ demisto.debug(f'{SCRIPT_NAME} - Fetching installed packs from marketplace.') args = {'uri': '/contentpacks/metadata/installed'} if self.instance_name: args['using'] = self.instance_name status, res = execute_command( 'core-api-get', args, fail_on_error=False, ) if not status: error_message = f'{SCRIPT_NAME} - {res}' demisto.debug(error_message) return packs_data: List[Dict[str, str]] = res.get('response', []) for pack in packs_data: self.installed_packs[pack['id']] = parse(pack['currentVersion']) self.already_on_machine_packs[pack['id']] = parse(pack['currentVersion']) def get_pack_data_from_marketplace(self, pack_id: str) -> Dict[str, str]: """Returns the marketplace's data for a specific pack. Args: pack_id (str): The pack ID for which to get the data. Returns: Dict[str, str]. The pack's data from marketplace. """ if pack_id in self.packs_data: demisto.debug(f'{SCRIPT_NAME} - Using cached data of {pack_id} that already been fetched.') return self.packs_data[pack_id] demisto.debug(f'{SCRIPT_NAME} - Fetching {pack_id} data from marketplace.') args = {'uri': f'/contentpacks/marketplace/{pack_id}'} if self.instance_name: args['using'] = self.instance_name status, res = execute_command( 'core-api-get', args, fail_on_error=False, ) if not status: error_message = f'{SCRIPT_NAME} - {res}' demisto.debug(error_message) if type(res) == list: res = res[0] self.packs_data[pack_id] = res return res def get_pack_dependencies_from_marketplace(self, pack_data: Dict[str, str]) -> Dict[str, Dict[str, str]]: """Returns the dependencies of the pack from marketplace's data. Args: pack_data (Dict[str, str]): Packs' data for installation. Returns: Dict[str, Dict]. The pack's dependencies data from marketplace. """ pack_key = self.PACK_ID_VERSION_FORMAT.format(pack_data['id'], pack_data['version']) if pack_key in self.packs_dependencies: demisto.debug(f'{SCRIPT_NAME} - Using cached dependencies data of {pack_key} that already been fetched.') return self.packs_dependencies[pack_key] demisto.debug(f'{SCRIPT_NAME} - Fetching {pack_key} dependencies data from marketplace.') args = {'uri': '/contentpacks/marketplace/search/dependencies', 'body': [pack_data]} if self.instance_name: args['using'] = self.instance_name status, res = execute_command( 'core-api-post', args, fail_on_error=False, ) if not status: error_message = f'{SCRIPT_NAME} - {res}' demisto.debug(error_message) try: self.packs_dependencies[pack_key] = res.get('response', {}).get('packs', [])[0] \ .get('extras', {}).get('pack', {}).get('dependencies') except Exception as e: demisto.debug(f'{SCRIPT_NAME} - Unable to parse {pack_data["id"]} pack dependencies from response.\n{e}') return {} return self.packs_dependencies[pack_key] def get_latest_version_for_pack(self, pack_id: str) -> str: """Gets the latest version of the pack from the marketplace data. Args: pack_id (str): The pack name for which to get the data. Returns: str. The latest version of the pack. """ res = self.get_pack_data_from_marketplace(pack_id) return res.get('response', {}).get('currentVersion') # type: ignore[call-overload, union-attr] def get_packs_data_for_installation(self, packs_to_install: List[Dict[str, str]]) -> List[Dict[str, str]]: """Creates a list of packs' data for the installation request. Args: packs_to_install (List[Dict[str, str]]): The packs to get data for. Returns: List[Dict[str, str]]. Updated list of data objects per pack, which hasn't been installed it. """ latest_version_packs_to_install = [] for pack in packs_to_install: latest_version = self.get_latest_version_for_pack(pack['id']) if parse(latest_version) > self.installed_packs.get(pack['id'], parse('1.0.0')): pack['version'] = latest_version latest_version_packs_to_install.append(pack) return latest_version_packs_to_install def install_packs(self, packs_to_install: List[Dict[str, str]]) -> None: """Sends the request to install the packs in the machine. Args: packs_to_install (List[Dict[str, str]]): The packs data to be used for the installation. """ if not packs_to_install: demisto.debug(f'{SCRIPT_NAME} - No packs were sent for installation.') return data = { 'packs': packs_to_install, 'ignoreWarnings': True } # make the pack installation request packs_names_versions = {pack['id']: parse(pack['version']) for pack in packs_to_install} demisto.debug(f'{SCRIPT_NAME} - Sending installation request for: {packs_names_versions}') args = {'uri': '/contentpacks/marketplace/install', 'body': data} if self.instance_name: args['using'] = self.instance_name execute_command( 'core-api-post', args, extract_contents=False ) self.installed_packs.update(packs_names_versions) self.newly_installed_packs.update(packs_names_versions) # type: ignore[arg-type] def get_dependencies_for_pack(self, pack_data: Dict[str, str]) -> List[Dict[str, str]]: """Retrieves the packs' dependencies from the marketplace data. Args: pack_data (Dict[str, str]): Packs' data for installation. Returns: List[Dict[str, str]]. List of the packs' dependencies. """ dependencies_to_install = [] if not pack_data: return [] if type(pack_data) is list: pack_data = pack_data[0] # type: ignore pack_dependencies = self.get_pack_dependencies_from_marketplace(pack_data) for dependency_id, dependency_data in pack_dependencies.items(): if dependency_data.get('mandatory'): dependency_version = dependency_data.get('minVersion', '1.0.0') if parse(dependency_version) > self.installed_packs.get(dependency_id, parse('1.0.0')): dependencies_to_install.append({ 'id': dependency_id, 'version': dependency_version }) pack_key = self.PACK_ID_VERSION_FORMAT.format(pack_data['id'], pack_data['version']) demisto.debug(f'{SCRIPT_NAME} - Dependencies found for {pack_key}: {dependencies_to_install}') return dependencies_to_install def is_pack_already_installed(self, pack_data: Dict[str, str]) -> bool: """Returns whether the pack is already installed on the machine or not. Args: pack_data (Dict[str, str]): Packs' data for installation. Returns: bool. Whether the pack is already installed. """ if pack_data['id'] not in self.installed_packs: return False try: if pack_data['version'] in ['latest', '*']: return parse(self.get_latest_version_for_pack(pack_data['id'])) == self.installed_packs[pack_data['id']] return parse(pack_data['version']) <= self.installed_packs[pack_data['id']] except Exception: return parse(self.get_latest_version_for_pack(pack_data['id'])) == self.installed_packs[pack_data['id']] def install_pack_and_its_dependencies_recursively(self, pack_data: Dict[str, str]) -> None: """Recursive method for installing a pack and it's prerequisites in order. Args: pack_data (Dict[str, str]): Packs' data for installation. """ if self.is_pack_already_installed(pack_data): pack_info_for_message = self.PACK_ID_VERSION_FORMAT.format(pack_data['id'], pack_data['version']) demisto.debug(f'{SCRIPT_NAME} - pack "{pack_info_for_message}" is already installed') return demisto.debug(f'{SCRIPT_NAME} - Initiating installation process for pack {pack_data["id"]}') if pack_data['version'] == 'latest': pack_data = self.get_packs_data_for_installation([pack_data]) # type: ignore if pack_data and type(pack_data) == list: pack_data = pack_data[0] # type: ignore dependencies_to_install = self.get_dependencies_for_pack(pack_data) for dependency in dependencies_to_install: self.install_pack_and_its_dependencies_recursively(dependency) dependencies_to_install = self.get_packs_data_for_installation(dependencies_to_install) if not dependencies_to_install and not pack_data: return demisto.debug(f'{SCRIPT_NAME} - Updated dependencies for {pack_data["id"]}: {dependencies_to_install}') if dependencies_to_install: self.install_packs(dependencies_to_install) if pack_data: self.install_packs([pack_data]) def format_packs_data_for_installation(args: Dict) -> List[Dict[str, str]]: """Creates the body of the installation request from the raw data. Returns: List[Dict[str, str]]: Installable objects list. """ packs_data = argToList(args.get('packs_data', [])) id_key = args.get('pack_id_key') version_key = args.get('pack_version_key') try: return [ { 'id': pack[id_key], 'version': pack[version_key], } for pack in packs_data ] except KeyError as e: raise DemistoException(f'The following key does not exist in the pack data: {e}.') from e except Exception as e: raise DemistoException(f'Unknown error occurred while processing the packs data.\n{e}') from e def create_context(packs_to_install: List[Dict[str, str]], content_packs_installer: ContentPackInstaller) \ -> List[Dict[str, str]]: """Creates context entry including all relevant data. Args: packs_to_install (List[Dict[str, str]]): The list of packs that should have been installed. content_packs_installer (ContentPackInstaller): The content packs installer. Returns: List[Dict[str, str]]. Formatted context data after installation processes. """ context_data = [] requested_packs_ids = [pack['id'] for pack in packs_to_install] for pack_id, pack_version in content_packs_installer.newly_installed_packs.items(): content_installed = { 'packid': pack_id, 'packversion': str(pack_version), 'installationstatus': 'Success.' if pack_id in requested_packs_ids else 'Installed as requirement.', } context_data.append(content_installed) for pack_id in requested_packs_ids: if pack_id in content_packs_installer.already_on_machine_packs: content_installed = { 'packid': pack_id, 'packversion': str(content_packs_installer.installed_packs[pack_id]), 'installationstatus': 'Already Installed on the machine.', } context_data.append(content_installed) return context_data def main(): try: args = demisto.args() instance_name = args.get('using') installer = ContentPackInstaller(instance_name) packs_to_install = format_packs_data_for_installation(args) for pack in packs_to_install: installer.install_pack_and_its_dependencies_recursively(pack) return_results( CommandResults( outputs_prefix='ConfigurationSetup.MarketplacePacks', outputs_key_field='packid', outputs=create_context(packs_to_install, installer), ) ) except Exception as e: return_error(f'{SCRIPT_NAME} - Error occurred while setting up machine.\n{e}') if __name__ in ('__main__', '__builtin__', 'builtins'): main()
README
Troubleshooting
Multi-tenant environments should be configured with the Cortex Rest API instance when using this
automation. Make sure the Use tenant parameter (in the Cortex Rest API integration) is checked
to ensure that API calls are made to the current tenant instead of the master tenant.