mirror of
https://github.com/KevinMidboe/mktxp-no-cli.git
synced 2025-10-29 17:50:23 +00:00
teach mktxp to check for updates
This commit is contained in:
6
main.py
Normal file
6
main.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
from mktxp.cli.dispatch import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user