2012-11-24 03:00:59 +01:00
#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
2014-03-28 19:56:59 +01:00
# (c) 2012, Afterburn <http://github.com/afterburn>
# (c) 2013, Aaron Bull Schaefer <aaron@elasticdog.com>
2015-08-04 02:10:50 +02:00
# (c) 2015, Indrajit Raychaudhuri <irc+code@indrajit.com>
2012-11-24 03:00:59 +01:00
#
2014-03-28 19:56:59 +01:00
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
2012-11-24 03:00:59 +01:00
# 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.
#
2014-03-28 19:56:59 +01:00
# Ansible is distributed in the hope that it will be useful,
2012-11-24 03:00:59 +01:00
# 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
2014-03-28 19:56:59 +01:00
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
2012-11-24 03:00:59 +01:00
DOCUMENTATION = '''
- - -
module : pacman
2014-03-28 19:56:59 +01:00
short_description : Manage packages with I ( pacman )
2012-11-24 03:00:59 +01:00
description :
2014-03-28 19:56:59 +01:00
- Manage packages with the I ( pacman ) package manager , which is used by
Arch Linux and its variants .
2012-12-16 23:21:35 +01:00
version_added : " 1.0 "
2015-05-14 06:21:51 +02:00
author :
2015-08-04 02:10:50 +02:00
- " Indrajit Raychaudhuri (@indrajitr) "
2015-05-15 20:52:37 +02:00
- " ' Aaron Bull Schaefer (@elasticdog) ' <aaron@elasticdog.com> "
2015-05-14 06:21:51 +02:00
- " Afterburn "
2014-03-28 19:56:59 +01:00
notes : [ ]
requirements : [ ]
2012-11-24 03:00:59 +01:00
options :
2012-12-16 23:18:42 +01:00
name :
2012-11-24 03:00:59 +01:00
description :
2014-03-28 19:56:59 +01:00
- Name of the package to install , upgrade , or remove .
required : false
default : null
2016-08-05 17:48:08 +02:00
aliases : [ ' pkg ' , ' package ' ]
2012-11-24 03:00:59 +01:00
2012-12-16 23:18:42 +01:00
state :
description :
2014-03-28 19:56:59 +01:00
- Desired state of the package .
2012-12-16 23:18:42 +01:00
required : false
2014-03-28 19:56:59 +01:00
default : " present "
2015-01-11 05:56:05 +01:00
choices : [ " present " , " absent " , " latest " ]
2012-12-16 23:18:42 +01:00
2014-03-28 19:56:59 +01:00
recurse :
2012-12-16 23:18:42 +01:00
description :
2014-03-28 19:56:59 +01:00
- When removing a package , also remove its dependencies , provided
that they are not required by other packages and were not
explicitly installed by a user .
2012-12-16 23:18:42 +01:00
required : false
2015-08-04 02:10:50 +02:00
default : no
2014-03-28 19:56:59 +01:00
choices : [ " yes " , " no " ]
version_added : " 1.3 "
2012-12-16 23:18:42 +01:00
2015-03-31 10:28:35 +02:00
force :
description :
2016-02-11 13:34:44 +01:00
- When removing package - force remove package , without any
checks . When update_cache - force redownload repo
databases .
2015-03-31 10:28:35 +02:00
required : false
2015-08-04 02:10:50 +02:00
default : no
2015-03-31 10:28:35 +02:00
choices : [ " yes " , " no " ]
2015-04-01 11:27:31 +02:00
version_added : " 2.0 "
2015-03-31 10:28:35 +02:00
2014-03-28 19:56:59 +01:00
update_cache :
2013-07-23 15:01:13 +02:00
description :
2014-03-28 19:56:59 +01:00
- Whether or not to refresh the master package lists . This can be
run as part of a package installation or as a separate step .
2013-07-23 15:01:13 +02:00
required : false
2015-08-04 02:10:50 +02:00
default : no
2014-03-28 19:56:59 +01:00
choices : [ " yes " , " no " ]
2016-08-05 17:48:08 +02:00
aliases : [ ' update-cache ' ]
2015-03-31 10:18:22 +02:00
2015-04-01 11:27:31 +02:00
upgrade :
2015-03-31 10:18:22 +02:00
description :
- Whether or not to upgrade whole system
required : false
2015-08-04 02:10:50 +02:00
default : no
2015-03-31 10:18:22 +02:00
choices : [ " yes " , " no " ]
2015-04-01 11:27:31 +02:00
version_added : " 2.0 "
2013-06-14 11:53:43 +02:00
'''
EXAMPLES = '''
# Install package foo
2014-03-28 19:56:59 +01:00
- pacman : name = foo state = present
2013-06-14 11:53:43 +02:00
2015-01-10 16:40:03 +01:00
# Upgrade package foo
2015-01-11 05:56:05 +01:00
- pacman : name = foo state = latest update_cache = yes
2015-01-10 16:40:03 +01:00
2014-03-28 19:56:59 +01:00
# Remove packages foo and bar
2013-06-14 11:53:43 +02:00
- pacman : name = foo , bar state = absent
2013-07-23 15:01:13 +02:00
# Recursively remove package baz
2013-08-03 20:27:11 +02:00
- pacman : name = baz state = absent recurse = yes
2013-07-23 15:01:13 +02:00
2015-05-11 23:58:08 +02:00
# Run the equivalent of "pacman -Sy" as a separate step
2014-03-28 19:56:59 +01:00
- pacman : update_cache = yes
2015-03-31 10:18:22 +02:00
# Run the equivalent of "pacman -Su" as a separate step
- pacman : upgrade = yes
2015-03-31 10:28:35 +02:00
2015-07-26 20:14:14 +02:00
# Run the equivalent of "pacman -Syu" as a separate step
- pacman : update_cache = yes upgrade = yes
2015-03-31 10:28:35 +02:00
# Run the equivalent of "pacman -Rdd", force remove package baz
- pacman : name = baz state = absent force = yes
2012-11-24 03:00:59 +01:00
'''
import shlex
import os
2013-08-17 20:46:16 +02:00
import re
2012-11-24 03:00:59 +01:00
import sys
2015-01-10 16:40:03 +01:00
def get_version ( pacman_output ) :
""" Take pacman -Qi or pacman -Si output and get the Version """
lines = pacman_output . split ( ' \n ' )
for line in lines :
if ' Version ' in line :
return line . split ( ' : ' ) [ 1 ] . strip ( )
return None
2015-07-25 06:07:20 +02:00
def query_package ( module , pacman_path , name , state = " present " ) :
2015-12-16 07:03:18 +01:00
""" Query the package status in both the local system and the repository. Returns a boolean to indicate if the package is installed, a second boolean to indicate if the package is up-to-date and a third boolean to indicate whether online information were available """
2014-03-28 19:56:59 +01:00
if state == " present " :
2015-07-25 06:07:20 +02:00
lcmd = " %s -Qi %s " % ( pacman_path , name )
2015-01-10 16:40:03 +01:00
lrc , lstdout , lstderr = module . run_command ( lcmd , check_rc = False )
if lrc != 0 :
# package is not installed locally
2015-12-16 07:03:18 +01:00
return False , False , False
2015-07-25 06:07:20 +02:00
2015-01-10 16:40:03 +01:00
# get the version installed locally (if any)
lversion = get_version ( lstdout )
2015-07-25 06:07:20 +02:00
rcmd = " %s -Si %s " % ( pacman_path , name )
2015-01-10 16:40:03 +01:00
rrc , rstdout , rstderr = module . run_command ( rcmd , check_rc = False )
# get the version in the repository
rversion = get_version ( rstdout )
if rrc == 0 :
# Return True to indicate that the package is installed locally, and the result of the version number comparison
# to determine if the package is up-to-date.
2015-12-16 07:03:18 +01:00
return True , ( lversion == rversion ) , False
2016-02-11 13:34:44 +01:00
2015-12-16 07:03:18 +01:00
# package is installed but cannot fetch remote Version. Last True stands for the error
return True , True , True
2012-11-24 03:00:59 +01:00
2015-07-25 06:07:20 +02:00
def update_package_db ( module , pacman_path ) :
2016-02-11 13:34:44 +01:00
if module . params [ " force " ] :
args = " Syy "
else :
args = " Sy "
cmd = " %s - %s " % ( pacman_path , args )
2014-03-10 22:11:24 +01:00
rc , stdout , stderr = module . run_command ( cmd , check_rc = False )
2012-11-24 03:00:59 +01:00
2014-03-28 19:56:59 +01:00
if rc == 0 :
return True
else :
2012-11-24 03:00:59 +01:00
module . fail_json ( msg = " could not update package db " )
2014-03-28 19:56:59 +01:00
2015-07-25 06:07:20 +02:00
def upgrade ( module , pacman_path ) :
cmdupgrade = " %s -Suq --noconfirm " % ( pacman_path )
cmdneedrefresh = " %s -Qqu " % ( pacman_path )
2015-03-31 10:18:22 +02:00
rc , stdout , stderr = module . run_command ( cmdneedrefresh , check_rc = False )
if rc == 0 :
2015-07-26 20:14:14 +02:00
if module . check_mode :
data = stdout . split ( ' \n ' )
2015-12-16 07:03:18 +01:00
module . exit_json ( changed = True , msg = " %s package(s) would be upgraded " % ( len ( data ) - 1 ) )
2015-07-25 06:07:20 +02:00
rc , stdout , stderr = module . run_command ( cmdupgrade , check_rc = False )
if rc == 0 :
module . exit_json ( changed = True , msg = ' System upgraded ' )
2015-03-31 10:18:22 +02:00
else :
2015-07-26 20:14:14 +02:00
module . fail_json ( msg = " Could not upgrade " )
2015-03-31 10:18:22 +02:00
else :
2015-07-25 06:07:20 +02:00
module . exit_json ( changed = False , msg = ' Nothing to upgrade ' )
2012-11-24 03:00:59 +01:00
2015-07-25 06:07:20 +02:00
def remove_packages ( module , pacman_path , packages ) :
2016-02-11 13:34:44 +01:00
if module . params [ " recurse " ] or module . params [ " force " ] :
if module . params [ " recurse " ] :
args = " Rs "
if module . params [ " force " ] :
args = " Rdd "
if module . params [ " recurse " ] and module . params [ " force " ] :
args = " Rdds "
2015-03-31 10:28:35 +02:00
else :
args = " R "
2012-11-24 03:00:59 +01:00
remove_c = 0
# Using a for loop incase of error, we can report the package that failed
for package in packages :
# Query the package first, to see if we even need to remove
2015-12-16 07:03:18 +01:00
installed , updated , unknown = query_package ( module , pacman_path , package )
2015-01-10 16:40:03 +01:00
if not installed :
2012-11-24 03:00:59 +01:00
continue
2015-07-25 06:07:20 +02:00
cmd = " %s - %s %s --noconfirm " % ( pacman_path , args , package )
2014-03-10 22:11:24 +01:00
rc , stdout , stderr = module . run_command ( cmd , check_rc = False )
2012-11-24 03:00:59 +01:00
if rc != 0 :
module . fail_json ( msg = " failed to remove %s " % ( package ) )
2014-03-28 19:56:59 +01:00
2012-11-24 03:00:59 +01:00
remove_c + = 1
if remove_c > 0 :
module . exit_json ( changed = True , msg = " removed %s package(s) " % remove_c )
module . exit_json ( changed = False , msg = " package(s) already absent " )
2015-07-25 06:07:20 +02:00
def install_packages ( module , pacman_path , state , packages , package_files ) :
2012-11-24 03:00:59 +01:00
install_c = 0
2015-12-16 07:03:18 +01:00
package_err = [ ]
message = " "
2012-11-24 03:00:59 +01:00
2013-08-17 20:46:16 +02:00
for i , package in enumerate ( packages ) :
2015-01-11 05:56:05 +01:00
# if the package is installed and state == present or state == latest and is up-to-date then skip
2015-12-16 07:03:18 +01:00
installed , updated , latestError = query_package ( module , pacman_path , package )
if latestError and state == ' latest ' :
package_err . append ( package )
2016-02-11 13:34:44 +01:00
2015-01-11 05:56:05 +01:00
if installed and ( state == ' present ' or ( state == ' latest ' and updated ) ) :
2012-11-24 03:00:59 +01:00
continue
2013-08-17 20:46:16 +02:00
if package_files [ i ] :
params = ' -U %s ' % package_files [ i ]
else :
params = ' -S %s ' % package
2016-02-13 15:37:08 +01:00
cmd = " %s %s --noconfirm --needed " % ( pacman_path , params )
2014-03-10 22:11:24 +01:00
rc , stdout , stderr = module . run_command ( cmd , check_rc = False )
2012-11-24 03:00:59 +01:00
if rc != 0 :
module . fail_json ( msg = " failed to install %s " % ( package ) )
install_c + = 1
2016-02-11 13:34:44 +01:00
2015-12-16 07:03:18 +01:00
if state == ' latest ' and len ( package_err ) > 0 :
message = " But could not ensure ' latest ' state for %s package(s) as remote version could not be fetched. " % ( package_err )
2016-02-11 13:34:44 +01:00
2012-11-24 03:00:59 +01:00
if install_c > 0 :
2015-12-16 07:03:18 +01:00
module . exit_json ( changed = True , msg = " installed %s package(s). %s " % ( install_c , message ) )
2016-02-11 13:34:44 +01:00
2015-12-16 07:03:18 +01:00
module . exit_json ( changed = False , msg = " package(s) already installed. %s " % ( message ) )
2016-02-11 13:34:44 +01:00
2015-07-25 06:07:20 +02:00
def check_packages ( module , pacman_path , packages , state ) :
2013-08-17 23:44:41 +02:00
would_be_changed = [ ]
for package in packages :
2015-12-16 07:03:18 +01:00
installed , updated , unknown = query_package ( module , pacman_path , package )
2015-01-11 05:56:05 +01:00
if ( ( state in [ " present " , " latest " ] and not installed ) or
( state == " absent " and installed ) or
( state == " latest " and not updated ) ) :
2013-08-17 23:44:41 +02:00
would_be_changed . append ( package )
if would_be_changed :
if state == " absent " :
state = " removed "
module . exit_json ( changed = True , msg = " %s package(s) would be %s " % (
len ( would_be_changed ) , state ) )
else :
2015-09-18 09:32:10 +02:00
module . exit_json ( changed = False , msg = " package(s) already %s " % state )
2013-08-17 23:44:41 +02:00
2012-12-16 23:18:42 +01:00
2016-02-11 21:44:30 +01:00
def expand_package_groups ( module , pacman_path , pkgs ) :
expanded = [ ]
for pkg in pkgs :
cmd = " %s -Sgq %s " % ( pacman_path , pkg )
rc , stdout , stderr = module . run_command ( cmd , check_rc = False )
if rc == 0 :
# A group was found matching the name, so expand it
for name in stdout . split ( ' \n ' ) :
name = name . strip ( )
if name :
expanded . append ( name )
else :
expanded . append ( pkg )
return expanded
2012-11-24 03:00:59 +01:00
def main ( ) :
module = AnsibleModule (
2014-03-28 19:56:59 +01:00
argument_spec = dict (
2015-09-29 00:11:10 +02:00
name = dict ( aliases = [ ' pkg ' , ' package ' ] , type = ' list ' ) ,
2015-01-11 05:56:05 +01:00
state = dict ( default = ' present ' , choices = [ ' present ' , ' installed ' , " latest " , ' absent ' , ' removed ' ] ) ,
2015-08-04 02:10:50 +02:00
recurse = dict ( default = False , type = ' bool ' ) ,
force = dict ( default = False , type = ' bool ' ) ,
upgrade = dict ( default = False , type = ' bool ' ) ,
2015-09-29 00:11:10 +02:00
update_cache = dict ( default = False , aliases = [ ' update-cache ' ] , type = ' bool ' )
) ,
2015-03-31 10:18:22 +02:00
required_one_of = [ [ ' name ' , ' update_cache ' , ' upgrade ' ] ] ,
2014-03-28 19:56:59 +01:00
supports_check_mode = True )
2012-11-24 03:00:59 +01:00
2015-07-25 06:07:20 +02:00
pacman_path = module . get_bin_path ( ' pacman ' , True )
2012-11-24 03:00:59 +01:00
p = module . params
2014-03-28 19:56:59 +01:00
# normalize the state parameter
if p [ ' state ' ] in [ ' present ' , ' installed ' ] :
p [ ' state ' ] = ' present '
elif p [ ' state ' ] in [ ' absent ' , ' removed ' ] :
p [ ' state ' ] = ' absent '
2013-08-17 23:44:41 +02:00
if p [ " update_cache " ] and not module . check_mode :
2015-07-25 06:07:20 +02:00
update_package_db ( module , pacman_path )
2015-07-26 20:14:14 +02:00
if not ( p [ ' name ' ] or p [ ' upgrade ' ] ) :
module . exit_json ( changed = True , msg = ' Updated the package master lists ' )
2014-03-28 19:56:59 +01:00
2015-07-26 20:14:14 +02:00
if p [ ' update_cache ' ] and module . check_mode and not ( p [ ' name ' ] or p [ ' upgrade ' ] ) :
2014-07-12 17:57:30 +02:00
module . exit_json ( changed = True , msg = ' Would have updated the package cache ' )
2015-03-31 10:18:22 +02:00
if p [ ' upgrade ' ] :
2015-07-25 06:07:20 +02:00
upgrade ( module , pacman_path )
2015-03-31 10:18:22 +02:00
2014-03-28 19:56:59 +01:00
if p [ ' name ' ] :
2016-02-11 21:44:30 +01:00
pkgs = expand_package_groups ( module , pacman_path , p [ ' name ' ] )
2014-03-28 19:56:59 +01:00
pkg_files = [ ]
for i , pkg in enumerate ( pkgs ) :
if pkg . endswith ( ' .pkg.tar.xz ' ) :
# The package given is a filename, extract the raw pkg name from
# it and store the filename
pkg_files . append ( pkg )
pkgs [ i ] = re . sub ( ' -[0-9].*$ ' , ' ' , pkgs [ i ] . split ( ' / ' ) [ - 1 ] )
else :
pkg_files . append ( None )
if module . check_mode :
2015-07-25 06:07:20 +02:00
check_packages ( module , pacman_path , pkgs , p [ ' state ' ] )
2014-03-28 19:56:59 +01:00
2015-01-11 05:56:05 +01:00
if p [ ' state ' ] in [ ' present ' , ' latest ' ] :
2015-07-25 06:07:20 +02:00
install_packages ( module , pacman_path , p [ ' state ' ] , pkgs , pkg_files )
2014-03-28 19:56:59 +01:00
elif p [ ' state ' ] == ' absent ' :
2015-07-25 06:07:20 +02:00
remove_packages ( module , pacman_path , pkgs )
2012-11-24 03:00:59 +01: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-07-25 06:07:20 +02:00
if __name__ == " __main__ " :
main ( )