From b0a1baa981168dc619a318610d26ed2ebd5b309a Mon Sep 17 00:00:00 2001 From: Cristian van Ee Date: Tue, 30 Sep 2014 10:25:40 +0200 Subject: [PATCH] Created dnf package manager module, based on the yum package manager module. --- lib/ansible/modules/extras/packaging/dnf.py | 841 ++++++++++++++++++++ 1 file changed, 841 insertions(+) create mode 100644 lib/ansible/modules/extras/packaging/dnf.py diff --git a/lib/ansible/modules/extras/packaging/dnf.py b/lib/ansible/modules/extras/packaging/dnf.py new file mode 100644 index 00000000000..1c0a94a6704 --- /dev/null +++ b/lib/ansible/modules/extras/packaging/dnf.py @@ -0,0 +1,841 @@ +#!/usr/bin/python -tt +# -*- coding: utf-8 -*- + +# Written by Cristian van Ee +# +# This file is part of Ansible +# +# Ansible 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 3 of the License, or +# (at your option) any later version. +# +# Ansible 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 Ansible. If not, see . +# + + +import traceback +import os +import dnf + +try: + from dnf import find_unfinished_transactions, find_ts_remaining + from rpmUtils.miscutils import splitFilename + transaction_helpers = True +except: + transaction_helpers = False + +DOCUMENTATION = ''' +--- +module: dnf +version_added: historical +short_description: Manages packages with the I(dnf) package manager +description: + - Installs, upgrade, removes, and lists packages and groups with the I(dnf) package manager. +options: + name: + description: + - "Package name, or package specifier with version, like C(name-1.0). When using state=latest, this can be '*' which means run: dnf -y update. You can also pass a url or a local path to a rpm file." + required: true + version_added: "1.8" + default: null + aliases: [] + list: + description: + - Various (non-idempotent) commands for usage with C(/usr/bin/ansible) and I(not) playbooks. See examples. + required: false + version_added: "1.8" + default: null + state: + description: + - Whether to install (C(present), C(latest)), or remove (C(absent)) a package. + required: false + choices: [ "present", "latest", "absent" ] + version_added: "1.8" + default: "present" + enablerepo: + description: + - I(Repoid) of repositories to enable for the install/update operation. + These repos will not persist beyond the transaction. + When specifying multiple repos, separate them with a ",". + required: false + version_added: "1.8" + default: null + aliases: [] + + disablerepo: + description: + - I(Repoid) of repositories to disable for the install/update operation. + These repos will not persist beyond the transaction. + When specifying multiple repos, separate them with a ",". + required: false + version_added: "1.8" + default: null + aliases: [] + + conf_file: + description: + - The remote dnf configuration file to use for the transaction. + required: false + version_added: "1.8" + default: null + aliases: [] + + disable_gpg_check: + description: + - Whether to disable the GPG checking of signatures of packages being + installed. Has an effect only if state is I(present) or I(latest). + required: false + version_added: "1.8" + default: "no" + choices: ["yes", "no"] + aliases: [] + +notes: [] +# informational: requirements for nodes +requirements: [ dnf ] +author: Cristian van Ee +''' + +EXAMPLES = ''' +- name: install the latest version of Apache + dnf: name=httpd state=latest + +- name: remove the Apache package + dnf: name=httpd state=absent + +- name: install the latest version of Apache from the testing repo + dnf: name=httpd enablerepo=testing state=present + +- name: upgrade all packages + dnf: name=* state=latest + +- name: install the nginx rpm from a remote repo + dnf: name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present + +- name: install nginx rpm from a local file + dnf: name=/usr/local/src/nginx-release-centos-6-0.el6.ngx.noarch.rpm state=present + +- name: install the 'Development tools' package group + dnf: name="@Development tools" state=present + +''' + +def_qf = "%{name}-%{version}-%{release}.%{arch}" + +repoquery='/usr/bin/repoquery' +if not os.path.exists(repoquery): + repoquery = None + +dnfbin='/usr/bin/dnf' + +import syslog + +def log(msg): + syslog.openlog('ansible-dnf', 0, syslog.LOG_USER) + syslog.syslog(syslog.LOG_NOTICE, msg) + +def dnf_base(conf_file=None, cachedir=False): + + my = dnf.Base() + my.logging.verbose_level=0 + my.logging.verbose_level=0 + if conf_file and os.path.exists(conf_file): + my.config = conf_file + if cachedir or os.geteuid() != 0: + if cachedir or os.geteuid() != 0: + if hasattr(my, 'setCacheDir'): + my.setCacheDir() +# else: +# cachedir = dnf.misc.getCacheDir() +# my.repos.setCacheDir(cachedir) +# my.conf.cache = 0 + + return my + +def install_dnf_utils(module): + + if not module.check_mode: + dnf_path = module.get_bin_path('dnf') + if dnf_path: + rc, so, se = module.run_command('%s -y install dnf-plugins-core' % dnf_path) + if rc == 0: + this_path = module.get_bin_path('repoquery') + global repoquery + repoquery = this_path + +def po_to_nevra(po): + + if hasattr(po, 'ui_nevra'): + return po.ui_nevra + else: + return '%s-%s-%s.%s' % (po.name, po.version, po.release, po.arch) + +def is_installed(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=[], dis_repos=[], is_pkg=False): + + if not repoq: + + pkgs = [] + try: + my = dnf_base(conf_file) + for rid in en_repos: + my.repos.enableRepo(rid) + for rid in dis_repos: + my.repos.disableRepo(rid) + + e,m,u = my.rpmdb.matchPackageNames([pkgspec]) + pkgs = e + m + if not pkgs: + pkgs.extend(my.returnInstalledPackagesByDep(pkgspec)) + except Exception, e: + module.fail_json(msg="Failure talking to dnf: %s" % e) + + return [ po_to_nevra(p) for p in pkgs ] + + else: + + cmd = repoq + ["--disablerepo=*", "--pkgnarrow=installed", "--qf", qf, pkgspec] + rc,out,err = module.run_command(cmd) + if not is_pkg: + cmd = repoq + ["--disablerepo=*", "--pkgnarrow=installed", "--qf", qf, "--whatprovides", pkgspec] + rc2,out2,err2 = module.run_command(cmd) + else: + rc2,out2,err2 = (0, '', '') + + if rc == 0 and rc2 == 0: + out += out2 + return [ p for p in out.split('\n') if p.strip() ] + else: + module.fail_json(msg='Error from repoquery: %s: %s' % (cmd, err + err2)) + + return [] + +def is_available(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=[], dis_repos=[]): + + if not repoq: + + pkgs = [] + try: + my = dnf_base(conf_file) + for rid in en_repos: + my.repos.enableRepo(rid) + for rid in dis_repos: + my.repos.disableRepo(rid) + + e,m,u = my.pkgSack.matchPackageNames([pkgspec]) + pkgs = e + m + if not pkgs: + pkgs.extend(my.returnPackagesByDep(pkgspec)) + except Exception, e: + module.fail_json(msg="Failure talking to dnf: %s" % e) + + return [ po_to_nevra(p) for p in pkgs ] + + else: + myrepoq = list(repoq) + + for repoid in dis_repos: + r_cmd = ['--disablerepo', repoid] + myrepoq.extend(r_cmd) + + for repoid in en_repos: + r_cmd = ['--enablerepo', repoid] + myrepoq.extend(r_cmd) + + cmd = myrepoq + ["--qf", qf, pkgspec] + rc,out,err = module.run_command(cmd) + if rc == 0: + return [ p for p in out.split('\n') if p.strip() ] + else: + module.fail_json(msg='Error from repoquery: %s: %s' % (cmd, err)) + + + return [] + +def is_update(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=[], dis_repos=[]): + + if not repoq: + + retpkgs = [] + pkgs = [] + updates = [] + + try: + my = dnf_base(conf_file) + for rid in en_repos: + my.repos.enableRepo(rid) + for rid in dis_repos: + my.repos.disableRepo(rid) + + pkgs = my.returnPackagesByDep(pkgspec) + my.returnInstalledPackagesByDep(pkgspec) + if not pkgs: + e,m,u = my.pkgSack.matchPackageNames([pkgspec]) + pkgs = e + m + updates = my.doPackageLists(pkgnarrow='updates').updates + except Exception, e: + module.fail_json(msg="Failure talking to dnf: %s" % e) + + for pkg in pkgs: + if pkg in updates: + retpkgs.append(pkg) + + return set([ po_to_nevra(p) for p in retpkgs ]) + + else: + myrepoq = list(repoq) + for repoid in dis_repos: + r_cmd = ['--disablerepo', repoid] + myrepoq.extend(r_cmd) + + for repoid in en_repos: + r_cmd = ['--enablerepo', repoid] + myrepoq.extend(r_cmd) + + cmd = myrepoq + ["--pkgnarrow=updates", "--qf", qf, pkgspec] + rc,out,err = module.run_command(cmd) + + if rc == 0: + return set([ p for p in out.split('\n') if p.strip() ]) + else: + module.fail_json(msg='Error from repoquery: %s: %s' % (cmd, err)) + + return [] + +def what_provides(module, repoq, req_spec, conf_file, qf=def_qf, en_repos=[], dis_repos=[]): + + if not repoq: + + pkgs = [] + try: + my = dnf_base(conf_file) + for rid in en_repos: + my.repos.enableRepo(rid) + for rid in dis_repos: + my.repos.disableRepo(rid) + + pkgs = my.returnPackagesByDep(req_spec) + my.returnInstalledPackagesByDep(req_spec) + if not pkgs: + e,m,u = my.pkgSack.matchPackageNames([req_spec]) + pkgs.extend(e) + pkgs.extend(m) + e,m,u = my.rpmdb.matchPackageNames([req_spec]) + pkgs.extend(e) + pkgs.extend(m) + except Exception, e: + module.fail_json(msg="Failure talking to dnf: %s" % e) + + return set([ po_to_nevra(p) for p in pkgs ]) + + else: + myrepoq = list(repoq) + for repoid in dis_repos: + r_cmd = ['--disablerepo', repoid] + myrepoq.extend(r_cmd) + + for repoid in en_repos: + r_cmd = ['--enablerepo', repoid] + myrepoq.extend(r_cmd) + + cmd = myrepoq + ["--qf", qf, "--whatprovides", req_spec] + rc,out,err = module.run_command(cmd) + cmd = myrepoq + ["--qf", qf, req_spec] + rc2,out2,err2 = module.run_command(cmd) + if rc == 0 and rc2 == 0: + out += out2 + pkgs = set([ p for p in out.split('\n') if p.strip() ]) + if not pkgs: + pkgs = is_installed(module, repoq, req_spec, conf_file, qf=qf) + return pkgs + else: + module.fail_json(msg='Error from repoquery: %s: %s' % (cmd, err + err2)) + + return [] + +def transaction_exists(pkglist): + """ + checks the package list to see if any packages are + involved in an incomplete transaction + """ + + conflicts = [] + if not transaction_helpers: + return conflicts + + # first, we create a list of the package 'nvreas' + # so we can compare the pieces later more easily + pkglist_nvreas = [] + for pkg in pkglist: + pkglist_nvreas.append(splitFilename(pkg)) + + # next, we build the list of packages that are + # contained within an unfinished transaction + unfinished_transactions = find_unfinished_transactions() + for trans in unfinished_transactions: + steps = find_ts_remaining(trans) + for step in steps: + # the action is install/erase/etc., but we only + # care about the package spec contained in the step + (action, step_spec) = step + (n,v,r,e,a) = splitFilename(step_spec) + # and see if that spec is in the list of packages + # requested for installation/updating + for pkg in pkglist_nvreas: + # if the name and arch match, we're going to assume + # this package is part of a pending transaction + # the label is just for display purposes + label = "%s-%s" % (n,a) + if n == pkg[0] and a == pkg[4]: + if label not in conflicts: + conflicts.append("%s-%s" % (n,a)) + break + return conflicts + +def local_nvra(module, path): + """return nvra of a local rpm passed in""" + + cmd = ['/bin/rpm', '-qp' ,'--qf', + '%{name}-%{version}-%{release}.%{arch}\n', path ] + rc, out, err = module.run_command(cmd) + if rc != 0: + return None + nvra = out.split('\n')[0] + return nvra + +def pkg_to_dict(pkgstr): + + if pkgstr.strip(): + n,e,v,r,a,repo = pkgstr.split('|') + else: + return {'error_parsing': pkgstr} + + d = { + 'name':n, + 'arch':a, + 'epoch':e, + 'release':r, + 'version':v, + 'repo':repo, + 'nevra': '%s:%s-%s-%s.%s' % (e,n,v,r,a) + } + + if repo == 'installed': + d['dnfstate'] = 'installed' + else: + d['dnfstate'] = 'available' + + return d + +def repolist(module, repoq, qf="%{repoid}"): + + cmd = repoq + ["--qf", qf, "-a"] + rc,out,err = module.run_command(cmd) + ret = [] + if rc == 0: + ret = set([ p for p in out.split('\n') if p.strip() ]) + return ret + +def list_stuff(module, conf_file, stuff): + + qf = "%{name}|%{epoch}|%{version}|%{release}|%{arch}|%{repoid}" + repoq = [repoquery, '--show-duplicates', '--plugins', '--quiet', '-q'] + if conf_file and os.path.exists(conf_file): + repoq += ['-c', conf_file] + + if stuff == 'installed': + return [ pkg_to_dict(p) for p in is_installed(module, repoq, '-a', conf_file, qf=qf) if p.strip() ] + elif stuff == 'updates': + return [ pkg_to_dict(p) for p in is_update(module, repoq, '-a', conf_file, qf=qf) if p.strip() ] + elif stuff == 'available': + return [ pkg_to_dict(p) for p in is_available(module, repoq, '-a', conf_file, qf=qf) if p.strip() ] + elif stuff == 'repos': + return [ dict(repoid=name, state='enabled') for name in repolist(module, repoq) if name.strip() ] + else: + return [ pkg_to_dict(p) for p in is_installed(module, repoq, stuff, conf_file, qf=qf) + is_available(module, repoq, stuff, conf_file, qf=qf) if p.strip() ] + +def install(module, items, repoq, dnf_basecmd, conf_file, en_repos, dis_repos): + + res = {} + res['results'] = [] + res['msg'] = '' + res['rc'] = 0 + res['changed'] = False + + for spec in items: + pkg = None + + # check if pkgspec is installed (if possible for idempotence) + # localpkg + if spec.endswith('.rpm') and '://' not in spec: + # get the pkg name-v-r.arch + if not os.path.exists(spec): + res['msg'] += "No Package file matching '%s' found on system" % spec + module.fail_json(**res) + + nvra = local_nvra(module, spec) + # look for them in the rpmdb + if is_installed(module, repoq, nvra, conf_file, en_repos=en_repos, dis_repos=dis_repos): + # if they are there, skip it + continue + pkg = spec + + # URL + elif '://' in spec: + pkg = spec + + #groups :( + elif spec.startswith('@'): + # complete wild ass guess b/c it's a group + pkg = spec + + # range requires or file-requires or pkgname :( + else: + # most common case is the pkg is already installed and done + # short circuit all the bs - and search for it as a pkg in is_installed + # if you find it then we're done + if not set(['*','?']).intersection(set(spec)): + pkgs = is_installed(module, repoq, spec, conf_file, en_repos=en_repos, dis_repos=dis_repos, is_pkg=True) + if pkgs: + res['results'].append('%s providing %s is already installed' % (pkgs[0], spec)) + continue + + # look up what pkgs provide this + pkglist = what_provides(module, repoq, spec, conf_file, en_repos=en_repos, dis_repos=dis_repos) + if not pkglist: + res['msg'] += "No Package matching '%s' found available, installed or updated" % spec + module.fail_json(**res) + + # if any of the packages are involved in a transaction, fail now + # so that we don't hang on the dnf operation later + conflicts = transaction_exists(pkglist) + if len(conflicts) > 0: + res['msg'] += "The following packages have pending transactions: %s" % ", ".join(conflicts) + module.fail_json(**res) + + # if any of them are installed + # then nothing to do + + found = False + for this in pkglist: + if is_installed(module, repoq, this, conf_file, en_repos=en_repos, dis_repos=dis_repos, is_pkg=True): + found = True + res['results'].append('%s providing %s is already installed' % (this, spec)) + break + + # if the version of the pkg you have installed is not in ANY repo, but there are + # other versions in the repos (both higher and lower) then the previous checks won't work. + # so we check one more time. This really only works for pkgname - not for file provides or virt provides + # but virt provides should be all caught in what_provides on its own. + # highly irritating + if not found: + if is_installed(module, repoq, spec, conf_file, en_repos=en_repos, dis_repos=dis_repos): + found = True + res['results'].append('package providing %s is already installed' % (spec)) + + if found: + continue + + # if not - then pass in the spec as what to install + # we could get here if nothing provides it but that's not + # the error we're catching here + pkg = spec + + cmd = dnf_basecmd + ['install', pkg] + + if module.check_mode: + module.exit_json(changed=True) + + changed = True + + rc, out, err = module.run_command(cmd) + + # Fail on invalid urls: + if (rc == 1 and '://' in spec and ('No package %s available.' % spec in out or 'Cannot open: %s. Skipping.' % spec in err)): + err = 'Package at %s could not be installed' % spec + module.fail_json(changed=False,msg=err,rc=1) + elif (rc != 0 and 'Nothing to do' in err) or 'Nothing to do' in out: + # avoid failing in the 'Nothing To Do' case + # this may happen with an URL spec. + # for an already installed group, + # we get rc = 0 and 'Nothing to do' in out, not in err. + rc = 0 + err = '' + out = '%s: Nothing to do' % spec + changed = False + + res['rc'] += rc + res['results'].append(out) + res['msg'] += err + + # FIXME - if we did an install - go and check the rpmdb to see if it actually installed + # look for the pkg in rpmdb + # look for the pkg via obsoletes + + # accumulate any changes + res['changed'] |= changed + + module.exit_json(**res) + + +def remove(module, items, repoq, dnf_basecmd, conf_file, en_repos, dis_repos): + + res = {} + res['results'] = [] + res['msg'] = '' + res['changed'] = False + res['rc'] = 0 + + for pkg in items: + is_group = False + # group remove - this is doom on a stick + if pkg.startswith('@'): + is_group = True + else: + if not is_installed(module, repoq, pkg, conf_file, en_repos=en_repos, dis_repos=dis_repos): + res['results'].append('%s is not installed' % pkg) + continue + + # run an actual dnf transaction + cmd = dnf_basecmd + ["remove", pkg] + + if module.check_mode: + module.exit_json(changed=True) + + rc, out, err = module.run_command(cmd) + + res['rc'] += rc + res['results'].append(out) + res['msg'] += err + + # compile the results into one batch. If anything is changed + # then mark changed + # at the end - if we've end up failed then fail out of the rest + # of the process + + # at this point we should check to see if the pkg is no longer present + + if not is_group: # we can't sensibly check for a group being uninstalled reliably + # look to see if the pkg shows up from is_installed. If it doesn't + if not is_installed(module, repoq, pkg, conf_file, en_repos=en_repos, dis_repos=dis_repos): + res['changed'] = True + else: + module.fail_json(**res) + + if rc != 0: + module.fail_json(**res) + + module.exit_json(**res) + +def latest(module, items, repoq, dnf_basecmd, conf_file, en_repos, dis_repos): + + res = {} + res['results'] = [] + res['msg'] = '' + res['changed'] = False + res['rc'] = 0 + + for spec in items: + + pkg = None + basecmd = 'update' + cmd = '' + # groups, again + if spec.startswith('@'): + pkg = spec + + elif spec == '*': #update all + # use check-update to see if there is any need + rc,out,err = module.run_command(dnf_basecmd + ['check-update']) + if rc == 100: + cmd = dnf_basecmd + [basecmd] + else: + res['results'].append('All packages up to date') + continue + + # dep/pkgname - find it + else: + if is_installed(module, repoq, spec, conf_file, en_repos=en_repos, dis_repos=dis_repos): + basecmd = 'update' + else: + basecmd = 'install' + + pkglist = what_provides(module, repoq, spec, conf_file, en_repos=en_repos, dis_repos=dis_repos) + if not pkglist: + res['msg'] += "No Package matching '%s' found available, installed or updated" % spec + module.fail_json(**res) + + nothing_to_do = True + for this in pkglist: + if basecmd == 'install' and is_available(module, repoq, this, conf_file, en_repos=en_repos, dis_repos=dis_repos): + nothing_to_do = False + break + + if basecmd == 'update' and is_update(module, repoq, this, conf_file, en_repos=en_repos, dis_repos=en_repos): + nothing_to_do = False + break + + if nothing_to_do: + res['results'].append("All packages providing %s are up to date" % spec) + continue + + # if any of the packages are involved in a transaction, fail now + # so that we don't hang on the dnf operation later + conflicts = transaction_exists(pkglist) + if len(conflicts) > 0: + res['msg'] += "The following packages have pending transactions: %s" % ", ".join(conflicts) + module.fail_json(**res) + + pkg = spec + if not cmd: + cmd = dnf_basecmd + [basecmd, pkg] + + if module.check_mode: + return module.exit_json(changed=True) + + rc, out, err = module.run_command(cmd) + + res['rc'] += rc + res['results'].append(out) + res['msg'] += err + + # FIXME if it is - update it and check to see if it applied + # check to see if there is no longer an update available for the pkgspec + + if rc: + res['failed'] = True + else: + res['changed'] = True + + module.exit_json(**res) + +def ensure(module, state, pkgspec, conf_file, enablerepo, disablerepo, + disable_gpg_check): + + # take multiple args comma separated + items = pkgspec.split(',') + + # need debug level 2 to get 'Nothing to do' for groupinstall. + dnf_basecmd = [dnfbin, '-d', '2', '-y'] + + + if not repoquery: + repoq = None + else: + repoq = [repoquery, '--show-duplicates', '--plugins', '--quiet', '-q'] + + if conf_file and os.path.exists(conf_file): + dnf_basecmd += ['-c', conf_file] + if repoq: + repoq += ['-c', conf_file] + + dis_repos =[] + en_repos = [] + if disablerepo: + dis_repos = disablerepo.split(',') + if enablerepo: + en_repos = enablerepo.split(',') + + for repoid in dis_repos: + r_cmd = ['--disablerepo=%s' % repoid] + dnf_basecmd.extend(r_cmd) + + for repoid in en_repos: + r_cmd = ['--enablerepo=%s' % repoid] + dnf_basecmd.extend(r_cmd) + + if state in ['installed', 'present', 'latest']: + my = dnf_base(conf_file) + try: + for r in dis_repos: + my.repos.disableRepo(r) + + current_repos = dnf.yum.config.RepoConf() + for r in en_repos: + try: + my.repos.enableRepo(r) + new_repos = my.repos.repos.keys() + for i in new_repos: + if not i in current_repos: + rid = my.repos.getRepo(i) + a = rid.repoXML.repoid + current_repos = new_repos + except dnf.exceptions.Error, e: + module.fail_json(msg="Error setting/accessing repo %s: %s" % (r, e)) + except dnf.exceptions.Error, e: + module.fail_json(msg="Error accessing repos: %s" % e) + + if state in ['installed', 'present']: + if disable_gpg_check: + dnf_basecmd.append('--nogpgcheck') + install(module, items, repoq, dnf_basecmd, conf_file, en_repos, dis_repos) + elif state in ['removed', 'absent']: + remove(module, items, repoq, dnf_basecmd, conf_file, en_repos, dis_repos) + elif state == 'latest': + if disable_gpg_check: + dnf_basecmd.append('--nogpgcheck') + latest(module, items, repoq, dnf_basecmd, conf_file, en_repos, dis_repos) + + # should be caught by AnsibleModule argument_spec + return dict(changed=False, failed=True, results='', errors='unexpected state') + +def main(): + + # state=installed name=pkgspec + # state=removed name=pkgspec + # state=latest name=pkgspec + # + # informational commands: + # list=installed + # list=updates + # list=available + # list=repos + # list=pkgspec + + module = AnsibleModule( + argument_spec = dict( + name=dict(aliases=['pkg']), + # removed==absent, installed==present, these are accepted as aliases + state=dict(default='installed', choices=['absent','present','installed','removed','latest']), + enablerepo=dict(), + disablerepo=dict(), + list=dict(), + conf_file=dict(default=None), + disable_gpg_check=dict(required=False, default="no", type='bool'), + # this should not be needed, but exists as a failsafe + install_repoquery=dict(required=False, default="yes", type='bool'), + ), + required_one_of = [['name','list']], + mutually_exclusive = [['name','list']], + supports_check_mode = True + ) + + # this should not be needed, but exists as a failsafe + params = module.params + if params['install_repoquery'] and not repoquery and not module.check_mode: + install_dnf_utils(module) + + if params['list']: + if not repoquery: + module.fail_json(msg="repoquery is required to use list= with this module. Please install the dnf-utils package.") + results = dict(results=list_stuff(module, params['conf_file'], params['list'])) + module.exit_json(**results) + + else: + pkg = params['name'] + state = params['state'] + enablerepo = params.get('enablerepo', '') + disablerepo = params.get('disablerepo', '') + disable_gpg_check = params['disable_gpg_check'] + res = ensure(module, state, pkg, params['conf_file'], enablerepo, + disablerepo, disable_gpg_check) + module.fail_json(msg="we should never get here unless this all failed", **res) + +# import module snippets +from ansible.module_utils.basic import * +main() +