2013-03-19 17:07:36 +01:00
#!/usr/bin/python
2012-08-03 03:29:10 +02:00
# -*- coding: utf-8 -*-
2012-03-26 21:49:13 +02:00
# (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/>.
#
2012-09-28 22:35:29 +02:00
DOCUMENTATION = '''
- - -
module : apt
2012-09-29 01:51:55 +02:00
short_description : Manages apt - packages
2012-09-28 22:35:29 +02:00
description :
2012-11-21 18:49:30 +01:00
- Manages I ( apt ) packages ( such as for Debian / Ubuntu ) .
2013-11-28 03:23:03 +01:00
version_added : " 0.0.2 "
2012-09-28 22:35:29 +02:00
options :
2014-04-29 21:48:51 +02:00
name :
2012-09-28 22:35:29 +02:00
description :
2015-05-07 17:15:37 +02:00
- A package name , like C ( foo ) , or package specifier with version , like C ( foo = 1.0 ) . Name wildcards ( fnmatch ) like C ( apt * ) and version wildcards like C ( foo = 1.0 * ) are also supported . Note that the apt - get commandline supports implicit regex matches here but we do not because it can let typos through easier ( If you typo C ( foo ) as C ( fo ) apt - get would install packages that have " fo " in their name with a warning and a prompt for the user . Since we don ' t have warnings and prompts before installing we disallow this. Use an explicit fnmatch pattern if you want wildcarding)
2013-06-19 01:25:46 +02:00
required : false
2012-09-28 22:35:29 +02:00
default : null
2015-11-24 16:55:31 +01:00
aliases : [ ' pkg ' , ' package ' ]
2012-09-28 22:35:29 +02:00
state :
description :
2015-01-26 18:36:35 +01:00
- Indicates the desired package state . C ( latest ) ensures that the latest version is installed . C ( build - dep ) ensures the package build dependencies are installed .
2012-09-28 22:35:29 +02:00
required : false
default : present
2015-01-26 18:36:35 +01:00
choices : [ " latest " , " absent " , " present " , " build-dep " ]
2012-09-28 22:35:29 +02:00
update_cache :
description :
2014-03-21 21:52:36 +01:00
- 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 .
2012-09-28 22:35:29 +02:00
required : false
2013-10-09 13:52:23 +02:00
default : no
2012-09-28 22:35:29 +02:00
choices : [ " yes " , " no " ]
2013-04-23 21:55:04 +02:00
cache_valid_time :
description :
- If C ( update_cache ) is specified and the last run is less or equal than I ( cache_valid_time ) seconds ago , the C ( update_cache ) gets skipped .
required : false
2013-10-09 13:52:23 +02:00
default : no
2012-09-28 22:35:29 +02:00
purge :
description :
2012-11-21 18:49:30 +01:00
- Will force purging of configuration files if the module state is set to I ( absent ) .
2012-09-28 22:35:29 +02:00
required : false
2013-10-09 13:52:23 +02:00
default : no
2012-09-28 22:35:29 +02:00
choices : [ " yes " , " no " ]
default_release :
description :
2012-09-29 01:51:55 +02:00
- Corresponds to the C ( - t ) option for I ( apt ) and sets pin priorities
2012-09-28 22:35:29 +02:00
required : false
default : null
install_recommends :
description :
2015-08-15 18:41:42 +02:00
- Corresponds to the C ( - - no - install - recommends ) option for I ( apt ) . C ( yes ) installs recommended packages . C ( no ) does not install recommended packages . By default , Ansible will use the same defaults as the operating system . Suggested packages are never installed .
2012-09-28 22:35:29 +02:00
required : false
2015-11-02 22:03:18 +01:00
default : null
2012-09-28 22:35:29 +02:00
choices : [ " yes " , " no " ]
force :
description :
2012-10-03 04:32:17 +02:00
- If C ( yes ) , force installs / removes .
2012-09-28 22:35:29 +02:00
required : false
default : " no "
choices : [ " yes " , " no " ]
2016-03-23 12:55:58 +01:00
allow_unauthenticated :
description :
- Ignore if packages cannot be authenticated . This is useful for bootstrapping environments that manage their own apt - key setup .
required : false
default : " no "
choices : [ " yes " , " no " ]
2016-03-23 20:52:14 +01:00
version_added : " 2.1 "
2013-02-03 10:46:23 +01:00
upgrade :
description :
2013-04-11 14:15:35 +02:00
- ' If yes or safe, performs an aptitude safe-upgrade. '
- ' If full, performs an aptitude full-upgrade. '
- ' If dist, performs an apt-get dist-upgrade. '
- ' Note: This does not upgrade a specific package, use state=latest for that. '
2013-02-03 19:14:37 +01:00
version_added : " 1.1 "
2013-02-03 10:46:23 +01:00
required : false
2015-07-03 20:43:21 +02:00
default : " no "
choices : [ " no " , " yes " , " safe " , " full " , " dist " ]
2013-10-03 19:14:52 +02:00
dpkg_options :
description :
- Add dpkg options to apt command . Defaults to ' -o " Dpkg::Options::=--force-confdef " -o " Dpkg::Options::=--force-confold " '
- Options should be supplied as comma separated list
required : false
default : ' force-confdef,force-confold '
2014-02-08 01:52:55 +01:00
deb :
description :
2014-06-06 18:18:44 +02:00
- Path to a . deb package on the remote machine .
2015-12-03 05:24:23 +01:00
- If : / / in the path , ansible will attempt to download deb before installing . ( Version added 2.1 )
2014-02-08 01:52:55 +01:00
required : false
2014-04-04 12:59:57 +02:00
version_added : " 1.6 "
2016-02-05 19:42:25 +01:00
autoremove :
description :
2016-03-18 17:09:10 +01:00
- If C ( yes ) , remove unused dependency packages for all module states except I ( build - dep ) .
2016-02-05 19:42:25 +01:00
required : false
default : no
choices : [ " yes " , " no " ]
aliases : [ ' autoclean ' ]
2016-03-08 14:31:22 +01:00
version_added : " 2.1 "
2016-02-19 09:52:53 +01:00
only_upgrade :
description :
2016-04-18 13:24:33 +02:00
- Only install / upgrade a package if it is already installed .
2016-03-18 17:09:10 +01:00
required : false
default : false
version_added : " 2.1 "
2016-02-19 09:52:53 +01:00
2013-05-11 16:31:47 +02:00
requirements : [ python - apt , aptitude ]
2015-06-15 21:53:30 +02:00
author : " Matthew Williams (@mgwilliams) "
2013-05-11 16:31:47 +02:00
notes :
2013-05-17 16:12:30 +02:00
- Three of the upgrade modes ( C ( full ) , C ( safe ) and its alias C ( yes ) ) require C ( aptitude ) , otherwise
2013-05-11 16:31:47 +02:00
C ( apt - get ) suffices .
2012-09-28 22:35:29 +02:00
'''
2013-06-14 11:53:43 +02:00
EXAMPLES = '''
# Update repositories cache and install "foo" package
2014-04-29 21:48:51 +02:00
- apt : name = foo update_cache = yes
2013-06-14 11:53:43 +02:00
# Remove "foo" package
2014-04-29 21:48:51 +02:00
- apt : name = foo state = absent
2013-06-14 11:53:43 +02:00
# Install the package "foo"
2014-04-29 21:48:51 +02:00
- apt : name = foo state = present
2013-06-14 11:53:43 +02:00
# Install the version '1.00' of package "foo"
2014-04-29 21:48:51 +02:00
- apt : name = foo = 1.00 state = present
2013-06-14 11:53:43 +02:00
# Update the repository cache and update package "nginx" to latest version using default release squeeze-backport
2014-04-29 21:48:51 +02:00
- apt : name = nginx state = latest default_release = squeeze - backports update_cache = yes
2013-06-14 11:53:43 +02:00
2013-10-03 19:14:52 +02:00
# Install latest version of "openjdk-6-jdk" ignoring "install-recommends"
2014-04-29 21:48:51 +02:00
- apt : name = openjdk - 6 - jdk state = latest install_recommends = no
2013-06-14 11:53:43 +02:00
# Update all packages to the latest version
- apt : upgrade = dist
# Run the equivalent of "apt-get update" as a separate step
- apt : update_cache = yes
2014-06-21 15:27:57 +02:00
# Only run "update_cache=yes" if the last one is more than 3600 seconds ago
2013-06-14 11:53:43 +02:00
- apt : update_cache = yes cache_valid_time = 3600
2013-10-03 19:14:52 +02:00
# Pass options to dpkg on run
- apt : upgrade = dist update_cache = yes dpkg_options = ' force-confold,force-confdef '
2014-02-08 01:52:55 +01:00
# Install a .deb package
- apt : deb = / tmp / mypackage . deb
2015-01-26 15:56:35 +01:00
# Install the build dependencies for package "foo"
2015-01-26 18:36:35 +01:00
- apt : pkg = foo state = build - dep
2015-12-03 05:24:23 +01:00
# Install a .deb package from the internet.
- apt : deb = https : / / example . com / python - ppq_0 .1 - 1 _all . deb
2013-06-14 11:53:43 +02:00
'''
2015-03-08 16:47:08 +01:00
RETURN = '''
cache_updated :
description : if the cache was updated or not
returned : success , in some cases
type : boolean
sample : True
cache_update_time :
description : time of the last cache update ( 0 if unknown )
returned : success , in some cases
type : datetime
sample : 1425828348000
stdout :
description : output from apt
returned : success , when needed
type : string
sample : " Reading package lists... \n Building dependency tree... \n Reading state information... \n The following extra packages will be installed: \n apache2-bin ... "
stderr :
description : error output from apt
returned : success , when needed
type : string
sample : " AH00558: apache2: Could not reliably determine the server ' s fully qualified domain name, using 127.0.1.1. Set the ' ServerName ' directive globally to ... "
'''
2013-06-14 11:53:43 +02:00
2012-03-26 22:48:02 +02:00
import traceback
2012-07-11 01:50:08 +02:00
# added to stave off future warnings about apt api
2012-08-11 18:35:58 +02:00
import warnings
2012-07-11 01:50:08 +02:00
warnings . filterwarnings ( ' ignore ' , " apt API not stable yet " , FutureWarning )
2013-04-23 21:54:35 +02:00
import os
import datetime
2013-05-13 21:01:02 +02:00
import fnmatch
2014-12-28 03:19:00 +01:00
import itertools
2013-04-23 21:54:35 +02:00
2012-07-26 13:59:15 +02:00
# APT related constants
2014-03-11 13:39:28 +01:00
APT_ENV_VARS = dict (
2016-02-07 22:20:26 +01:00
DEBIAN_FRONTEND = ' noninteractive ' ,
DEBIAN_PRIORITY = ' critical ' ,
# We screenscrape apt-get and aptitude output for information so we need
# to make sure we use the C locale when running commands
LANG = ' C ' ,
LC_ALL = ' C ' ,
LC_MESSAGES = ' C ' ,
LC_CTYPE = ' C ' ,
2014-03-11 13:39:28 +01:00
)
2013-10-03 19:14:52 +02:00
DPKG_OPTIONS = ' force-confdef,force-confold '
2015-08-25 18:15:33 +02:00
APT_GET_ZERO = " \n 0 upgraded, 0 newly installed "
APTITUDE_ZERO = " \n 0 packages upgraded, 0 newly installed "
2013-04-23 21:54:35 +02:00
APT_LISTS_PATH = " /var/lib/apt/lists "
2013-04-24 22:21:38 +02:00
APT_UPDATE_SUCCESS_STAMP_PATH = " /var/lib/apt/periodic/update-success-stamp "
2012-03-26 21:49:13 +02:00
2013-09-12 06:33:59 +02:00
HAS_PYTHON_APT = True
try :
import apt
2014-02-08 01:52:55 +01:00
import apt . debfile
2013-09-12 06:33:59 +02:00
import apt_pkg
2014-03-14 02:13:20 +01:00
except ImportError :
2013-09-12 06:33:59 +02:00
HAS_PYTHON_APT = False
2012-04-22 02:48:58 +02:00
def package_split ( pkgspec ) :
2014-11-13 19:53:25 +01:00
parts = pkgspec . split ( ' = ' , 1 )
2012-04-22 02:48:58 +02:00
if len ( parts ) > 1 :
return parts [ 0 ] , parts [ 1 ]
else :
return parts [ 0 ] , None
2014-12-24 23:55:44 +01:00
def package_versions ( pkgname , pkg , pkg_cache ) :
try :
2014-12-25 09:25:02 +01:00
versions = set ( p . version for p in pkg . versions )
2014-12-24 23:55:44 +01:00
except AttributeError :
# assume older version of python-apt is installed
# apt.package.Package#versions require python-apt >= 0.7.9.
2014-12-25 09:25:02 +01:00
pkg_cache_list = ( p for p in pkg_cache . Packages if p . Name == pkgname )
2014-12-28 03:19:00 +01:00
pkg_versions = ( p . VersionList for p in pkg_cache_list )
versions = set ( p . VerStr for p in itertools . chain ( * pkg_versions ) )
2014-12-24 23:55:44 +01:00
return versions
def package_version_compare ( version , other_version ) :
try :
return apt_pkg . version_compare ( version , other_version )
except AttributeError :
return apt_pkg . VersionCompare ( version , other_version )
2013-02-19 23:18:17 +01:00
def package_status ( m , pkgname , version , cache , state ) :
2012-03-26 21:49:13 +02:00
try :
2013-09-12 06:33:59 +02:00
# get the package from the cache, as well as the
# the low-level apt_pkg.Package object which contains
# state fields not directly acccesible from the
# higher-level apt.package.Package object.
2012-04-22 02:48:58 +02:00
pkg = cache [ pkgname ]
2013-09-12 06:33:59 +02:00
ll_pkg = cache . _cache [ pkgname ] # the low-level package object
2012-04-22 02:48:58 +02:00
except KeyError :
2013-02-19 23:18:17 +01:00
if state == ' install ' :
2014-12-27 21:30:56 +01:00
try :
2015-05-18 10:10:22 +02:00
provided_packages = cache . get_providing_packages ( pkgname )
if provided_packages :
2015-06-30 23:23:28 +02:00
is_installed = False
2015-08-15 11:40:00 +02:00
# when virtual package providing only one package, look up status of target package
2015-05-18 10:10:22 +02:00
if cache . is_virtual_package ( pkgname ) and len ( provided_packages ) == 1 :
2015-06-30 23:23:28 +02:00
package = provided_packages [ 0 ]
2015-05-18 10:10:22 +02:00
installed , upgradable , has_files = package_status ( m , package . name , version , cache , state = ' install ' )
if installed :
is_installed = True
2016-04-18 17:14:57 +02:00
return is_installed , upgradable , False
2014-12-28 03:19:00 +01:00
m . fail_json ( msg = " No package matching ' %s ' is available " % pkgname )
2014-12-27 21:30:56 +01:00
except AttributeError :
2014-12-28 03:19:00 +01:00
# python-apt version too old to detect virtual packages
2014-12-28 18:52:48 +01:00
# mark as upgradable and let apt-get install deal with it
return False , True , False
2013-02-19 23:18:17 +01:00
else :
2013-06-19 09:06:33 +02:00
return False , False , False
2013-06-19 10:56:43 +02:00
try :
has_files = len ( pkg . installed_files ) > 0
2013-11-13 15:48:56 +01:00
except UnicodeDecodeError :
has_files = True
2013-06-19 10:56:43 +02:00
except AttributeError :
has_files = False # older python-apt cannot be used to determine non-purged
2013-11-11 13:32:01 +01:00
try :
2014-02-08 01:52:55 +01:00
package_is_installed = ll_pkg . current_state == apt_pkg . CURSTATE_INSTALLED
2013-11-11 13:32:01 +01:00
except AttributeError : # python-apt 0.7.X has very weak low-level object
try :
# might not be necessary as python-apt post-0.7.X should have current_state property
package_is_installed = pkg . is_installed
except AttributeError :
# assume older version of python-apt is installed
package_is_installed = pkg . isInstalled
2014-11-14 01:24:21 +01:00
if version :
2014-12-24 23:55:44 +01:00
versions = package_versions ( pkgname , pkg , cache . _cache )
avail_upgrades = fnmatch . filter ( versions , version )
2014-11-14 01:24:21 +01:00
if package_is_installed :
2014-11-14 19:01:30 +01:00
try :
installed_version = pkg . installed . version
except AttributeError :
installed_version = pkg . installedVersion
2014-11-14 01:24:21 +01:00
# Only claim the package is installed if the version is matched as well
package_is_installed = fnmatch . fnmatch ( installed_version , version )
# Only claim the package is upgradable if a candidate matches the version
package_is_upgradable = False
for candidate in avail_upgrades :
2014-12-25 09:25:02 +01:00
if package_version_compare ( candidate , installed_version ) > 0 :
2014-11-14 01:24:21 +01:00
package_is_upgradable = True
break
else :
package_is_upgradable = bool ( avail_upgrades )
2012-04-22 02:48:58 +02:00
else :
2013-11-11 13:32:01 +01:00
try :
package_is_upgradable = pkg . is_upgradable
2012-07-31 00:20:43 +02:00
except AttributeError :
2013-11-11 13:32:01 +01:00
# assume older version of python-apt is installed
package_is_upgradable = pkg . isUpgradable
2014-11-14 01:24:21 +01:00
return package_is_installed , package_is_upgradable , has_files
2012-03-26 22:48:02 +02:00
2013-10-03 19:14:52 +02:00
def expand_dpkg_options ( dpkg_options_compressed ) :
options_list = dpkg_options_compressed . split ( ' , ' )
dpkg_options = " "
for dpkg_option in options_list :
dpkg_options = ' %s -o " Dpkg::Options::=-- %s " ' \
% ( dpkg_options , dpkg_option )
return dpkg_options . strip ( )
2013-08-12 15:58:02 +02:00
def expand_pkgspec_from_fnmatches ( m , pkgspec , cache ) :
2015-05-07 17:15:37 +02:00
# Note: apt-get does implicit regex matching when an exact package name
# match is not found. Something like this:
# matches = [pkg.name for pkg in cache if re.match(pkgspec, pkg.name)]
# (Should also deal with the ':' for multiarch like the fnmatch code below)
#
# We have decided not to do similar implicit regex matching but might take
# a PR to add some sort of explicit regex matching:
# https://github.com/ansible/ansible-modules-core/issues/1258
2013-08-12 15:58:02 +02:00
new_pkgspec = [ ]
2014-11-13 19:53:25 +01:00
for pkgspec_pattern in pkgspec :
pkgname_pattern , version = package_split ( pkgspec_pattern )
2014-11-13 20:20:37 +01:00
2014-11-13 20:28:50 +01:00
# note that none of these chars is allowed in a (debian) pkgname
if frozenset ( ' *?[]! ' ) . intersection ( pkgname_pattern ) :
2013-10-03 19:14:52 +02:00
# handle multiarch pkgnames, the idea is that "apt*" should
2013-08-12 15:58:02 +02:00
# only select native packages. But "apt*:i386" should still work
2014-11-13 19:53:25 +01:00
if not " : " in pkgname_pattern :
2014-11-13 20:20:37 +01:00
try :
pkg_name_cache = _non_multiarch
except NameError :
pkg_name_cache = _non_multiarch = [ pkg . name for pkg in cache if not ' : ' in pkg . name ]
2013-08-12 15:58:02 +02:00
else :
2014-11-13 20:20:37 +01:00
try :
pkg_name_cache = _all_pkg_names
except NameError :
pkg_name_cache = _all_pkg_names = [ pkg . name for pkg in cache ]
matches = fnmatch . filter ( pkg_name_cache , pkgname_pattern )
2013-08-12 18:26:31 +02:00
if len ( matches ) == 0 :
2014-11-13 19:53:25 +01:00
m . fail_json ( msg = " No package(s) matching ' %s ' available " % str ( pkgname_pattern ) )
2013-08-12 18:26:31 +02:00
else :
new_pkgspec . extend ( matches )
2013-08-12 15:58:02 +02:00
else :
2014-11-13 20:20:37 +01:00
# No wildcards in name
2014-11-13 19:53:25 +01:00
new_pkgspec . append ( pkgspec_pattern )
2013-08-12 15:58:02 +02:00
return new_pkgspec
2016-04-18 16:18:07 +02:00
def parse_diff ( output ) :
diff = output . splitlines ( )
try :
# check for start marker from aptitude
diff_start = diff . index ( ' Resolving dependencies... ' )
except ValueError :
try :
# check for start marker from apt-get
diff_start = diff . index ( ' Reading state information... ' )
except ValueError :
diff_start = - 1
diff . insert ( 0 , ' Unexpected apt output for --diff. Showing everything: ' )
try :
# check for end marker line from both apt-get and aptitude
diff_end = ( i for i , item in enumerate ( diff ) if re . match ( ' [0-9]+ (packages )?upgraded ' , item ) ) . next ( )
except StopIteration :
diff_end = len ( diff )
diff_start + = 1
diff_end + = 1
return { ' prepared ' : ' \n ' . join ( diff [ diff_start : diff_end ] ) }
2013-10-03 19:14:52 +02:00
def install ( m , pkgspec , cache , upgrade = False , default_release = None ,
2015-11-02 22:03:18 +01:00
install_recommends = None , force = False ,
2015-01-26 18:36:35 +01:00
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) ,
2016-03-23 12:55:58 +01:00
build_dep = False , autoremove = False , only_upgrade = False ,
allow_unauthenticated = False ) :
2014-11-14 01:24:21 +01:00
pkg_list = [ ]
2012-08-01 18:43:39 +02:00
packages = " "
2013-08-12 15:58:02 +02:00
pkgspec = expand_pkgspec_from_fnmatches ( m , pkgspec , cache )
2012-08-01 18:09:30 +02:00
for package in pkgspec :
name , version = package_split ( package )
2013-06-19 09:06:33 +02:00
installed , upgradable , has_files = package_status ( m , name , version , cache , state = ' install ' )
2015-01-26 18:36:35 +01:00
if build_dep :
# Let apt decide what to install
pkg_list . append ( " ' %s ' " % package )
continue
2012-08-01 18:09:30 +02:00
if not installed or ( upgrade and upgradable ) :
2014-11-14 01:24:21 +01:00
pkg_list . append ( " ' %s ' " % package )
if installed and upgradable and version :
# This happens when the package is installed, a newer version is
# available, and the version is a wildcard that matches both
#
# We do not apply the upgrade flag because we cannot specify both
# a version and state=latest. (This behaviour mirrors how apt
# treats a version with wildcard in the package)
pkg_list . append ( " ' %s ' " % package )
packages = ' ' . join ( pkg_list )
2012-08-07 02:07:02 +02:00
2012-08-01 18:43:39 +02:00
if len ( packages ) != 0 :
2012-07-06 17:17:59 +02:00
if force :
force_yes = ' --force-yes '
else :
force_yes = ' '
2013-04-24 12:02:05 +02:00
if m . check_mode :
check_arg = ' --simulate '
else :
check_arg = ' '
2016-02-05 19:42:25 +01:00
if autoremove :
autoremove = ' --auto-remove '
else :
autoremove = ' '
2016-02-19 09:52:53 +01:00
if only_upgrade :
only_upgrade = ' --only-upgrade '
else :
only_upgrade = ' '
2015-01-26 18:36:35 +01:00
if build_dep :
2016-03-17 11:22:07 +01:00
cmd = " %s -y %s %s %s %s build-dep %s " % ( APT_GET_CMD , dpkg_options , only_upgrade , force_yes , check_arg , packages )
2015-01-26 18:36:35 +01:00
else :
2016-02-19 09:52:53 +01:00
cmd = " %s -y %s %s %s %s %s install %s " % ( APT_GET_CMD , dpkg_options , only_upgrade , force_yes , autoremove , check_arg , packages )
2013-04-24 12:02:05 +02:00
2012-04-23 00:17:07 +02:00
if default_release :
cmd + = " -t ' %s ' " % ( default_release , )
2015-11-02 22:03:18 +01:00
if install_recommends is False :
2015-08-15 11:40:00 +02:00
cmd + = " -o APT::Install-Recommends=no "
2015-11-02 22:03:18 +01:00
elif install_recommends is True :
2015-08-15 11:40:00 +02:00
cmd + = " -o APT::Install-Recommends=yes "
2015-11-02 22:03:18 +01:00
# install_recommends is None uses the OS default
2012-07-26 12:51:49 +02:00
2016-03-23 12:55:58 +01:00
if allow_unauthenticated :
cmd + = " --allow-unauthenticated "
Update modules to use run_command in module_common.py
This updates apt, apt_repository, command, cron, easy_install, facter,
fireball, git, group, mount, ohai, pip, service, setup, subversion,
supervisorctl, svr4pkg, user, and yum to take advantage of run_command
in module_common.py.
2013-01-12 07:10:21 +01:00
rc , out , err = m . run_command ( cmd )
2016-04-18 16:18:07 +02:00
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
2012-07-26 13:59:15 +02:00
if rc :
2015-01-26 18:36:35 +01:00
return ( False , dict ( msg = " ' %s ' failed: %s " % ( cmd , err ) , stdout = out , stderr = err ) )
2012-07-26 13:59:15 +02:00
else :
2016-04-18 16:18:07 +02:00
return ( True , dict ( changed = True , stdout = out , stderr = err , diff = diff ) )
2012-04-03 21:44:53 +02:00
else :
2014-02-08 01:52:55 +01:00
return ( True , dict ( changed = False ) )
2016-03-23 12:55:58 +01:00
def install_deb ( m , debs , cache , force , install_recommends , allow_unauthenticated , dpkg_options ) :
2014-02-08 01:52:55 +01:00
changed = False
2014-06-22 03:06:48 +02:00
deps_to_install = [ ]
pkgs_to_install = [ ]
for deb_file in debs . split ( ' , ' ) :
2015-02-13 17:06:06 +01:00
try :
pkg = apt . debfile . DebPackage ( deb_file )
2014-06-22 03:06:48 +02:00
2015-08-18 20:59:35 +02:00
# Check if it's already installed
if pkg . compare_to_version_in_cache ( ) == pkg . VERSION_SAME :
continue
# Check if package is installable
if not pkg . check ( ) and not force :
m . fail_json ( msg = pkg . _failure_string )
# add any missing deps to the list of deps we need
# to install so they're all done in one shot
deps_to_install . extend ( pkg . missing_deps )
2014-06-22 03:06:48 +02:00
2015-08-18 20:59:35 +02:00
except Exception , e :
m . fail_json ( msg = " Unable to install package: %s " % str ( e ) )
2014-06-22 03:06:48 +02:00
# and add this deb to the list of packages to install
pkgs_to_install . append ( deb_file )
# install the deps through apt
retvals = { }
if len ( deps_to_install ) > 0 :
( success , retvals ) = install ( m = m , pkgspec = deps_to_install , cache = cache ,
install_recommends = install_recommends ,
dpkg_options = expand_dpkg_options ( dpkg_options ) )
if not success :
m . fail_json ( * * retvals )
changed = retvals . get ( ' changed ' , False )
if len ( pkgs_to_install ) > 0 :
options = ' ' . join ( [ " -- %s " % x for x in dpkg_options . split ( " , " ) ] )
if m . check_mode :
options + = " --simulate "
if force :
2014-09-30 03:55:34 +02:00
options + = " --force-all "
2014-02-08 01:52:55 +01:00
2014-06-22 03:06:48 +02:00
cmd = " dpkg %s -i %s " % ( options , " " . join ( pkgs_to_install ) )
rc , out , err = m . run_command ( cmd )
if " stdout " in retvals :
stdout = retvals [ " stdout " ] + out
else :
stdout = out
2016-04-18 16:18:07 +02:00
if " diff " in retvals :
diff = retvals [ " diff " ]
diff [ " prepared " ] + = ' \n \n ' + out
else :
diff = out
2014-06-22 03:06:48 +02:00
if " stderr " in retvals :
stderr = retvals [ " stderr " ] + err
else :
stderr = err
2014-02-08 01:52:55 +01:00
2014-06-22 03:06:48 +02:00
if rc == 0 :
2016-04-18 16:18:07 +02:00
m . exit_json ( changed = True , stdout = stdout , stderr = stderr , diff = diff )
2014-06-22 03:06:48 +02:00
else :
m . fail_json ( msg = " %s failed " % cmd , stdout = stdout , stderr = stderr )
2014-02-08 01:52:55 +01:00
else :
2016-04-18 16:18:07 +02:00
m . exit_json ( changed = changed , stdout = retvals . get ( ' stdout ' , ' ' ) , stderr = retvals . get ( ' stderr ' , ' ' ) , diff = retvals . get ( ' diff ' , ' ' ) )
2014-02-08 01:52:55 +01:00
2013-10-03 19:14:52 +02:00
def remove ( m , pkgspec , cache , purge = False ,
2016-02-05 19:42:25 +01:00
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) , autoremove = False ) :
2014-11-14 01:24:21 +01:00
pkg_list = [ ]
2013-12-05 17:54:43 +01:00
pkgspec = expand_pkgspec_from_fnmatches ( m , pkgspec , cache )
2012-08-01 18:09:30 +02:00
for package in pkgspec :
name , version = package_split ( package )
2013-06-19 09:06:33 +02:00
installed , upgradable , has_files = package_status ( m , name , version , cache , state = ' remove ' )
if installed or ( has_files and purge ) :
2014-11-14 01:24:21 +01:00
pkg_list . append ( " ' %s ' " % package )
packages = ' ' . join ( pkg_list )
2012-08-07 02:07:02 +02:00
2012-08-01 18:09:30 +02:00
if len ( packages ) == 0 :
2012-07-26 13:59:15 +02:00
m . exit_json ( changed = False )
2012-03-26 21:49:13 +02:00
else :
2013-02-19 23:18:17 +01:00
if purge :
purge = ' --purge '
2013-06-19 08:37:14 +02:00
else :
purge = ' '
2014-03-11 13:39:28 +01:00
2016-02-05 19:42:25 +01:00
if autoremove :
autoremove = ' --auto-remove '
else :
autoremove = ' '
2013-02-18 08:15:27 +01:00
if m . check_mode :
2016-04-18 16:18:07 +02:00
check_arg = ' --simulate '
else :
check_arg = ' '
2016-04-19 16:33:55 +02:00
cmd = " %s -q -y %s %s %s %s remove %s " % ( APT_GET_CMD , dpkg_options , purge , autoremove , check_arg , packages )
2013-02-18 08:15:27 +01:00
Update modules to use run_command in module_common.py
This updates apt, apt_repository, command, cron, easy_install, facter,
fireball, git, group, mount, ohai, pip, service, setup, subversion,
supervisorctl, svr4pkg, user, and yum to take advantage of run_command
in module_common.py.
2013-01-12 07:10:21 +01:00
rc , out , err = m . run_command ( cmd )
2016-04-18 16:18:07 +02:00
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
2012-07-26 13:59:15 +02:00
if rc :
2013-09-29 01:26:08 +02:00
m . fail_json ( msg = " ' apt-get remove %s ' failed: %s " % ( packages , err ) , stdout = out , stderr = err )
2016-04-18 16:18:07 +02:00
m . exit_json ( changed = True , stdout = out , stderr = err , diff = diff )
2012-07-31 00:20:43 +02:00
2014-05-17 19:32:20 +02:00
def upgrade ( m , mode = " yes " , force = False , default_release = None ,
2013-10-03 19:14:52 +02:00
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) ) :
2013-04-01 03:48:08 +02:00
if m . check_mode :
check_arg = ' --simulate '
else :
check_arg = ' '
2013-07-22 21:15:55 +02:00
apt_cmd = None
2014-11-11 06:45:27 +01:00
prompt_regex = None
2013-02-03 10:46:23 +01:00
if mode == " dist " :
2013-04-11 14:15:35 +02:00
# apt-get dist-upgrade
apt_cmd = APT_GET_CMD
upgrade_command = " dist-upgrade "
2013-10-03 19:14:52 +02:00
elif mode == " full " :
2013-04-11 14:15:35 +02:00
# aptitude full-upgrade
apt_cmd = APTITUDE_CMD
upgrade_command = " full-upgrade "
2013-04-01 03:41:58 +02:00
else :
2013-04-11 14:15:35 +02:00
# aptitude safe-upgrade # mode=yes # default
apt_cmd = APTITUDE_CMD
upgrade_command = " safe-upgrade "
2014-11-11 06:45:27 +01:00
prompt_regex = r " (^Do you want to ignore this warning and proceed anyway \ ?|^ \ * \ * \ *.* \ [default=.* \ ]) "
2013-04-11 14:15:35 +02:00
2013-07-09 10:57:19 +02:00
if force :
2014-01-07 20:43:22 +01:00
if apt_cmd == APT_GET_CMD :
force_yes = ' --force-yes '
else :
2014-11-11 06:45:27 +01:00
force_yes = ' --assume-yes --allow-untrusted '
2013-07-09 10:57:19 +02:00
else :
force_yes = ' '
2013-04-11 14:15:35 +02:00
apt_cmd_path = m . get_bin_path ( apt_cmd , required = True )
2014-03-11 15:47:53 +01:00
cmd = ' %s -y %s %s %s %s ' % ( apt_cmd_path , dpkg_options ,
2013-07-09 10:57:19 +02:00
force_yes , check_arg , upgrade_command )
2014-05-17 19:32:20 +02:00
if default_release :
cmd + = " -t ' %s ' " % ( default_release , )
2014-11-11 06:45:27 +01:00
rc , out , err = m . run_command ( cmd , prompt_regex = prompt_regex )
2016-04-18 16:18:07 +02:00
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
2013-04-11 14:15:35 +02:00
if rc :
2013-11-12 04:27:47 +01:00
m . fail_json ( msg = " ' %s %s ' failed: %s " % ( apt_cmd , upgrade_command , err ) , stdout = out )
2013-04-11 14:15:35 +02:00
if ( apt_cmd == APT_GET_CMD and APT_GET_ZERO in out ) or ( apt_cmd == APTITUDE_CMD and APTITUDE_ZERO in out ) :
2013-09-29 01:26:08 +02:00
m . exit_json ( changed = False , msg = out , stdout = out , stderr = err )
2016-04-18 16:18:07 +02:00
m . exit_json ( changed = True , msg = out , stdout = out , stderr = err , diff = diff )
2012-03-26 21:49:13 +02:00
2015-12-03 05:24:23 +01:00
def download ( module , deb ) :
tempdir = os . path . dirname ( __file__ )
package = os . path . join ( tempdir , str ( deb . rsplit ( ' / ' , 1 ) [ 1 ] ) )
# When downloading a deb, how much of the deb to download before
# saving to a tempfile (64k)
BUFSIZE = 65536
try :
rsp , info = fetch_url ( module , deb )
f = open ( package , ' w ' )
# Read 1kb at a time to save on ram
while True :
data = rsp . read ( BUFSIZE )
if data == " " :
break # End of file, break while loop
f . write ( data )
f . close ( )
deb = package
except Exception , e :
module . fail_json ( msg = " Failure downloading %s , %s " % ( deb , e ) )
return deb
2012-07-26 12:51:49 +02:00
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
2015-01-26 18:36:35 +01:00
state = dict ( default = ' present ' , choices = [ ' installed ' , ' latest ' , ' removed ' , ' absent ' , ' present ' , ' build-dep ' ] ) ,
2013-10-09 13:47:08 +02:00
update_cache = dict ( default = False , aliases = [ ' update-cache ' ] , type = ' bool ' ) ,
2013-04-23 21:54:35 +02:00
cache_valid_time = dict ( type = ' int ' ) ,
2013-10-09 13:47:08 +02:00
purge = dict ( default = False , type = ' bool ' ) ,
2014-06-24 02:12:19 +02:00
package = dict ( default = None , aliases = [ ' pkg ' , ' name ' ] , type = ' list ' ) ,
2014-02-08 01:52:55 +01:00
deb = dict ( default = None ) ,
2012-07-26 12:51:49 +02:00
default_release = dict ( default = None , aliases = [ ' default-release ' ] ) ,
2015-11-02 22:03:18 +01:00
install_recommends = dict ( default = None , aliases = [ ' install-recommends ' ] , type = ' bool ' ) ,
2013-10-03 19:14:52 +02:00
force = dict ( default = ' no ' , type = ' bool ' ) ,
2015-07-03 20:43:21 +02:00
upgrade = dict ( choices = [ ' no ' , ' yes ' , ' safe ' , ' full ' , ' dist ' ] ) ,
2016-02-05 19:42:25 +01:00
dpkg_options = dict ( default = DPKG_OPTIONS ) ,
2016-02-19 09:52:53 +01:00
autoremove = dict ( type = ' bool ' , default = False , aliases = [ ' autoclean ' ] ) ,
2016-03-23 12:55:58 +01:00
only_upgrade = dict ( type = ' bool ' , default = False ) ,
allow_unauthenticated = dict ( default = ' no ' , aliases = [ ' allow-unauthenticated ' ] , type = ' bool ' ) ,
2013-02-18 08:15:27 +01:00
) ,
2014-02-08 01:52:55 +01:00
mutually_exclusive = [ [ ' package ' , ' upgrade ' , ' deb ' ] ] ,
2015-01-26 20:32:34 +01:00
required_one_of = [ [ ' package ' , ' upgrade ' , ' update_cache ' , ' deb ' ] ] ,
2013-02-18 08:15:27 +01:00
supports_check_mode = True
2012-07-26 12:51:49 +02:00
)
2016-02-07 22:20:26 +01:00
module . run_command_environ_update = APT_ENV_VARS
2013-09-12 06:33:59 +02:00
if not HAS_PYTHON_APT :
2016-01-31 11:51:34 +01:00
if module . check_mode :
2016-02-11 07:22:21 +01:00
module . fail_json ( msg = " python-apt must be installed to use check mode. If run normally this module can autoinstall it " )
2013-10-21 16:39:18 +02:00
try :
2016-01-31 11:22:59 +01:00
module . run_command ( ' apt-get update ' , check_rc = True )
module . run_command ( ' apt-get install python-apt -y -q ' , check_rc = True )
2013-10-21 16:39:18 +02:00
global apt , apt_pkg
import apt
2015-03-19 22:54:59 +01:00
import apt . debfile
2013-10-21 16:39:18 +02:00
import apt_pkg
2014-04-21 16:35:18 +02:00
except ImportError :
2013-10-21 16:39:18 +02:00
module . fail_json ( msg = " Could not import python modules: apt, apt_pkg. Please install python-apt package. " )
2012-07-26 13:59:15 +02:00
2013-05-19 01:49:25 +02:00
global APTITUDE_CMD
APTITUDE_CMD = module . get_bin_path ( " aptitude " , False )
global APT_GET_CMD
APT_GET_CMD = module . get_bin_path ( " apt-get " )
2013-05-15 06:50:28 +02:00
2013-07-22 21:15:55 +02:00
p = module . params
2015-07-03 20:43:21 +02:00
if p [ ' upgrade ' ] == ' no ' :
p [ ' upgrade ' ] = None
2013-07-22 21:15:55 +02:00
if not APTITUDE_CMD and p . get ( ' upgrade ' , None ) in [ ' full ' , ' safe ' , ' yes ' ] :
2013-07-20 18:47:46 +02:00
module . fail_json ( msg = " Could not find aptitude. Please ensure it is installed. " )
2013-06-30 05:36:13 +02:00
2015-03-08 16:47:08 +01:00
updated_cache = False
updated_cache_time = 0
2013-02-23 19:59:52 +01:00
install_recommends = p [ ' install_recommends ' ]
2016-03-23 12:55:58 +01:00
allow_unauthenticated = p [ ' allow_unauthenticated ' ]
2013-10-03 19:14:52 +02:00
dpkg_options = expand_dpkg_options ( p [ ' dpkg_options ' ] )
2016-02-05 19:42:25 +01:00
autoremove = p [ ' autoremove ' ]
2012-07-31 00:20:43 +02:00
2014-11-12 23:01:14 +01:00
# Deal with deprecated aliases
if p [ ' state ' ] == ' installed ' :
p [ ' state ' ] = ' present '
if p [ ' state ' ] == ' removed ' :
p [ ' state ' ] = ' absent '
2013-03-13 23:11:23 +01:00
try :
cache = apt . Cache ( )
if p [ ' default_release ' ] :
2014-02-02 19:49:23 +01:00
try :
apt_pkg . config [ ' APT::Default-Release ' ] = p [ ' default_release ' ]
except AttributeError :
apt_pkg . Config [ ' APT::Default-Release ' ] = p [ ' default_release ' ]
2013-03-13 23:11:23 +01:00
# reopen cache w/ modified config
cache . open ( progress = None )
if p [ ' update_cache ' ] :
2013-04-23 21:54:35 +02:00
# Default is: always update the cache
cache_valid = False
2015-03-08 16:47:08 +01:00
now = datetime . datetime . now ( )
if p . get ( ' cache_valid_time ' , False ) :
2013-04-24 22:21:38 +02:00
try :
mtime = os . stat ( APT_UPDATE_SUCCESS_STAMP_PATH ) . st_mtime
except :
# Looks like the update-success-stamp is not available
# Fallback: Checking the mtime of the lists
try :
mtime = os . stat ( APT_LISTS_PATH ) . st_mtime
except :
2015-03-08 16:47:08 +01:00
# No mtime could be read. We update the cache to be safe
2013-04-24 22:21:38 +02:00
mtime = False
2015-03-08 16:47:08 +01:00
if mtime :
tdelta = datetime . timedelta ( seconds = p [ ' cache_valid_time ' ] )
2013-04-24 22:21:38 +02:00
mtimestamp = datetime . datetime . fromtimestamp ( mtime )
2015-03-08 16:47:08 +01:00
if mtimestamp + tdelta > = now :
2013-04-24 22:21:38 +02:00
cache_valid = True
2015-03-08 16:47:08 +01:00
updated_cache_time = int ( time . mktime ( mtimestamp . timetuple ( ) ) )
2013-04-23 21:54:35 +02:00
if cache_valid is not True :
2015-01-28 13:48:46 +01:00
for retry in xrange ( 3 ) :
try :
cache . update ( )
break
except apt . cache . FetchFailedException :
pass
else :
#out of retries, pass on the exception
raise
2013-04-23 21:54:35 +02:00
cache . open ( progress = None )
2015-03-08 16:47:08 +01:00
updated_cache = True
updated_cache_time = int ( time . mktime ( now . timetuple ( ) ) )
2014-02-08 01:52:55 +01:00
if not p [ ' package ' ] and not p [ ' upgrade ' ] and not p [ ' deb ' ] :
2015-03-08 16:47:08 +01:00
module . exit_json ( changed = False , cache_updated = updated_cache , cache_update_time = updated_cache_time )
else :
updated_cache = False
updated_cache_time = 0
2012-07-31 00:20:43 +02:00
2013-03-13 23:11:23 +01:00
force_yes = p [ ' force ' ]
2012-07-31 00:20:43 +02:00
2013-03-13 23:11:23 +01:00
if p [ ' upgrade ' ] :
2015-03-08 16:47:08 +01:00
upgrade ( module , p [ ' upgrade ' ] , force_yes , p [ ' default_release ' ] , dpkg_options )
2012-08-07 02:07:02 +02:00
2014-02-08 01:52:55 +01:00
if p [ ' deb ' ] :
2014-11-12 23:16:02 +01:00
if p [ ' state ' ] != ' present ' :
2014-11-12 20:20:21 +01:00
module . fail_json ( msg = " deb only supports state=present " )
2015-12-03 05:24:23 +01:00
if ' :// ' in p [ ' deb ' ] :
p [ ' deb ' ] = download ( module , p [ ' deb ' ] )
2014-02-08 01:52:55 +01:00
install_deb ( module , p [ ' deb ' ] , cache ,
install_recommends = install_recommends ,
2016-03-23 12:55:58 +01:00
allow_unauthenticated = allow_unauthenticated ,
2014-02-08 01:52:55 +01:00
force = force_yes , dpkg_options = p [ ' dpkg_options ' ] )
2014-06-24 02:12:19 +02:00
packages = p [ ' package ' ]
2013-03-13 23:11:23 +01:00
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 )
2013-02-03 10:46:23 +01:00
2015-01-26 18:36:35 +01:00
if p [ ' state ' ] in ( ' latest ' , ' present ' , ' build-dep ' ) :
2015-01-27 16:28:56 +01:00
state_upgrade = False
state_builddep = False
2015-01-26 21:16:42 +01:00
if p [ ' state ' ] == ' latest ' :
2015-01-27 16:28:56 +01:00
state_upgrade = True
2015-01-26 21:16:42 +01:00
if p [ ' state ' ] == ' build-dep ' :
2015-01-27 16:28:56 +01:00
state_builddep = True
result = install ( module , packages , cache , upgrade = state_upgrade ,
2013-03-13 23:11:23 +01:00
default_release = p [ ' default_release ' ] ,
install_recommends = install_recommends ,
2015-01-26 18:36:35 +01:00
force = force_yes , dpkg_options = dpkg_options ,
2016-02-19 09:52:53 +01:00
build_dep = state_builddep , autoremove = autoremove ,
2016-03-23 12:55:58 +01:00
only_upgrade = p [ ' only_upgrade ' ] ,
allow_unauthenticated = allow_unauthenticated )
2014-02-08 01:52:55 +01:00
( success , retvals ) = result
2015-03-08 16:47:08 +01:00
retvals [ ' cache_updated ' ] = updated_cache
retvals [ ' cache_update_time ' ] = updated_cache_time
2014-02-08 01:52:55 +01:00
if success :
module . exit_json ( * * retvals )
else :
module . fail_json ( * * retvals )
2014-11-12 23:01:14 +01:00
elif p [ ' state ' ] == ' absent ' :
2016-02-05 19:42:25 +01:00
remove ( module , packages , cache , p [ ' purge ' ] , dpkg_options , autoremove )
2012-07-31 00:20:43 +02:00
2013-03-13 23:21:16 +01:00
except apt . cache . LockFailedException :
2013-03-13 23:11:23 +01:00
module . fail_json ( msg = " Failed to lock apt for exclusive operation " )
2015-01-14 23:22:05 +01:00
except apt . cache . FetchFailedException :
module . fail_json ( msg = " Could not fetch updated apt files " )
2012-07-31 00:20:43 +02:00
2013-12-02 21:13:49 +01:00
# import module snippets
2013-12-02 21:11:23 +01:00
from ansible . module_utils . basic import *
2015-12-03 05:24:23 +01:00
from ansible . module_utils . urls import *
2012-03-26 21:49:13 +02:00
2014-11-13 19:32:38 +01:00
if __name__ == " __main__ " :
2014-10-29 20:44:44 +01:00
main ( )