ansible/library/yum
Michael DeHaan 7e9e29011e Add encoding lines to python modules such that they can take unicode options if they are fed them, since the
AnsibleModule stuff no longer base64 encodes for simplicity and speed reasons.
2012-08-02 21:29:10 -04:00

421 lines
12 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
def_qf = "%{name}-%{version}-%{release}.%{arch}"
repoquery='/usr/bin/repoquery'
yumbin='/usr/bin/yum'
def is_installed(repoq, pkgspec, qf=def_qf):
cmd = repoq + "--disablerepo=\* --pkgnarrow=installed --qf '%s' %s " % (qf, pkgspec)
rc,out,err = run(cmd)
if rc == 0:
return [ p for p in out.split('\n') if p.strip() ]
return []
def is_available(repoq, pkgspec, qf=def_qf):
cmd = repoq + "--qf '%s' %s " % (qf, pkgspec)
rc,out,err = run(cmd)
if rc == 0:
return [ p for p in out.split('\n') if p.strip() ]
return []
def is_update(repoq, pkgspec, qf=def_qf):
cmd = repoq + "--pkgnarrow=updates --qf '%s' %s " % (qf, pkgspec)
rc,out,err = run(cmd)
if rc == 0:
return set([ p for p in out.split('\n') if p.strip() ])
return []
def what_provides(repoq, req_spec, qf=def_qf):
cmd = repoq + "--qf '%s' --whatprovides %s" % (qf, req_spec)
rc,out,err = run(cmd)
ret = []
if rc == 0:
ret = set([ p for p in out.split('\n') if p.strip() ])
return ret
def local_nvra(path):
"""return nvra of a local rpm passed in"""
cmd = "/bin/rpm -qp --qf='%%{name}-%%{version}-%%{release}.%%{arch}\n' %s'" % 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 '%s' -a" % (qf)
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(conf_file, stuff):
qf = "%{name}|%{epoch}|%{version}|%{release}|%{arch}|%{repoid}"
repoq = '%s --plugins --quiet -q ' % repoquery
if conf_file and os.path.exists(conf_file):
repoq = '%s -c %s --plugins --quiet -q ' % (repoquery,conf_file)
if stuff == 'installed':
return [ pkg_to_dict(p) for p in is_installed(repoq, '-a', qf=qf) if p.strip() ]
elif stuff == 'updates':
return [ pkg_to_dict(p) for p in is_update(repoq, '-a', qf=qf) if p.strip() ]
elif stuff == 'available':
return [ pkg_to_dict(p) for p in is_available(repoq, '-a', 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(repoq, stuff, qf=qf) + is_available(repoq, stuff, qf=qf) if p.strip() ]
def run(command):
try:
cmd = subprocess.Popen(command, shell=True,
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):
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):
# 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(repoq, spec)
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(repoq, this):
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 = "%s install '%s'" % (yum_basecmd, 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):
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 = what_provides(repoq, spec)
if not pkglist:
res['msg'] += "No Package matching '%s' found available, installed or updated" % spec
res['failed']=True
module.exit_json(**res)
found = False
for this in pkglist:
if is_installed(repoq, this):
found = True
if not found:
res['results'].append('%s is not installed' % spec)
continue
pkg = spec
cmd = "%s remove '%s'" % (yum_basecmd, 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):
res = {}
res['results'] = []
res['msg'] = ''
res['changed'] = False
res['rc'] = 0
for spec in items:
pkg = None
# groups, again
if spec.startswith('@'):
pkg = spec
# dep/pkgname - find it
else:
pkglist = what_provides(repoq, spec)
if not pkglist:
res['msg'] += "No Package matching '%s' found available, installed or updated" % spec
res['failed']=True
module.exit_json(**res)
found = False
nothing_to_do = False
can_be_installed = True
for this in pkglist:
if is_installed(repoq, this):
if is_update(repoq, this):
found = True
else:
nothing_to_do = True
if nothing_to_do:
res['results'].append("All packages providing %s are up to date" % spec)
continue
if not found:
basecmd = 'install'
else:
basecmd = 'update'
pkg = spec
cmd = "%s %s '%s'" % (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):
res = {}
stdout = ""
stderr = ""
# take multiple args comma separated
items = [pkgspec]
if pkgspec.find(',') != -1:
items = pkgspec.split(',')
yum_basecmd = '%s -d 1 -y ' % yumbin
repoq = '%s --plugins --quiet -q ' % repoquery
if conf_file and os.path.exists(conf_file):
yum_basecmd = '%s -c %s -d 1 -y' % (yumbin, conf_file)
repoq = '%s -c %s --plugins --quiet -q ' % (repoquery,conf_file)
if state in ['installed', 'present']:
install(module, items, repoq, yum_basecmd)
elif state in ['removed', 'absent']:
remove(module, items, repoq, yum_basecmd)
elif state == 'latest':
latest(module, items, repoq, yum_basecmd)
# should be caught by AnsibleModule argument_spec
return dict(changed=False, failed=True, results='', errors='unexpected state')
def remove_only(pkgspec):
# remove this pkg and only this pkg - fail if it will require more to remove
pass
def main():
# state=installed pkg=pkgspec
# state=removed pkg=pkgspec
# state=latest pkg=pkgspec
#
# informational commands:
# list=installed
# list=updates
# list=available
# list=repos
# list=pkgspec
module = AnsibleModule(
argument_spec = dict(
pkg=dict(aliases=['name']),
# removed==absent, installed==present, these are accepted as aliases
state=dict(default='installed', choices=['absent','present','installed','removed','latest']),
list=dict(choices=['installed','updates','available','repos','pkgspec']),
conf_file=dict(default=None),
)
)
params = module.params
if params['list'] and params['pkg']:
module.fail_json(msg="expected 'list=' or 'name=', but not both")
if not os.path.exists(repoquery):
module.fail_json(msg="%s is required to run this module. Please install the yum-utils package." % repoquery)
if params['list']:
results = dict(results=list_stuff(params['conf_file'], params['list']))
module.exit_json(**results)
else:
pkg = params['pkg']
if 'pkg' is None:
module.fail_json(msg="expected 'list=' or 'name='")
else:
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()