summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wiest <twiest@redhat.com>2017-01-25 14:07:22 -0500
committerThomas Wiest <twiest@redhat.com>2017-01-31 08:15:37 -0500
commitb415e4855970131a77112940646a95641d3bd27b (patch)
tree951ce174c71c495db66b522fa0730816e4878f93
parentfca215887b2e4224779b58e8fd1b7662ec993f83 (diff)
downloadopenshift-b415e4855970131a77112940646a95641d3bd27b.tar.gz
openshift-b415e4855970131a77112940646a95641d3bd27b.tar.bz2
openshift-b415e4855970131a77112940646a95641d3bd27b.tar.xz
openshift-b415e4855970131a77112940646a95641d3bd27b.zip
Added repoquery to lib_utils.
-rw-r--r--roles/lib_openshift/src/sources.yml7
-rwxr-xr-xroles/lib_openshift/src/test/unit/oc_secret.py2
-rw-r--r--roles/lib_utils/library/repoquery.py607
-rw-r--r--roles/lib_utils/library/yedit.py19
-rw-r--r--roles/lib_utils/src/ansible/repoquery.py35
-rw-r--r--roles/lib_utils/src/class/import.py11
-rw-r--r--roles/lib_utils/src/class/repoquery.py156
-rw-r--r--roles/lib_utils/src/doc/repoquery275
-rw-r--r--roles/lib_utils/src/lib/import.py14
-rw-r--r--roles/lib_utils/src/lib/repoquery.py92
-rw-r--r--roles/lib_utils/src/sources.yml11
-rwxr-xr-xroles/lib_utils/src/test/integration/repoquery.yml136
-rwxr-xr-xroles/lib_utils/src/test/unit/repoquery.py87
13 files changed, 1431 insertions, 21 deletions
diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml
index 8a825a402..28929c02a 100644
--- a/roles/lib_openshift/src/sources.yml
+++ b/roles/lib_openshift/src/sources.yml
@@ -8,6 +8,7 @@ oadm_manage_node.py:
- lib/base.py
- class/oadm_manage_node.py
- ansible/oadm_manage_node.py
+
oc_edit.py:
- doc/generated
- doc/license
@@ -17,6 +18,7 @@ oc_edit.py:
- lib/base.py
- class/oc_edit.py
- ansible/oc_edit.py
+
oc_obj.py:
- doc/generated
- doc/license
@@ -26,6 +28,7 @@ oc_obj.py:
- lib/base.py
- class/oc_obj.py
- ansible/oc_obj.py
+
oc_route.py:
- doc/generated
- doc/license
@@ -36,6 +39,7 @@ oc_route.py:
- lib/route.py
- class/oc_route.py
- ansible/oc_route.py
+
oc_secret.py:
- doc/generated
- doc/license
@@ -46,6 +50,7 @@ oc_secret.py:
- lib/secret.py
- class/oc_secret.py
- ansible/oc_secret.py
+
oc_scale.py:
- doc/generated
- doc/license
@@ -57,6 +62,7 @@ oc_scale.py:
- lib/replicationcontroller.py
- class/oc_scale.py
- ansible/oc_scale.py
+
oc_version.py:
- doc/generated
- doc/license
@@ -66,6 +72,7 @@ oc_version.py:
- lib/base.py
- class/oc_version.py
- ansible/oc_version.py
+
oc_serviceaccount.py:
- doc/generated
- doc/license
diff --git a/roles/lib_openshift/src/test/unit/oc_secret.py b/roles/lib_openshift/src/test/unit/oc_secret.py
index 221f00ed6..835918b95 100755
--- a/roles/lib_openshift/src/test/unit/oc_secret.py
+++ b/roles/lib_openshift/src/test/unit/oc_secret.py
@@ -81,7 +81,7 @@ class OCSecretTest(unittest.TestCase):
# Making sure our mock was called as we expected
mock_openshift_cmd.assert_has_calls([
- mock.call(['get', 'secrets', '-o', 'json', 'secretname'], output=True),
+ mock.call(['get', 'secrets', 'secretname', '-o', 'json'], output=True),
mock.call(['secrets', 'new', 'secretname', 'somesecret.json=/tmp/somesecret.json']),
])
diff --git a/roles/lib_utils/library/repoquery.py b/roles/lib_utils/library/repoquery.py
new file mode 100644
index 000000000..7f0105290
--- /dev/null
+++ b/roles/lib_utils/library/repoquery.py
@@ -0,0 +1,607 @@
+#!/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 json # noqa: F401
+import os # noqa: F401
+import re # noqa: F401
+# pylint: disable=import-error
+import ruamel.yaml as yaml # noqa: F401
+import shutil # 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
+ def __init__(self, name, query_type, show_duplicates,
+ match_version, 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.verbose = verbose
+
+ if self.match_version:
+ self.show_duplicates = True
+
+ self.query_format = "%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release}"
+
+ 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')
+
+ 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.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'] = 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 '''
+
+ 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
+
+ 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['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'),
+ ),
+ supports_check_mode=False,
+ required_if=[('show_duplicates', True, ['name'])],
+ )
+
+ rval = Repoquery.run_ansible(module.params, module.check_mode)
+
+ if 'failed' in rval:
+ module.fail_json(**rval)
+
+ module.exit_json(**rval)
+
+
+if __name__ == "__main__":
+ main()
+
+# -*- -*- -*- End included fragment: ansible/repoquery.py -*- -*- -*-
diff --git a/roles/lib_utils/library/yedit.py b/roles/lib_utils/library/yedit.py
index 8a2bd92f9..7ad2b7181 100644
--- a/roles/lib_utils/library/yedit.py
+++ b/roles/lib_utils/library/yedit.py
@@ -24,18 +24,21 @@
# limitations under the License.
#
-# -*- -*- -*- Begin included fragment: class/import.py -*- -*- -*-
+# -*- -*- -*- Begin included fragment: lib/import.py -*- -*- -*-
-# pylint: disable=wrong-import-order
-import json
-import os
-import re
+# pylint: disable=wrong-import-order,wrong-import-position,unused-import
+
+from __future__ import print_function # noqa: F401
+import json # noqa: F401
+import os # noqa: F401
+import re # noqa: F401
# pylint: disable=import-error
-import ruamel.yaml as yaml
-import shutil
+import ruamel.yaml as yaml # noqa: F401
+import shutil # noqa: F401
+
from ansible.module_utils.basic import AnsibleModule
-# -*- -*- -*- End included fragment: class/import.py -*- -*- -*-
+# -*- -*- -*- End included fragment: lib/import.py -*- -*- -*-
# -*- -*- -*- Begin included fragment: doc/yedit -*- -*- -*-
diff --git a/roles/lib_utils/src/ansible/repoquery.py b/roles/lib_utils/src/ansible/repoquery.py
new file mode 100644
index 000000000..cb4efa6c1
--- /dev/null
+++ b/roles/lib_utils/src/ansible/repoquery.py
@@ -0,0 +1,35 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+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'),
+ ),
+ supports_check_mode=False,
+ required_if=[('show_duplicates', True, ['name'])],
+ )
+
+ rval = Repoquery.run_ansible(module.params, module.check_mode)
+
+ if 'failed' in rval:
+ module.fail_json(**rval)
+
+ module.exit_json(**rval)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/roles/lib_utils/src/class/import.py b/roles/lib_utils/src/class/import.py
deleted file mode 100644
index 249e07228..000000000
--- a/roles/lib_utils/src/class/import.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# flake8: noqa
-# pylint: skip-file
-
-# pylint: disable=wrong-import-order
-import json
-import os
-import re
-# pylint: disable=import-error
-import ruamel.yaml as yaml
-import shutil
-from ansible.module_utils.basic import AnsibleModule
diff --git a/roles/lib_utils/src/class/repoquery.py b/roles/lib_utils/src/class/repoquery.py
new file mode 100644
index 000000000..2447719e2
--- /dev/null
+++ b/roles/lib_utils/src/class/repoquery.py
@@ -0,0 +1,156 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+class Repoquery(RepoqueryCLI):
+ ''' Class to wrap the repoquery
+ '''
+ # pylint: disable=too-many-arguments
+ def __init__(self, name, query_type, show_duplicates,
+ match_version, 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.verbose = verbose
+
+ if self.match_version:
+ self.show_duplicates = True
+
+ self.query_format = "%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release}"
+
+ 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')
+
+ 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.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'] = 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 '''
+
+ 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
+
+ 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['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'}
diff --git a/roles/lib_utils/src/doc/repoquery b/roles/lib_utils/src/doc/repoquery
new file mode 100644
index 000000000..82e273a42
--- /dev/null
+++ b/roles/lib_utils/src/doc/repoquery
@@ -0,0 +1,275 @@
+# flake8: noqa
+# pylint: skip-file
+
+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"
+# }
+# }
+
+'''
diff --git a/roles/lib_utils/src/lib/import.py b/roles/lib_utils/src/lib/import.py
new file mode 100644
index 000000000..d892353a1
--- /dev/null
+++ b/roles/lib_utils/src/lib/import.py
@@ -0,0 +1,14 @@
+# flake8: noqa
+# pylint: skip-file
+
+# pylint: disable=wrong-import-order,wrong-import-position,unused-import
+
+from __future__ import print_function # noqa: F401
+import json # noqa: F401
+import os # noqa: F401
+import re # noqa: F401
+# pylint: disable=import-error
+import ruamel.yaml as yaml # noqa: F401
+import shutil # noqa: F401
+
+from ansible.module_utils.basic import AnsibleModule
diff --git a/roles/lib_utils/src/lib/repoquery.py b/roles/lib_utils/src/lib/repoquery.py
new file mode 100644
index 000000000..91ccd9815
--- /dev/null
+++ b/roles/lib_utils/src/lib/repoquery.py
@@ -0,0 +1,92 @@
+# pylint: skip-file
+# flake8: noqa
+
+'''
+ 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
diff --git a/roles/lib_utils/src/sources.yml b/roles/lib_utils/src/sources.yml
index 9cf3a0981..053b59f77 100644
--- a/roles/lib_utils/src/sources.yml
+++ b/roles/lib_utils/src/sources.yml
@@ -2,7 +2,16 @@
yedit.py:
- doc/generated
- doc/license
-- class/import.py
+- lib/import.py
- doc/yedit
- class/yedit.py
- ansible/yedit.py
+
+repoquery.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/repoquery
+- lib/repoquery.py
+- class/repoquery.py
+- ansible/repoquery.py
diff --git a/roles/lib_utils/src/test/integration/repoquery.yml b/roles/lib_utils/src/test/integration/repoquery.yml
new file mode 100755
index 000000000..425324387
--- /dev/null
+++ b/roles/lib_utils/src/test/integration/repoquery.yml
@@ -0,0 +1,136 @@
+#!/usr/bin/ansible-playbook --module-path=../../../library/
+---
+- hosts: localhost
+ gather_facts: no
+
+ tasks:
+ - name: basic query test - Act
+ repoquery:
+ name: bash
+ register: rq_out
+
+ - name: Set a real package version to be used later
+ set_fact:
+ latest_available_bash_version: "{{ rq_out.results.versions.latest }}"
+ latest_available_full_bash_version: "{{ rq_out.results.versions.latest_full }}"
+
+ - name: basic query test - Assert
+ assert:
+ that:
+ - "rq_out.state == 'list'"
+ - "rq_out.changed == False"
+ - "rq_out.results.returncode == 0"
+ - "rq_out.results.package_found == True"
+ - "rq_out.results.package_name == 'bash'"
+ - "rq_out.results.versions.available_versions | length == 1"
+ - "rq_out.results.versions.available_versions_full | length == 1"
+ - "rq_out.results.versions.latest is defined"
+ - "rq_out.results.versions.latest in rq_out.results.versions.available_versions"
+ - "rq_out.results.versions.latest_full is defined"
+ - "rq_out.results.versions.latest_full in rq_out.results.versions.available_versions_full"
+
+ - name: show_duplicates query test - Act
+ repoquery:
+ name: bash
+ show_duplicates: True
+ register: rq_out
+
+ - name: show_duplicates query test - Assert
+ assert:
+ that:
+ - "rq_out.state == 'list'"
+ - "rq_out.changed == False"
+ - "rq_out.results.returncode == 0"
+ - "rq_out.results.package_found == True"
+ - "rq_out.results.package_name == 'bash'"
+ - "rq_out.results.versions.available_versions | length >= 1"
+ - "rq_out.results.versions.available_versions_full | length >= 1"
+ - "rq_out.results.versions.latest is defined"
+ - "rq_out.results.versions.latest in rq_out.results.versions.available_versions"
+ - "rq_out.results.versions.latest_full is defined"
+ - "rq_out.results.versions.latest_full in rq_out.results.versions.available_versions_full"
+
+ - name: show_duplicates verbose query test - Act
+ repoquery:
+ name: bash
+ show_duplicates: True
+ verbose: True
+ register: rq_out
+
+ - name: show_duplicates verbose query test - Assert
+ assert:
+ that:
+ - "rq_out.state == 'list'"
+ - "rq_out.changed == False"
+ - "rq_out.results.returncode == 0"
+ - "rq_out.results.package_found == True"
+ - "rq_out.results.package_name == 'bash'"
+ - "rq_out.results.raw_versions | length > 0"
+ - "rq_out.results.versions.available_versions | length > 0"
+ - "rq_out.results.versions.available_versions_full | length > 0"
+ - "rq_out.results.versions.latest is defined"
+ - "rq_out.results.versions.latest in rq_out.results.versions.available_versions"
+ - "rq_out.results.versions.latest_full is defined"
+ - "rq_out.results.versions.latest_full in rq_out.results.versions.available_versions_full"
+
+ - name: query package does not exist query test - Act
+ repoquery:
+ name: somemadeuppackagenamethatwontmatch
+ show_duplicates: True
+ register: rq_out
+
+ - name: query package does not exist query test - Assert
+ assert:
+ that:
+ - "rq_out.state == 'list'"
+ - "rq_out.changed == False"
+ - "rq_out.results.returncode == 0"
+ - "rq_out.results.package_found == False"
+ - "rq_out.results.results == ''"
+
+
+ - name: query match_version does not exist query test - Act
+ repoquery:
+ name: bash
+ show_duplicates: True
+ match_version: somemadeupversionnotexist
+ register: rq_out
+
+ - name: query match_version does not exist query test - Assert
+ assert:
+ that:
+ - "rq_out.state == 'list'"
+ - "rq_out.changed == False"
+ - "rq_out.results.returncode == 0"
+ - "rq_out.results.package_found == True"
+ - "rq_out.results.package_name == 'bash'"
+ - "rq_out.results.versions.matched_version_found == False"
+ - "rq_out.results.versions.available_versions | length > 0"
+ - "rq_out.results.versions.available_versions_full | length > 0"
+ - "rq_out.results.versions.latest is defined"
+ - "rq_out.results.versions.latest in rq_out.results.versions.available_versions"
+ - "rq_out.results.versions.latest_full is defined"
+ - "rq_out.results.versions.latest_full in rq_out.results.versions.available_versions_full"
+
+ - name: query match_version exists query test - Act
+ repoquery:
+ name: bash
+ show_duplicates: True
+ match_version: "{{ latest_available_bash_version }}"
+ register: rq_out
+
+ - name: query match_version exists query test - Assert
+ assert:
+ that:
+ - "rq_out.state == 'list'"
+ - "rq_out.changed == False"
+ - "rq_out.results.returncode == 0"
+ - "rq_out.results.package_found == True"
+ - "rq_out.results.package_name == 'bash'"
+ - "rq_out.results.versions.matched_version_found == True"
+ - "rq_out.results.versions.available_versions | length > 0"
+ - "rq_out.results.versions.available_versions_full | length > 0"
+ - "rq_out.results.versions.latest is defined"
+ - "rq_out.results.versions.latest in rq_out.results.versions.available_versions"
+ - "rq_out.results.versions.latest_full is defined"
+ - "rq_out.results.versions.latest_full in rq_out.results.versions.available_versions_full"
diff --git a/roles/lib_utils/src/test/unit/repoquery.py b/roles/lib_utils/src/test/unit/repoquery.py
new file mode 100755
index 000000000..c487ab254
--- /dev/null
+++ b/roles/lib_utils/src/test/unit/repoquery.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for repoquery
+'''
+# To run:
+# ./repoquery.py
+#
+# .
+# Ran 1 test in 0.002s
+#
+# OK
+
+import os
+import sys
+import unittest
+import mock
+
+# Removing invalid variable names for tests so that I can
+# keep them brief
+# pylint: disable=invalid-name,no-name-in-module
+# Disable import-error b/c our libraries aren't loaded in jenkins
+# pylint: disable=import-error,wrong-import-position
+# place class in our python path
+module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library') # noqa: E501
+sys.path.insert(0, module_path)
+from repoquery import Repoquery # noqa: E402
+
+
+class RepoQueryTest(unittest.TestCase):
+ '''
+ Test class for RepoQuery
+ '''
+
+ def setUp(self):
+ ''' setup method for other tests '''
+ pass
+
+ @mock.patch('repoquery._run')
+ def test_querying_a_package(self, mock_cmd):
+ ''' Testing querying a package '''
+
+ # Arrange
+
+ # run_ansible input parameters
+ params = {
+ 'state': 'list',
+ 'name': 'bash',
+ 'query_type': 'repos',
+ 'verbose': False,
+ 'show_duplicates': False,
+ 'match_version': None,
+ }
+
+ valid_stderr = '''Repo rhel-7-server-extras-rpms forced skip_if_unavailable=True due to: /etc/pki/entitlement/3268107132875399464-key.pem
+ Repo rhel-7-server-rpms forced skip_if_unavailable=True due to: /etc/pki/entitlement/4128505182875899164-key.pem''' # not real
+
+ # Return values of our mocked function call. These get returned once per call.
+ mock_cmd.side_effect = [
+ (0, '4.2.46|21.el7_3|x86_64|rhel-7-server-rpms|4.2.46-21.el7_3', valid_stderr), # first call to the mock
+ ]
+
+ # Act
+ results = Repoquery.run_ansible(params, False)
+
+ # Assert
+ self.assertEqual(results['state'], 'list')
+ self.assertFalse(results['changed'])
+ self.assertTrue(results['results']['package_found'])
+ self.assertEqual(results['results']['returncode'], 0)
+ self.assertEqual(results['results']['package_name'], 'bash')
+ self.assertEqual(results['results']['versions'], {'latest_full': '4.2.46-21.el7_3',
+ 'available_versions': ['4.2.46'],
+ 'available_versions_full': ['4.2.46-21.el7_3'],
+ 'latest': '4.2.46'})
+
+ # Making sure our mock was called as we expected
+ mock_cmd.assert_has_calls([
+ mock.call(['/usr/bin/repoquery', '--plugins', '--quiet', '--pkgnarrow=repos', '--queryformat=%{version}|%{release}|%{arch}|%{repo}|%{version}-%{release}', 'bash']),
+ ])
+
+ def tearDown(self):
+ '''TearDown method'''
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()