From fc37d736f52a5ee355ed14a56416926ae495249b Mon Sep 17 00:00:00 2001 From: Sven Velt Date: Mon, 3 Apr 2023 20:38:51 +0200 Subject: [PATCH] New plugin: check_apk for Alpine Linux --- check_apk.py | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100755 check_apk.py diff --git a/check_apk.py b/check_apk.py new file mode 100755 index 0000000..875ba5e --- /dev/null +++ b/check_apk.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8 -*- + +##################################################################### +# (c) 2016- by Sven Velt, Germany # +# sven-mymonplugins@velt.biz # +# # +# This file is part of "velt.biz - My Monitoring Plugins" # +# a fork of "team(ix) Monitoring Plugins" in 2015 # +# URL: https://gogs.velt.biz/velt.biz/MyMonPlugins/ # +# # +# This file is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published # +# by the Free Software Foundation, either version 2 of the License, # +# or (at your option) any later version. # +# # +# This file is distributed in the hope that it will be useful, but # +# WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this file. If not, see . # +##################################################################### + +import os +import subprocess +import sys +import time + +try: + from MyMonPlugin import MonitoringPlugin +except ImportError: + print('==========================') + print('AIKS! Python import error!') + print('==========================\n') + print('Could not find class "MonitoringPlugin"!\n') + print('Did you download "%s"' % os.path.basename(sys.argv[0])) + print('without "MyMonPlugin/"?\n') + print('Please go back to') + print('https://gogs.velt.biz/velt.biz/MyMonPlugins and:') + print('- get a full archive at http://gogs.velt.biz/velt.biz/MyMonPlugins/releases') + print('- or a master snapshot at http://gogs.velt.biz/velt.biz/MyMonPlugins/archive/master.tar.gz\n') + sys.exit(127) + + +plugin = MonitoringPlugin( + pluginname='check_apk', + tagforstatusline='APK', + description='Check APK package manager for updates', + version='0.1', + ) + +plugin.add_cmdlineoption('-P', '--path-apk', 'apk', 'full path to apk', default='/sbin/apk') +plugin.add_cmdlineoption('-S', '--sync-repo-index', 'sync_repo_index', 'sync repository index files at startup', default=False, action='store_true') +plugin.add_cmdlineoption('', '--ignore-sync-failure', 'fail_on_sync_failure', 'ignore repo index sync failures', default=True, action='store_false') +plugin.add_cmdlineoption('', '--sudo', 'sudo', 'call "apk" with sudo', default=False, action='store_true') + +plugin.add_cmdlineoption('', '--mymonplugins-testmode', 'mymonplugins_testmode', None, default=False, action='store_true') + +plugin.parse_cmdlineoptions() + +############################################################################## +##### Testmode + +if plugin.options.mymonplugins_testmode: + mymonplugins_testmode = {} + + mymonplugins_testmode['apk update'] = '''fetch http://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz +fetch http://dl-cdn.alpinelinux.org/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz +v3.17.3-13-g98fa1f428e9 [http://dl-cdn.alpinelinux.org/alpine/latest-stable/main] +v3.17.3-12-g492f704d069 [http://dl-cdn.alpinelinux.org/alpine/latest-stable/community] +OK: 17824 distinct packages available +'''.split('\n') + + mymonplugins_testmode['apk -s upgrade'] = '''(1/6) Upgrading alpine-release (3.17.2-r0 -> 3.17.3-r0) +(2/6) Upgrading libcrypto3 (3.0.8-r1 -> 3.0.8-r3) +(3/6) Upgrading libssl3 (3.0.8-r1 -> 3.0.8-r3) +(4/6) Upgrading alpine-base (3.17.2-r0 -> 3.17.3-r0) +(5/6) Upgrading mkinitfs (3.7.0-r0 -> 3.7.0-r1) +(6/6) Upgrading openssl (3.0.8-r1 -> 3.0.8-r3) +OK: 258 MiB in 220 packages +'''.split('\n') + +############################################################################## + +def run_command(cmdline, needs_sudo=False): + tstart = time.time() + if needs_sudo and plugin.options.sudo: + new = ['sudo', '-n', '--'] + new.extend(cmdline) + cmdline = new + plugin.verbose(1, 'Running command line: %s' % subprocess.list2cmdline(cmdline)) + try: + cmd = subprocess.Popen( + cmdline, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE + ) + except OSError: + plugin.back2nagios(plugin.RETURNCODE['UNKNOWN'], 'Could not execute command line: %s' % subprocess.list2cmdline(cmdline)) + + (sout,serr) = cmd.communicate() + plugin.verbose(2, 'Runtime %.3fs' % (time.time() - tstart, ) ) + + sout = sout.rstrip().decode('utf-8').split('\n') + if sout == ['']: + sout = [] + serr = serr.rstrip().decode('utf-8').split('\n') + if serr == ['']: + serr = [] + + return( (sout, serr, cmd.returncode,) ) + +############################################################################## + +if plugin.options.sync_repo_index: + plugin.verbose(1, '-S/--sync_repo_index given') + cmdline = [plugin.options.apk, 'update', '--no-progress',] + (sout, serr, rc) = run_command(cmdline, needs_sudo=True) + sout and plugin.verbose(3, sout, prefix='stdout: ') + serr and plugin.verbose(3, serr, prefix='stderr: ') + plugin.verbose(2, 'Return code: %d' % rc) + if plugin.options.fail_on_sync_failure and (rc != 0 or 'ERROR:' in ' '.join(serr)): + if 'Permission denied' in ' '.join(serr): + plugin.back2nagios(plugin.RETURNCODE['CRITICAL'], 'Syncing of repository index files failed, permission denied. Do you need "--sudo"?', multiline=serr) + plugin.back2nagios(plugin.RETURNCODE['CRITICAL'], 'Syncing of repository index files failed', multiline=serr) + +############################################################################## + +cmdline = [plugin.options.apk, 'upgrade', '--simulate', '--no-progress',] +(sout, serr, rc) = run_command(cmdline) +sout and plugin.verbose(3, sout, prefix='stdout: ') +serr and plugin.verbose(3, serr, prefix='stderr: ') +plugin.verbose(2, 'Return code: %d' % rc) + +if plugin.options.sudo and 'sudo: ' in ' '.join(serr): + # Running with sudo + if rc == 1: + # sudo RC 1: a password is required + plugin.back2nagios(plugin.RETURNCODE['CRITICAL'], ' '.join(serr)) +if rc == 8: + # RC 8: Transaction aborted due to unresolved shlibs. + plugin.back2nagios(plugin.RETURNCODE['WARNING'], serr[-1], serr[:-1]) +elif rc not in [0, 6]: + # RC 0: Packages to update + # RC 6: Package(s) already installed + plugin.back2nagios(plugin.RETURNCODE['WARNING'], 'Unknown returncode "%s", please contact the author of plugin or open an issue!' % rc) + +action = {'Upgrading':[], 'Installing':[], 'Purging':[], } +action_verb = {'Upgrading':'upgrade', 'Installing':'install', 'Purging':'purge', } + +for line in sout[0:-1]: + cols = line.split(' ') + # Append package name to action type list + action[cols[1]].append(cols[2]) + +if len(sout) == 1: + plugin.remember_check('Updates', plugin.RETURNCODE['OK'], 'Everything uptodate') +else: + out = [] + multiline = [] + for act in list(action.keys()): + pkgs = action.pop(act) + pkgs.sort() + l = len(pkgs) + out.append('%s package%s to %s' % (l, l != 1 and 's' or '', action_verb[act]) ) + l and multiline.append('%s(%s): %s' % (act, l, ', '.join(pkgs)) ) + + out = ', '.join(out) + + plugin.remember_check('Updates', plugin.RETURNCODE['CRITICAL'], out, multilineoutput=multiline) + +# Exit +plugin.brain2output() +plugin.exit() +