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_REMOTE_DHCP_ENTRY = 'remote_dhcp_entry'
 | 
			
		||||
 | 
			
		||||
    FE_CHECK_FOR_UPDATES = 'check_for_updates'
 | 
			
		||||
 | 
			
		||||
    MKTXP_SOCKET_TIMEOUT = 'socket_timeout'
 | 
			
		||||
    MKTXP_INITIAL_DELAY = 'initial_delay_on_failure'
 | 
			
		||||
    MKTXP_MAX_DELAY = 'max_delay_on_failure'
 | 
			
		||||
@@ -127,7 +129,7 @@ class MKTXPConfigKeys:
 | 
			
		||||
    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}
 | 
			
		||||
 | 
			
		||||
    # 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_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.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,
 | 
			
		||||
                                                       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)
 | 
			
		||||
 | 
			
		||||
    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.flow.processor.output import BaseOutputProcessor
 | 
			
		||||
from mktxp.datasource.system_resource_ds import SystemResourceMetricsDataSource
 | 
			
		||||
from mktxp.utils.utils import check_for_updates
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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'])
 | 
			
		||||
            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
 | 
			
		||||
    @staticmethod
 | 
			
		||||
 
 | 
			
		||||
@@ -11,14 +11,16 @@
 | 
			
		||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
## GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import os, sys, shlex, tempfile, shutil, re
 | 
			
		||||
import subprocess, hashlib
 | 
			
		||||
import subprocess, hashlib, urllib
 | 
			
		||||
from timeit import default_timer
 | 
			
		||||
from collections.abc import Iterable
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
from multiprocessing import Process, Event
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from pkg_resources import packaging
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
''' Utilities / Helpers
 | 
			
		||||
@@ -270,3 +272,65 @@ class Benchmark:
 | 
			
		||||
        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