From 3e791dcaf3d82a7bdb03550b39d45a3cacc92c3c Mon Sep 17 00:00:00 2001 From: "th.l" <thl-cmk@outlook.com> Date: Mon, 13 Jan 2025 06:25:50 +0100 Subject: [PATCH] added "use network as prefix" option --- README.md | 2 +- mkp/cisco_meraki-1.4.2-20250104.mkp | Bin 0 -> 44016 bytes source/cmk_addons_plugins/meraki/lib/agent.py | 504 +++++++++--------- source/cmk_addons_plugins/meraki/lib/utils.py | 75 ++- source/cmk_plugins/cisco/rulesets/meraki.py | 273 +++++----- .../server_side_calls/cisco_meraki.py | 5 + source/packages/cisco_meraki | 2 +- 7 files changed, 442 insertions(+), 419 deletions(-) create mode 100644 mkp/cisco_meraki-1.4.2-20250104.mkp diff --git a/README.md b/README.md index 781da5f..38083b3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/mkp/cisco_meraki-1.4.1-20241217.mkp "cisco_meraki-1.4.1-20241217.mkp" +[PACKAGE]: ../../raw/master/mkp/cisco_meraki-1.4.2-20250104.mkp "cisco_meraki-1.4.2-20250104.mkp" [SDK]: ../../raw/master/mkp/MerakiSDK-1.52.0-20241116.mkp "MerakiSDK-1.52.0-20241116.mkp" # Cisco Meraki special agent diff --git a/mkp/cisco_meraki-1.4.2-20250104.mkp b/mkp/cisco_meraki-1.4.2-20250104.mkp new file mode 100644 index 0000000000000000000000000000000000000000..f506f10af7ce9924efabb819c0a539db8e78bc0d GIT binary patch literal 44016 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 HD6szm)oTy( literal 0 HcmV?d00001 diff --git a/source/cmk_addons_plugins/meraki/lib/agent.py b/source/cmk_addons_plugins/meraki/lib/agent.py index 50516ca..b13c979 100644 --- a/source/cmk_addons_plugins/meraki/lib/agent.py +++ b/source/cmk_addons_plugins/meraki/lib/agent.py @@ -42,11 +42,17 @@ # 2024-11-16: fixed crash on missing items in MerakiGetOrganizationSwitchPortsStatusesBySwitch (ThX to Stephan Bergfeld) # 2024-11-23: added appliance port api call -> not yet active # 2024-12-13: fixed crash in SwitchPortStatus if response has no data (>Response [503}>) +# 2025-01-04: added "use network as prefix" option + +# Deprecation information +# https://developer.cisco.com/meraki/api-v1/deprecated-operations/#deprecated-operations +# # ToDo: create inventory from Networks, is per organisation, not sure where/how to put this in the inventory # ToDo: list Connected Datacenters like Umbrella https://developer.cisco.com/meraki/api-v1/list-data-centers/ # ToDo: https://developer.cisco.com/meraki/api-v1/list-tunnels/ # ToDo: https://developer.cisco.com/meraki/api-v1/get-organization-wireless-clients-overview-by-device/ +# ToDo: https://developer.cisco.com/meraki/api-v1/get-device-lldp-cdp/ # if the following is available (right now only with early access enabled) # ToDO: https://developer.cisco.com/meraki/api-v1/get-organization-switch-ports-statuses-by-switch/ # (done) @@ -69,12 +75,10 @@ from json import JSONDecodeError from logging import getLogger from os import environ from pathlib import Path -# from random import randrange from requests import request, RequestException from time import strftime, gmtime, time as now_time from time import time_ns from typing import Final, TypedDict, Any, List -# from urllib.request import getproxies import meraki # type: ignore[import] @@ -92,91 +96,91 @@ from cmk_addons.plugins.meraki.lib.utils import ( MerakiNetwork, # parameter names - _SEC_NAME_APPLIANCE_UPLINKS, - _SEC_NAME_APPLIANCE_PORTS, - _SEC_NAME_APPLIANCE_UPLINKS_USAGE, - _SEC_NAME_APPLIANCE_VPNS, - _SEC_NAME_APPLIANCE_PERFORMANCE, - _SEC_NAME_CELLULAR_UPLINKS, - _SEC_NAME_DEVICE_INFO, - _SEC_NAME_DEVICE_STATUSES, - _SEC_NAME_DEVICE_UPLINKS_INFO, - _SEC_NAME_LICENSES_OVERVIEW, - _SEC_NAME_NETWORKS, - _SEC_NAME_ORGANISATIONS, - _SEC_NAME_ORG_API_REQUESTS, - _SEC_NAME_SENSOR_READINGS, - _SEC_NAME_SWITCH_PORTS_STATUSES, - _SEC_NAME_WIRELESS_DEVICE_STATUS, - _SEC_NAME_WIRELESS_ETHERNET_STATUSES, + SEC_NAME_APPLIANCE_UPLINKS, + SEC_NAME_APPLIANCE_PORTS, + SEC_NAME_APPLIANCE_UPLINKS_USAGE, + SEC_NAME_APPLIANCE_VPNS, + SEC_NAME_APPLIANCE_PERFORMANCE, + SEC_NAME_CELLULAR_UPLINKS, + SEC_NAME_DEVICE_INFO, + SEC_NAME_DEVICE_STATUSES, + SEC_NAME_DEVICE_UPLINKS_INFO, + SEC_NAME_LICENSES_OVERVIEW, + SEC_NAME_NETWORKS, + SEC_NAME_ORGANISATIONS, + SEC_NAME_ORG_API_REQUESTS, + SEC_NAME_SENSOR_READINGS, + SEC_NAME_SWITCH_PORTS_STATUSES, + SEC_NAME_WIRELESS_DEVICE_STATUS, + SEC_NAME_WIRELESS_ETHERNET_STATUSES, # Early Access - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + SEC_NAME_ORG_SWITCH_PORTS_STATUSES, # api cache defaults per section - _SEC_CACHE_APPLIANCE_PERFORMANCE, - _SEC_CACHE_APPLIANCE_UPLINKS_USAGE, - _SEC_CACHE_APPLIANCE_UPLINKS, - _SEC_CACHE_APPLIANCE_VPNS, - _SEC_CACHE_CELLULAR_UPLINKS, - _SEC_CACHE_DEVICE_INFO, - _SEC_CACHE_DEVICE_STATUSES, - _SEC_CACHE_DEVICE_UPLINKS_INFO, - _SEC_CACHE_LICENSES_OVERVIEW, - _SEC_CACHE_NETWORKS, - _SEC_CACHE_ORG_API_REQUESTS, - _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, - _SEC_CACHE_ORGANISATIONS, - _SEC_CACHE_SENSOR_READINGS, - _SEC_CACHE_SWITCH_PORTS_STATUSES, - _SEC_CACHE_WIRELESS_DEVICE_STATUS, - _SEC_CACHE_WIRELESS_ETHERNET_STATUSES, - _SEC_CACHE_APPLIANCE_PORTS, + SEC_CACHE_APPLIANCE_PERFORMANCE, + SEC_CACHE_APPLIANCE_UPLINKS_USAGE, + SEC_CACHE_APPLIANCE_UPLINKS, + SEC_CACHE_APPLIANCE_VPNS, + SEC_CACHE_CELLULAR_UPLINKS, + SEC_CACHE_DEVICE_INFO, + SEC_CACHE_DEVICE_STATUSES, + SEC_CACHE_DEVICE_UPLINKS_INFO, + SEC_CACHE_LICENSES_OVERVIEW, + SEC_CACHE_NETWORKS, + SEC_CACHE_ORG_API_REQUESTS, + SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, + SEC_CACHE_ORGANISATIONS, + SEC_CACHE_SENSOR_READINGS, + SEC_CACHE_SWITCH_PORTS_STATUSES, + SEC_CACHE_WIRELESS_DEVICE_STATUS, + SEC_CACHE_WIRELESS_ETHERNET_STATUSES, + SEC_CACHE_APPLIANCE_PORTS, ) -_MERAKI_SDK_MIN_VERSION: Final = '1.46.0' - -_LOGGER = getLogger('agent_cisco_meraki') - -_API_NAME_API: Final = 'api' -_API_NAME_DEVICE_NAME: Final = 'name' -_API_NAME_DEVICE_PRODUCT_TYPE: Final = 'productType' -_API_NAME_DEVICE_SERIAL: Final = 'serial' -_API_NAME_DEVICE_TYPE_APPLIANCE: Final = 'appliance' -_API_NAME_DEVICE_TYPE_CAMERA: Final = 'camera' -_API_NAME_DEVICE_TYPE_CELLULAR: Final = 'cellularGateway' -_API_NAME_DEVICE_TYPE_SENSOR: Final = 'sensor' -_API_NAME_DEVICE_TYPE_SWITCH: Final = 'switch' -_API_NAME_DEVICE_TYPE_WIRELESS: Final = 'wireless' -_API_NAME_ENABLED: Final = 'enabled' -_API_NAME_NETWORK_ID: Final = 'networkId' -_API_NAME_ORGANISATION_ID: Final = 'id' -_API_NAME_ORGANISATION_NAME: Final = 'name' +MERAKI_SDK_MIN_VERSION: Final = '1.46.0' + +LOGGER = getLogger('agent_cisco_meraki') + +API_NAME_API: Final = 'api' +API_NAME_DEVICE_NAME: Final = 'name' +API_NAME_DEVICE_PRODUCT_TYPE: Final = 'productType' +API_NAME_DEVICE_SERIAL: Final = 'serial' +API_NAME_DEVICE_TYPE_APPLIANCE: Final = 'appliance' +API_NAME_DEVICE_TYPE_CAMERA: Final = 'camera' +API_NAME_DEVICE_TYPE_CELLULAR: Final = 'cellularGateway' +API_NAME_DEVICE_TYPE_SENSOR: Final = 'sensor' +API_NAME_DEVICE_TYPE_SWITCH: Final = 'switch' +API_NAME_DEVICE_TYPE_WIRELESS: Final = 'wireless' +API_NAME_ENABLED: Final = 'enabled' +API_NAME_NETWORK_ID: Final = 'networkId' +API_NAME_ORGANISATION_ID: Final = 'id' +API_NAME_ORGANISATION_NAME: Final = 'name' # map section parameter name to python name (do we really need this, why not use the name ('-' -> '_')? -_SECTION_NAME_MAP = { - _SEC_NAME_APPLIANCE_UPLINKS: 'appliance_uplinks', - _SEC_NAME_APPLIANCE_UPLINKS_USAGE: 'appliance_uplinks_usage', - _SEC_NAME_APPLIANCE_PORTS: 'appliance_ports', - _SEC_NAME_APPLIANCE_VPNS: 'appliance_vpns', - _SEC_NAME_APPLIANCE_PERFORMANCE: 'appliance_performance', - _SEC_NAME_CELLULAR_UPLINKS: 'cellular_uplinks', - _SEC_NAME_DEVICE_INFO: 'device_info', - _SEC_NAME_DEVICE_STATUSES: 'device_status', - _SEC_NAME_DEVICE_UPLINKS_INFO: 'device_uplinks_info', - _SEC_NAME_LICENSES_OVERVIEW: 'licenses_overview', - _SEC_NAME_NETWORKS: 'networks', - _SEC_NAME_ORGANISATIONS: 'organisations', - _SEC_NAME_ORG_API_REQUESTS: 'api_requests_by_organization', - _SEC_NAME_SENSOR_READINGS: 'sensor_readings', - _SEC_NAME_SWITCH_PORTS_STATUSES: 'switch_ports_statuses', - _SEC_NAME_WIRELESS_DEVICE_STATUS: 'wireless_device_status', - _SEC_NAME_WIRELESS_ETHERNET_STATUSES: 'wireless_ethernet_statuses', +SECTION_NAME_MAP = { + SEC_NAME_APPLIANCE_UPLINKS: 'appliance_uplinks', + SEC_NAME_APPLIANCE_UPLINKS_USAGE: 'appliance_uplinks_usage', + SEC_NAME_APPLIANCE_PORTS: 'appliance_ports', + SEC_NAME_APPLIANCE_VPNS: 'appliance_vpns', + SEC_NAME_APPLIANCE_PERFORMANCE: 'appliance_performance', + SEC_NAME_CELLULAR_UPLINKS: 'cellular_uplinks', + SEC_NAME_DEVICE_INFO: 'device_info', + SEC_NAME_DEVICE_STATUSES: 'device_status', + SEC_NAME_DEVICE_UPLINKS_INFO: 'device_uplinks_info', + SEC_NAME_LICENSES_OVERVIEW: 'licenses_overview', + SEC_NAME_NETWORKS: 'networks', + SEC_NAME_ORGANISATIONS: 'organisations', + SEC_NAME_ORG_API_REQUESTS: 'api_requests_by_organization', + SEC_NAME_SENSOR_READINGS: 'sensor_readings', + SEC_NAME_SWITCH_PORTS_STATUSES: 'switch_ports_statuses', + SEC_NAME_WIRELESS_DEVICE_STATUS: 'wireless_device_status', + SEC_NAME_WIRELESS_ETHERNET_STATUSES: 'wireless_ethernet_statuses', # Early Access - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES: 'org_switch_ports_statuses', + SEC_NAME_ORG_SWITCH_PORTS_STATUSES: 'org_switch_ports_statuses', } -# _MIN_CACHE_INTERVAL = 300 -# _RANDOM_CACHE_INTERVAL = 300 +# MIN_CACHE_INTERVAL = 300 +# RANDOM_CACHE_INTERVAL = 300 MerakiCacheFilePath = Path(tmp_dir) / 'agents' / 'agent_cisco_meraki' MerakiAPIData = Mapping[str, object] @@ -192,7 +196,7 @@ MerakiAPIData = Mapping[str, object] # '----------------------------------------------------------------------' -def _configure_meraki_dashboard( +def configure_meraki_dashboard( api_key: str, debug: bool, proxy: str | None, @@ -218,15 +222,16 @@ def _configure_meraki_dashboard( @dataclass(frozen=True) class MerakiConfig: - dashboard: meraki.DashboardAPI - hostname: str # section_names: Sequence[str] + api_key: str # needed for Early Access + dashboard: meraki.DashboardAPI excluded_sections: Sequence[str] - use_cache: bool + hostname: str + net_id_as_prefix: bool org_id_as_prefix: bool - api_key: str # needed for Early Access proxy: str # needed for Early Access timespan: int + use_cache: bool cache_per_section: CachePerSection | None = None @@ -374,7 +379,7 @@ class MerakiGetOrganizations(MerakiSection): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Get organisations: %r', e) + LOGGER.debug('Get organisations: %r', e) return [] @@ -387,7 +392,7 @@ class MerakiGetOrganization(MerakiSectionOrg): try: return self._config.dashboard.organizations.getOrganization(self._org_id) except meraki.exceptions.APIError as e: - _LOGGER.debug(f'Get organisation by id {self._org_id}: {e}') + LOGGER.debug(f'Get organisation by id {self._org_id}: {e}') return {} @@ -410,7 +415,7 @@ class MerakiGetOrganizationApiRequestsOverviewResponseCodesByInterval(MerakiSect ) except meraki.APIError as e: - _LOGGER.debug(f'Get API requests by id {self._org_id}: {e}') + LOGGER.debug(f'Get API requests by id {self._org_id}: {e}') return {} @@ -425,7 +430,7 @@ class MerakiGetOrganizationLicensesOverview(MerakiSectionOrg): self._org_id, ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get license overview: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get license overview: %r', self._org_id, e) return [] @@ -441,7 +446,7 @@ class MerakiGetOrganizationDevices(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get devices: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get devices: %r', self._org_id, e) return {} @@ -457,7 +462,7 @@ class MerakiGetOrganizationNetworks(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get networks: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get networks: %r', self._org_id, e) return [] @@ -473,7 +478,7 @@ class MerakiGetOrganizationDevicesStatuses(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get device statuses: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get device statuses: %r', self._org_id, e) return [] @@ -489,7 +494,7 @@ class MerakiGetOrganizationDevicesUplinksAddressesByDevice(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get device statuses: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get device statuses: %r', self._org_id, e) return [] @@ -505,7 +510,7 @@ class MerakiGetOrganizationApplianceUplinkStatuses(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get Appliance uplink status by network: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get Appliance uplink status by network: %r', self._org_id, e) return [] @@ -516,7 +521,7 @@ class MerakiGetOrganizationApplianceUplinksUsageByNetwork(MerakiSectionOrg): def get_live_data(self): if meraki.__version__ < '1.39.0': - _LOGGER.debug(f'Meraki SDK is to old. Installed: {meraki.__version__}, excepted: 1.39.0') + LOGGER.debug(f'Meraki SDK is to old. Installed: {meraki.__version__}, excepted: 1.39.0') return [] try: return self._config.dashboard.appliance.getOrganizationApplianceUplinksUsageByNetwork( @@ -525,7 +530,7 @@ class MerakiGetOrganizationApplianceUplinksUsageByNetwork(MerakiSectionOrg): timespan=60 # default=86400 (one day), maximum=1209600 (14 days), needs to match value in check ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get Appliance uplink usage by network: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get Appliance uplink usage by network: %r', self._org_id, e) return [] @@ -542,7 +547,7 @@ class MerakiGetOrganizationApplianceVpnStatuses(MerakiSectionOrg): ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get Appliance VPN status by network: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get Appliance VPN status by network: %r', self._org_id, e) return [] @@ -557,8 +562,8 @@ class MerakiGetDeviceAppliancePerformance(MerakiSectionSerial): return self._config.dashboard.appliance.getDeviceAppliancePerformance( self._serial) except meraki.exceptions.APIError as e: - _LOGGER.debug('Serial: %r: Get appliance device performance: %r', - self._serial, e) + LOGGER.debug('Serial: %r: Get appliance device performance: %r', + self._serial, e) return [] @@ -574,7 +579,7 @@ class MerakiGetOrganizationSensorReadingsLatest(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get sensor readings: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get sensor readings: %r', self._org_id, e) return [] @@ -591,7 +596,7 @@ class MerakiGetDeviceSwitchPortsStatuses(MerakiSectionSerial): timespan=max(self._config.timespan, 900), ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Serial: %r: Get Switch Port Statuses: %r', self._serial, e) + LOGGER.debug('Serial: %r: Get Switch Port Statuses: %r', self._serial, e) return [] @@ -602,7 +607,7 @@ class MerakiGetOrganizationWirelessDevicesEthernetStatuses(MerakiSectionOrg): def get_live_data(self): if meraki.__version__ < '1.39.0': - _LOGGER.debug(f'Meraki SDK is to old. Installed: {meraki.__version__}, expceted: 1.39.0') + LOGGER.debug(f'Meraki SDK is to old. Installed: {meraki.__version__}, expceted: 1.39.0') return [] try: return self._config.dashboard.wireless.getOrganizationWirelessDevicesEthernetStatuses( @@ -610,7 +615,7 @@ class MerakiGetOrganizationWirelessDevicesEthernetStatuses(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get wireless devices ethernet statuses: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get wireless devices ethernet statuses: %r', self._org_id, e) return [] @@ -623,7 +628,7 @@ class MerakiGetDeviceWirelessStatus(MerakiSectionSerial): try: return self._config.dashboard.wireless.getDeviceWirelessStatus(self._serial) except meraki.exceptions.APIError as e: - _LOGGER.debug('Serial: %r: Get wireless device status: %r', self._serial, e) + LOGGER.debug('Serial: %r: Get wireless device status: %r', self._serial, e) return [] @@ -639,7 +644,7 @@ class MerakiGetOrganizationCellularGatewayUplinkStatuses(MerakiSectionOrg): total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Organisation ID: %r: Get cellular gateways uplink statuses: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get cellular gateways uplink statuses: %r', self._org_id, e) return [] @@ -675,12 +680,12 @@ class MerakiGetOrganizationSwitchPortsStatusesBySwitch(MerakiSectionOrg): timeout=3, ) except RequestException as e: - _LOGGER.debug('Organisation ID: %r: Get Ports statuses by switch: %r', self._org_id, e) + LOGGER.debug('Organisation ID: %r: Get Ports statuses by switch: %r', self._org_id, e) return [] try: _response = response.json() except JSONDecodeError: - _LOGGER.debug('Organisation ID: %r: Get Ports statuses by switch: %r', self._org_id, response) + LOGGER.debug('Organisation ID: %r: Get Ports statuses by switch: %r', self._org_id, response) return [] if _response: return _response.get('items', []) @@ -699,7 +704,7 @@ class MerakiGetNetworkAppliancePorts(MerakiSectionNetwork): # total_pages='all', ) except meraki.exceptions.APIError as e: - _LOGGER.debug('Network ID: %r: Get appliance ports: %r', self._network_id, e) + LOGGER.debug('Network ID: %r: Get appliance ports: %r', self._network_id, e) return [] @@ -717,11 +722,11 @@ class MerakiOrganisation: @property def organisation_id(self) -> str: - return self.organisation[_API_NAME_ORGANISATION_ID] + return self.organisation[API_NAME_ORGANISATION_ID] @property def organisation_name(self) -> str: - return self.organisation[_API_NAME_ORGANISATION_NAME] + return self.organisation[API_NAME_ORGANISATION_NAME] def query(self) -> Iterator[Section]: if organisation := MerakiGetOrganization( @@ -730,28 +735,28 @@ class MerakiOrganisation: cache_interval=self.config.cache_per_section.organisations, ).get_data(use_cache=self.config.use_cache): yield self._make_section( - name=_SEC_NAME_ORGANISATIONS, + name=SEC_NAME_ORGANISATIONS, data=organisation, ) - if not organisation[_API_NAME_API][_API_NAME_ENABLED]: + if not organisation[API_NAME_API][API_NAME_ENABLED]: # stop here if API is not enabled for this organisation return - if _SEC_NAME_ORG_API_REQUESTS not in self.config.excluded_sections: + if SEC_NAME_ORG_API_REQUESTS not in self.config.excluded_sections: if api_requests := MerakiGetOrganizationApiRequestsOverviewResponseCodesByInterval( config=self.config, org_id=self.organisation_id, cache_interval=self.config.cache_per_section.org_api_requests, ).get_data(use_cache=False): # here we want always life data yield self._make_section( - name=_SEC_NAME_ORG_API_REQUESTS, + name=SEC_NAME_ORG_API_REQUESTS, data={'org_id': self.organisation_id, 'requests': api_requests} ) - if _SEC_NAME_LICENSES_OVERVIEW not in self.config.excluded_sections: + if SEC_NAME_LICENSES_OVERVIEW not in self.config.excluded_sections: if licenses_overview := self._get_licenses_overview(): yield self._make_section( - name=_SEC_NAME_LICENSES_OVERVIEW, + name=SEC_NAME_LICENSES_OVERVIEW, data=licenses_overview, ) @@ -773,28 +778,18 @@ class MerakiOrganisation: devices_by_type = {} for _device in devices_by_serial.values(): - if not devices_by_type.get(_device[_API_NAME_DEVICE_PRODUCT_TYPE]): - devices_by_type[_device[_API_NAME_DEVICE_PRODUCT_TYPE]] = [] - devices_by_type[_device[_API_NAME_DEVICE_PRODUCT_TYPE]].append(_device) + if not devices_by_type.get(_device[API_NAME_DEVICE_PRODUCT_TYPE]): + devices_by_type[_device[API_NAME_DEVICE_PRODUCT_TYPE]] = [] + devices_by_type[_device[API_NAME_DEVICE_PRODUCT_TYPE]].append(_device) for device in devices_by_serial.values(): - try: - device_piggyback = device[_API_NAME_DEVICE_NAME] - except KeyError as e: - _LOGGER.debug( - 'Organisation ID: %r: Get device piggyback: %r', self.organisation_id, e - ) - continue - - if not device_piggyback: # skip devices without name, could use serial instead - device_piggyback = f'{device[_API_NAME_DEVICE_SERIAL]}-{device[_API_NAME_DEVICE_PRODUCT_TYPE]}' yield self._make_section( - name=_SEC_NAME_DEVICE_INFO, + name=SEC_NAME_DEVICE_INFO, data=device, - piggyback=self._adjust_piggyback(device_piggyback), + piggyback=self._get_device_piggyback(device, devices_by_serial) ) - if _SEC_NAME_DEVICE_STATUSES not in self.config.excluded_sections: + if SEC_NAME_DEVICE_STATUSES not in self.config.excluded_sections: for device_status in MerakiGetOrganizationDevicesStatuses( config=self.config, org_id=self.organisation_id, @@ -802,11 +797,11 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(device_status, devices_by_serial): yield self._make_section( - name=_SEC_NAME_DEVICE_STATUSES, + name=SEC_NAME_DEVICE_STATUSES, data=device_status, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if _SEC_NAME_DEVICE_UPLINKS_INFO not in self.config.excluded_sections: + if SEC_NAME_DEVICE_UPLINKS_INFO not in self.config.excluded_sections: for device_uplink in MerakiGetOrganizationDevicesUplinksAddressesByDevice( config=self.config, org_id=self.organisation_id, @@ -814,13 +809,13 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(device_uplink, devices_by_serial): yield self._make_section( - name=_SEC_NAME_DEVICE_UPLINKS_INFO, + name=SEC_NAME_DEVICE_UPLINKS_INFO, data=device_uplink, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if devices_by_type.get(_API_NAME_DEVICE_TYPE_SENSOR): - if _SEC_NAME_SENSOR_READINGS not in self.config.excluded_sections: + if devices_by_type.get(API_NAME_DEVICE_TYPE_SENSOR): + if SEC_NAME_SENSOR_READINGS not in self.config.excluded_sections: for sensor_reading in MerakiGetOrganizationSensorReadingsLatest( config=self.config, org_id=self.organisation_id, @@ -828,14 +823,14 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(sensor_reading, devices_by_serial): yield self._make_section( - name=_SEC_NAME_SENSOR_READINGS, + name=SEC_NAME_SENSOR_READINGS, data=sensor_reading, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if devices_by_type.get(_API_NAME_DEVICE_TYPE_APPLIANCE): + if devices_by_type.get(API_NAME_DEVICE_TYPE_APPLIANCE): usage_by_serial = {} - if _SEC_NAME_APPLIANCE_UPLINKS_USAGE not in self.config.excluded_sections: + if SEC_NAME_APPLIANCE_UPLINKS_USAGE not in self.config.excluded_sections: uplink_usage_by_network = MerakiGetOrganizationApplianceUplinksUsageByNetwork( config=self.config, org_id=self.organisation_id, @@ -846,13 +841,13 @@ class MerakiOrganisation: # usage_by_serial = {} for network in uplink_usage_by_network: for uplink in network['byUplink']: - usage_by_serial[uplink[_API_NAME_DEVICE_SERIAL]] = usage_by_serial.get( - uplink[_API_NAME_DEVICE_SERIAL], {} + usage_by_serial[uplink[API_NAME_DEVICE_SERIAL]] = usage_by_serial.get( + uplink[API_NAME_DEVICE_SERIAL], {} ) - usage_by_serial[uplink[_API_NAME_DEVICE_SERIAL]].update( + usage_by_serial[uplink[API_NAME_DEVICE_SERIAL]].update( { uplink['interface']: {'sent': uplink['sent'], 'received': uplink['received']}, - _API_NAME_DEVICE_SERIAL: uplink[_API_NAME_DEVICE_SERIAL] + API_NAME_DEVICE_SERIAL: uplink[API_NAME_DEVICE_SERIAL] } ) @@ -861,10 +856,10 @@ class MerakiOrganisation: # yield self._make_section( # name=_SEC_NAME_APPLIANCE_UPLINKS_USAGE, # data=usage_by_serial[appliance], - # piggyback=self._adjust_piggyback(host=piggyback), + # piggyback=piggyback, # ) - if _SEC_NAME_APPLIANCE_UPLINKS not in self.config.excluded_sections: + if SEC_NAME_APPLIANCE_UPLINKS not in self.config.excluded_sections: for appliance_uplinks in MerakiGetOrganizationApplianceUplinkStatuses( config=self.config, org_id=self.organisation_id, @@ -872,49 +867,47 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(appliance_uplinks, devices_by_serial): # add network name - if self._networks.get(appliance_uplinks[_API_NAME_NETWORK_ID]): + if self._networks.get(appliance_uplinks[API_NAME_NETWORK_ID]): appliance_uplinks['networkName'] = self._networks[ - appliance_uplinks[_API_NAME_NETWORK_ID]].name + appliance_uplinks[API_NAME_NETWORK_ID]].name # add uplink usage - if appliance_usage := usage_by_serial.get(appliance_uplinks[_API_NAME_DEVICE_SERIAL], None): + if appliance_usage := usage_by_serial.get(appliance_uplinks[API_NAME_DEVICE_SERIAL], None): for uplink in appliance_uplinks['uplinks']: uplink.update(appliance_usage.get(uplink['interface'], {})) yield self._make_section( - name=_SEC_NAME_APPLIANCE_UPLINKS, + name=SEC_NAME_APPLIANCE_UPLINKS, data=appliance_uplinks, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if _SEC_NAME_APPLIANCE_VPNS not in self.config.excluded_sections: + if SEC_NAME_APPLIANCE_VPNS not in self.config.excluded_sections: for appliance_vpn in MerakiGetOrganizationApplianceVpnStatuses( config=self.config, org_id=self.organisation_id, cache_interval=self.config.cache_per_section.appliance_vpns, ).get_data(use_cache=self.config.use_cache): - appliance_vpn.update({_API_NAME_DEVICE_SERIAL: appliance_vpn['deviceSerial']}) + appliance_vpn.update({API_NAME_DEVICE_SERIAL: appliance_vpn['deviceSerial']}) if piggyback := self._get_device_piggyback(appliance_vpn, devices_by_serial): yield self._make_section( - name=_SEC_NAME_APPLIANCE_VPNS, + name=SEC_NAME_APPLIANCE_VPNS, data=appliance_vpn, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if _SEC_NAME_APPLIANCE_PERFORMANCE not in self.config.excluded_sections: - for device in devices_by_type[_API_NAME_DEVICE_TYPE_APPLIANCE]: + if SEC_NAME_APPLIANCE_PERFORMANCE not in self.config.excluded_sections: + for device in devices_by_type[API_NAME_DEVICE_TYPE_APPLIANCE]: appliance_performance = MerakiGetDeviceAppliancePerformance( config=self.config, - serial=device[_API_NAME_DEVICE_SERIAL], + serial=device[API_NAME_DEVICE_SERIAL], cache_interval=self.config.cache_per_section.appliance_performance, ).get_data(use_cache=self.config.use_cache) - if piggyback := self._get_device_piggyback({ - _API_NAME_DEVICE_SERIAL: device[_API_NAME_DEVICE_SERIAL] - }, devices_by_serial): + if piggyback := self._get_device_piggyback(device, devices_by_serial): yield self._make_section( - name=_SEC_NAME_APPLIANCE_PERFORMANCE, + name=SEC_NAME_APPLIANCE_PERFORMANCE, data=appliance_performance, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) # if _SEC_NAME_APPLIANCE_PORTS not in self.config.excluded_sections and networks: @@ -971,25 +964,23 @@ class MerakiOrganisation: # } # ] - if devices_by_type.get(_API_NAME_DEVICE_TYPE_SWITCH): - if _SEC_NAME_SWITCH_PORTS_STATUSES not in self.config.excluded_sections: - for switch in devices_by_type[_API_NAME_DEVICE_TYPE_SWITCH]: + if devices_by_type.get(API_NAME_DEVICE_TYPE_SWITCH): + if SEC_NAME_SWITCH_PORTS_STATUSES not in self.config.excluded_sections: + for switch in devices_by_type[API_NAME_DEVICE_TYPE_SWITCH]: ports_statuses = MerakiGetDeviceSwitchPortsStatuses( config=self.config, - serial=switch[_API_NAME_DEVICE_SERIAL], + serial=switch[API_NAME_DEVICE_SERIAL], cache_interval=self.config.cache_per_section.switch_ports_statuses, ).get_data(use_cache=self.config.use_cache) - if piggyback := self._get_device_piggyback( - {_API_NAME_DEVICE_SERIAL: switch[_API_NAME_DEVICE_SERIAL]}, devices_by_serial - ): + if piggyback := self._get_device_piggyback(switch, devices_by_serial): yield self._make_section( - name=_SEC_NAME_SWITCH_PORTS_STATUSES, + name=SEC_NAME_SWITCH_PORTS_STATUSES, data=ports_statuses, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if devices_by_type.get(_API_NAME_DEVICE_TYPE_WIRELESS): - if _SEC_NAME_WIRELESS_ETHERNET_STATUSES not in self.config.excluded_sections: + if devices_by_type.get(API_NAME_DEVICE_TYPE_WIRELESS): + if SEC_NAME_WIRELESS_ETHERNET_STATUSES not in self.config.excluded_sections: for device in MerakiGetOrganizationWirelessDevicesEthernetStatuses( config=self.config, org_id=self.organisation_id, @@ -997,29 +988,27 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(device, devices_by_serial): yield self._make_section( - name=_SEC_NAME_WIRELESS_ETHERNET_STATUSES, + name=SEC_NAME_WIRELESS_ETHERNET_STATUSES, data=device, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if _SEC_NAME_WIRELESS_DEVICE_STATUS not in self.config.excluded_sections: - for device in devices_by_type[_API_NAME_DEVICE_TYPE_WIRELESS]: + if SEC_NAME_WIRELESS_DEVICE_STATUS not in self.config.excluded_sections: + for device in devices_by_type[API_NAME_DEVICE_TYPE_WIRELESS]: wireless_statuses = MerakiGetDeviceWirelessStatus( config=self.config, - serial=device[_API_NAME_DEVICE_SERIAL], + serial=device[API_NAME_DEVICE_SERIAL], cache_interval=self.config.cache_per_section.wireless_device_status, ).get_data(use_cache=self.config.use_cache) - if piggyback := self._get_device_piggyback({ - _API_NAME_DEVICE_SERIAL: device[_API_NAME_DEVICE_SERIAL] - }, devices_by_serial): + if piggyback := self._get_device_piggyback(device, devices_by_serial): yield self._make_section( - name=_SEC_NAME_WIRELESS_DEVICE_STATUS, + name=SEC_NAME_WIRELESS_DEVICE_STATUS, data=wireless_statuses, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) - if devices_by_type.get(_API_NAME_DEVICE_TYPE_CELLULAR): - if _SEC_NAME_CELLULAR_UPLINKS not in self.config.excluded_sections: + if devices_by_type.get(API_NAME_DEVICE_TYPE_CELLULAR): + if SEC_NAME_CELLULAR_UPLINKS not in self.config.excluded_sections: for gateway in MerakiGetOrganizationCellularGatewayUplinkStatuses( config=self.config, org_id=self.organisation_id, @@ -1027,13 +1016,13 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(gateway, devices_by_serial): yield self._make_section( - name=_SEC_NAME_CELLULAR_UPLINKS, + name=SEC_NAME_CELLULAR_UPLINKS, data=gateway, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) # Early Access - if _SEC_NAME_ORG_SWITCH_PORTS_STATUSES not in self.config.excluded_sections: + if SEC_NAME_ORG_SWITCH_PORTS_STATUSES not in self.config.excluded_sections: for switch in MerakiGetOrganizationSwitchPortsStatusesBySwitch( config=self.config, org_id=self.organisation_id, @@ -1041,13 +1030,13 @@ class MerakiOrganisation: ).get_data(use_cache=self.config.use_cache): if piggyback := self._get_device_piggyback(switch, devices_by_serial): yield self._make_section( - name=_SEC_NAME_SWITCH_PORTS_STATUSES, + name=SEC_NAME_SWITCH_PORTS_STATUSES, data=switch, - piggyback=self._adjust_piggyback(host=piggyback), + piggyback=piggyback, ) def _add_networks(self, networks): - self._networks = {_API_NAME_ORGANISATION_NAME: self.organisation_name} + self._networks = {API_NAME_ORGANISATION_NAME: self.organisation_name} for network in networks: network.update({'organizationName': self.organisation_name}) self._networks[network['id']] = MerakiNetwork( @@ -1064,15 +1053,10 @@ class MerakiOrganisation: url=network['url'], ) yield self._make_section( - name=_SEC_NAME_NETWORKS, + name=SEC_NAME_NETWORKS, data=networks, ) - def _adjust_piggyback(self, host: str) -> str: - if self.config.org_id_as_prefix: - return f'{self.organisation_id}-{host}' - return host - def _get_licenses_overview(self) -> MerakiAPIData | None: def _update_licenses_overview( licenses_overview: dict[str, object] | None @@ -1101,13 +1085,13 @@ class MerakiOrganisation: { 'organisation_id': self.organisation_id, 'organisation_name': self.organisation_name, - 'network_name': self._networks.get(device.get(_API_NAME_NETWORK_ID)).name, + 'network_name': self._networks.get(device.get(API_NAME_NETWORK_ID)).name, } ) return device return { - str(device[_API_NAME_DEVICE_SERIAL]): _update_device(device) + str(device[API_NAME_DEVICE_SERIAL]): _update_device(device) for device in MerakiGetOrganizationDevices( config=self.config, org_id=self.organisation_id, @@ -1118,28 +1102,42 @@ class MerakiOrganisation: def _get_device_piggyback( self, device: MerakiAPIData, devices_by_serial: Mapping[str, MerakiAPIData] ) -> str | None: + LOGGER.critical(device) + prefix = '' + if self.config.org_id_as_prefix: + prefix=self.organisation_id +'-' + if self.config.net_id_as_prefix: + try: + prefix += device['networkId'] + '-' + except KeyError: + try: + prefix += device['network']['id'] + '-' + except KeyError: + # print(device) + pass + try: - serial = device[_API_NAME_DEVICE_SERIAL] - if devices_by_serial[serial][_API_NAME_DEVICE_NAME]: - return devices_by_serial[serial][_API_NAME_DEVICE_NAME] + serial = device[API_NAME_DEVICE_SERIAL] + if devices_by_serial[serial][API_NAME_DEVICE_NAME]: + return f'{prefix}{devices_by_serial[serial][API_NAME_DEVICE_NAME]}' else: - _LOGGER.debug(f'Host without name _get_device_piggyback serial: {serial}') - return None + LOGGER.debug(f'Host without name _get_device_piggyback, use serial: {serial}') + return f'{prefix}{serial}-{device[API_NAME_DEVICE_PRODUCT_TYPE]}' except KeyError as e: - _LOGGER.debug('Organisation ID: %r: Get device piggyback: %r', self.organisation_id, e) + LOGGER.debug('Organisation ID: %r: Get device piggyback: %r', self.organisation_id, e) return None @staticmethod def _make_section(*, name: str, data: MerakiAPIData, piggyback: str | None = None) -> Section: return Section( api_data_source=MerakiAPIDataSource.org, - name=_SECTION_NAME_MAP[name], + name=SECTION_NAME_MAP[name], data=data, piggyback=piggyback, ) -def _query_meraki_objects( +def query_meraki_objects( *, organisations: Sequence[MerakiOrganisation], ) -> Iterable[Section]: @@ -1147,7 +1145,7 @@ def _query_meraki_objects( yield from organisation.query() -def _write_sections(sections: Iterable[Section]) -> None: +def write_sections(sections: Iterable[Section]) -> None: sections_by_piggyback: dict = {} for section in sections: sections_by_piggyback.setdefault(section.piggyback, {}).setdefault( @@ -1201,6 +1199,7 @@ class Args(Namespace): hostname: str no_cache: bool org_id_as_prefix: bool + net_id_as_prefix: bool orgs: Sequence[str] proxy: str sections: Sequence[str] @@ -1229,7 +1228,7 @@ def parse_arguments(argv: Sequence[str] | None) -> Args: parser.add_argument( '--excluded-sections', nargs='*', - choices=list(_SECTION_NAME_MAP), + choices=list(SECTION_NAME_MAP), default=[], help='Sections that are excluded form data collected.', ) @@ -1265,31 +1264,37 @@ def parse_arguments(argv: Sequence[str] | None) -> Args: const=True, help='Use organisation ID as hostname prefix.' ) - + parser.add_argument( + '--net-id-as-prefix', + default=False, + action='store_const', + const=True, + help='Use network ID as hostname prefix.' + ) parser.add_argument( '--cache-per-section', nargs='+', type=int, help='List of cache time per section in minutes', default=[ - _SEC_CACHE_APPLIANCE_PERFORMANCE, - _SEC_CACHE_APPLIANCE_PORTS, - _SEC_CACHE_APPLIANCE_UPLINKS, - _SEC_CACHE_APPLIANCE_UPLINKS_USAGE, - _SEC_CACHE_APPLIANCE_VPNS, - _SEC_CACHE_CELLULAR_UPLINKS, - _SEC_CACHE_DEVICE_INFO, - _SEC_CACHE_DEVICE_STATUSES, - _SEC_CACHE_DEVICE_UPLINKS_INFO, - _SEC_CACHE_LICENSES_OVERVIEW, - _SEC_CACHE_NETWORKS, - _SEC_CACHE_ORGANISATIONS, - _SEC_CACHE_ORG_API_REQUESTS, - _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, - _SEC_CACHE_SENSOR_READINGS, - _SEC_CACHE_SWITCH_PORTS_STATUSES, - _SEC_CACHE_WIRELESS_DEVICE_STATUS, - _SEC_CACHE_WIRELESS_ETHERNET_STATUSES, + SEC_CACHE_APPLIANCE_PERFORMANCE, + SEC_CACHE_APPLIANCE_PORTS, + SEC_CACHE_APPLIANCE_UPLINKS, + SEC_CACHE_APPLIANCE_UPLINKS_USAGE, + SEC_CACHE_APPLIANCE_VPNS, + SEC_CACHE_CELLULAR_UPLINKS, + SEC_CACHE_DEVICE_INFO, + SEC_CACHE_DEVICE_STATUSES, + SEC_CACHE_DEVICE_UPLINKS_INFO, + SEC_CACHE_LICENSES_OVERVIEW, + SEC_CACHE_NETWORKS, + SEC_CACHE_ORGANISATIONS, + SEC_CACHE_ORG_API_REQUESTS, + SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, + SEC_CACHE_SENSOR_READINGS, + SEC_CACHE_SWITCH_PORTS_STATUSES, + SEC_CACHE_WIRELESS_DEVICE_STATUS, + SEC_CACHE_WIRELESS_ETHERNET_STATUSES, ] ) @@ -1300,16 +1305,16 @@ def _need_devices(section_names: Sequence[str]) -> bool: return any( s not in section_names for s in [ - _SEC_NAME_APPLIANCE_UPLINKS, - _SEC_NAME_APPLIANCE_UPLINKS_USAGE, - _SEC_NAME_APPLIANCE_VPNS, - _SEC_NAME_CELLULAR_UPLINKS, - _SEC_NAME_DEVICE_STATUSES, - _SEC_NAME_DEVICE_UPLINKS_INFO, - _SEC_NAME_SENSOR_READINGS, - _SEC_NAME_SWITCH_PORTS_STATUSES, - _SEC_NAME_WIRELESS_DEVICE_STATUS, - _SEC_NAME_WIRELESS_ETHERNET_STATUSES, + SEC_NAME_APPLIANCE_UPLINKS, + SEC_NAME_APPLIANCE_UPLINKS_USAGE, + SEC_NAME_APPLIANCE_VPNS, + SEC_NAME_CELLULAR_UPLINKS, + SEC_NAME_DEVICE_STATUSES, + SEC_NAME_DEVICE_UPLINKS_INFO, + SEC_NAME_SENSOR_READINGS, + SEC_NAME_SWITCH_PORTS_STATUSES, + SEC_NAME_WIRELESS_DEVICE_STATUS, + SEC_NAME_WIRELESS_ETHERNET_STATUSES, ] ) @@ -1317,8 +1322,8 @@ def _need_devices(section_names: Sequence[str]) -> bool: def _get_organisations(config: MerakiConfig, org_ids: Sequence[str]) -> Sequence[_Organisation]: organisations = [ _Organisation( - id=organisation[_API_NAME_ORGANISATION_ID], - name=organisation[_API_NAME_ORGANISATION_NAME], + id=organisation[API_NAME_ORGANISATION_ID], + name=organisation[API_NAME_ORGANISATION_NAME], ) for organisation in MerakiGetOrganizations( config=config ).get_data(use_cache=config.use_cache) @@ -1326,7 +1331,7 @@ def _get_organisations(config: MerakiConfig, org_ids: Sequence[str]) -> Sequence if org_ids: organisations = [ - organisation for organisation in organisations if organisation[_API_NAME_ORGANISATION_ID] in org_ids + organisation for organisation in organisations if organisation[API_NAME_ORGANISATION_ID] in org_ids ] return organisations @@ -1353,16 +1358,16 @@ def get_proxy(raw_proxy: str) -> str | None: def agent_cisco_meraki_main(args: Args) -> int: - if meraki.__version__ < _MERAKI_SDK_MIN_VERSION: + if meraki.__version__ < MERAKI_SDK_MIN_VERSION: print( - f'This Agent needs at least Meraki SDK version {_MERAKI_SDK_MIN_VERSION}, installed is {meraki.__version__}' + f'This Agent needs at least Meraki SDK version {MERAKI_SDK_MIN_VERSION}, installed is {meraki.__version__}' ) exit(1) # don't remove used for runtime logging at the end start_time = time_ns() config = MerakiConfig( - dashboard=_configure_meraki_dashboard( + dashboard=configure_meraki_dashboard( args.apikey, args.debug, get_proxy(args.proxy) @@ -1372,6 +1377,7 @@ def agent_cisco_meraki_main(args: Args) -> int: excluded_sections=args.excluded_sections, use_cache=not args.no_cache, org_id_as_prefix=args.org_id_as_prefix, + net_id_as_prefix=args.net_id_as_prefix, api_key=args.apikey, proxy=get_proxy(args.proxy), timespan=60, @@ -1382,14 +1388,14 @@ def agent_cisco_meraki_main(args: Args) -> int: for organisation in _get_organisations(config, args.orgs) ] - sections = _query_meraki_objects( + sections = query_meraki_objects( organisations=organisations, ) - _write_sections(sections) + write_sections(sections) - _LOGGER.warning(f'Time taken: {(time_ns() - start_time) / 1e9}/s') - _LOGGER.warning(f'Meraki SDK version: {meraki.__version__}') + LOGGER.warning(f'Time taken: {(time_ns() - start_time) / 1e9}/s') + LOGGER.warning(f'Meraki SDK version: {meraki.__version__}') return 0 diff --git a/source/cmk_addons_plugins/meraki/lib/utils.py b/source/cmk_addons_plugins/meraki/lib/utils.py index 2301c95..941ef4c 100644 --- a/source/cmk_addons_plugins/meraki/lib/utils.py +++ b/source/cmk_addons_plugins/meraki/lib/utils.py @@ -23,48 +23,47 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import CheckResul MerakiAPIData = Mapping[str, object] # parameter names for agent options -_SEC_NAME_ORGANISATIONS: Final = '_organisations' # internal use runs always -_SEC_NAME_DEVICE_INFO: Final = '_device_info' # Not configurable, needed for piggyback -_SEC_NAME_NETWORKS: Final = '_networks' # internal use, runs always, needed for network names -_SEC_NAME_ORG_API_REQUESTS: Final = 'api-requests-by-organization' # internal use, runs always - -_SEC_NAME_APPLIANCE_UPLINKS: Final = 'appliance-uplinks' -_SEC_NAME_APPLIANCE_PORTS: Final = 'appliance-ports' -_SEC_NAME_APPLIANCE_UPLINKS_USAGE: Final = 'appliance-uplinks-usage' -_SEC_NAME_APPLIANCE_VPNS: Final = 'appliance-vpns' -_SEC_NAME_APPLIANCE_PERFORMANCE: Final = 'appliance-performance' -_SEC_NAME_CELLULAR_UPLINKS: Final = 'cellular-uplinks' -_SEC_NAME_DEVICE_STATUSES: Final = 'device-status' -_SEC_NAME_DEVICE_UPLINKS_INFO: Final = 'device-uplinks-info' -_SEC_NAME_LICENSES_OVERVIEW: Final = 'licenses-overview' -_SEC_NAME_SENSOR_READINGS: Final = 'sensor-readings' -_SEC_NAME_SWITCH_PORTS_STATUSES: Final = 'switch-ports-statuses' -_SEC_NAME_WIRELESS_DEVICE_STATUS: Final = 'wireless-device-status' -_SEC_NAME_WIRELESS_ETHERNET_STATUSES: Final = 'wireless-ethernet-statuses' - +SEC_NAME_ORGANISATIONS: Final = '_organisations' # internal use runs always +SEC_NAME_DEVICE_INFO: Final = '_device_info' # Not configurable, needed for piggyback +SEC_NAME_NETWORKS: Final = '_networks' # internal use, runs always, needed for network names +SEC_NAME_ORG_API_REQUESTS: Final = 'api-requests-by-organization' # internal use, runs always + +SEC_NAME_APPLIANCE_UPLINKS: Final = 'appliance-uplinks' +SEC_NAME_APPLIANCE_PORTS: Final = 'appliance-ports' +SEC_NAME_APPLIANCE_UPLINKS_USAGE: Final = 'appliance-uplinks-usage' +SEC_NAME_APPLIANCE_VPNS: Final = 'appliance-vpns' +SEC_NAME_APPLIANCE_PERFORMANCE: Final = 'appliance-performance' +SEC_NAME_CELLULAR_UPLINKS: Final = 'cellular-uplinks' +SEC_NAME_DEVICE_STATUSES: Final = 'device-status' +SEC_NAME_DEVICE_UPLINKS_INFO: Final = 'device-uplinks-info' +SEC_NAME_LICENSES_OVERVIEW: Final = 'licenses-overview' +SEC_NAME_SENSOR_READINGS: Final = 'sensor-readings' +SEC_NAME_SWITCH_PORTS_STATUSES: Final = 'switch-ports-statuses' +SEC_NAME_WIRELESS_DEVICE_STATUS: Final = 'wireless-device-status' +SEC_NAME_WIRELESS_ETHERNET_STATUSES: Final = 'wireless-ethernet-statuses' # api cache defaults per section -_SEC_CACHE_APPLIANCE_PERFORMANCE = 0 -_SEC_CACHE_APPLIANCE_UPLINKS_USAGE = 0 -_SEC_CACHE_APPLIANCE_UPLINKS = 60 -_SEC_CACHE_APPLIANCE_VPNS = 60 -_SEC_CACHE_CELLULAR_UPLINKS = 60 -_SEC_CACHE_DEVICE_INFO = 60 -_SEC_CACHE_DEVICE_STATUSES = 60 -_SEC_CACHE_DEVICE_UPLINKS_INFO = 60 -_SEC_CACHE_LICENSES_OVERVIEW = 600 -_SEC_CACHE_NETWORKS = 600 -_SEC_CACHE_ORG_API_REQUESTS = 0 -_SEC_CACHE_ORG_SWITCH_PORTS_STATUSES = 0 -_SEC_CACHE_ORGANISATIONS = 600 -_SEC_CACHE_SENSOR_READINGS = 0 -_SEC_CACHE_SWITCH_PORTS_STATUSES = 0 -_SEC_CACHE_WIRELESS_DEVICE_STATUS = 30 -_SEC_CACHE_WIRELESS_ETHERNET_STATUSES = 30 -_SEC_CACHE_APPLIANCE_PORTS = 30 +SEC_CACHE_APPLIANCE_PERFORMANCE = 0 +SEC_CACHE_APPLIANCE_UPLINKS_USAGE = 0 +SEC_CACHE_APPLIANCE_UPLINKS = 60 +SEC_CACHE_APPLIANCE_VPNS = 60 +SEC_CACHE_CELLULAR_UPLINKS = 60 +SEC_CACHE_DEVICE_INFO = 60 +SEC_CACHE_DEVICE_STATUSES = 60 +SEC_CACHE_DEVICE_UPLINKS_INFO = 60 +SEC_CACHE_LICENSES_OVERVIEW = 600 +SEC_CACHE_NETWORKS = 600 +SEC_CACHE_ORG_API_REQUESTS = 0 +SEC_CACHE_ORG_SWITCH_PORTS_STATUSES = 0 +SEC_CACHE_ORGANISATIONS = 600 +SEC_CACHE_SENSOR_READINGS = 0 +SEC_CACHE_SWITCH_PORTS_STATUSES = 0 +SEC_CACHE_WIRELESS_DEVICE_STATUS = 30 +SEC_CACHE_WIRELESS_ETHERNET_STATUSES = 30 +SEC_CACHE_APPLIANCE_PORTS = 30 # Early Access -_SEC_NAME_ORG_SWITCH_PORTS_STATUSES: Final = 'org-switch-ports-statuses' +SEC_NAME_ORG_SWITCH_PORTS_STATUSES: Final = 'org-switch-ports-statuses' @dataclass(frozen=True) diff --git a/source/cmk_plugins/cisco/rulesets/meraki.py b/source/cmk_plugins/cisco/rulesets/meraki.py index 244c40b..aaefefa 100644 --- a/source/cmk_plugins/cisco/rulesets/meraki.py +++ b/source/cmk_plugins/cisco/rulesets/meraki.py @@ -31,60 +31,60 @@ from cmk.rulesets.v1.form_specs import ( from cmk.rulesets.v1.form_specs.validators import ValidationError, NumberInRange from cmk.rulesets.v1.rule_specs import SpecialAgent, Topic from cmk_addons.plugins.meraki.lib.utils import ( - _SEC_CACHE_APPLIANCE_PERFORMANCE, - _SEC_CACHE_APPLIANCE_UPLINKS_USAGE, - _SEC_CACHE_APPLIANCE_UPLINKS, - _SEC_CACHE_APPLIANCE_VPNS, - _SEC_CACHE_CELLULAR_UPLINKS, - _SEC_CACHE_DEVICE_INFO, - _SEC_CACHE_DEVICE_STATUSES, - _SEC_CACHE_DEVICE_UPLINKS_INFO, - _SEC_CACHE_LICENSES_OVERVIEW, - _SEC_CACHE_NETWORKS, - _SEC_CACHE_ORG_API_REQUESTS, - _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, - _SEC_CACHE_ORGANISATIONS, - _SEC_CACHE_SENSOR_READINGS, - _SEC_CACHE_SWITCH_PORTS_STATUSES, - _SEC_CACHE_WIRELESS_DEVICE_STATUS, - _SEC_CACHE_WIRELESS_ETHERNET_STATUSES, + SEC_CACHE_APPLIANCE_PERFORMANCE, + SEC_CACHE_APPLIANCE_UPLINKS_USAGE, + SEC_CACHE_APPLIANCE_UPLINKS, + SEC_CACHE_APPLIANCE_VPNS, + SEC_CACHE_CELLULAR_UPLINKS, + SEC_CACHE_DEVICE_INFO, + SEC_CACHE_DEVICE_STATUSES, + SEC_CACHE_DEVICE_UPLINKS_INFO, + SEC_CACHE_LICENSES_OVERVIEW, + SEC_CACHE_NETWORKS, + SEC_CACHE_ORG_API_REQUESTS, + SEC_CACHE_ORG_SWITCH_PORTS_STATUSES, + SEC_CACHE_ORGANISATIONS, + SEC_CACHE_SENSOR_READINGS, + SEC_CACHE_SWITCH_PORTS_STATUSES, + SEC_CACHE_WIRELESS_DEVICE_STATUS, + SEC_CACHE_WIRELESS_ETHERNET_STATUSES, ) -_SEC_NAME_APPLIANCE_UPLINKS = "appliance_uplinks" -_SEC_NAME_APPLIANCE_UPLINKS_USAGE = "appliance_uplinks_usage" -_SEC_NAME_APPLIANCE_VPNS = "appliance_vpns" -_SEC_NAME_APPLIANCE_PERFORMANCE = "appliance_performance" -_SEC_NAME_CELLULAR_UPLINKS = "cellular_uplinks" -_SEC_NAME_DEVICE_INFO = "device_info" -_SEC_NAME_DEVICE_STATUSES = "device_status" -_SEC_NAME_DEVICE_UPLINKS_INFO = "device_uplinks_info" -_SEC_NAME_LICENSES_OVERVIEW = "licenses_overview" -_SEC_NAME_NETWORKS = "networks" -_SEC_NAME_ORGANISATIONS = "organisations" -_SEC_NAME_ORG_API_REQUESTS = "api_requests_by_organization" -_SEC_NAME_SENSOR_READINGS = "sensor_readings" -_SEC_NAME_SWITCH_PORTS_STATUSES = "switch_ports_statuses" -_SEC_NAME_WIRELESS_DEVICE_STATUS = "wireless_device_status" -_SEC_NAME_WIRELESS_ETHERNET_STATUSES = "wireless_ethernet_statuses" -_SEC_NAME_ORG_SWITCH_PORTS_STATUSES = "org_switch_ports_statuses" +SEC_NAME_APPLIANCE_UPLINKS = 'appliance_uplinks' +SEC_NAME_APPLIANCE_UPLINKS_USAGE = 'appliance_uplinks_usage' +SEC_NAME_APPLIANCE_VPNS = 'appliance_vpns' +SEC_NAME_APPLIANCE_PERFORMANCE = 'appliance_performance' +SEC_NAME_CELLULAR_UPLINKS = 'cellular_uplinks' +SEC_NAME_DEVICE_INFO = 'device_info' +SEC_NAME_DEVICE_STATUSES = 'device_status' +SEC_NAME_DEVICE_UPLINKS_INFO = 'device_uplinks_info' +SEC_NAME_LICENSES_OVERVIEW = 'licenses_overview' +SEC_NAME_NETWORKS = 'networks' +SEC_NAME_ORGANISATIONS = 'organisations' +SEC_NAME_ORG_API_REQUESTS = 'api_requests_by_organization' +SEC_NAME_SENSOR_READINGS = 'sensor_readings' +SEC_NAME_SWITCH_PORTS_STATUSES = 'switch_ports_statuses' +SEC_NAME_WIRELESS_DEVICE_STATUS = 'wireless_device_status' +SEC_NAME_WIRELESS_ETHERNET_STATUSES = 'wireless_ethernet_statuses' +SEC_NAME_ORG_SWITCH_PORTS_STATUSES = 'org_switch_ports_statuses' -_SEC_TITLE_DEVICE_INFO = 'Device info (Organization)' -_SEC_TITLE_NETWORKS = 'Network info (Organization)' -_SEC_TITLE_ORGANISATIONS = 'Organization (Agent)' -_SEC_TITLE_ORG_API_REQUESTS = 'API request (Organizaion)' -_SEC_TITLE_APPLIANCE_UPLINKS = 'Appliances uplinks (Organizaion)' -_SEC_TITLE_APPLIANCE_UPLINKS_USAGE = 'Appliances uplinks usage (Organizaion)' -_SEC_TITLE_APPLIANCE_VPNS = 'Appliances VPNs (Organizaion)' -_SEC_TITLE_APPLIANCE_PERFORMANCE = 'Appliances Utilization (Device)' -_SEC_TITLE_CELLULAR_UPLINKS = 'Cellular devices uplinks (Organizaion)' -_SEC_TITLE_DEVICE_STATUSES = 'Devices status (Organizaion)' -_SEC_TITLE_DEVICE_UPLINKS_INFO = 'Devices uplink info (Organizaion)' -_SEC_TITLE_LICENSES_OVERVIEW = 'Licenses overview (Organizaion)' -_SEC_TITLE_SENSOR_READINGS = 'Sensors readings (Organizaion)' -_SEC_TITLE_SWITCH_PORTS_STATUSES = 'Switch ports status (Device)' -_SEC_TITLE_WIRELESS_ETHERNET_STATUSES = 'Wireless devices ethernet status (Organizaion)' -_SEC_TITLE_WIRELESS_DEVICE_STATUS = 'Wireless devices SSIDs status (Device)' -_SEC_TITLE_ORG_SWITCH_PORTS_STATUSES = 'Switch port status (Organizaion/Early Access)' +SEC_TITLE_DEVICE_INFO = 'Device info (Organization)' +SEC_TITLE_NETWORKS = 'Network info (Organization)' +SEC_TITLE_ORGANISATIONS = 'Organization (Agent)' +SEC_TITLE_ORG_API_REQUESTS = 'API request (Organization)' +SEC_TITLE_APPLIANCE_UPLINKS = 'Appliances uplinks (Organization)' +SEC_TITLE_APPLIANCE_UPLINKS_USAGE = 'Appliances uplinks usage (Organization)' +SEC_TITLE_APPLIANCE_VPNS = 'Appliances VPNs (Organization)' +SEC_TITLE_APPLIANCE_PERFORMANCE = 'Appliances Utilization (Device)' +SEC_TITLE_CELLULAR_UPLINKS = 'Cellular devices uplinks (Organization)' +SEC_TITLE_DEVICE_STATUSES = 'Devices status (Organization)' +SEC_TITLE_DEVICE_UPLINKS_INFO = 'Devices uplink info (Organization)' +SEC_TITLE_LICENSES_OVERVIEW = 'Licenses overview (Organization)' +SEC_TITLE_SENSOR_READINGS = 'Sensors readings (Organization)' +SEC_TITLE_SWITCH_PORTS_STATUSES = 'Switch ports status (Device)' +SEC_TITLE_WIRELESS_ETHERNET_STATUSES = 'Wireless devices ethernet status (Organization)' +SEC_TITLE_WIRELESS_DEVICE_STATUS = 'Wireless devices SSIDs status (Device)' +SEC_TITLE_ORG_SWITCH_PORTS_STATUSES = 'Switch port status (Organization/Early Access)' class DuplicateInList: # pylint: disable=too-few-public-methods @@ -97,7 +97,7 @@ class DuplicateInList: # pylint: disable=too-few-public-methods @staticmethod def _get_default_errmsg(_duplicates: Sequence) -> Message: - return Message(f"Duplicate element in list. Duplicate elements: {', '.join(_duplicates)}") + return Message(f'Duplicate element in list. Duplicate elements: {", ".join(_duplicates)}') def __call__(self, value: List[str] | None) -> None: if not isinstance(value, list): @@ -110,13 +110,13 @@ class DuplicateInList: # pylint: disable=too-few-public-methods def _migrate_to_valid_ident(value: object) -> Sequence[str]: if not isinstance(value, Iterable): - raise ValueError("Invalid value {value} for sections") + raise ValueError('Invalid value {value} for sections') - name_mapping = { - "licenses-overview": "licenses_overview", - "device-statuses": "device_statuses", - "sensor-readings": "sensor_readings", - } + # name_mapping = { + # 'licenses-overview': 'licenses_overview', + # 'device-statuses': 'device_statuses', + # 'sensor-readings': 'sensor_readings', + # } # return [name_mapping.get(s, s) for s in value] return [s.replace('-', '_') for s in value] @@ -124,135 +124,148 @@ def _migrate_to_valid_ident(value: object) -> Sequence[str]: def _form_special_agent_cisco_meraki() -> Dictionary: return Dictionary( - title=Title("Cisco Meraki"), + title=Title('Cisco Meraki'), elements={ - "api_key": DictElement( + 'api_key': DictElement( parameter_form=Password( - title=Title("API Key"), + title=Title('API Key'), migrate=migrate_to_password ), required=True, ), - "proxy": DictElement( + 'proxy': DictElement( parameter_form=Proxy( migrate=migrate_to_proxy ) ), - "no_cache": DictElement( + 'no_cache': DictElement( parameter_form=FixedValue( # BooleanChoice needs 2 clicks :-( - title=Title("Disable Cache"), + title=Title('Disable Cache'), help_text=Help( - "Never use cached information. By default the agent will cache received " - "data to avoid API limits and speed up the data retrievel." + 'Never use cached information. By default the agent will cache received ' + 'data to avoid API limits and speed up the data retrievel.' ), - label=Label("API Cache is disabled"), + label=Label('API Cache is disabled'), value=True, ) ), 'org_id_as_prefix': DictElement( parameter_form=FixedValue( value=True, - title=Title('Uese organisation ID as host prefix'), - label=Label("The Organization-id will be used as host name prefix"), + title=Title('Use Organisation-ID as host prefix'), + label=Label('The Organization-ID will be used as host name prefix'), help_text=Help( - 'The organisation ID will be used as prefix for the hostname (separated by a "\'"). Use ' + 'The Organisation-ID will be used as prefix for the hostname (separated by a -). Use ' 'this option together with a "Hostname translation for piggybacked hosts" to add a ' 'organisation prefix to the hosts from the Cisco Meraki cloud to avoid conflicting ' 'hostnames. You can also use this option along with the "Dynamic host management" to ' 'sort the host in organisation specific folders.' ) )), - "excluded_sections": DictElement( + 'net_id_as_prefix': DictElement( + parameter_form=FixedValue( + value=True, + title=Title('Use Network-ID as host prefix'), + label=Label('The Network-ID will be used as host name prefix'), + help_text=Help( + 'The Network-ID will be used as prefix for the hostname (separated by a -). Use ' + 'this option together with a "Hostname translation for piggybacked hosts" to add a ' + 'network prefix to the hosts from the Cisco Meraki cloud to avoid conflicting ' + 'hostnames. You can also use this option along with the "Dynamic host management" to ' + 'sort the host in location specific folders.' + ) + )), + 'excluded_sections': DictElement( parameter_form=MultipleChoice( - title=Title("Exclude Sections"), + title=Title('Exclude Sections'), elements=[ - MultipleChoiceElement(name=_SEC_NAME_ORG_API_REQUESTS, - title=Title(_SEC_TITLE_ORG_API_REQUESTS)), - MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_UPLINKS, - title=Title(_SEC_TITLE_APPLIANCE_UPLINKS)), - MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_UPLINKS_USAGE, - title=Title(_SEC_TITLE_APPLIANCE_UPLINKS_USAGE)), - MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_VPNS, title=Title(_SEC_TITLE_APPLIANCE_VPNS)), - MultipleChoiceElement(name=_SEC_NAME_APPLIANCE_PERFORMANCE, - title=Title(_SEC_TITLE_APPLIANCE_PERFORMANCE)), - MultipleChoiceElement(name=_SEC_NAME_CELLULAR_UPLINKS, - title=Title(_SEC_TITLE_CELLULAR_UPLINKS)), - MultipleChoiceElement(name=_SEC_NAME_DEVICE_STATUSES, title=Title(_SEC_TITLE_DEVICE_STATUSES)), - MultipleChoiceElement(name=_SEC_NAME_DEVICE_UPLINKS_INFO, - title=Title(_SEC_TITLE_DEVICE_UPLINKS_INFO)), - MultipleChoiceElement(name=_SEC_NAME_LICENSES_OVERVIEW, - title=Title(_SEC_TITLE_LICENSES_OVERVIEW)), - MultipleChoiceElement(name=_SEC_NAME_SENSOR_READINGS, title=Title(_SEC_TITLE_SENSOR_READINGS)), - MultipleChoiceElement(name=_SEC_NAME_SWITCH_PORTS_STATUSES, - title=Title(_SEC_TITLE_SWITCH_PORTS_STATUSES)), - MultipleChoiceElement(name=_SEC_NAME_WIRELESS_ETHERNET_STATUSES, - title=Title(_SEC_TITLE_WIRELESS_ETHERNET_STATUSES)), - MultipleChoiceElement(name=_SEC_NAME_WIRELESS_DEVICE_STATUS, - title=Title(_SEC_TITLE_WIRELESS_DEVICE_STATUS)), - MultipleChoiceElement(name=_SEC_NAME_ORG_SWITCH_PORTS_STATUSES, - title=Title(_SEC_TITLE_ORG_SWITCH_PORTS_STATUSES)), + MultipleChoiceElement(name=SEC_NAME_ORG_API_REQUESTS, + title=Title(SEC_TITLE_ORG_API_REQUESTS)), + MultipleChoiceElement(name=SEC_NAME_APPLIANCE_UPLINKS, + title=Title(SEC_TITLE_APPLIANCE_UPLINKS)), + MultipleChoiceElement(name=SEC_NAME_APPLIANCE_UPLINKS_USAGE, + title=Title(SEC_TITLE_APPLIANCE_UPLINKS_USAGE)), + MultipleChoiceElement(name=SEC_NAME_APPLIANCE_VPNS, title=Title(SEC_TITLE_APPLIANCE_VPNS)), + MultipleChoiceElement(name=SEC_NAME_APPLIANCE_PERFORMANCE, + title=Title(SEC_TITLE_APPLIANCE_PERFORMANCE)), + MultipleChoiceElement(name=SEC_NAME_CELLULAR_UPLINKS, + title=Title(SEC_TITLE_CELLULAR_UPLINKS)), + MultipleChoiceElement(name=SEC_NAME_DEVICE_STATUSES, title=Title(SEC_TITLE_DEVICE_STATUSES)), + MultipleChoiceElement(name=SEC_NAME_DEVICE_UPLINKS_INFO, + title=Title(SEC_TITLE_DEVICE_UPLINKS_INFO)), + MultipleChoiceElement(name=SEC_NAME_LICENSES_OVERVIEW, + title=Title(SEC_TITLE_LICENSES_OVERVIEW)), + MultipleChoiceElement(name=SEC_NAME_SENSOR_READINGS, title=Title(SEC_TITLE_SENSOR_READINGS)), + MultipleChoiceElement(name=SEC_NAME_SWITCH_PORTS_STATUSES, + title=Title(SEC_TITLE_SWITCH_PORTS_STATUSES)), + MultipleChoiceElement(name=SEC_NAME_WIRELESS_ETHERNET_STATUSES, + title=Title(SEC_TITLE_WIRELESS_ETHERNET_STATUSES)), + MultipleChoiceElement(name=SEC_NAME_WIRELESS_DEVICE_STATUS, + title=Title(SEC_TITLE_WIRELESS_DEVICE_STATUS)), + MultipleChoiceElement(name=SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + title=Title(SEC_TITLE_ORG_SWITCH_PORTS_STATUSES)), ], prefill=DefaultValue([ - _SEC_NAME_APPLIANCE_PERFORMANCE, - _SEC_NAME_SWITCH_PORTS_STATUSES, - _SEC_NAME_WIRELESS_DEVICE_STATUS, - _SEC_NAME_ORG_SWITCH_PORTS_STATUSES, + SEC_NAME_APPLIANCE_PERFORMANCE, + SEC_NAME_SWITCH_PORTS_STATUSES, + SEC_NAME_WIRELESS_DEVICE_STATUS, + SEC_NAME_ORG_SWITCH_PORTS_STATUSES, ]), # migrate=_migrate_to_valid_ident, ), required=True, ), - "orgs": DictElement( + 'orgs': DictElement( parameter_form=List( - element_template=String(macro_support=True), title=Title("Organizations"), + element_template=String(macro_support=True), title=Title('Organizations'), custom_validate=(DuplicateInList(),), ), ), - "cache_per_section": DictElement( + 'cache_per_section': DictElement( parameter_form=Dictionary( - title=Title("Set Cache time per section"), + title=Title('Set Cache time per section'), elements={ sec_name: DictElement( parameter_form=Integer( title=Title(sec_title), prefill=DefaultValue(sec_cache), - unit_symbol="minutes", + unit_symbol='minutes', custom_validate=(NumberInRange(min_value=0),) ) ) for sec_name, sec_title, sec_cache in [ - (_SEC_NAME_APPLIANCE_PERFORMANCE, _SEC_TITLE_APPLIANCE_PERFORMANCE, _SEC_CACHE_APPLIANCE_PERFORMANCE), - (_SEC_NAME_APPLIANCE_UPLINKS_USAGE, _SEC_TITLE_APPLIANCE_UPLINKS_USAGE, _SEC_CACHE_APPLIANCE_UPLINKS_USAGE), - (_SEC_NAME_APPLIANCE_UPLINKS, _SEC_TITLE_APPLIANCE_UPLINKS, _SEC_CACHE_APPLIANCE_UPLINKS), - (_SEC_NAME_APPLIANCE_VPNS, _SEC_TITLE_APPLIANCE_VPNS, _SEC_CACHE_APPLIANCE_VPNS), - (_SEC_NAME_CELLULAR_UPLINKS, _SEC_TITLE_CELLULAR_UPLINKS, _SEC_CACHE_CELLULAR_UPLINKS), - (_SEC_NAME_DEVICE_INFO, _SEC_TITLE_DEVICE_INFO, _SEC_CACHE_DEVICE_INFO), - (_SEC_NAME_DEVICE_STATUSES, _SEC_TITLE_DEVICE_STATUSES, _SEC_CACHE_DEVICE_STATUSES), - (_SEC_NAME_DEVICE_UPLINKS_INFO, _SEC_TITLE_DEVICE_UPLINKS_INFO, _SEC_CACHE_DEVICE_UPLINKS_INFO), - (_SEC_NAME_LICENSES_OVERVIEW, _SEC_TITLE_LICENSES_OVERVIEW, _SEC_CACHE_LICENSES_OVERVIEW), - (_SEC_NAME_NETWORKS, _SEC_TITLE_NETWORKS, _SEC_CACHE_NETWORKS), - (_SEC_NAME_ORG_API_REQUESTS, _SEC_TITLE_ORG_API_REQUESTS, _SEC_CACHE_ORG_API_REQUESTS), - (_SEC_NAME_ORG_SWITCH_PORTS_STATUSES, _SEC_TITLE_ORG_SWITCH_PORTS_STATUSES, _SEC_CACHE_ORG_SWITCH_PORTS_STATUSES), - (_SEC_NAME_ORGANISATIONS, _SEC_TITLE_ORGANISATIONS, _SEC_CACHE_ORGANISATIONS), - (_SEC_NAME_SENSOR_READINGS, _SEC_TITLE_SENSOR_READINGS, _SEC_CACHE_SENSOR_READINGS), - (_SEC_NAME_SWITCH_PORTS_STATUSES, _SEC_TITLE_SWITCH_PORTS_STATUSES, _SEC_CACHE_SWITCH_PORTS_STATUSES), - (_SEC_NAME_WIRELESS_DEVICE_STATUS, _SEC_TITLE_WIRELESS_DEVICE_STATUS, _SEC_CACHE_WIRELESS_DEVICE_STATUS), - (_SEC_NAME_WIRELESS_ETHERNET_STATUSES, _SEC_TITLE_WIRELESS_ETHERNET_STATUSES, _SEC_CACHE_WIRELESS_ETHERNET_STATUSES), + (SEC_NAME_APPLIANCE_PERFORMANCE, SEC_TITLE_APPLIANCE_PERFORMANCE, SEC_CACHE_APPLIANCE_PERFORMANCE), + (SEC_NAME_APPLIANCE_UPLINKS_USAGE, SEC_TITLE_APPLIANCE_UPLINKS_USAGE, SEC_CACHE_APPLIANCE_UPLINKS_USAGE), + (SEC_NAME_APPLIANCE_UPLINKS, SEC_TITLE_APPLIANCE_UPLINKS, SEC_CACHE_APPLIANCE_UPLINKS), + (SEC_NAME_APPLIANCE_VPNS, SEC_TITLE_APPLIANCE_VPNS, SEC_CACHE_APPLIANCE_VPNS), + (SEC_NAME_CELLULAR_UPLINKS, SEC_TITLE_CELLULAR_UPLINKS, SEC_CACHE_CELLULAR_UPLINKS), + (SEC_NAME_DEVICE_INFO, SEC_TITLE_DEVICE_INFO, SEC_CACHE_DEVICE_INFO), + (SEC_NAME_DEVICE_STATUSES, SEC_TITLE_DEVICE_STATUSES, SEC_CACHE_DEVICE_STATUSES), + (SEC_NAME_DEVICE_UPLINKS_INFO, SEC_TITLE_DEVICE_UPLINKS_INFO, SEC_CACHE_DEVICE_UPLINKS_INFO), + (SEC_NAME_LICENSES_OVERVIEW, SEC_TITLE_LICENSES_OVERVIEW, SEC_CACHE_LICENSES_OVERVIEW), + (SEC_NAME_NETWORKS, SEC_TITLE_NETWORKS, SEC_CACHE_NETWORKS), + (SEC_NAME_ORG_API_REQUESTS, SEC_TITLE_ORG_API_REQUESTS, SEC_CACHE_ORG_API_REQUESTS), + (SEC_NAME_ORG_SWITCH_PORTS_STATUSES, SEC_TITLE_ORG_SWITCH_PORTS_STATUSES, SEC_CACHE_ORG_SWITCH_PORTS_STATUSES), + (SEC_NAME_ORGANISATIONS, SEC_TITLE_ORGANISATIONS, SEC_CACHE_ORGANISATIONS), + (SEC_NAME_SENSOR_READINGS, SEC_TITLE_SENSOR_READINGS, SEC_CACHE_SENSOR_READINGS), + (SEC_NAME_SWITCH_PORTS_STATUSES, SEC_TITLE_SWITCH_PORTS_STATUSES, SEC_CACHE_SWITCH_PORTS_STATUSES), + (SEC_NAME_WIRELESS_DEVICE_STATUS, SEC_TITLE_WIRELESS_DEVICE_STATUS, SEC_CACHE_WIRELESS_DEVICE_STATUS), + (SEC_NAME_WIRELESS_ETHERNET_STATUSES, SEC_TITLE_WIRELESS_ETHERNET_STATUSES, SEC_CACHE_WIRELESS_ETHERNET_STATUSES), ] } ) ), - "sections": DictElement( + 'sections': DictElement( parameter_form=MultipleChoice( - title=Title("Sections"), + title=Title('Sections'), elements=[ MultipleChoiceElement( - name="licenses_overview", title=Title("Organization licenses overview") + name='licenses_overview', title=Title('Organization licenses overview') ), MultipleChoiceElement( - name="device_statuses", title=Title("Organization device statuses") + name='device_statuses', title=Title('Organization device statuses') ), MultipleChoiceElement( - name="sensor_readings", title=Title("Organization sensor readings") + name='sensor_readings', title=Title('Organization sensor readings') ), ], # migrate=_migrate_to_valid_ident, @@ -265,8 +278,8 @@ def _form_special_agent_cisco_meraki() -> Dictionary: rule_spec_cisco_meraki = SpecialAgent( - name="cisco_meraki", - title=Title("Cisco Meraki"), + name='cisco_meraki', + title=Title('Cisco Meraki'), topic=Topic.APPLICATIONS, parameter_form=_form_special_agent_cisco_meraki, ) diff --git a/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py b/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py index 9e91fac..9acf297 100644 --- a/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py +++ b/source/cmk_plugins/collection/server_side_calls/cisco_meraki.py @@ -27,6 +27,7 @@ __param = { ), 'no_cache': True, 'org_id_as_prefix': True, + 'net_id_as_prefix': True, 'excluded_sections': [ 'appliance_performance', 'switch_ports_statuses', @@ -82,6 +83,7 @@ class Params(BaseModel): orgs: Sequence[str] | None = None excluded_sections: Sequence[str] | None = None org_id_as_prefix: bool | None = None + net_id_as_prefix: bool | None = None no_cache: bool | None = None cache_per_section: CachePerSection | None = None @@ -153,6 +155,9 @@ def agent_cisco_meraki_arguments( if params.org_id_as_prefix is True: args.append("--org-id-as-prefix") + if params.net_id_as_prefix is True: + args.append("--net-id-as-prefix") + if params.no_cache is True: args.append("--no-cache") diff --git a/source/packages/cisco_meraki b/source/packages/cisco_meraki index 0687aaf..9704007 100644 --- a/source/packages/cisco_meraki +++ b/source/packages/cisco_meraki @@ -63,7 +63,7 @@ 'web': ['plugins/views/cisco_meraki.py']}, 'name': 'cisco_meraki', 'title': 'Cisco Meraki special agent', - 'version': '1.4.1-20241217', + 'version': '1.4.2-20250104', 'version.min_required': '2.3.0b1', 'version.packaged': 'cmk-mkp-tool 0.2.0', 'version.usable_until': '2.4.0b1'} -- GitLab