2014-09-26 01:01:01 +00:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
2017-12-03 16:14:49 +00:00
# Copyright: (c) 2012, Flowroute LLC
2014-09-26 01:01:01 +00:00
# Written by Matthew Williams <matthew@flowroute.com>
# Based on yum module written by Seth Vidal <skvidal at fedoraproject.org>
2017-12-03 16:14:49 +00:00
2017-08-01 21:37:37 +00:00
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import , division , print_function
__metaclass__ = type
2017-08-16 03:16:38 +00:00
ANSIBLE_METADATA = { ' metadata_version ' : ' 1.1 ' ,
2017-03-14 16:07:22 +00:00
' status ' : [ ' stableinterface ' ] ,
' supported_by ' : ' core ' }
2014-09-26 01:01:01 +00:00
DOCUMENTATION = '''
- - -
module : apt
short_description : Manages apt - packages
description :
- Manages I ( apt ) packages ( such as for Debian / Ubuntu ) .
version_added : " 0.0.2 "
options :
name :
description :
2017-11-20 15:16:19 +00:00
- A list of package names , 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 .
2017-12-03 16:14:49 +00:00
aliases : [ package , pkg ]
2014-09-26 01:01:01 +00:00
state :
description :
2017-03-23 01:50:28 +00: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 .
2014-09-26 01:01:01 +00:00
default : present
2017-12-03 16:14:49 +00:00
choices : [ absent , build - dep , latest , present ]
2014-09-26 01:01:01 +00:00
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 .
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2014-09-26 01:01:01 +00:00
cache_valid_time :
description :
2016-09-28 09:40:02 +00:00
- Update the apt cache if its older than the I ( cache_valid_time ) . This option is set in seconds .
2017-08-07 12:36:51 +00:00
As of Ansible 2.4 , this implicitly sets I ( update_cache ) if set .
2016-09-28 09:40:02 +00:00
default : 0
2014-09-26 01:01:01 +00:00
purge :
description :
- Will force purging of configuration files if the module state is set to I ( absent ) .
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2014-09-26 01:01:01 +00:00
default_release :
description :
- Corresponds to the C ( - t ) option for I ( apt ) and sets pin priorities
install_recommends :
description :
2017-03-23 01:50:28 +00: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 .
2018-01-16 12:18:51 +00:00
aliases : [ ' install-recommends ' ]
2017-12-03 16:14:49 +00:00
type : bool
2014-09-26 01:01:01 +00:00
force :
description :
2017-11-01 22:54:11 +00:00
- ' Corresponds to the C(--force-yes) to I(apt-get) and implies C(allow_unauthenticated: yes) '
2018-03-15 08:24:32 +00:00
- " This option will disable checking both the packages ' signatures and the certificates of the
web servers they are downloaded from . "
2017-11-01 22:54:11 +00:00
- ' This option *is not* the equivalent of passing the C(-f) flag to I(apt-get) on the command line '
- ' **This is a destructive operation with the potential to destroy your system, and it should almost never be used.**
Please also see C ( man apt - get ) for more information . '
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2016-03-23 11:55:58 +00:00
allow_unauthenticated :
description :
- Ignore if packages cannot be authenticated . This is useful for bootstrapping environments that manage their own apt - key setup .
2018-03-15 09:07:18 +00:00
- ' C(allow_unauthenticated) is only supported with state: I(install)/I(present) '
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2016-03-23 19:52:14 +00:00
version_added : " 2.1 "
2014-09-26 01:01:01 +00:00
upgrade :
description :
2017-12-03 16:14:49 +00: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 .
2014-09-26 01:01:01 +00:00
- ' Note: This does not upgrade a specific package, use state=latest for that. '
2017-08-08 13:53:44 +00:00
- ' Note: Since 2.4, apt-get is used as a fall-back if aptitude is not present. '
2014-09-26 01:01:01 +00:00
version_added : " 1.1 "
2017-12-03 16:14:49 +00:00
choices : [ dist , full , ' no ' , safe , ' yes ' ]
default : ' no '
2014-09-26 01:01:01 +00: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
2017-12-03 16:14:49 +00:00
default : force - confdef , force - confold
2014-09-26 01:01:01 +00:00
deb :
description :
- Path to a . deb package on the remote machine .
2015-12-03 04:24:23 +00:00
- If : / / in the path , ansible will attempt to download deb before installing . ( Version added 2.1 )
2014-09-26 01:01:01 +00:00
required : false
version_added : " 1.6 "
2016-02-05 18:42:25 +00:00
autoremove :
description :
2017-03-01 17:50:26 +00:00
- If C ( yes ) , remove unused dependency packages for all module states except I ( build - dep ) . It can also be used as the only option .
2017-05-25 19:52:41 +00:00
- Previous to version 2.4 , autoclean was also an alias for autoremove , now it is its own separate command . See documentation for further information .
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2016-03-08 13:31:22 +00:00
version_added : " 2.1 "
2017-05-25 19:52:41 +00:00
autoclean :
description :
- If C ( yes ) , cleans the local repository of retrieved package files that can no longer be downloaded .
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2017-05-25 19:52:41 +00:00
version_added : " 2.4 "
2016-02-19 08:52:53 +00:00
only_upgrade :
description :
2017-02-06 09:50:44 +00:00
- Only upgrade a package if it is already installed .
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2016-03-18 16:09:10 +00:00
version_added : " 2.1 "
2017-08-08 13:53:44 +00:00
force_apt_get :
description :
- Force usage of apt - get instead of aptitude
2017-12-03 16:14:49 +00:00
type : bool
default : ' no '
2017-08-08 13:53:44 +00:00
version_added : " 2.4 "
2016-09-14 20:26:09 +00:00
requirements :
- python - apt ( python 2 )
- python3 - apt ( python 3 )
2017-08-08 13:53:44 +00:00
- aptitude ( before 2.4 )
2015-06-15 19:53:30 +00:00
author : " Matthew Williams (@mgwilliams) "
2014-09-26 01:01:01 +00:00
notes :
2017-10-05 14:29:53 +00:00
- Three of the upgrade modes ( C ( full ) , C ( safe ) and its alias C ( yes ) ) required C ( aptitude ) up to 2.3 , since 2.4 C ( apt - get ) is used as a fall - back .
- apt starts newly installed services by default , this is what the underlying tooling does ,
to avoid this you can set the ` ` RUNLEVEL ` ` environment variable to 1.
2017-11-20 15:16:19 +00:00
- 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)
- When used with a ` loop : ` each package will be processed individually , it is much more efficient to pass the list directly to the ` name ` option .
2014-09-26 01:01:01 +00:00
'''
EXAMPLES = '''
2016-10-17 06:17:32 +00:00
- name : Update repositories cache and install " foo " package
apt :
name : foo
update_cache : yes
2014-09-26 01:01:01 +00:00
2017-10-05 14:29:53 +00:00
- name : Install apache service but avoid starting it immediately
apt : name = apache2 state = present
environment :
RUNLEVLEL : 1
2016-10-17 06:17:32 +00:00
- name : Remove " foo " package
2017-10-05 14:29:53 +00:00
apt : name = foo state = absent
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Install the package " foo "
apt :
name : foo
state : present
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Install the version ' 1.00 ' of package " foo "
apt :
name : foo = 1.00
state : present
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Update the repository cache and update package " nginx " to latest version using default release squeeze - backport
apt :
name : nginx
state : latest
default_release : squeeze - backports
update_cache : yes
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Install latest version of " openjdk-6-jdk " ignoring " install-recommends "
apt :
name : openjdk - 6 - jdk
state : latest
install_recommends : no
2014-09-26 01:01:01 +00:00
2017-05-24 23:04:38 +00:00
- name : Upgrade all packages to the latest version
apt :
name : " * "
state : latest
2016-10-17 06:17:32 +00:00
- name : Update all packages to the latest version
apt :
upgrade : dist
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Run the equivalent of " apt-get update " as a separate step
apt :
update_cache : yes
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Only run " update_cache=yes " if the last one is more than 3600 seconds ago
apt :
update_cache : yes
cache_valid_time : 3600
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Pass options to dpkg on run
apt :
upgrade : dist
update_cache : yes
2016-10-12 21:55:51 +00:00
dpkg_options : ' force-confold,force-confdef '
2014-09-26 01:01:01 +00:00
2016-10-17 06:17:32 +00:00
- name : Install a . deb package
apt :
deb : / tmp / mypackage . deb
2015-01-26 14:56:35 +00:00
2016-10-17 06:17:32 +00:00
- name : Install the build dependencies for package " foo "
apt :
pkg : foo
state : build - dep
2015-12-03 04:24:23 +00:00
2016-10-17 06:17:32 +00:00
- name : Install a . deb package from the internet .
apt :
deb : https : / / example . com / python - ppq_0 .1 - 1 _all . deb
2017-05-25 19:52:41 +00:00
- name : Remove useless packages from the cache
apt :
autoclean : yes
- name : Remove dependencies that are no longer required
apt :
autoremove : yes
2017-10-05 14:29:53 +00:00
2014-09-26 01:01:01 +00:00
'''
2015-03-08 15:47:08 +00: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
2017-04-26 14:56:13 +00:00
type : int
2015-03-08 15:47:08 +00:00
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 ... "
2017-03-23 01:50:28 +00:00
''' # NOQA
2014-09-26 01:01:01 +00:00
# added to stave off future warnings about apt api
import warnings
warnings . filterwarnings ( ' ignore ' , " apt API not stable yet " , FutureWarning )
import datetime
import fnmatch
2014-12-28 02:19:00 +00:00
import itertools
2016-10-22 15:36:29 +00:00
import os
import re
2016-09-14 20:26:09 +00:00
import sys
2016-10-22 15:36:29 +00:00
import time
2014-09-26 01:01:01 +00:00
2016-10-22 15:36:29 +00:00
from ansible . module_utils . basic import AnsibleModule
2017-02-19 17:02:44 +00:00
from ansible . module_utils . _text import to_bytes , to_native
2016-10-22 15:36:29 +00:00
from ansible . module_utils . urls import fetch_url
2016-09-09 00:56:09 +00:00
2014-09-26 01:01:01 +00:00
# APT related constants
APT_ENV_VARS = dict (
2017-05-01 15:25:39 +00:00
DEBIAN_FRONTEND = ' noninteractive ' ,
DEBIAN_PRIORITY = ' critical ' ,
2017-01-29 07:28:53 +00:00
# We screenscrape apt-get and aptitude output for information so we need
# to make sure we use the C locale when running commands
2017-05-01 15:25:39 +00:00
LANG = ' C ' ,
LC_ALL = ' C ' ,
LC_MESSAGES = ' C ' ,
LC_CTYPE = ' C ' ,
2014-09-26 01:01:01 +00:00
)
DPKG_OPTIONS = ' force-confdef,force-confold '
2015-08-25 16:15:33 +00:00
APT_GET_ZERO = " \n 0 upgraded, 0 newly installed "
APTITUDE_ZERO = " \n 0 packages upgraded, 0 newly installed "
2014-09-26 01:01:01 +00:00
APT_LISTS_PATH = " /var/lib/apt/lists "
APT_UPDATE_SUCCESS_STAMP_PATH = " /var/lib/apt/periodic/update-success-stamp "
2018-03-23 11:23:12 +00:00
APT_MARK_INVALID_OP = ' Invalid operation '
2014-09-26 01:01:01 +00:00
HAS_PYTHON_APT = True
try :
import apt
import apt . debfile
import apt_pkg
except ImportError :
HAS_PYTHON_APT = False
2016-09-14 20:26:09 +00:00
if sys . version_info [ 0 ] < 3 :
PYTHON_APT = ' python-apt '
else :
PYTHON_APT = ' python3-apt '
2014-09-26 01:01:01 +00:00
def package_split ( pkgspec ) :
2014-11-13 18:53:25 +00:00
parts = pkgspec . split ( ' = ' , 1 )
2017-09-15 21:06:07 +00:00
version = None
2014-09-26 01:01:01 +00:00
if len ( parts ) > 1 :
2017-09-15 21:06:07 +00:00
version = parts [ 1 ]
return parts [ 0 ] , version
2014-09-26 01:01:01 +00:00
2016-09-14 20:26:09 +00:00
2014-12-24 22:55:44 +00:00
def package_versions ( pkgname , pkg , pkg_cache ) :
try :
2014-12-25 08:25:02 +00:00
versions = set ( p . version for p in pkg . versions )
2014-12-24 22:55:44 +00:00
except AttributeError :
# assume older version of python-apt is installed
# apt.package.Package#versions require python-apt >= 0.7.9.
2014-12-25 08:25:02 +00:00
pkg_cache_list = ( p for p in pkg_cache . Packages if p . Name == pkgname )
2014-12-28 02:19:00 +00: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 22:55:44 +00:00
return versions
2016-09-14 20:26:09 +00:00
2014-12-24 22:55:44 +00:00
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 )
2016-09-14 20:26:09 +00:00
2014-09-26 01:01:01 +00:00
def package_status ( m , pkgname , version , cache , state ) :
try :
# get the package from the cache, as well as the
2016-10-13 14:47:50 +00:00
# low-level apt_pkg.Package object which contains
# state fields not directly accessible from the
2014-09-26 01:01:01 +00:00
# higher-level apt.package.Package object.
pkg = cache [ pkgname ]
2017-12-03 16:14:49 +00:00
ll_pkg = cache . _cache [ pkgname ] # the low-level package object
2014-09-26 01:01:01 +00:00
except KeyError :
if state == ' install ' :
2014-12-27 20:30:56 +00:00
try :
2015-05-18 08:10:22 +00:00
provided_packages = cache . get_providing_packages ( pkgname )
if provided_packages :
2015-06-30 21:23:28 +00:00
is_installed = False
2016-05-02 13:38:29 +00:00
upgradable = False
2015-08-15 09:40:00 +00:00
# when virtual package providing only one package, look up status of target package
2015-05-18 08:10:22 +00:00
if cache . is_virtual_package ( pkgname ) and len ( provided_packages ) == 1 :
2015-06-30 21:23:28 +00:00
package = provided_packages [ 0 ]
2015-05-18 08:10:22 +00:00
installed , upgradable , has_files = package_status ( m , package . name , version , cache , state = ' install ' )
if installed :
is_installed = True
2016-04-18 15:14:57 +00:00
return is_installed , upgradable , False
2014-12-28 02:19:00 +00:00
m . fail_json ( msg = " No package matching ' %s ' is available " % pkgname )
2014-12-27 20:30:56 +00:00
except AttributeError :
2014-12-28 02:19:00 +00:00
# python-apt version too old to detect virtual packages
2014-12-28 17:52:48 +00:00
# mark as upgradable and let apt-get install deal with it
return False , True , False
2014-09-26 01:01:01 +00:00
else :
return False , False , False
try :
has_files = len ( pkg . installed_files ) > 0
except UnicodeDecodeError :
has_files = True
except AttributeError :
has_files = False # older python-apt cannot be used to determine non-purged
try :
package_is_installed = ll_pkg . current_state == apt_pkg . CURSTATE_INSTALLED
2017-12-03 16:14:49 +00:00
except AttributeError : # python-apt 0.7.X has very weak low-level object
2014-09-26 01:01:01 +00:00
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 00:24:21 +00:00
if version :
2014-12-24 22:55:44 +00:00
versions = package_versions ( pkgname , pkg , cache . _cache )
avail_upgrades = fnmatch . filter ( versions , version )
2014-11-14 00:24:21 +00:00
if package_is_installed :
2014-11-14 18:01:30 +00:00
try :
installed_version = pkg . installed . version
except AttributeError :
installed_version = pkg . installedVersion
2014-11-14 00:24:21 +00: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 08:25:02 +00:00
if package_version_compare ( candidate , installed_version ) > 0 :
2014-11-14 00:24:21 +00:00
package_is_upgradable = True
break
else :
package_is_upgradable = bool ( avail_upgrades )
2014-09-26 01:01:01 +00:00
else :
try :
package_is_upgradable = pkg . is_upgradable
except AttributeError :
# assume older version of python-apt is installed
package_is_upgradable = pkg . isUpgradable
2014-11-14 00:24:21 +00:00
return package_is_installed , package_is_upgradable , has_files
2014-09-26 01:01:01 +00:00
2016-09-14 20:26:09 +00:00
2014-09-26 01:01:01 +00: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 ( )
2016-09-14 20:26:09 +00:00
2014-09-26 01:01:01 +00:00
def expand_pkgspec_from_fnmatches ( m , pkgspec , cache ) :
2015-05-07 15:15:37 +00: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
2014-09-26 01:01:01 +00:00
new_pkgspec = [ ]
2017-03-01 17:50:26 +00:00
if pkgspec :
for pkgspec_pattern in pkgspec :
pkgname_pattern , version = package_split ( pkgspec_pattern )
# note that none of these chars is allowed in a (debian) pkgname
if frozenset ( ' *?[]! ' ) . intersection ( pkgname_pattern ) :
# handle multiarch pkgnames, the idea is that "apt*" should
# only select native packages. But "apt*:i386" should still work
if " : " not in pkgname_pattern :
# Filter the multiarch packages from the cache only once
try :
pkg_name_cache = _non_multiarch
except NameError :
pkg_name_cache = _non_multiarch = [ pkg . name for pkg in cache if ' : ' not in pkg . name ] # noqa: F841
else :
# Create a cache of pkg_names including multiarch only once
try :
pkg_name_cache = _all_pkg_names
except NameError :
pkg_name_cache = _all_pkg_names = [ pkg . name for pkg in cache ] # noqa: F841
2016-10-22 15:36:29 +00:00
2017-03-01 17:50:26 +00:00
matches = fnmatch . filter ( pkg_name_cache , pkgname_pattern )
2014-09-26 01:01:01 +00:00
2017-09-15 21:06:07 +00:00
if not matches :
2017-03-01 17:50:26 +00:00
m . fail_json ( msg = " No package(s) matching ' %s ' available " % str ( pkgname_pattern ) )
else :
new_pkgspec . extend ( matches )
2014-09-26 01:01:01 +00:00
else :
2017-03-01 17:50:26 +00:00
# No wildcards in name
new_pkgspec . append ( pkgspec_pattern )
2014-09-26 01:01:01 +00:00
return new_pkgspec
2016-09-14 20:26:09 +00:00
2016-04-18 14:18:07 +00:00
def parse_diff ( output ) :
2016-09-09 00:56:09 +00:00
diff = to_native ( output ) . splitlines ( )
2016-04-18 14:18:07 +00:00
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 :
2016-06-01 21:38:00 +00:00
# show everything
2016-04-18 14:18:07 +00:00
diff_start = - 1
try :
# check for end marker line from both apt-get and aptitude
2016-09-09 00:56:09 +00:00
diff_end = next ( i for i , item in enumerate ( diff ) if re . match ( ' [0-9]+ (packages )?upgraded ' , item ) )
2016-04-18 14:18:07 +00:00
except StopIteration :
diff_end = len ( diff )
diff_start + = 1
diff_end + = 1
return { ' prepared ' : ' \n ' . join ( diff [ diff_start : diff_end ] ) }
2016-09-14 20:26:09 +00:00
2018-03-23 11:23:12 +00:00
def mark_installed_manually ( m , packages ) :
2018-03-23 14:06:03 +00:00
if not packages :
return
2018-03-23 11:23:12 +00:00
apt_mark_cmd_path = m . get_bin_path ( " apt-mark " , required = True )
cmd = " %s manual %s " % ( apt_mark_cmd_path , ' ' . join ( packages ) )
rc , out , err = m . run_command ( cmd )
if APT_MARK_INVALID_OP in err :
cmd = " %s unmarkauto %s " % ( apt_mark_cmd_path , ' ' . join ( packages ) )
rc , out , err = m . run_command ( cmd )
if rc != 0 :
m . fail_json ( msg = " ' %s ' failed: %s " % ( cmd , err ) , stdout = out , stderr = err , rc = rc )
2014-09-26 01:01:01 +00:00
def install ( m , pkgspec , cache , upgrade = False , default_release = None ,
2015-11-02 21:03:18 +00:00
install_recommends = None , force = False ,
2015-01-26 17:36:35 +00:00
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) ,
2016-03-23 11:55:58 +00:00
build_dep = False , autoremove = False , only_upgrade = False ,
allow_unauthenticated = False ) :
2014-11-14 00:24:21 +00:00
pkg_list = [ ]
2014-09-26 01:01:01 +00:00
packages = " "
pkgspec = expand_pkgspec_from_fnmatches ( m , pkgspec , cache )
2018-03-23 11:23:12 +00:00
package_names = [ ]
2014-09-26 01:01:01 +00:00
for package in pkgspec :
2015-01-26 17:36:35 +00:00
if build_dep :
# Let apt decide what to install
pkg_list . append ( " ' %s ' " % package )
continue
2016-10-22 15:36:29 +00:00
name , version = package_split ( package )
2018-03-23 11:23:12 +00:00
package_names . append ( name )
2016-10-22 15:36:29 +00:00
installed , upgradable , has_files = package_status ( m , name , version , cache , state = ' install ' )
2017-08-30 18:46:52 +00:00
if ( not installed and not only_upgrade ) or ( upgrade and upgradable ) :
2014-11-14 00:24:21 +00: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 )
2014-09-26 01:01:01 +00:00
2017-09-15 21:06:07 +00:00
if packages :
2014-09-26 01:01:01 +00:00
if force :
force_yes = ' --force-yes '
else :
force_yes = ' '
if m . check_mode :
check_arg = ' --simulate '
else :
check_arg = ' '
2016-02-05 18:42:25 +00:00
if autoremove :
autoremove = ' --auto-remove '
else :
autoremove = ' '
2016-02-19 08:52:53 +00:00
if only_upgrade :
only_upgrade = ' --only-upgrade '
else :
only_upgrade = ' '
2015-01-26 17:36:35 +00:00
if build_dep :
2016-03-17 10:22:07 +00: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 17:36:35 +00:00
else :
2016-02-19 08:52:53 +00:00
cmd = " %s -y %s %s %s %s %s install %s " % ( APT_GET_CMD , dpkg_options , only_upgrade , force_yes , autoremove , check_arg , packages )
2014-09-26 01:01:01 +00:00
if default_release :
cmd + = " -t ' %s ' " % ( default_release , )
2015-11-02 21:03:18 +00:00
if install_recommends is False :
2015-08-15 09:40:00 +00:00
cmd + = " -o APT::Install-Recommends=no "
2015-11-02 21:03:18 +00:00
elif install_recommends is True :
2015-08-15 09:40:00 +00:00
cmd + = " -o APT::Install-Recommends=yes "
2015-11-02 21:03:18 +00:00
# install_recommends is None uses the OS default
2014-09-26 01:01:01 +00:00
2016-03-23 11:55:58 +00:00
if allow_unauthenticated :
cmd + = " --allow-unauthenticated "
2014-09-26 01:01:01 +00:00
rc , out , err = m . run_command ( cmd )
2016-04-18 14:18:07 +00:00
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
2017-09-15 21:06:07 +00:00
status = True
data = dict ( changed = True , stdout = out , stderr = err , diff = diff )
2014-09-26 01:01:01 +00:00
if rc :
2017-09-15 21:06:07 +00:00
status = False
data = dict ( msg = " ' %s ' failed: %s " % ( cmd , err ) , stdout = out , stderr = err , rc = rc )
2014-09-26 01:01:01 +00:00
else :
2018-03-23 11:23:12 +00:00
status = True
data = dict ( changed = False )
if not build_dep :
mark_installed_manually ( m , package_names )
return ( status , data )
2014-09-26 01:01:01 +00:00
2016-09-14 20:26:09 +00:00
2016-05-06 07:17:04 +00:00
def get_field_of_deb ( m , deb_file , field = " Version " ) :
cmd_dpkg = m . get_bin_path ( " dpkg " , True )
cmd = cmd_dpkg + " --field %s %s " % ( deb_file , field )
rc , stdout , stderr = m . run_command ( cmd )
if rc != 0 :
m . fail_json ( msg = " %s failed " % cmd , stdout = stdout , stderr = stderr )
2016-09-09 00:56:09 +00:00
return to_native ( stdout ) . strip ( ' \n ' )
2016-05-06 07:17:04 +00:00
2016-09-14 20:26:09 +00:00
2016-03-23 11:55:58 +00:00
def install_deb ( m , debs , cache , force , install_recommends , allow_unauthenticated , dpkg_options ) :
2017-05-01 15:25:39 +00:00
changed = False
2014-09-26 01:01:01 +00:00
deps_to_install = [ ]
pkgs_to_install = [ ]
for deb_file in debs . split ( ' , ' ) :
2015-02-13 16:06:06 +00:00
try :
pkg = apt . debfile . DebPackage ( deb_file )
2016-05-06 07:17:04 +00:00
pkg_name = get_field_of_deb ( m , deb_file , " Package " )
pkg_version = get_field_of_deb ( m , deb_file , " Version " )
2017-05-30 20:09:43 +00:00
if len ( apt_pkg . get_architectures ( ) ) > 1 :
pkg_arch = get_field_of_deb ( m , deb_file , " Architecture " )
pkg_key = " %s : %s " % ( pkg_name , pkg_arch )
else :
pkg_key = pkg_name
2016-05-06 07:17:04 +00:00
try :
2017-05-30 20:09:43 +00:00
installed_pkg = apt . Cache ( ) [ pkg_key ]
2016-05-06 07:17:04 +00:00
installed_version = installed_pkg . installed . version
if package_version_compare ( pkg_version , installed_version ) == 0 :
# Does not need to down-/upgrade, move on to next package
continue
2016-05-20 14:48:10 +00:00
except Exception :
2016-05-06 07:17:04 +00:00
# Must not be installed, continue with installation
pass
2015-08-18 18:59:35 +00:00
# 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-09-26 01:01:01 +00:00
2017-12-26 03:03:21 +00:00
except Exception as e :
m . fail_json ( msg = " Unable to install package: %s " % to_native ( e ) )
2014-09-26 01:01:01 +00:00
# and add this deb to the list of packages to install
pkgs_to_install . append ( deb_file )
# install the deps through apt
retvals = { }
2017-09-15 21:06:07 +00:00
if deps_to_install :
2014-09-26 01:01:01 +00:00
( 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 )
2017-09-15 21:06:07 +00:00
if pkgs_to_install :
2017-12-03 16:14:49 +00:00
options = ' ' . join ( [ " -- %s " % x for x in dpkg_options . split ( " , " ) ] )
2014-09-26 01:01:01 +00:00
if m . check_mode :
options + = " --simulate "
if force :
2014-09-30 01:55:34 +00:00
options + = " --force-all "
2014-09-26 01:01:01 +00: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 14:18:07 +00:00
if " diff " in retvals :
diff = retvals [ " diff " ]
2016-06-01 20:43:34 +00:00
if ' prepared ' in diff :
diff [ ' prepared ' ] + = ' \n \n ' + out
2016-04-18 14:18:07 +00:00
else :
2016-06-01 21:38:00 +00:00
diff = parse_diff ( out )
2014-09-26 01:01:01 +00:00
if " stderr " in retvals :
stderr = retvals [ " stderr " ] + err
else :
stderr = err
if rc == 0 :
2016-04-18 14:18:07 +00:00
m . exit_json ( changed = True , stdout = stdout , stderr = stderr , diff = diff )
2014-09-26 01:01:01 +00:00
else :
m . fail_json ( msg = " %s failed " % cmd , stdout = stdout , stderr = stderr )
else :
2017-05-01 15:25:39 +00:00
m . exit_json ( changed = changed , stdout = retvals . get ( ' stdout ' , ' ' ) , stderr = retvals . get ( ' stderr ' , ' ' ) , diff = retvals . get ( ' diff ' , ' ' ) )
2014-09-26 01:01:01 +00:00
2016-09-14 20:26:09 +00:00
2016-06-22 05:56:09 +00:00
def remove ( m , pkgspec , cache , purge = False , force = False ,
2016-02-05 18:42:25 +00:00
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) , autoremove = False ) :
2014-11-14 00:24:21 +00:00
pkg_list = [ ]
2014-09-26 01:01:01 +00:00
pkgspec = expand_pkgspec_from_fnmatches ( m , pkgspec , cache )
for package in pkgspec :
name , version = package_split ( package )
installed , upgradable , has_files = package_status ( m , name , version , cache , state = ' remove ' )
if installed or ( has_files and purge ) :
2014-11-14 00:24:21 +00:00
pkg_list . append ( " ' %s ' " % package )
packages = ' ' . join ( pkg_list )
2014-09-26 01:01:01 +00:00
2017-09-15 21:06:07 +00:00
if not packages :
2014-09-26 01:01:01 +00:00
m . exit_json ( changed = False )
else :
2016-06-22 05:56:09 +00:00
if force :
force_yes = ' --force-yes '
else :
force_yes = ' '
2014-09-26 01:01:01 +00:00
if purge :
purge = ' --purge '
else :
purge = ' '
2016-02-05 18:42:25 +00:00
if autoremove :
autoremove = ' --auto-remove '
else :
autoremove = ' '
2014-09-26 01:01:01 +00:00
if m . check_mode :
2016-04-18 14:18:07 +00:00
check_arg = ' --simulate '
else :
check_arg = ' '
2017-05-01 15:25:39 +00:00
cmd = " %s -q -y %s %s %s %s %s remove %s " % ( APT_GET_CMD , dpkg_options , purge , force_yes , autoremove , check_arg , packages )
2014-09-26 01:01:01 +00:00
rc , out , err = m . run_command ( cmd )
2016-04-18 14:18:07 +00:00
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
2014-09-26 01:01:01 +00:00
if rc :
2017-04-10 14:59:56 +00:00
m . fail_json ( msg = " ' apt-get remove %s ' failed: %s " % ( packages , err ) , stdout = out , stderr = err , rc = rc )
2016-04-18 14:18:07 +00:00
m . exit_json ( changed = True , stdout = out , stderr = err , diff = diff )
2014-09-26 01:01:01 +00:00
2016-09-14 20:26:09 +00:00
2017-05-25 19:52:41 +00:00
def cleanup ( m , purge = False , force = False , operation = None ,
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) ) :
if force :
force_yes = ' --force-yes '
else :
force_yes = ' '
if purge :
purge = ' --purge '
else :
purge = ' '
if m . check_mode :
check_arg = ' --simulate '
else :
check_arg = ' '
cmd = " %s -y %s %s %s %s %s " % ( APT_GET_CMD , dpkg_options , purge , force_yes , operation , check_arg )
rc , out , err = m . run_command ( cmd )
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
if rc :
m . fail_json ( msg = " ' apt-get %s ' failed: %s " % ( operation , err ) , stdout = out , stderr = err , rc = rc )
m . exit_json ( changed = bool ( len ( diff ) ) , stdout = out , stderr = err , diff = diff )
2014-09-26 01:01:01 +00:00
def upgrade ( m , mode = " yes " , force = False , default_release = None ,
2017-08-08 13:53:44 +00:00
use_apt_get = False ,
2018-03-28 10:18:36 +00:00
dpkg_options = expand_dpkg_options ( DPKG_OPTIONS ) , autoremove = False ) :
2017-09-22 11:50:17 +00:00
if autoremove :
autoremove = ' --auto-remove '
else :
autoremove = ' '
2014-09-26 01:01:01 +00:00
if m . check_mode :
check_arg = ' --simulate '
else :
check_arg = ' '
apt_cmd = None
2014-11-11 05:45:27 +00:00
prompt_regex = None
2017-08-08 13:53:44 +00:00
if mode == " dist " or ( mode == " full " and use_apt_get ) :
2014-09-26 01:01:01 +00:00
# apt-get dist-upgrade
apt_cmd = APT_GET_CMD
2018-03-28 10:18:36 +00:00
upgrade_command = " dist-upgrade %s " % ( autoremove )
2017-08-08 13:53:44 +00:00
elif mode == " full " and not use_apt_get :
2014-09-26 01:01:01 +00:00
# aptitude full-upgrade
apt_cmd = APTITUDE_CMD
upgrade_command = " full-upgrade "
else :
2017-08-08 13:53:44 +00:00
if use_apt_get :
apt_cmd = APT_GET_CMD
2017-09-22 11:50:17 +00:00
upgrade_command = " upgrade --with-new-pkgs %s " % ( autoremove )
2017-08-08 13:53:44 +00:00
else :
# aptitude safe-upgrade # mode=yes # default
apt_cmd = APTITUDE_CMD
upgrade_command = " safe-upgrade "
prompt_regex = r " (^Do you want to ignore this warning and proceed anyway \ ?|^ \ * \ * \ *.* \ [default=.* \ ]) "
2014-09-26 01:01:01 +00:00
if force :
if apt_cmd == APT_GET_CMD :
force_yes = ' --force-yes '
else :
2014-11-11 05:45:27 +00:00
force_yes = ' --assume-yes --allow-untrusted '
2014-09-26 01:01:01 +00:00
else :
force_yes = ' '
2017-12-20 04:39:15 +00:00
if apt_cmd is None :
if use_apt_get :
apt_cmd = APT_GET_CMD
else :
m . fail_json ( msg = " Unable to find APTITUDE in path. Please make sure "
" to have APTITUDE in path or use ' force_apt_get=True ' " )
2014-09-26 01:01:01 +00:00
apt_cmd_path = m . get_bin_path ( apt_cmd , required = True )
cmd = ' %s -y %s %s %s %s ' % ( apt_cmd_path , dpkg_options ,
2017-05-01 15:25:39 +00:00
force_yes , check_arg , upgrade_command )
2014-09-26 01:01:01 +00:00
if default_release :
cmd + = " -t ' %s ' " % ( default_release , )
2014-11-11 05:45:27 +00:00
rc , out , err = m . run_command ( cmd , prompt_regex = prompt_regex )
2016-04-18 14:18:07 +00:00
if m . _diff :
diff = parse_diff ( out )
else :
diff = { }
2014-09-26 01:01:01 +00:00
if rc :
2017-04-10 14:59:56 +00:00
m . fail_json ( msg = " ' %s %s ' failed: %s " % ( apt_cmd , upgrade_command , err ) , stdout = out , rc = rc )
2014-09-26 01:01:01 +00:00
if ( apt_cmd == APT_GET_CMD and APT_GET_ZERO in out ) or ( apt_cmd == APTITUDE_CMD and APTITUDE_ZERO in out ) :
m . exit_json ( changed = False , msg = out , stdout = out , stderr = err )
2016-04-18 14:18:07 +00:00
m . exit_json ( changed = True , msg = out , stdout = out , stderr = err , diff = diff )
2014-09-26 01:01:01 +00:00
2016-09-14 20:26:09 +00:00
2015-12-03 04:24:23 +00: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 :
2017-04-26 15:35:40 +00:00
rsp , info = fetch_url ( module , deb , method = ' GET ' )
if info [ ' status ' ] != 200 :
module . fail_json ( msg = " Failed to download %s , %s " % ( deb ,
info [ ' msg ' ] ) )
2017-02-19 17:02:44 +00:00
# Ensure file is open in binary mode for Python 3
f = open ( package , ' wb ' )
2015-12-03 04:24:23 +00:00
# Read 1kb at a time to save on ram
while True :
data = rsp . read ( BUFSIZE )
2017-02-19 17:02:44 +00:00
data = to_bytes ( data , errors = ' surrogate_or_strict ' )
2015-12-03 04:24:23 +00:00
2017-02-19 17:02:44 +00:00
if len ( data ) < 1 :
2017-12-03 16:14:49 +00:00
break # End of file, break while loop
2015-12-03 04:24:23 +00:00
f . write ( data )
f . close ( )
deb = package
2017-12-26 03:03:21 +00:00
except Exception as e :
module . fail_json ( msg = " Failure downloading %s , %s " % ( deb , to_native ( e ) ) )
2015-12-03 04:24:23 +00:00
return deb
2016-09-14 20:26:09 +00:00
2016-09-28 09:40:02 +00:00
def get_cache_mtime ( ) :
""" Return mtime of a valid apt cache file.
Stat the apt cache file and if no cache file is found return 0
: returns : ` ` int ` `
"""
2017-09-15 21:06:07 +00:00
cache_time = 0
2016-09-28 09:40:02 +00:00
if os . path . exists ( APT_UPDATE_SUCCESS_STAMP_PATH ) :
2017-09-15 21:06:07 +00:00
cache_time = os . stat ( APT_UPDATE_SUCCESS_STAMP_PATH ) . st_mtime
2016-09-28 09:40:02 +00:00
elif os . path . exists ( APT_LISTS_PATH ) :
2017-09-15 21:06:07 +00:00
cache_time = os . stat ( APT_LISTS_PATH ) . st_mtime
return cache_time
2016-09-28 09:40:02 +00:00
def get_updated_cache_time ( ) :
""" Return the mtime time stamp and the updated cache time.
Always retrieve the mtime of the apt cache or set the ` cache_mtime `
variable to 0
: returns : ` ` tuple ` `
"""
cache_mtime = get_cache_mtime ( )
mtimestamp = datetime . datetime . fromtimestamp ( cache_mtime )
updated_cache_time = int ( time . mktime ( mtimestamp . timetuple ( ) ) )
return mtimestamp , updated_cache_time
2016-11-01 23:30:50 +00:00
# https://github.com/ansible/ansible-modules-core/issues/2951
def get_cache ( module ) :
''' Attempt to get the cache object and update till it works '''
cache = None
try :
cache = apt . Cache ( )
2017-12-26 03:03:21 +00:00
except SystemError as e :
2018-02-02 00:30:08 +00:00
if ' /var/lib/apt/lists/ ' in to_native ( e ) . lower ( ) :
2016-11-01 23:30:50 +00:00
# update cache until files are fixed or retries exceeded
retries = 0
while retries < 2 :
( rc , so , se ) = module . run_command ( [ ' apt-get ' , ' update ' , ' -q ' ] )
retries + = 1
if rc == 0 :
break
if rc != 0 :
2018-02-02 00:30:08 +00:00
module . fail_json ( msg = ' Updating the cache to correct corrupt package lists failed: \n %s \n %s ' % ( to_native ( e ) , so + se ) , rc = rc )
2016-11-01 23:30:50 +00:00
# try again
cache = apt . Cache ( )
else :
2018-03-19 13:30:58 +00:00
module . fail_json ( msg = to_native ( e ) )
2016-11-01 23:30:50 +00:00
return cache
2017-01-27 23:45:23 +00:00
2016-11-01 23:30:50 +00:00
2014-09-26 01:01:01 +00:00
def main ( ) :
module = AnsibleModule (
2017-05-01 15:25:39 +00:00
argument_spec = dict (
2017-12-03 16:14:49 +00:00
state = dict ( type = ' str ' , default = ' present ' , choices = [ ' absent ' , ' build-dep ' , ' installed ' , ' latest ' , ' present ' , ' removed ' , ' present ' ] ) ,
update_cache = dict ( type = ' bool ' , aliases = [ ' update-cache ' ] ) ,
2017-05-01 15:25:39 +00:00
cache_valid_time = dict ( type = ' int ' , default = 0 ) ,
2017-12-03 16:14:49 +00:00
purge = dict ( type = ' bool ' , default = False ) ,
package = dict ( type = ' list ' , aliases = [ ' pkg ' , ' name ' ] ) ,
deb = dict ( type = ' path ' ) ,
default_release = dict ( type = ' str ' , aliases = [ ' default-release ' ] ) ,
install_recommends = dict ( type = ' bool ' , aliases = [ ' install-recommends ' ] ) ,
force = dict ( type = ' bool ' , default = False ) ,
upgrade = dict ( type = ' str ' , choices = [ ' dist ' , ' full ' , ' no ' , ' safe ' , ' yes ' ] ) ,
dpkg_options = dict ( type = ' str ' , default = DPKG_OPTIONS ) ,
autoremove = dict ( type = ' bool ' , default = False ) ,
autoclean = dict ( type = ' bool ' , default = False ) ,
2017-05-01 15:25:39 +00:00
only_upgrade = dict ( type = ' bool ' , default = False ) ,
2017-08-08 13:53:44 +00:00
force_apt_get = dict ( type = ' bool ' , default = False ) ,
2017-12-03 16:14:49 +00:00
allow_unauthenticated = dict ( type = ' bool ' , default = False , aliases = [ ' allow-unauthenticated ' ] ) ,
2014-09-26 01:01:01 +00:00
) ,
2017-12-03 16:14:49 +00:00
mutually_exclusive = [ [ ' deb ' , ' package ' , ' upgrade ' ] ] ,
required_one_of = [ [ ' autoremove ' , ' deb ' , ' package ' , ' update_cache ' , ' upgrade ' ] ] ,
supports_check_mode = True ,
2014-09-26 01:01:01 +00:00
)
2016-02-07 21:20:26 +00:00
module . run_command_environ_update = APT_ENV_VARS
2014-09-26 01:01:01 +00:00
if not HAS_PYTHON_APT :
2016-01-31 10:51:34 +00:00
if module . check_mode :
2016-09-14 20:26:09 +00:00
module . fail_json ( msg = " %s must be installed to use check mode. "
" If run normally this module can auto-install it. " % PYTHON_APT )
2014-09-26 01:01:01 +00:00
try :
2016-09-14 20:26:09 +00:00
module . run_command ( [ ' apt-get ' , ' update ' ] , check_rc = True )
2018-03-01 13:10:58 +00:00
module . run_command ( [ ' apt-get ' , ' install ' , ' --no-install-recommends ' , PYTHON_APT , ' -y ' , ' -q ' ] , check_rc = True )
2014-09-26 01:01:01 +00:00
global apt , apt_pkg
import apt
2015-03-19 21:54:59 +00:00
import apt . debfile
2014-09-26 01:01:01 +00:00
import apt_pkg
except ImportError :
2016-09-14 20:26:09 +00:00
module . fail_json ( msg = " Could not import python modules: apt, apt_pkg. "
" Please install %s package. " % PYTHON_APT )
2014-09-26 01:01:01 +00: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 " )
p = module . params
2015-07-03 18:43:21 +00:00
if p [ ' upgrade ' ] == ' no ' :
p [ ' upgrade ' ] = None
2017-08-08 13:53:44 +00:00
use_apt_get = p [ ' force_apt_get ' ]
if not use_apt_get and not APTITUDE_CMD and p . get ( ' upgrade ' , None ) in [ ' full ' , ' safe ' , ' yes ' ] :
2017-09-26 20:22:55 +00:00
module . warn ( " Could not find aptitude. Using apt-get instead. " )
2017-08-08 13:53:44 +00:00
use_apt_get = True
2014-09-26 01:01:01 +00:00
2015-03-08 15:47:08 +00:00
updated_cache = False
updated_cache_time = 0
2014-09-26 01:01:01 +00:00
install_recommends = p [ ' install_recommends ' ]
2016-03-23 11:55:58 +00:00
allow_unauthenticated = p [ ' allow_unauthenticated ' ]
2014-09-26 01:01:01 +00:00
dpkg_options = expand_dpkg_options ( p [ ' dpkg_options ' ] )
2016-02-05 18:42:25 +00:00
autoremove = p [ ' autoremove ' ]
2017-05-25 19:52:41 +00:00
autoclean = p [ ' autoclean ' ]
2014-09-26 01:01:01 +00:00
2014-11-12 22:01:14 +00:00
# Deal with deprecated aliases
if p [ ' state ' ] == ' installed ' :
2017-10-09 16:15:56 +00:00
module . deprecate ( " State ' installed ' is deprecated. Using state ' present ' instead. " , version = " 2.9 " )
2014-11-12 22:01:14 +00:00
p [ ' state ' ] = ' present '
if p [ ' state ' ] == ' removed ' :
2017-10-09 16:15:56 +00:00
module . deprecate ( " State ' removed ' is deprecated. Using state ' absent ' instead. " , version = " 2.9 " )
2014-11-12 22:01:14 +00:00
p [ ' state ' ] = ' absent '
2016-11-01 23:30:50 +00:00
# Get the cache object
cache = get_cache ( module )
2014-09-26 01:01:01 +00:00
try :
if p [ ' default_release ' ] :
try :
apt_pkg . config [ ' APT::Default-Release ' ] = p [ ' default_release ' ]
except AttributeError :
apt_pkg . Config [ ' APT::Default-Release ' ] = p [ ' default_release ' ]
# reopen cache w/ modified config
cache . open ( progress = None )
2016-09-28 09:40:02 +00:00
mtimestamp , updated_cache_time = get_updated_cache_time ( )
# Cache valid time is default 0, which will update the cache if
# needed and `update_cache` was set to true
updated_cache = False
2017-08-07 12:36:51 +00:00
if p [ ' update_cache ' ] or p [ ' cache_valid_time ' ] :
2015-03-08 15:47:08 +00:00
now = datetime . datetime . now ( )
2016-09-28 09:40:02 +00:00
tdelta = datetime . timedelta ( seconds = p [ ' cache_valid_time ' ] )
if not mtimestamp + tdelta > = now :
# Retry to update the cache up to 3 times
2018-03-15 16:32:25 +00:00
err = ' '
2016-09-14 20:26:09 +00:00
for retry in range ( 3 ) :
2015-01-28 12:48:46 +00:00
try :
cache . update ( )
break
2018-03-15 16:32:25 +00:00
except apt . cache . FetchFailedException as e :
err = to_native ( e )
2015-01-28 12:48:46 +00:00
else :
2018-03-15 16:32:25 +00:00
module . fail_json ( msg = ' Failed to update apt cache: %s ' % err )
2014-09-26 01:01:01 +00:00
cache . open ( progress = None )
2015-03-08 15:47:08 +00:00
updated_cache = True
2016-09-28 09:40:02 +00:00
mtimestamp , updated_cache_time = get_updated_cache_time ( )
2016-10-13 14:47:50 +00:00
# If there is nothing else to do exit. This will set state as
2016-09-28 09:40:02 +00:00
# changed based on if the cache was updated.
2014-09-26 01:01:01 +00:00
if not p [ ' package ' ] and not p [ ' upgrade ' ] and not p [ ' deb ' ] :
2016-09-28 09:40:02 +00:00
module . exit_json (
changed = updated_cache ,
cache_updated = updated_cache ,
cache_update_time = updated_cache_time
)
2014-09-26 01:01:01 +00:00
force_yes = p [ ' force ' ]
if p [ ' upgrade ' ] :
2017-09-22 11:50:17 +00:00
upgrade ( module , p [ ' upgrade ' ] , force_yes , p [ ' default_release ' ] , use_apt_get , dpkg_options , autoremove )
2014-09-26 01:01:01 +00:00
if p [ ' deb ' ] :
2014-11-12 22:16:02 +00:00
if p [ ' state ' ] != ' present ' :
2014-11-12 19:20:21 +00:00
module . fail_json ( msg = " deb only supports state=present " )
2015-12-03 04:24:23 +00:00
if ' :// ' in p [ ' deb ' ] :
p [ ' deb ' ] = download ( module , p [ ' deb ' ] )
2014-09-26 01:01:01 +00:00
install_deb ( module , p [ ' deb ' ] , cache ,
install_recommends = install_recommends ,
2016-03-23 11:55:58 +00:00
allow_unauthenticated = allow_unauthenticated ,
2014-09-26 01:01:01 +00:00
force = force_yes , dpkg_options = p [ ' dpkg_options ' ] )
2017-05-25 19:52:41 +00:00
unfiltered_packages = p [ ' package ' ] or ( )
packages = [ package for package in unfiltered_packages if package != ' * ' ]
all_installed = ' * ' in unfiltered_packages
2014-09-26 01:01:01 +00:00
latest = p [ ' state ' ] == ' latest '
2017-05-24 23:04:38 +00:00
if latest and all_installed :
if packages :
2017-12-01 06:42:59 +00:00
module . fail_json ( msg = ' unable to install additional packages when upgrading all installed packages ' )
2017-09-22 11:50:17 +00:00
upgrade ( module , ' yes ' , force_yes , p [ ' default_release ' ] , use_apt_get , dpkg_options , autoremove )
2017-05-24 23:04:38 +00:00
2017-03-01 17:50:26 +00:00
if packages :
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 )
2014-09-26 01:01:01 +00:00
2017-05-25 19:52:41 +00:00
if not packages :
if autoclean :
cleanup ( module , p [ ' purge ' ] , force = force_yes , operation = ' autoclean ' , dpkg_options = dpkg_options )
if autoremove :
cleanup ( module , p [ ' purge ' ] , force = force_yes , operation = ' autoremove ' , dpkg_options = dpkg_options )
2015-01-26 17:36:35 +00:00
if p [ ' state ' ] in ( ' latest ' , ' present ' , ' build-dep ' ) :
2015-01-27 15:28:56 +00:00
state_upgrade = False
state_builddep = False
2015-01-26 20:16:42 +00:00
if p [ ' state ' ] == ' latest ' :
2015-01-27 15:28:56 +00:00
state_upgrade = True
2015-01-26 20:16:42 +00:00
if p [ ' state ' ] == ' build-dep ' :
2015-01-27 15:28:56 +00:00
state_builddep = True
2016-09-28 09:40:02 +00:00
success , retvals = install (
module ,
packages ,
cache ,
upgrade = state_upgrade ,
default_release = p [ ' default_release ' ] ,
install_recommends = install_recommends ,
force = force_yes ,
dpkg_options = dpkg_options ,
build_dep = state_builddep ,
autoremove = autoremove ,
only_upgrade = p [ ' only_upgrade ' ] ,
allow_unauthenticated = allow_unauthenticated
)
# Store if the cache has been updated
retvals [ ' cache_updated ' ] = updated_cache
# Store when the update time was last
retvals [ ' cache_update_time ' ] = updated_cache_time
2014-09-26 01:01:01 +00:00
if success :
module . exit_json ( * * retvals )
else :
module . fail_json ( * * retvals )
2014-11-12 22:01:14 +00:00
elif p [ ' state ' ] == ' absent ' :
2016-06-22 05:56:09 +00:00
remove ( module , packages , cache , p [ ' purge ' ] , force = force_yes , dpkg_options = dpkg_options , autoremove = autoremove )
2014-09-26 01:01:01 +00:00
except apt . cache . LockFailedException :
module . fail_json ( msg = " Failed to lock apt for exclusive operation " )
2015-01-14 22:22:05 +00:00
except apt . cache . FetchFailedException :
module . fail_json ( msg = " Could not fetch updated apt files " )
2014-09-26 01:01:01 +00:00
2014-11-13 18:32:38 +00:00
if __name__ == " __main__ " :
2014-10-29 19:44:44 +00:00
main ( )