@ -1,4 +1,4 @@
#!/usr/bin/python -tt
#!/usr/bin/python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright 2015 Cristian van Ee <cristian at cvee.org>
# Copyright 2015 Cristian van Ee <cristian at cvee.org>
@ -20,15 +20,6 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
#
import traceback
import os
import operator
import functools
import dnf
import dnf . cli
import dnf . util
DOCUMENTATION = '''
DOCUMENTATION = '''
- - -
- - -
module : dnf
module : dnf
@ -44,12 +35,14 @@ options:
version_added : " 1.8 "
version_added : " 1.8 "
default : null
default : null
aliases : [ ]
aliases : [ ]
list :
list :
description :
description :
- Various ( non - idempotent ) commands for usage with C ( / usr / bin / ansible ) and I ( not ) playbooks . See examples .
- Various ( non - idempotent ) commands for usage with C ( / usr / bin / ansible ) and I ( not ) playbooks . See examples .
required : false
required : false
version_added : " 1.8 "
version_added : " 1.8 "
default : null
default : null
state :
state :
description :
description :
- Whether to install ( C ( present ) , C ( latest ) ) , or remove ( C ( absent ) ) a package .
- Whether to install ( C ( present ) , C ( latest ) ) , or remove ( C ( absent ) ) a package .
@ -57,6 +50,7 @@ options:
choices : [ " present " , " latest " , " absent " ]
choices : [ " present " , " latest " , " absent " ]
version_added : " 1.8 "
version_added : " 1.8 "
default : " present "
default : " present "
enablerepo :
enablerepo :
description :
description :
- I ( Repoid ) of repositories to enable for the install / update operation .
- I ( Repoid ) of repositories to enable for the install / update operation .
@ -128,178 +122,229 @@ EXAMPLES = '''
dnf : name = " @Development tools " state = present
dnf : name = " @Development tools " state = present
'''
'''
import os
import syslog
try :
import dnf
def log ( msg ) :
from dnf import cli , const , exceptions , subject , util
syslog . openlog ( ' ansible-dnf ' , 0 , syslog . LOG_USER )
HAS_DNF = True
syslog . syslog ( syslog . LOG_NOTICE , msg )
except ImportError :
HAS_DNF = False
def dnf_base ( conf_file = None ) :
""" Return a dnf Base object. You must call fill_sack. """
my = dnf . Base ( )
def _fail_if_no_dnf ( module ) :
my . conf . debuglevel = 0
""" Fail if unable to import dnf. """
if conf_file and os . path . exists ( conf_file ) :
if not HAS_DNF :
my . conf . config_file_path = conf_file
module . fail_json (
my . conf . read ( )
msg = " `python-dnf` is not installed, but it is required for the Ansible dnf module. " )
my . read_all_repos ( )
return my
def _configure_base ( module , base , conf_file , disable_gpg_check ) :
""" Configure the dnf Base object. """
def pkg_to_dict ( pkg ) :
conf = base . conf
"""
Args :
# Turn off debug messages in the output
pkg ( hawkey . Package ) : The package
conf . debuglevel = 0
"""
# Set whether to check gpg signatures
d = {
conf . gpgcheck = not disable_gpg_check
' name ' : pkg . name ,
' arch ' : pkg . arch ,
# Don't prompt for user confirmations
' epoch ' : str ( pkg . epoch ) ,
conf . assumeyes = True
' release ' : pkg . release ,
' version ' : pkg . version ,
# Change the configuration file path if provided
' repo ' : pkg . repoid ,
if conf_file :
}
# Fail if we can't read the configuration file.
d [ ' nevra ' ] = ' {epoch} : {name} - {version} - {release} . {arch} ' . format ( * * d )
if not os . access ( conf_file , os . R_OK ) :
module . fail_json (
if pkg . installed :
msg = " cannot read configuration file " , conf_file = conf_file )
d [ ' dnfstate ' ] = ' installed '
else :
else :
d [ ' dnfstate ' ] = ' available '
conf . config_file_path = conf_file
return d
# Read the configuration file
conf . read ( )
def list_stuff ( module , conf_file , stuff ) :
my = dnf_base ( conf_file )
my . fill_sack ( )
def _specify_repositories ( base , disablerepo , enablerepo ) :
""" Enable and disable repositories matching the provided patterns. """
if stuff == ' installed ' :
base . read_all_repos ( )
return [ pkg_to_dict ( p ) for p in my . sack . query ( ) . installed ( ) ]
repos = base . repos
elif stuff == ' updates ' :
return [ pkg_to_dict ( p ) for p in my . sack . query ( ) . upgrades ( ) ]
# Disable repositories
elif stuff == ' available ' :
for repo_pattern in disablerepo :
return [ pkg_to_dict ( p ) for p in my . sack . query ( ) . available ( ) ]
for repo in repos . get_matching ( repo_pattern ) :
elif stuff == ' repos ' :
repo . disable ( )
return [ dict ( repoid = repo . id , state = ' enabled ' ) for repo in my . repos . iter_enabled ( ) ]
# Enable repositories
for repo_pattern in enablerepo :
for repo in repos . get_matching ( repo_pattern ) :
repo . enable ( )
def _base ( module , conf_file , disable_gpg_check , disablerepo , enablerepo ) :
""" Return a fully configured dnf Base object. """
_fail_if_no_dnf ( module )
base = dnf . Base ( )
_configure_base ( module , base , conf_file , disable_gpg_check )
_specify_repositories ( base , disablerepo , enablerepo )
base . fill_sack ( )
return base
def _package_dict ( package ) :
""" Return a dictionary of information for the package. """
# NOTE: This no longer contains the 'dnfstate' field because it is
# already known based on the query type.
result = {
' name ' : package . name ,
' arch ' : package . arch ,
' epoch ' : str ( package . epoch ) ,
' release ' : package . release ,
' version ' : package . version ,
' repo ' : package . repoid }
result [ ' nevra ' ] = ' {epoch} : {name} - {version} - {release} . {arch} ' . format (
* * result )
return result
def list_items ( module , base , command ) :
""" List package info based on the command. """
# Rename updates to upgrades
if command == ' updates ' :
command = ' upgrades '
# Return the corresponding packages
if command in [ ' installed ' , ' upgrades ' , ' available ' ] :
results = [
_package_dict ( package )
for package in getattr ( base . sack . query ( ) , command ) ( ) ]
# Return the enabled repository ids
elif command in [ ' repos ' , ' repositories ' ] :
results = [
{ ' repoid ' : repo . id , ' state ' : ' enabled ' }
for repo in base . repos . iter_enabled ( ) ]
# Return any matching packages
else :
else :
return [ pkg_to_dict ( p ) for p in dnf . subject . Subject ( stuff ) . get_best_query ( my . sack ) ]
packages = subject . Subject ( command ) . get_best_query ( base . sack )
results = [ _package_dict ( package ) for package in packages ]
module . exit_json ( results = results )
def _mark_package_install ( my , res , pkg_spec ) :
def _mark_package_install ( m odule, base , pkg_spec ) :
""" Mark the package for install. """
""" Mark the package for install. """
try :
try :
my . install ( pkg_spec )
base . install ( pkg_spec )
except dnf . exceptions . MarkingError :
except exceptions . MarkingError :
res [ ' results ' ] . append ( ' No package %s available. ' % pkg_spec )
module . fail ( msg = " No package {} available. " . format ( pkg_spec ) )
res [ ' rc ' ] = 1
def ensure ( module , base , state , names ) :
def ensure ( module , state , pkgspec , conf_file , enablerepo , disablerepo , disable_gpg_check ) :
if not util . am_i_root ( ) :
my = dnf_base ( conf_file )
module . fail_json ( msg = " This command has to be run under the root user. " )
if disablerepo :
for repo in disablerepo . split ( ' , ' ) :
if names == [ ' * ' ] and state == ' latest ' :
[ r . disable ( ) for r in my . repos . get_matching ( repo ) ]
base . upgrade_all ( )
if enablerepo :
else :
for repo in enablerepo . split ( ' , ' ) :
pkg_specs , group_specs , filenames = cli . commands . parse_spec_group_file (
[ r . enable ( ) for r in my . repos . get_matching ( repo ) ]
names )
my . fill_sack ( )
if group_specs :
my . conf . gpgcheck = not disable_gpg_check
base . read_comps ( )
res = { }
groups = [ ]
res [ ' results ' ] = [ ]
for group_spec in group_specs :
res [ ' msg ' ] = ' '
group = base . comps . group_by_pattern ( group_spec )
res [ ' rc ' ] = 0
if group :
res [ ' changed ' ] = False
groups . append ( group )
if not dnf . util . am_i_root ( ) :
res [ ' msg ' ] = ' This command has to be run under the root user. '
res [ ' rc ' ] = 1
if pkgspec == ' * ' and state == ' latest ' :
my . upgrade_all ( )
else :
else :
items = pkgspec . split ( ' , ' )
module . fail_json (
pkg_specs , grp_specs , filenames = dnf . cli . commands . parse_spec_group_file ( items )
msg = " No group {} available. " . format ( group_spec ) )
if state in [ ' installed ' , ' present ' ] :
if state in [ ' installed ' , ' present ' ] :
# Install files.
# Install files.
for filename in filenames :
for filename in filenames :
my . package_install ( my . add_remote_rpm ( filename ) )
base. package_install ( base . add_remote_rpm ( filename ) )
# Install groups.
# Install groups.
if grp_specs :
for group in groups :
my . read_comps ( )
base . group_install ( group , const . GROUP_PACKAGE_TYPES )
my . env_group_install ( grp_specs , dnf . const . GROUP_PACKAGE_TYPES )
# Install packages.
# Install packages.
for pkg_spec in pkg_specs :
for pkg_spec in pkg_specs :
_mark_package_install ( my , res , pkg_spec )
_mark_package_install ( module , base , pkg_spec )
elif state == ' latest ' :
elif state == ' latest ' :
# These aren't implemented yet, so assert them out.
# "latest" is same as "installed" for filenames.
assert not filenames
for filename in filenames :
assert not grp_specs
base . package_install ( base . add_remote_rpm ( filename ) )
for group in groups :
try :
base . group_upgrade ( group )
except exceptions . CompsError :
# If not already installed, try to install.
base . group_install ( group , const . GROUP_PACKAGE_TYPES )
for pkg_spec in pkg_specs :
for pkg_spec in pkg_specs :
try :
try :
my . upgrade ( pkg_spec )
base . upgrade ( pkg_spec )
except dnf . exceptions . MarkingError :
except dnf . exceptions . MarkingError :
# If not already installed, try to install.
# If not already installed, try to install.
_mark_package_install ( my , res , pkg_spec )
_mark_package_install ( m odule, base , pkg_spec )
if not my . resolve ( ) and res [ ' rc ' ] == 0 :
res [ ' msg ' ] + = ' Nothing to do '
res [ ' changed ' ] = False
else :
else :
my . download_packages ( my . transaction . install_set )
if filenames :
my . do_transaction ( )
module . fail_json (
res [ ' changed ' ] = True
msg = " Cannot remove paths -- please specify package name. " )
[ res [ ' results ' ] . append ( ' Installed: %s ' % pkg ) for pkg in my . transaction . install_set ]
[ res [ ' results ' ] . append ( ' Removed: %s ' % pkg ) for pkg in my . transaction . remove_set ]
installed = base . sack . query ( ) . installed ( )
for group in groups :
if installed . filter ( name = group . name ) :
base . group_remove ( group )
for pkg_spec in pkg_specs :
if installed . filter ( name = pkg_spec ) :
base . remove ( pkg_spec )
module . exit_json ( * * res )
if not base . resolve ( ) :
module . exit_json ( msg = " Nothing to do " )
else :
if module . check_mode :
module . exit_json ( changed = True )
base . download_packages ( base . transaction . install_set )
base . do_transaction ( )
response = { ' changed ' : True , ' results ' : [ ] }
for package in base . transaction . install_set :
response [ ' results ' ] . append ( " Installed: {} " . format ( package ) )
for package in base . transaction . remove_set :
response [ ' results ' ] . append ( " Removed: {} " . format ( package ) )
def main ( ) :
module . exit_json ( * * response )
# state=installed name=pkgspec
# state=removed name=pkgspec
# state=latest name=pkgspec
#
# informational commands:
# list=installed
# list=updates
# list=available
# list=repos
# list=pkgspec
def main ( ) :
""" The main function. """
module = AnsibleModule (
module = AnsibleModule (
argument_spec = dict (
argument_spec = dict (
name = dict ( aliases = [ ' pkg ' ] ) ,
name = dict ( aliases = [ ' pkg ' ] , type = ' list ' ) ,
# removed==absent, installed==present, these are accepted as aliases
state = dict (
state = dict ( default = ' installed ' , choices = [ ' absent ' , ' present ' , ' installed ' , ' removed ' , ' latest ' ] ) ,
default = ' installed ' ,
enablerepo = dict ( ) ,
choices = [
disablerepo = dict ( ) ,
' absent ' , ' present ' , ' installed ' , ' removed ' , ' latest ' ] ) ,
enablerepo = dict ( type = ' list ' , default = [ ] ) ,
disablerepo = dict ( type = ' list ' , default = [ ] ) ,
list = dict ( ) ,
list = dict ( ) ,
conf_file = dict ( default = None ) ,
conf_file = dict ( default = None ) ,
disable_gpg_check = dict ( required= False , default = " no " , type = ' bool ' ) ,
disable_gpg_check = dict ( default= False , type = ' bool ' ) ,
) ,
) ,
required_one_of = [ [ ' name ' , ' list ' ] ] ,
required_one_of = [ [ ' name ' , ' list ' ] ] ,
mutually_exclusive = [ [ ' name ' , ' list ' ] ] ,
mutually_exclusive = [ [ ' name ' , ' list ' ] ] ,
supports_check_mode = True
supports_check_mode = True )
)
params = module . params
params = module . params
base = _base (
if not repoquery :
module , params [ ' conf_file ' ] , params [ ' disable_gpg_check ' ] ,
module. fail_json ( msg = " repoquery is required to use this module at this time. Please install the yum-utils package. " )
params[ ' disablerepo ' ] , params [ ' enablerepo ' ] )
if params [ ' list ' ] :
if params [ ' list ' ] :
results = dict ( results = list_stuff ( module , params [ ' conf_file ' ] , params [ ' list ' ] ) )
list_items ( module , base , params [ ' list ' ] )
module . exit_json ( * * results )
else :
else :
pkg = params [ ' name ' ]
ensure ( module , base , params [ ' state ' ] , params [ ' name ' ] )
state = params [ ' state ' ]
enablerepo = params . get ( ' enablerepo ' , ' ' )
disablerepo = params . get ( ' disablerepo ' , ' ' )
disable_gpg_check = params [ ' disable_gpg_check ' ]
res = ensure ( module , state , pkg , params [ ' conf_file ' ] , enablerepo ,
disablerepo , disable_gpg_check )
module . fail_json ( msg = " we should never get here unless this all failed " , * * res )
# import module snippets
# import module snippets
from ansible . module_utils . basic import *
from ansible . module_utils . basic import *