From a61d7b57646eb7ce26d5649fc45f2b50da01217f Mon Sep 17 00:00:00 2001 From: Matthew Howle Date: Fri, 28 May 2021 10:13:46 -0400 Subject: [PATCH] Initial commit of system_updates --- system_updates.py | 161 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 system_updates.py diff --git a/system_updates.py b/system_updates.py new file mode 100644 index 0000000..4cbb7fe --- /dev/null +++ b/system_updates.py @@ -0,0 +1,161 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: system_updates +short_description: Collect system updates as ansible_facts +description: + - Collects system updates from APT or Yum +''' + +from ansible.module_utils.basic import AnsibleModule + +HAVE_PYTHON_APT = False +HAVE_PYTHON_YUM = False + +try: + import apt + import apt_pkg + HAVE_PYTHON_APT = True +except ImportError: + pass + +try: + import yum + from yum.logginglevels import __NO_LOGGING + HAVE_PYTHON_YUM = True +except ImportError: + pass + + +def get_updates_apt(): + """Collect upgrades from APT""" + + def is_critical_package(version): + for origin in version.origins: + if 'security' in origin.site.lower(): + return True + if 'security' in origin.label.lower(): + return True + return False + + cache = apt.Cache(progress=None) + updated_packages = [] + + for pkg in cache: + for ver in pkg.versions: + if not ver.is_installed: + continue + + candidate_ver = pkg.candidate + if pkg.is_upgradable: + updated_packages.append(dict( + name=pkg.name, + version=candidate_ver.version, + arch=candidate_ver.architecture, + category=candidate_ver.section, + origin=candidate_ver.origins[0].origin, + installed_version=ver.version, + critical=is_critical_package(candidate_ver), + description=ver.summary + )) + return updated_packages + + +def get_updates_yum(): + """Collect upgrades from Yum""" + + def get_installed_version(package_name, arch=None): + search_results = yb.rpmdb.searchNevra(name=package_name, arch=arch) + return search_results[0].version if search_results else None + + def is_critical_update(metadata): + for ref in metadata['references']: + if ref['type'].lower() in ('cve', 'security'): + return True + if 'CVE' in ref['title']: + return True + return False + + # Make yum quiet + import logging + # grep -EIohr "getLogger\([^)]+\)" /usr/lib/python2.7/site-packages/yum/ + loggers = ['yum', 'yum.Depsolve', 'yum.filelogging', 'yum.filelogging.RPMInstallCallback', + 'yum.filelogging.YumBase', 'yum.plugin', 'yum.Repos', 'yum.ReposStorage', 'yum.update_md', 'yum.verbose', + 'yum.verbose.Depsolve', 'yum.verbose.plugin', 'yum.ProcessTrasactionBaseCallback', 'yum.verbose.Repos', + 'yum.verbose.update_md', 'yum.verbose.YumBase', 'yum.verbose.YumPlugins', 'yum.YumBase'] + + for name in loggers: + logger = logging.getLogger(name) + logger.setLevel(__NO_LOGGING) + + yb = yum.YumBase() + yb.setCacheDir() + + pkglist = yb.doPackageLists(pkgnarrow='updates') + + umd = yum.update_md.UpdateMetadata() + + repos = [] + for update in pkglist.updates: + if update.repo not in repos: + repos.append(update.repo) + try: + umd.add(update.repo) + except yum.Errors.RepoMDError: + pass + + updated_packages = [] + for update in pkglist.updates: + critical_update = False + notice = umd.get_notice((update.name, update.ver, update.rel)) + if notice: + metadata = notice.get_metadata() + critical_update = is_critical_update(metadata) + + candidate = dict( + name=update.name, + version=update.version, + release=update.release, + epoch=update.epoch, + arch=update.arch, + installed_version=get_installed_version(update.name, update.arch), + critical=critical_update, + description=update.description + ) + updated_packages.append(candidate) + + return updated_packages + + +def get_updates(): + if HAVE_PYTHON_APT: + return get_updates_apt() + if HAVE_PYTHON_YUM: + return get_updates_yum() + return None + + +def main(): + module = AnsibleModule(argument_spec=dict(), + supports_check_mode=True) + + results = dict() + facts = dict() + updates = get_updates() + + if updates is None: + results['skipped'] = True + results['msg'] = 'WARNING: Could not obtain updates' + else: + facts = updates + + results['changed'] = False + results['ansible_facts'] = {'system_updates': facts } + + module.exit_json(**results) + + +if __name__ == '__main__': + main()