811c711328
To be consistent with the table showing available options, use underscores in the example tasks, not hyphens, as the table doesn't list hyphenated versions of option names, so it looks like the examples could have typos in them.
249 lines
8.7 KiB
Python
249 lines
8.7 KiB
Python
#!/usr/bin/python -tt
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (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/>.
|
|
#
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: apt
|
|
short_description: Manages apt-packages
|
|
description:
|
|
- Manages I(apt) packages (such as for Debian/Ubuntu).
|
|
version_added: "0.0.2"
|
|
options:
|
|
pkg:
|
|
description:
|
|
- A package name or package specifier with version, like C(foo) or C(foo=1.0)
|
|
required: true
|
|
default: null
|
|
state:
|
|
description:
|
|
- Indicates the desired package state
|
|
required: false
|
|
default: present
|
|
choices: [ "latest", "absent", "present" ]
|
|
update_cache:
|
|
description:
|
|
- Run the equivalent of C(apt-get update) before the operation. Can be run as part of the package installation or as a separate step
|
|
required: false
|
|
default: "no"
|
|
choices: [ "yes", "no" ]
|
|
purge:
|
|
description:
|
|
- Will force purging of configuration files if the module state is set to I(absent).
|
|
required: false
|
|
default: "no"
|
|
choices: [ "yes", "no" ]
|
|
default_release:
|
|
description:
|
|
- Corresponds to the C(-t) option for I(apt) and sets pin priorities
|
|
required: false
|
|
default: null
|
|
install_recommends:
|
|
description:
|
|
- Corresponds to the C(--no-install-recommends) option for I(apt), default behavior works as apt's default behavior, C(no) does not install recommended packages. Suggested packages are never installed.
|
|
required: false
|
|
default: "no"
|
|
choices: [ "yes", "no" ]
|
|
force:
|
|
description:
|
|
- If C(yes), force installs/removes.
|
|
required: false
|
|
default: "no"
|
|
choices: [ "yes", "no" ]
|
|
author: Matthew Williams
|
|
notes: []
|
|
examples:
|
|
- code: "apt: pkg=foo update_cache=yes"
|
|
description: Update repositories cache and install C(foo) package
|
|
- code: "apt: pkg=foo state=removed"
|
|
description: Remove C(foo) package
|
|
- code: "apt: pkg=foo state=installed"
|
|
description: Install the package C(foo)
|
|
- code: "apt: pkg=foo=1.00 state=installed"
|
|
description: Install the version '1.00' of package C(foo)
|
|
- code: "apt: pkg=nginx state=latest default_release=squeeze-backports update_cache=yes"
|
|
description: Update the repository cache and update package C(ngnix) to latest version using default release C(squeeze-backport)
|
|
- code: "apt: pkg=openjdk-6-jdk state=latest install_recommends=no"
|
|
description: Install latest version of C(openjdk-6-jdk) ignoring C(install-reccomends)
|
|
'''
|
|
|
|
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):
|
|
packages = ""
|
|
for package in pkgspec:
|
|
name, version = package_split(package)
|
|
installed, upgradable = package_status(m, name, version, cache)
|
|
if not installed or (upgrade and upgradable):
|
|
packages += "'%s' " % package
|
|
|
|
if len(packages) != 0:
|
|
if force:
|
|
force_yes = '--force-yes'
|
|
else:
|
|
force_yes = ''
|
|
|
|
cmd = "%s --option Dpkg::Options::=--force-confold -q -y %s install %s" % (APT, force_yes,packages)
|
|
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" % (packages, err))
|
|
else:
|
|
m.exit_json(changed=True)
|
|
else:
|
|
m.exit_json(changed=False)
|
|
|
|
def remove(m, pkgspec, cache, purge=False):
|
|
packages = ""
|
|
for package in pkgspec:
|
|
name, version = package_split(package)
|
|
installed, upgradable = package_status(m, name, version, cache)
|
|
if installed:
|
|
packages += "'%s' " % package
|
|
|
|
if len(packages) == 0:
|
|
m.exit_json(changed=False)
|
|
else:
|
|
purge = '--purge' if purge else ''
|
|
cmd = "%s -q -y %s remove %s" % (APT, purge,packages)
|
|
rc, out, err = run_apt(cmd)
|
|
if rc:
|
|
m.fail_json(msg="'apt-get remove %s' failed: %s" % (packages, err))
|
|
m.exit_json(changed=True)
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec = dict(
|
|
state = dict(default='installed', choices=['installed', 'latest', 'removed', 'absent', 'present']),
|
|
update_cache = dict(default='no', choices=['yes', 'no'], aliases=['update-cache']),
|
|
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
|
|
import apt_pkg
|
|
except:
|
|
module.fail_json(msg="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 = module.boolean(p['install_recommends'])
|
|
|
|
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 module.boolean(p['update_cache']):
|
|
cache.update()
|
|
cache.open(progress=None)
|
|
if p['package'] == None:
|
|
module.exit_json(changed=False)
|
|
|
|
force_yes = module.boolean(p['force'])
|
|
|
|
packages = p['package'].split(',')
|
|
latest = p['state'] == 'latest'
|
|
for package in packages:
|
|
if package.count('=') > 1:
|
|
module.fail_json(msg="invalid package spec: %s" % package)
|
|
if latest and '=' in package:
|
|
module.fail_json(msg='version number inconsistent with state=latest: %s' % package)
|
|
|
|
if p['state'] == 'latest':
|
|
install(module, packages, cache, upgrade=True,
|
|
default_release=p['default_release'],
|
|
install_recommends=install_recommends,
|
|
force=force_yes)
|
|
elif p['state'] in [ 'installed', 'present' ]:
|
|
install(module, packages, cache, default_release=p['default_release'],
|
|
install_recommends=install_recommends,force=force_yes)
|
|
elif p['state'] in [ 'removed', 'absent' ]:
|
|
remove(module, packages, cache, purge = module.boolean(p['purge']))
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
main()
|
|
|