@ -1,8 +1,10 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Shaun Zinck
# Written by Shaun Zinck <shaun.zinck at gmail.com>
# Copyright (c) 2013 Shaun Zinck <shaun.zinck at gmail.com>
# Copyright (c) 2015 Lawrence Leonard Gilbert <larry@L2G.to>
#
# Written by Shaun Zinck
# Based on pacman module written by Afterburn <http://github.com/afterburn>
# that was based on apt module written by Matthew Williams <matthew@flowroute.com>
#
@ -23,27 +25,32 @@
DOCUMENTATION = '''
- - -
module : pkgin
short_description : Package manager for SmartOS
short_description : Package manager for SmartOS , NetBSD , et al .
description :
- Manages SmartOS packages
- " The standard package manager for SmartOS, but also usable on NetBSD
or any OS that uses C ( pkgsrc ) . ( Home : U ( http : / / pkgin . net / ) ) "
version_added : " 1.0 "
author : Shaun Zinck , Larry Gilbert
notes :
- " Known bug with pkgin < 0.8.0: if a package is removed and another
package depends on it , the other package will be silently removed as
well . New to Ansible 1.9 : check - mode support . "
options :
name :
description :
- name of package to install / remove
- Name of package to install / remove ;
- multiple names may be given , separated by commas
required : true
state :
description :
- state of the package
- Intended state of the package
choices : [ ' present ' , ' absent ' ]
required : false
default : present
author : Shaun Zinck
notes : [ ]
'''
EXAMPLES = '''
# install package foo "
# install package foo
- pkgin : name = foo state = present
# remove package foo
@ -60,64 +67,97 @@ import os
import sys
import pipes
def query_package ( module , pkgin_path , name , state = " present " ) :
def query_package ( module , pkgin_path , name ) :
""" Search for the package by name.
Possible return values :
* " present " - installed , no upgrade needed
* " outdated " - installed , but can be upgraded
* False - not installed or not found
"""
# Use "pkgin search" to find the package. The regular expression will
# only match on the complete name.
rc , out , err = module . run_command ( " %s search \" ^ %s $ \" " % ( pkgin_path , name ) )
# rc will not be 0 unless the search was a success
if rc == 0 :
# Get first line
line = out . split ( ' \n ' ) [ 0 ]
# Break up line at spaces. The first part will be the package with its
# version (e.g. 'gcc47-libs-4.7.2nb4'), and the second will be the state
# of the package:
# '' - not installed
# '<' - installed but out of date
# '=' - installed and up to date
# '>' - installed but newer than the repository version
pkgname_with_version , raw_state = out . split ( ' ' ) [ 0 : 2 ]
if state == " present " :
# Strip version
# (results in sth like 'gcc47-libs')
pkgname_without_version = ' - ' . join ( pkgname_with_version . split ( ' - ' ) [ : - 1 ] )
rc , out , err = module . run_command ( " %s -y list | grep ^ %s " % ( pipes . quote ( pkgin_path ) , pipes . quote ( name ) ) , use_unsafe_shell = True )
if name != pkgname_without_version :
return False
# no fall-through
if rc == 0 :
# At least one package with a package name that starts with ``name``
# is installed. For some cases this is not sufficient to determine
# wether the queried package is installed.
#
# E.g. for ``name='gcc47'``, ``gcc47`` not being installed, but
# ``gcc47-libs`` being installed, ``out`` would be:
#
# gcc47-libs-4.7.2nb4 The GNU Compiler Collection (GCC) support shared libraries.
#
# Multiline output is also possible, for example with the same query
# and bot ``gcc47`` and ``gcc47-libs`` being installed:
#
# gcc47-libs-4.7.2nb4 The GNU Compiler Collection (GCC) support shared libraries.
# gcc47-4.7.2nb3 The GNU Compiler Collection (GCC) - 4.7 Release Series
# The package was found; now return its state
if raw_state == ' < ' :
return ' outdated '
elif raw_state == ' = ' or raw_state == ' > ' :
return ' present '
else :
return False
# Loop over lines in ``out``
for line in out . split ( ' \n ' ) :
# Strip description
# (results in sth. like 'gcc47-libs-4.7.2nb4')
pkgname_with_version = out . split ( ' ' ) [ 0 ]
def format_action_message ( module , action , count ) :
vars = { " actioned " : action ,
" count " : count }
# Strip version
# (results in sth like 'gcc47-libs')
pkgname_without_version = ' - ' . join ( pkgname_with_version . split ( ' - ' ) [ : - 1 ] )
if module . check_mode :
message = " would have %(actioned)s %(count)d package " % vars
else :
message = " %(actioned)s %(count)d package " % vars
if name == pkgname_without_version :
return True
if count == 1 :
return message
else :
return message + " s "
return False
def format_pkgin_command ( module , pkgin_path , command , package ) :
vars = { " pkgin " : pkgin_path ,
" command " : command ,
" package " : package }
if module . check_mode :
return " %(pkgin)s -n %(command)s %(package)s " % vars
else :
return " %(pkgin)s -y %(command)s %(package)s " % vars
def remove_packages ( module , pkgin_path , packages ) :
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
if not query_package ( module , pkgin_path , package ) :
continue
rc , out , err = module . run_command ( " %s -y remove %s " % ( pkgin_path , package ) )
rc , out , err = module . run_command (
format_pkgin_command ( module , pkgin_path , " remove " , package ) )
if query_package ( module , pkgin_path , package ) :
if not module . check_mode and query_package ( module , pkgin_path , package ) :
module . fail_json ( msg = " failed to remove %s : %s " % ( package , out ) )
remove_c + = 1
if remove_c > 0 :
module . exit_json ( changed = True , msg = " removed %s package(s) " % remove_c )
module . exit_json ( changed = True , msg = format_action_message ( module , " removed " , remove_c ) )
module . exit_json ( changed = False , msg = " package(s) already absent " )
@ -130,15 +170,16 @@ def install_packages(module, pkgin_path, packages):
if query_package ( module , pkgin_path , package ) :
continue
rc , out , err = module . run_command ( " %s -y install %s " % ( pkgin_path , package ) )
rc , out , err = module . run_command (
format_pkgin_command ( module , pkgin_path , " install " , package ) )
if not query_package( module , pkgin_path , package ) :
if not module. check_mode and not query_package( module , pkgin_path , package ) :
module . fail_json ( msg = " failed to install %s : %s " % ( package , out ) )
install_c + = 1
if install_c > 0 :
module . exit_json ( changed = True , msg = " present %s package(s) " % ( install_c ) )
module . exit_json ( changed = True , msg = format_action_message ( module , " installed " , install_c ) )
module . exit_json ( changed = False , msg = " package(s) already present " )
@ -148,7 +189,8 @@ def main():
module = AnsibleModule (
argument_spec = dict (
state = dict ( default = " present " , choices = [ " present " , " absent " ] ) ,
name = dict ( aliases = [ " pkg " ] , required = True ) ) )
name = dict ( aliases = [ " pkg " ] , required = True ) ) ,
supports_check_mode = True )
pkgin_path = module . get_bin_path ( ' pkgin ' , True , [ ' /opt/local/bin ' ] )