123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- #!/usr/bin/env python
- # pylint: disable=missing-docstring
- # ___ ___ _ _ ___ ___ _ _____ ___ ___
- # / __| __| \| | __| _ \ /_\_ _| __| \
- # | (_ | _|| .` | _|| / / _ \| | | _|| |) |
- # \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____
- # | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|
- # | |) | (_) | | .` | (_) || | | _|| |) | | | |
- # |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|
- #
- # Copyright 2016 Red Hat, Inc. and/or its affiliates
- # and other contributors as indicated by the @author tags.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- #
- # -*- -*- -*- Begin included fragment: lib/import.py -*- -*- -*-
- # pylint: disable=wrong-import-order,wrong-import-position,unused-import
- from __future__ import print_function # noqa: F401
- import copy # noqa: F401
- import fcntl # noqa: F401
- import json # noqa: F401
- import os # noqa: F401
- import re # noqa: F401
- import shutil # noqa: F401
- import tempfile # noqa: F401
- import time # noqa: F401
- try:
- import ruamel.yaml as yaml # noqa: F401
- except ImportError:
- import yaml # noqa: F401
- from ansible.module_utils.basic import AnsibleModule
- # -*- -*- -*- End included fragment: lib/import.py -*- -*- -*-
- # -*- -*- -*- Begin included fragment: doc/repoquery -*- -*- -*-
- DOCUMENTATION = '''
- ---
- module: repoquery
- short_description: Query package information from Yum repositories
- description:
- - Query package information from Yum repositories.
- options:
- state:
- description:
- - The expected state. Currently only supports list.
- required: false
- default: list
- choices: ["list"]
- aliases: []
- name:
- description:
- - The name of the package to query
- required: true
- default: None
- aliases: []
- query_type:
- description:
- - Narrows the packages queried based off of this value.
- - If repos, it narrows the query to repositories defined on the machine.
- - If installed, it narrows the query to only packages installed on the machine.
- - If available, it narrows the query to packages that are available to be installed.
- - If recent, it narrows the query to only recently edited packages.
- - If updates, it narrows the query to only packages that are updates to existing installed packages.
- - If extras, it narrows the query to packages that are not present in any of the available repositories.
- - If all, it queries all of the above.
- required: false
- default: repos
- aliases: []
- verbose:
- description:
- - Shows more detail for the requested query.
- required: false
- default: false
- aliases: []
- show_duplicates:
- description:
- - Shows multiple versions of a package.
- required: false
- default: false
- aliases: []
- match_version:
- description:
- - Match the specific version given to the package.
- required: false
- default: None
- aliases: []
- author:
- - "Matt Woodson <mwoodson@redhat.com>"
- extends_documentation_fragment: []
- '''
- EXAMPLES = '''
- # Example 1: Get bash versions
- - name: Get bash version
- repoquery:
- name: bash
- show_duplicates: True
- register: bash_out
- # Results:
- # ok: [localhost] => {
- # "bash_out": {
- # "changed": false,
- # "results": {
- # "cmd": "/usr/bin/repoquery --quiet --pkgnarrow=repos --queryformat=%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release} --show-duplicates bash",
- # "package_found": true,
- # "package_name": "bash",
- # "returncode": 0,
- # "versions": {
- # "available_versions": [
- # "4.2.45",
- # "4.2.45",
- # "4.2.45",
- # "4.2.46",
- # "4.2.46",
- # "4.2.46",
- # "4.2.46"
- # ],
- # "available_versions_full": [
- # "4.2.45-5.el7",
- # "4.2.45-5.el7_0.2",
- # "4.2.45-5.el7_0.4",
- # "4.2.46-12.el7",
- # "4.2.46-19.el7",
- # "4.2.46-20.el7_2",
- # "4.2.46-21.el7_3"
- # ],
- # "latest": "4.2.46",
- # "latest_full": "4.2.46-21.el7_3"
- # }
- # },
- # "state": "present"
- # }
- # }
- # Example 2: Get bash versions verbosely
- - name: Get bash versions verbosely
- repoquery:
- name: bash
- show_duplicates: True
- verbose: True
- register: bash_out
- # Results:
- # ok: [localhost] => {
- # "bash_out": {
- # "changed": false,
- # "results": {
- # "cmd": "/usr/bin/repoquery --quiet --pkgnarrow=repos --queryformat=%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release} --show-duplicates bash",
- # "package_found": true,
- # "package_name": "bash",
- # "raw_versions": {
- # "4.2.45-5.el7": {
- # "arch": "x86_64",
- # "release": "5.el7",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.45",
- # "version_release": "4.2.45-5.el7"
- # },
- # "4.2.45-5.el7_0.2": {
- # "arch": "x86_64",
- # "release": "5.el7_0.2",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.45",
- # "version_release": "4.2.45-5.el7_0.2"
- # },
- # "4.2.45-5.el7_0.4": {
- # "arch": "x86_64",
- # "release": "5.el7_0.4",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.45",
- # "version_release": "4.2.45-5.el7_0.4"
- # },
- # "4.2.46-12.el7": {
- # "arch": "x86_64",
- # "release": "12.el7",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.46",
- # "version_release": "4.2.46-12.el7"
- # },
- # "4.2.46-19.el7": {
- # "arch": "x86_64",
- # "release": "19.el7",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.46",
- # "version_release": "4.2.46-19.el7"
- # },
- # "4.2.46-20.el7_2": {
- # "arch": "x86_64",
- # "release": "20.el7_2",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.46",
- # "version_release": "4.2.46-20.el7_2"
- # },
- # "4.2.46-21.el7_3": {
- # "arch": "x86_64",
- # "release": "21.el7_3",
- # "repo": "rhel-7-server-rpms",
- # "version": "4.2.46",
- # "version_release": "4.2.46-21.el7_3"
- # }
- # },
- # "results": "4.2.45|5.el7|x86_64|rhel-7-server-rpms|4.2.45-5.el7\n4.2.45|5.el7_0.2|x86_64|rhel-7-server-rpms|4.2.45-5.el7_0.2\n4.2.45|5.el7_0.4|x86_64|rhel-7-server-rpms|4.2.45-5.el7_0.4\n4.2.46|12.el7|x86_64|rhel-7-server-rpms|4.2.46-12.el7\n4.2.46|19.el7|x86_64|rhel-7-server-rpms|4.2.46-19.el7\n4.2.46|20.el7_2|x86_64|rhel-7-server-rpms|4.2.46-20.el7_2\n4.2.46|21.el7_3|x86_64|rhel-7-server-rpms|4.2.46-21.el7_3\n",
- # "returncode": 0,
- # "versions": {
- # "available_versions": [
- # "4.2.45",
- # "4.2.45",
- # "4.2.45",
- # "4.2.46",
- # "4.2.46",
- # "4.2.46",
- # "4.2.46"
- # ],
- # "available_versions_full": [
- # "4.2.45-5.el7",
- # "4.2.45-5.el7_0.2",
- # "4.2.45-5.el7_0.4",
- # "4.2.46-12.el7",
- # "4.2.46-19.el7",
- # "4.2.46-20.el7_2",
- # "4.2.46-21.el7_3"
- # ],
- # "latest": "4.2.46",
- # "latest_full": "4.2.46-21.el7_3"
- # }
- # },
- # "state": "present"
- # }
- # }
- # Example 3: Match a specific version
- - name: matched versions repoquery test
- repoquery:
- name: atomic-openshift
- show_duplicates: True
- match_version: 3.3
- register: openshift_out
- # Result:
- # ok: [localhost] => {
- # "openshift_out": {
- # "changed": false,
- # "results": {
- # "cmd": "/usr/bin/repoquery --quiet --pkgnarrow=repos --queryformat=%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release} --show-duplicates atomic-openshift",
- # "package_found": true,
- # "package_name": "atomic-openshift",
- # "returncode": 0,
- # "versions": {
- # "available_versions": [
- # "3.2.0.43",
- # "3.2.1.23",
- # "3.3.0.32",
- # "3.3.0.34",
- # "3.3.0.35",
- # "3.3.1.3",
- # "3.3.1.4",
- # "3.3.1.5",
- # "3.3.1.7",
- # "3.4.0.39"
- # ],
- # "available_versions_full": [
- # "3.2.0.43-1.git.0.672599f.el7",
- # "3.2.1.23-1.git.0.88a7a1d.el7",
- # "3.3.0.32-1.git.0.37bd7ea.el7",
- # "3.3.0.34-1.git.0.83f306f.el7",
- # "3.3.0.35-1.git.0.d7bd9b6.el7",
- # "3.3.1.3-1.git.0.86dc49a.el7",
- # "3.3.1.4-1.git.0.7c8657c.el7",
- # "3.3.1.5-1.git.0.62700af.el7",
- # "3.3.1.7-1.git.0.0988966.el7",
- # "3.4.0.39-1.git.0.5f32f06.el7"
- # ],
- # "latest": "3.4.0.39",
- # "latest_full": "3.4.0.39-1.git.0.5f32f06.el7",
- # "matched_version_found": true,
- # "matched_version_full_latest": "3.3.1.7-1.git.0.0988966.el7",
- # "matched_version_latest": "3.3.1.7",
- # "matched_versions": [
- # "3.3.0.32",
- # "3.3.0.34",
- # "3.3.0.35",
- # "3.3.1.3",
- # "3.3.1.4",
- # "3.3.1.5",
- # "3.3.1.7"
- # ],
- # "matched_versions_full": [
- # "3.3.0.32-1.git.0.37bd7ea.el7",
- # "3.3.0.34-1.git.0.83f306f.el7",
- # "3.3.0.35-1.git.0.d7bd9b6.el7",
- # "3.3.1.3-1.git.0.86dc49a.el7",
- # "3.3.1.4-1.git.0.7c8657c.el7",
- # "3.3.1.5-1.git.0.62700af.el7",
- # "3.3.1.7-1.git.0.0988966.el7"
- # ],
- # "requested_match_version": "3.3"
- # }
- # },
- # "state": "present"
- # }
- # }
- '''
- # -*- -*- -*- End included fragment: doc/repoquery -*- -*- -*-
- # -*- -*- -*- Begin included fragment: lib/repoquery.py -*- -*- -*-
- '''
- class that wraps the repoquery commands in a subprocess
- '''
- # pylint: disable=too-many-lines,wrong-import-position,wrong-import-order
- from collections import defaultdict # noqa: E402
- # pylint: disable=no-name-in-module,import-error
- # Reason: pylint errors with "No name 'version' in module 'distutils'".
- # This is a bug: https://github.com/PyCQA/pylint/issues/73
- from distutils.version import LooseVersion # noqa: E402
- import subprocess # noqa: E402
- class RepoqueryCLIError(Exception):
- '''Exception class for repoquerycli'''
- pass
- def _run(cmds):
- ''' Actually executes the command. This makes mocking easier. '''
- proc = subprocess.Popen(cmds,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = proc.communicate()
- return proc.returncode, stdout, stderr
- # pylint: disable=too-few-public-methods
- class RepoqueryCLI(object):
- ''' Class to wrap the command line tools '''
- def __init__(self,
- verbose=False):
- ''' Constructor for RepoqueryCLI '''
- self.verbose = verbose
- self.verbose = True
- def _repoquery_cmd(self, cmd, output=False, output_type='json'):
- '''Base command for repoquery '''
- cmds = ['/usr/bin/repoquery', '--plugins', '--quiet']
- cmds.extend(cmd)
- rval = {}
- results = ''
- err = None
- if self.verbose:
- print(' '.join(cmds))
- returncode, stdout, stderr = _run(cmds)
- rval = {
- "returncode": returncode,
- "results": results,
- "cmd": ' '.join(cmds),
- }
- if returncode == 0:
- if output:
- if output_type == 'raw':
- rval['results'] = stdout
- if self.verbose:
- print(stdout)
- print(stderr)
- if err:
- rval.update({
- "err": err,
- "stderr": stderr,
- "stdout": stdout,
- "cmd": cmds
- })
- else:
- rval.update({
- "stderr": stderr,
- "stdout": stdout,
- "results": {},
- })
- return rval
- # -*- -*- -*- End included fragment: lib/repoquery.py -*- -*- -*-
- # -*- -*- -*- Begin included fragment: class/repoquery.py -*- -*- -*-
- class Repoquery(RepoqueryCLI):
- ''' Class to wrap the repoquery
- '''
- # pylint: disable=too-many-arguments,too-many-instance-attributes
- def __init__(self, name, query_type, show_duplicates,
- match_version, ignore_excluders, verbose):
- ''' Constructor for YumList '''
- super(Repoquery, self).__init__(None)
- self.name = name
- self.query_type = query_type
- self.show_duplicates = show_duplicates
- self.match_version = match_version
- self.ignore_excluders = ignore_excluders
- self.verbose = verbose
- if self.match_version:
- self.show_duplicates = True
- self.query_format = "%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release}"
- self.tmp_file = None
- def build_cmd(self):
- ''' build the repoquery cmd options '''
- repo_cmd = []
- repo_cmd.append("--pkgnarrow=" + self.query_type)
- repo_cmd.append("--queryformat=" + self.query_format)
- if self.show_duplicates:
- repo_cmd.append('--show-duplicates')
- if self.ignore_excluders:
- repo_cmd.append('--config=' + self.tmp_file.name)
- repo_cmd.append(self.name)
- return repo_cmd
- @staticmethod
- def process_versions(query_output):
- ''' format the package data into something that can be presented '''
- version_dict = defaultdict(dict)
- for version in query_output.decode().split('\n'):
- pkg_info = version.split("|")
- pkg_version = {}
- pkg_version['version'] = pkg_info[0]
- pkg_version['release'] = pkg_info[1]
- pkg_version['arch'] = pkg_info[2]
- pkg_version['repo'] = pkg_info[3]
- pkg_version['version_release'] = pkg_info[4]
- version_dict[pkg_info[4]] = pkg_version
- return version_dict
- def format_versions(self, formatted_versions):
- ''' Gather and present the versions of each package '''
- versions_dict = {}
- versions_dict['available_versions_full'] = list(formatted_versions.keys())
- # set the match version, if called
- if self.match_version:
- versions_dict['matched_versions_full'] = []
- versions_dict['requested_match_version'] = self.match_version
- versions_dict['matched_versions'] = []
- # get the "full version (version - release)
- versions_dict['available_versions_full'].sort(key=LooseVersion)
- versions_dict['latest_full'] = versions_dict['available_versions_full'][-1]
- # get the "short version (version)
- versions_dict['available_versions'] = []
- for version in versions_dict['available_versions_full']:
- versions_dict['available_versions'].append(formatted_versions[version]['version'])
- if self.match_version:
- if version.startswith(self.match_version):
- versions_dict['matched_versions_full'].append(version)
- versions_dict['matched_versions'].append(formatted_versions[version]['version'])
- versions_dict['available_versions'].sort(key=LooseVersion)
- versions_dict['latest'] = versions_dict['available_versions'][-1]
- # finish up the matched version
- if self.match_version:
- if versions_dict['matched_versions_full']:
- versions_dict['matched_version_found'] = True
- versions_dict['matched_versions'].sort(key=LooseVersion)
- versions_dict['matched_version_latest'] = versions_dict['matched_versions'][-1]
- versions_dict['matched_version_full_latest'] = versions_dict['matched_versions_full'][-1]
- else:
- versions_dict['matched_version_found'] = False
- versions_dict['matched_versions'] = []
- versions_dict['matched_version_latest'] = ""
- versions_dict['matched_version_full_latest'] = ""
- return versions_dict
- def repoquery(self):
- '''perform a repoquery '''
- if self.ignore_excluders:
- # Duplicate yum.conf and reset exclude= line to an empty string
- # to clear a list of all excluded packages
- self.tmp_file = tempfile.NamedTemporaryFile()
- with open("/etc/yum.conf", "r") as file_handler:
- yum_conf_lines = file_handler.readlines()
- yum_conf_lines = ["exclude=" if l.startswith("exclude=") else l for l in yum_conf_lines]
- with open(self.tmp_file.name, "w") as file_handler:
- file_handler.writelines(yum_conf_lines)
- file_handler.flush()
- repoquery_cmd = self.build_cmd()
- rval = self._repoquery_cmd(repoquery_cmd, True, 'raw')
- # check to see if there are actual results
- if rval['results']:
- processed_versions = Repoquery.process_versions(rval['results'].strip())
- formatted_versions = self.format_versions(processed_versions)
- rval['package_found'] = True
- rval['versions'] = formatted_versions
- rval['package_name'] = self.name
- if self.verbose:
- rval['raw_versions'] = processed_versions
- else:
- del rval['results']
- # No packages found
- else:
- rval['package_found'] = False
- if self.ignore_excluders:
- self.tmp_file.close()
- return rval
- @staticmethod
- def run_ansible(params, check_mode):
- '''run the ansible idempotent code'''
- repoquery = Repoquery(
- params['name'],
- params['query_type'],
- params['show_duplicates'],
- params['match_version'],
- params['ignore_excluders'],
- params['verbose'],
- )
- state = params['state']
- if state == 'list':
- results = repoquery.repoquery()
- if results['returncode'] != 0:
- return {'failed': True,
- 'msg': results}
- return {'changed': False, 'results': results, 'state': 'list', 'check_mode': check_mode}
- return {'failed': True,
- 'changed': False,
- 'msg': 'Unknown state passed. %s' % state,
- 'state': 'unknown'}
- # -*- -*- -*- End included fragment: class/repoquery.py -*- -*- -*-
- # -*- -*- -*- Begin included fragment: ansible/repoquery.py -*- -*- -*-
- def main():
- '''
- ansible repoquery module
- '''
- module = AnsibleModule(
- argument_spec=dict(
- state=dict(default='list', type='str', choices=['list']),
- name=dict(default=None, required=True, type='str'),
- query_type=dict(default='repos', required=False, type='str',
- choices=[
- 'installed', 'available', 'recent',
- 'updates', 'extras', 'all', 'repos'
- ]),
- verbose=dict(default=False, required=False, type='bool'),
- show_duplicates=dict(default=False, required=False, type='bool'),
- match_version=dict(default=None, required=False, type='str'),
- ignore_excluders=dict(default=False, required=False, type='bool'),
- retries=dict(default=4, required=False, type='int'),
- retry_interval=dict(default=5, required=False, type='int'),
- ),
- supports_check_mode=False,
- required_if=[('show_duplicates', True, ['name'])],
- )
- tries = 1
- while True:
- rval = Repoquery.run_ansible(module.params, module.check_mode)
- if 'failed' not in rval:
- module.exit_json(**rval)
- elif tries > module.params['retries']:
- module.fail_json(**rval)
- tries += 1
- time.sleep(module.params['retry_interval'])
- if __name__ == "__main__":
- main()
- # -*- -*- -*- End included fragment: ansible/repoquery.py -*- -*- -*-
|