ansible/apt
2012-09-28 22:35:29 +02:00

262 lines
8.7 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 (such as for Debian/Ubuntu).
description:
- Manages apt-packages (such as for Debian/Ubuntu).
version_added: "0.0.2"
options:
name:
description:
- A package name or package specifier with version, like foo or foo=1.0
required: false
default: null
state:
description:
- Indicate the package state
required: false
default: present
choices: [ "installed", "latest", "remove", "absent", "present" ]
update_cache:
description:
- Run the equivalent of apt-get update before the operation. Can be run as part of the package installation or a seperate step
required: false
default: "no"
choices: [ "yes", "no" ]
purge:
description:
- Will forge purge of configuration files if state is set to 'absent'.
required: false
default: "no"
choices: [ "yes", "no" ]
default_release:
description:
- Corresponds to the -t option for apt and sets pin priorities
required: false
default: null
install_recommends:
description:
- Corresponds to the --no-install-recommends option for apt, default behavior works as apt's default behavior, 'no' does not install recommended packages. Suggested packages are never installed.
required: false
default: "no"
choices: [ "yes", "no" ]
force:
description:
- If yes, force installs/removes.
required: false
default: "no"
choices: [ "yes", "no" ]
author: Matthew Williams
'''
EXAMPLES = [
"""
- code: apt pkg=foo update-cache=yes
description: Update repositories cache and install 'foo' package
""",
"""
- code: apt pkg=foo state=removed
description: Remove 'foo' package
""",
"""
- code: apt pkg=foo state=installed
description: Install the the package 'foo'
""",
"""
- code: apt pkg=foo=1.00 state=installed
description: Install the version '1.00' of package 'foo'
""",
"""
- code: apt pkg=nginx state=latest default-release=squeeze-backports update-cache=yes
description: Update the repository cache and update package 'ngnix' to latest versione using default release 'squeeze-backport'
""",
"""
- code: apt pkg=openjdk-6-jdk state=latest install-recommends=no
description: Install latest version of 'openjdk-6-jdk' ignoring 'install-recomands'
"""
]
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()