teach mktxp to check for updates

This commit is contained in:
Leon Morten Richter
2023-05-28 14:11:12 +02:00
parent b706a08379
commit fb54356220
5 changed files with 90 additions and 5 deletions

6
main.py Normal file
View File

@@ -0,0 +1,6 @@
from mktxp.cli.dispatch import main
if __name__ == '__main__':
main()

View File

@@ -88,6 +88,8 @@ class MKTXPConfigKeys:
FE_QUEUE_KEY = 'queue' FE_QUEUE_KEY = 'queue'
FE_REMOTE_DHCP_ENTRY = 'remote_dhcp_entry' FE_REMOTE_DHCP_ENTRY = 'remote_dhcp_entry'
FE_CHECK_FOR_UPDATES = 'check_for_updates'
MKTXP_SOCKET_TIMEOUT = 'socket_timeout' MKTXP_SOCKET_TIMEOUT = 'socket_timeout'
MKTXP_INITIAL_DELAY = 'initial_delay_on_failure' MKTXP_INITIAL_DELAY = 'initial_delay_on_failure'
MKTXP_MAX_DELAY = 'max_delay_on_failure' MKTXP_MAX_DELAY = 'max_delay_on_failure'
@@ -127,7 +129,7 @@ class MKTXPConfigKeys:
DEFAULT_MKTXP_TOTAL_MAX_SCRAPE_DURATION = 30 DEFAULT_MKTXP_TOTAL_MAX_SCRAPE_DURATION = 30
BOOLEAN_KEYS_NO = {ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE, BOOLEAN_KEYS_NO = {ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE, FE_CHECK_FOR_UPDATES,
SSL_CERTIFICATE_VERIFY, FE_IPV6_FIREWALL_KEY, FE_IPV6_NEIGHBOR_KEY, FE_CONNECTION_STATS_KEY} SSL_CERTIFICATE_VERIFY, FE_IPV6_FIREWALL_KEY, FE_IPV6_NEIGHBOR_KEY, FE_CONNECTION_STATS_KEY}
# Feature keys enabled by default # Feature keys enabled by default
@@ -157,7 +159,7 @@ class ConfigEntry:
MKTXPConfigKeys.FE_FIREWALL_KEY, MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_WIRELESS_CLIENTS_KEY, MKTXPConfigKeys.FE_FIREWALL_KEY, MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_WIRELESS_CLIENTS_KEY,
MKTXPConfigKeys.FE_IP_CONNECTIONS_KEY, MKTXPConfigKeys.FE_CONNECTION_STATS_KEY, MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY, MKTXPConfigKeys.FE_POE_KEY, MKTXPConfigKeys.FE_NETWATCH_KEY, MKTXPConfigKeys.FE_IP_CONNECTIONS_KEY, MKTXPConfigKeys.FE_CONNECTION_STATS_KEY, MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY, MKTXPConfigKeys.FE_POE_KEY, MKTXPConfigKeys.FE_NETWATCH_KEY,
MKTXPConfigKeys.MKTXP_USE_COMMENTS_OVER_NAMES, MKTXPConfigKeys.FE_PUBLIC_IP_KEY, MKTXPConfigKeys.FE_IPV6_FIREWALL_KEY, MKTXPConfigKeys.FE_IPV6_NEIGHBOR_KEY, MKTXPConfigKeys.MKTXP_USE_COMMENTS_OVER_NAMES, MKTXPConfigKeys.FE_PUBLIC_IP_KEY, MKTXPConfigKeys.FE_IPV6_FIREWALL_KEY, MKTXPConfigKeys.FE_IPV6_NEIGHBOR_KEY,
MKTXPConfigKeys.FE_USER_KEY, MKTXPConfigKeys.FE_QUEUE_KEY, MKTXPConfigKeys.FE_REMOTE_DHCP_ENTRY MKTXPConfigKeys.FE_USER_KEY, MKTXPConfigKeys.FE_QUEUE_KEY, MKTXPConfigKeys.FE_REMOTE_DHCP_ENTRY, MKTXPConfigKeys.FE_CHECK_FOR_UPDATES
]) ])
MKTXPSystemEntry = namedtuple('MKTXPSystemEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT, MKTXPSystemEntry = namedtuple('MKTXPSystemEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT,
MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY, MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY,

View File

@@ -54,3 +54,5 @@
remote_dhcp_entry = None # An MKTXP entry for remote DHCP info resolution (capsman/wireless) remote_dhcp_entry = None # An MKTXP entry for remote DHCP info resolution (capsman/wireless)
use_comments_over_names = True # when available, forces using comments over the interfaces names use_comments_over_names = True # when available, forces using comments over the interfaces names
check_for_updates = False # check for available ROS updates

View File

@@ -15,6 +15,7 @@
from mktxp.collector.base_collector import BaseCollector from mktxp.collector.base_collector import BaseCollector
from mktxp.flow.processor.output import BaseOutputProcessor from mktxp.flow.processor.output import BaseOutputProcessor
from mktxp.datasource.system_resource_ds import SystemResourceMetricsDataSource from mktxp.datasource.system_resource_ds import SystemResourceMetricsDataSource
from mktxp.utils.utils import check_for_updates
class SystemResourceCollector(BaseCollector): class SystemResourceCollector(BaseCollector):
@@ -61,6 +62,16 @@ class SystemResourceCollector(BaseCollector):
cpu_frequency_metrics = BaseCollector.gauge_collector('system_cpu_frequency', 'Current CPU frequency', resource_records, 'cpu_frequency', ['version', 'board_name', 'cpu', 'architecture_name']) cpu_frequency_metrics = BaseCollector.gauge_collector('system_cpu_frequency', 'Current CPU frequency', resource_records, 'cpu_frequency', ['version', 'board_name', 'cpu', 'architecture_name'])
yield cpu_frequency_metrics yield cpu_frequency_metrics
# Check for updates
if router_entry.config_entry.check_for_updates:
for record in resource_records:
cur_version, newest_version = check_for_updates(record['version'])
record['newest_version'] = str(newest_version)
record['update_available'] = cur_version < newest_version
update_available_metrics = BaseCollector.gauge_collector('system_update_available', 'Is there a newer version available', resource_records, 'update_available', ['newest_version',])
yield update_available_metrics
# Helpers # Helpers
@staticmethod @staticmethod

View File

@@ -11,14 +11,16 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details. ## GNU General Public License for more details.
import os, sys, shlex, tempfile, shutil, re import os, sys, shlex, tempfile, shutil, re
import subprocess, hashlib import subprocess, hashlib, urllib
from timeit import default_timer from timeit import default_timer
from collections.abc import Iterable from collections.abc import Iterable
import xml.etree.ElementTree as ET
from contextlib import contextmanager from contextlib import contextmanager
from multiprocessing import Process, Event from multiprocessing import Process, Event
from datetime import timedelta from datetime import timedelta
from pkg_resources import packaging
''' Utilities / Helpers ''' Utilities / Helpers
@@ -270,3 +272,65 @@ class Benchmark:
self.time = default_timer() - self.start self.time = default_timer() - self.start
# mapping of channels to RSS feeds
CHANNEL_RSS_FEED_MAPPING = {
'development': 'https://mikrotik.com/development.rss',
'long-term': 'https://mikrotik.com/bugfix.rss',
'stable': 'https://mikrotik.com/current.rss',
'testing': 'https://mikrotik.com/candidate.rss',
}
def get_available_updates(channel):
"""Check the RSS feed for available updates for a given update channel.
This method fetches the RSS feed and yields all version from the parsed XML.
Version numbers are parsed into version.Version instances (part of setuptools)."""
rss_feed = CHANNEL_RSS_FEED_MAPPING[channel]
with urllib.request.urlopen(rss_feed) as response:
result = response.read()
root = ET.fromstring(result)
channel = root[0]
for child in channel:
# iterate over all updates
if child.tag == 'item':
title, _, _, _, _, _ = child
# extract and parse the version number from title
version_text = re.findall(r'[\d+\.]+', title.text)[0]
version_number = packaging.version.parse(version_text)
yield version_number
def parse_ros_version(string):
"""Parse the version returned from the /system/resource command.
Returns a tuple: (<version>, <channel>).
>>> parse_ros_version('1.2.3 (stable)')
1.2.3, stable
"""
version, channel = re.findall(r'([\d\.]+).*?([\w]+)', string)[0]
return packaging.version.parse(version), channel
def check_for_updates(cur_version):
"""Try to check if there is a newer version available.
If anything goes wrong, it returns the same version.
Returns a tuple: (<current version>, <newest version>)"""
error = False
try:
cur_version, channel = parse_ros_version(cur_version)
available_versions = get_available_updates(channel)
newest_version = sorted(available_versions)[-1]
except KeyError:
print(f'unknown update channel {channel}')
error = True
except urllib.error.HTTPError as err:
print(f'update feed returned: {str(err)}')
error = True
except Exception as err:
print(f'could not check for updates, because: {str(err)}')
error = True
if error:
return cur_version, cur_version
return cur_version, newest_version