da26266ef3
include local_nvra change the remove behavior to pretty much NEVEr error out if the pkg is not there (or anywhere)
510 lines
16 KiB
Python
Executable file
510 lines
16 KiB
Python
Executable file
#!/usr/bin/python -tt
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2012, Red Hat, Inc
|
|
# Written by Seth Vidal <skvidal at fedoraproject.org>
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
|
|
import traceback
|
|
import os
|
|
import yum
|
|
|
|
|
|
def_qf = "%{name}-%{version}-%{release}.%{arch}"
|
|
repoquery='/usr/bin/repoquery'
|
|
if not os.path.exists(repoquery):
|
|
repoquery = None
|
|
yumbin='/usr/bin/yum'
|
|
|
|
|
|
def yum_base(conf_file=None, cachedir=False):
|
|
my = yum.YumBase()
|
|
my.preconf.debuglevel=0
|
|
my.preconf.errorlevel=0
|
|
if conf_file and os.path.exists(conf_file):
|
|
my.preconf.fn = conf_file
|
|
if cachedir or os.geteuid() != 0:
|
|
if hasattr(my, 'setCacheDir'):
|
|
my.setCacheDir()
|
|
else:
|
|
cachedir = yum.misc.getCacheDir()
|
|
my.repos.setCacheDir(cachedir)
|
|
my.conf.cache = 0
|
|
|
|
return my
|
|
|
|
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):
|
|
if not repoq:
|
|
pkgs = []
|
|
try:
|
|
my = yum_base(conf_file)
|
|
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 yum: %s" % e)
|
|
|
|
return [ po_to_nevra(p) for p in pkgs ]
|
|
else:
|
|
cmd = repoq + ["--disablerepo=*", "--pkgnarrow=installed", "--qf", qf, pkgspec]
|
|
rc,out,err = run(cmd)
|
|
cmd = repoq + ["--disablerepo=*", "--pkgnarrow=installed", "--qf", qf, "--whatprovides", pkgspec]
|
|
rc2,out2,err2 = run(cmd)
|
|
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' % err + err2)
|
|
|
|
return []
|
|
|
|
def is_available(module, repoq, pkgspec, conf_file, qf=def_qf):
|
|
if not repoq:
|
|
pkgs = []
|
|
try:
|
|
my = yum_base(conf_file)
|
|
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 yum: %s" % e)
|
|
|
|
return [ po_to_nevra(p) for p in pkgs ]
|
|
else:
|
|
cmd = repoq + ["--qf", qf, pkgspec]
|
|
rc,out,err = run(cmd)
|
|
if rc == 0:
|
|
return [ p for p in out.split('\n') if p.strip() ]
|
|
else:
|
|
module.fail_json(msg='Error from repoquery: %s' % err)
|
|
|
|
return []
|
|
|
|
def is_update(module, repoq, pkgspec, conf_file, qf=def_qf):
|
|
if not repoq:
|
|
retpkgs = []
|
|
pkgs = []
|
|
updates = []
|
|
try:
|
|
my = yum_base(conf_file)
|
|
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 yum: %s" % e)
|
|
|
|
for pkg in pkgs:
|
|
if pkg in updates:
|
|
retpkgs.append(pkg)
|
|
|
|
return set([ po_to_nevra(p) for p in retpkgs ])
|
|
|
|
else:
|
|
cmd = repoq + ["--pkgnarrow=updates", "--qf", qf, pkgspec]
|
|
rc,out,err = run(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' % err)
|
|
|
|
return []
|
|
|
|
def what_provides(module, repoq, req_spec, conf_file, qf=def_qf):
|
|
if not repoq:
|
|
pkgs = []
|
|
try:
|
|
my = yum_base(conf_file)
|
|
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 yum: %s" % e)
|
|
|
|
return set([ po_to_nevra(p) for p in pkgs ])
|
|
else:
|
|
cmd = repoq + ["--qf", qf, "--whatprovides", req_spec]
|
|
rc,out,err = run(cmd)
|
|
cmd = repoq + ["--qf", qf, req_spec]
|
|
rc2,out2,err2 = run(cmd)
|
|
if rc == 0 and rc2 == 0:
|
|
out += out2
|
|
return set([ p for p in out.split('\n') if p.strip() ])
|
|
else:
|
|
module.fail_json(msg='Error from repoquery: %s' % err + err2)
|
|
|
|
return []
|
|
|
|
def local_nvra(path):
|
|
"""return nvra of a local rpm passed in"""
|
|
|
|
cmd = ['/bin/rpm', '-qp' ,'--qf',
|
|
'%%{name}-%%{version}-%%{release}.%%{arch}\n', path ]
|
|
rc, out, err = run(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['yumstate'] = 'installed'
|
|
else:
|
|
d['yumstate'] = 'available'
|
|
|
|
return d
|
|
|
|
def repolist(repoq, qf="%{repoid}"):
|
|
cmd = repoq + ["--qf", qf, "-a"]
|
|
rc,out,err = run(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(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 run(command):
|
|
try:
|
|
cmd = subprocess.Popen(command,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
out, err = cmd.communicate()
|
|
except (OSError, IOError), e:
|
|
rc = 1
|
|
err = str(e)
|
|
out = ''
|
|
except:
|
|
rc = 1
|
|
err = traceback.format_exc()
|
|
out = ''
|
|
if out is None:
|
|
out = ''
|
|
if err is None:
|
|
err = ''
|
|
else:
|
|
rc = cmd.returncode
|
|
|
|
return rc, out, err
|
|
|
|
|
|
def install(module, items, repoq, yum_basecmd, conf_file):
|
|
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'):
|
|
# get the pkg name-v-r.arch
|
|
nvra = local_nvra(spec)
|
|
# look for them in the rpmdb
|
|
if is_installed(repoq, nvra, conf_file):
|
|
# if they are there, skip it
|
|
continue
|
|
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:
|
|
# look up what pkgs provide this
|
|
pkglist = what_provides(module, repoq, spec, conf_file)
|
|
if not pkglist:
|
|
res['msg'] += "No Package matching '%s' found available, installed or updated" % spec
|
|
res['failed'] = True
|
|
module.exit_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):
|
|
found = True
|
|
res['results'].append('%s providing %s is already installed' % (this, 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 = yum_basecmd + ['install', pkg]
|
|
rc, out, err = run(cmd)
|
|
# 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
|
|
if rc:
|
|
res['changed'] = False
|
|
res['rc'] = rc
|
|
res['results'].append(out)
|
|
res['msg'] += err
|
|
else:
|
|
res['changed'] = True
|
|
res['rc'] = 0
|
|
res['results'].append(out)
|
|
res['msg'] += err
|
|
|
|
module.exit_json(**res)
|
|
|
|
|
|
def remove(module, items, repoq, yum_basecmd, conf_file):
|
|
res = {}
|
|
res['results'] = []
|
|
res['msg'] = ''
|
|
res['changed'] = False
|
|
res['rc'] = 0
|
|
|
|
for spec in items:
|
|
pkg = None
|
|
|
|
# group remove - hope you like things dying!
|
|
if spec.startswith('@'):
|
|
pkg = spec
|
|
# req or pkgname remove
|
|
else:
|
|
pkglist = is_installed(module, repoq, spec, conf_file)
|
|
if not pkglist:
|
|
res['msg'] += "No Package matching '%s' found installed" % spec
|
|
res['failed']=True
|
|
module.exit_json(**res)
|
|
|
|
found = False
|
|
for this in pkglist:
|
|
if is_installed(module, repoq, this, conf_file):
|
|
found = True
|
|
|
|
if not found:
|
|
res['results'].append('%s is not installed' % spec)
|
|
continue
|
|
pkg = spec
|
|
|
|
cmd = yum_basecmd + ["remove", pkg]
|
|
rc, out, err = run(cmd)
|
|
|
|
# FIXME if we ran the remove - check to make sure it actually removed :(
|
|
# look for the pkg in the rpmdb - this is notoriously hard for groups :(
|
|
if rc != 0:
|
|
res['changed'] = False
|
|
res['failed'] = True
|
|
res['rc'] = rc
|
|
res['results'].append(out)
|
|
res['msg'] += err
|
|
else:
|
|
res['changed'] = True
|
|
res['rc'] = 0
|
|
res['results'].append(out)
|
|
res['msg'] += err
|
|
|
|
module.exit_json(**res)
|
|
|
|
def latest(module, items, repoq, yum_basecmd, conf_file):
|
|
res = {}
|
|
res['results'] = []
|
|
res['msg'] = ''
|
|
res['changed'] = False
|
|
res['rc'] = 0
|
|
|
|
for spec in items:
|
|
pkg = None
|
|
basecmd = 'update'
|
|
# groups, again
|
|
if spec.startswith('@'):
|
|
pkg = spec
|
|
# dep/pkgname - find it
|
|
else:
|
|
if is_installed(module, repoq, spec, conf_file):
|
|
basecmd = 'update'
|
|
else:
|
|
basecmd = 'install'
|
|
|
|
pkglist = what_provides(module, repoq, spec, conf_file)
|
|
if not pkglist:
|
|
res['msg'] += "No Package matching '%s' found available, installed or updated" % spec
|
|
res['failed']=True
|
|
module.exit_json(**res)
|
|
|
|
nothing_to_do = True
|
|
for this in pkglist:
|
|
if basecmd == 'install' and is_available(module, repoq, this, conf_file):
|
|
nothing_to_do = False
|
|
break
|
|
|
|
if basecmd == 'update' and is_update(module, repoq, this, conf_file):
|
|
nothing_to_do = False
|
|
break
|
|
|
|
if nothing_to_do:
|
|
res['results'].append("All packages providing %s are up to date" % spec)
|
|
continue
|
|
|
|
|
|
pkg = spec
|
|
|
|
cmd = yum_basecmd + [basecmd, pkg]
|
|
rc, out, err = run(cmd)
|
|
|
|
# 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:
|
|
changed = False
|
|
failed = True
|
|
else:
|
|
changed = True
|
|
failed = False
|
|
|
|
|
|
if rc:
|
|
res['changed'] = False
|
|
res['failed'] = True
|
|
res['rc'] = rc
|
|
res['results'].append(out)
|
|
res['msg'] += err
|
|
else:
|
|
res['changed'] = True
|
|
res['rc'] = 0
|
|
res['results'].append(out)
|
|
res['msg'] += err
|
|
|
|
module.exit_json(**res)
|
|
|
|
def ensure(module, state, pkgspec, conf_file):
|
|
# take multiple args comma separated
|
|
items = pkgspec.split(',')
|
|
|
|
yum_basecmd = [yumbin, '-d', '1', '-y']
|
|
if not repoquery:
|
|
repoq = None
|
|
else:
|
|
repoq = [repoquery, '--show-duplicates', '--plugins', '--quiet', '-q']
|
|
if conf_file and os.path.exists(conf_file):
|
|
yum_basecmd += ['-c', conf_file]
|
|
if repoq:
|
|
repoq += ['-c', conf_file]
|
|
|
|
|
|
if state in ['installed', 'present']:
|
|
install(module, items, repoq, yum_basecmd, conf_file)
|
|
elif state in ['removed', 'absent']:
|
|
remove(module, items, repoq, yum_basecmd, conf_file)
|
|
elif state == 'latest':
|
|
latest(module, items, repoq, yum_basecmd, conf_file)
|
|
|
|
|
|
# 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=['name']),
|
|
# removed==absent, installed==present, these are accepted as aliases
|
|
state=dict(default='installed', choices=['absent','present','installed','removed','latest']),
|
|
list=dict(),
|
|
conf_file=dict(default=None),
|
|
),
|
|
required_one_of = [['name','list']],
|
|
mutually_exclusive = [['name','list']]
|
|
)
|
|
|
|
params = module.params
|
|
|
|
if params['list']:
|
|
if not repoquery:
|
|
module.fail_json(msg="repoquery is required to use list= with this module. Please install the yum-utils package.")
|
|
results = dict(results=list_stuff(module, params['conf_file'], params['list']))
|
|
module.exit_json(**results)
|
|
|
|
else:
|
|
pkg = params['name']
|
|
state = params['state']
|
|
res = ensure(module, state, pkg, params['conf_file'])
|
|
module.fail_json(msg="we should never get here unless this all failed", **res)
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
main()
|
|
|