8d283f8194
1. Passing the module to the various functions so that they can use module.fail_json and module.exit_json methods inside. 2. Because of point 1, install and remove methods do not return anything. Instead, they use the module functions itself. 3. Move the import statement (for apt and apt_pkg) inside main function so on import error, we can use module.fail_json to print the error.
173 lines
6 KiB
Python
Executable file
173 lines
6 KiB
Python
Executable file
#!/usr/bin/python -tt
|
|
# (c) 2012, Flowroute LLC
|
|
# Written by Matthew Williams <matthew@flowroute.com>
|
|
# Based on yum module written by Seth Vidal <skvidal at fedoraproject.org>
|
|
#
|
|
# This module 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.
|
|
#
|
|
# This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import traceback
|
|
# added to stave off future warnings about apt api
|
|
import warnings;
|
|
warnings.filterwarnings('ignore', "apt API not stable yet", FutureWarning)
|
|
|
|
# APT related constants
|
|
APT_PATH = "/usr/bin/apt-get"
|
|
APT = "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical %s" % APT_PATH
|
|
|
|
def run_apt(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 = ''
|
|
else:
|
|
rc = cmd.returncode
|
|
return rc, out, err
|
|
|
|
def package_split(pkgspec):
|
|
parts = pkgspec.split('=')
|
|
if len(parts) > 1:
|
|
return parts[0], parts[1]
|
|
else:
|
|
return parts[0], None
|
|
|
|
def package_status(m, pkgname, version, cache):
|
|
try:
|
|
pkg = cache[pkgname]
|
|
except KeyError:
|
|
m.fail_json(msg="No package matching '%s' is available" % pkgname)
|
|
if version:
|
|
try :
|
|
return pkg.is_installed and pkg.installed.version == version, False
|
|
except AttributeError:
|
|
#assume older version of python-apt is installed
|
|
return pkg.isInstalled and pkg.installedVersion == version, False
|
|
else:
|
|
try :
|
|
return pkg.is_installed, pkg.is_upgradable
|
|
except AttributeError:
|
|
#assume older version of python-apt is installed
|
|
return pkg.isInstalled, pkg.isUpgradable
|
|
|
|
def install(m, pkgspec, cache, upgrade=False, default_release=None, install_recommends=True, force=False):
|
|
name, version = package_split(pkgspec)
|
|
installed, upgradable = package_status(m, name, version, cache)
|
|
if not installed or (upgrade and upgradable):
|
|
if force:
|
|
force_yes = '--force-yes'
|
|
else:
|
|
force_yes = ''
|
|
|
|
cmd = "%s --option Dpkg::Options::=--force-confold -q -y %s install '%s'" % (APT, force_yes, pkgspec)
|
|
if default_release:
|
|
cmd += " -t '%s'" % (default_release,)
|
|
if not install_recommends:
|
|
cmd += " --no-install-recommends"
|
|
|
|
rc, out, err = run_apt(cmd)
|
|
if rc:
|
|
m.fail_json(msg="'apt-get install %s' failed: %s" % (pkgspec, err))
|
|
else:
|
|
m.exit_json(changed=True)
|
|
else:
|
|
m.exit_json(changed=False)
|
|
|
|
def remove(m, pkgspec, cache, purge=False):
|
|
name, version = package_split(pkgspec)
|
|
installed, upgradable = package_status(m, name, version, cache)
|
|
if not installed:
|
|
m.exit_json(changed=False)
|
|
else:
|
|
purge = '--purge' if purge else ''
|
|
cmd = "%s -q -y %s remove '%s'" % (APT, purge, name)
|
|
rc, out, err = run_apt(cmd)
|
|
if rc:
|
|
m.fail_json(msg="'apt-get remove %s' failed: %s" % (name, err))
|
|
m.exit_json(changed=True)
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec = dict(
|
|
state = dict(default='installed', choices=['installed', 'latest', 'removed']),
|
|
update_cache = dict(default='no', choices=['yes', 'no']),
|
|
purge = dict(default='no', choices=['yes', 'no']),
|
|
package = dict(default=None, aliases=['pkg', 'name']),
|
|
default_release = dict(default=None, aliases=['default-release']),
|
|
install_recommends = dict(default='yes', aliases=['install-recommends'], choices=['yes', 'no']),
|
|
force = dict(default='no', choices=['yes', 'no'])
|
|
)
|
|
)
|
|
|
|
try:
|
|
import apt, apt_pkg
|
|
except:
|
|
module.fail_json("Could not import python modules: apt, apt_pkg. Please install python-apt package.")
|
|
|
|
if not os.path.exists(APT_PATH):
|
|
module.fail_json(msg="Cannot find apt-get")
|
|
|
|
p = module.params
|
|
if p['package'] is None and p['update_cache'] != 'yes':
|
|
module.fail_json(msg='pkg=name and/or update-cache=yes is required')
|
|
|
|
install_recommends = (p['install_recommends'] == 'yes')
|
|
|
|
cache = apt.Cache()
|
|
if p['default_release']:
|
|
apt_pkg.config['APT::Default-Release'] = p['default_release']
|
|
# reopen cache w/ modified config
|
|
cache.open(progress=None)
|
|
|
|
if p['update_cache'] == 'yes':
|
|
cache.update()
|
|
cache.open(progress=None)
|
|
if p['package'] == None:
|
|
module.exit_json(changed=False)
|
|
|
|
if p['force'] == 'yes':
|
|
force_yes = True
|
|
else:
|
|
force_yes = False
|
|
|
|
if p['package'].count('=') > 1:
|
|
module.fail_json(msg='invalid package spec')
|
|
|
|
if p['state'] == 'latest':
|
|
if '=' in p['package']:
|
|
module.fail_json(msg='version number inconsistent with state=latest')
|
|
install(module, p['package'], cache, upgrade=True,
|
|
default_release=p['default_release'],
|
|
install_recommends=install_recommends,
|
|
force=force_yes)
|
|
|
|
elif p['state'] == 'installed':
|
|
install(module, p['package'], cache, default_release=p['default_release'],
|
|
install_recommends=install_recommends,force=force_yes)
|
|
elif p['state'] == 'removed':
|
|
remove(module, p['package'], cache, purge == 'yes')
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
main()
|
|
|