2015-07-11 07:29:18 +00:00
#!/usr/bin/python
2014-09-30 08:25:40 +00:00
# -*- coding: utf-8 -*-
2015-05-22 19:44:32 +00:00
# Copyright 2015 Cristian van Ee <cristian at cvee.org>
# Copyright 2015 Igor Gnatenko <i.gnatenko.brain@gmail.com>
2014-09-30 08:25:40 +00:00
#
# This file is part of Ansible
#
# Ansible 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.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
#
2016-12-06 10:35:25 +00:00
ANSIBLE_METADATA = { ' status ' : [ ' stableinterface ' ] ,
' supported_by ' : ' core ' ,
' version ' : ' 1.0 ' }
2014-09-30 08:25:40 +00:00
DOCUMENTATION = '''
- - -
module : dnf
2016-12-08 05:34:16 +00:00
version_added : 1.9
2014-09-30 08:25:40 +00:00
short_description : Manages packages with the I ( dnf ) package manager
description :
- Installs , upgrade , removes , and lists packages and groups with the I ( dnf ) package manager .
options :
name :
description :
- " Package name, or package specifier with version, like C(name-1.0). When using state=latest, this can be ' * ' which means run: dnf -y update. You can also pass a url or a local path to a rpm file. "
required : true
default : null
aliases : [ ]
2015-07-11 07:29:18 +00:00
2014-09-30 08:25:40 +00:00
list :
description :
- Various ( non - idempotent ) commands for usage with C ( / usr / bin / ansible ) and I ( not ) playbooks . See examples .
required : false
default : null
2015-07-11 07:29:18 +00:00
2014-09-30 08:25:40 +00:00
state :
description :
- Whether to install ( C ( present ) , C ( latest ) ) , or remove ( C ( absent ) ) a package .
required : false
choices : [ " present " , " latest " , " absent " ]
default : " present "
2015-07-11 07:29:18 +00:00
2014-09-30 08:25:40 +00:00
enablerepo :
description :
- I ( Repoid ) of repositories to enable for the install / update operation .
These repos will not persist beyond the transaction .
When specifying multiple repos , separate them with a " , " .
required : false
default : null
aliases : [ ]
2016-12-08 14:38:05 +00:00
2014-09-30 08:25:40 +00:00
disablerepo :
description :
- I ( Repoid ) of repositories to disable for the install / update operation .
These repos will not persist beyond the transaction .
When specifying multiple repos , separate them with a " , " .
required : false
default : null
aliases : [ ]
conf_file :
description :
- The remote dnf configuration file to use for the transaction .
required : false
default : null
aliases : [ ]
disable_gpg_check :
description :
- Whether to disable the GPG checking of signatures of packages being
installed . Has an effect only if state is I ( present ) or I ( latest ) .
required : false
default : " no "
choices : [ " yes " , " no " ]
aliases : [ ]
2017-01-05 08:24:20 +00:00
installroot :
description :
- Specifies an alternative installroot , relative to which all packages
will be installed .
required : false
version_added : " 2.3 "
default : " / "
2014-09-30 08:25:40 +00:00
notes : [ ]
# informational: requirements for nodes
2015-05-22 19:44:32 +00:00
requirements :
- " python >= 2.6 "
2015-10-05 04:32:10 +00:00
- python - dnf
2015-05-22 19:44:32 +00:00
author :
2015-07-11 07:29:18 +00:00
- ' " Igor Gnatenko (@ignatenkobrain) " <i.gnatenko.brain@gmail.com> '
2015-05-22 19:44:32 +00:00
- ' " Cristian van Ee (@DJMuggs) " <cristian at cvee.org> '
2017-01-05 08:24:20 +00:00
- " Berend De Schouwer (github.com/berenddeschouwer) "
2014-09-30 08:25:40 +00:00
'''
EXAMPLES = '''
- name : install the latest version of Apache
2016-12-01 16:07:13 +00:00
dnf :
name : httpd
state : latest
2014-09-30 08:25:40 +00:00
- name : remove the Apache package
2016-12-01 16:07:13 +00:00
dnf :
name : httpd
state : absent
2014-09-30 08:25:40 +00:00
- name : install the latest version of Apache from the testing repo
2016-12-01 16:07:13 +00:00
dnf :
name : httpd
enablerepo : testing
state : present
2014-09-30 08:25:40 +00:00
- name : upgrade all packages
2016-12-01 16:07:13 +00:00
dnf :
2017-01-07 16:50:10 +00:00
name : " * "
2016-12-01 16:07:13 +00:00
state : latest
2014-09-30 08:25:40 +00:00
- name : install the nginx rpm from a remote repo
2016-12-01 16:07:13 +00:00
dnf :
name : ' http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm '
state : present
2014-09-30 08:25:40 +00:00
- name : install nginx rpm from a local file
2016-12-01 16:07:13 +00:00
dnf :
name : / usr / local / src / nginx - release - centos - 6 - 0. el6 . ngx . noarch . rpm
state : present
2014-09-30 08:25:40 +00:00
- name : install the ' Development tools ' package group
2016-12-01 16:07:13 +00:00
dnf :
name : ' @Development tools '
state : present
2014-09-30 08:25:40 +00:00
'''
2015-07-11 07:29:18 +00:00
import os
2014-09-30 08:25:40 +00:00
2015-07-11 07:29:18 +00:00
try :
import dnf
2016-10-27 16:28:22 +00:00
import dnf
import dnf . cli
import dnf . const
import dnf . exceptions
import dnf . subject
import dnf . util
2015-07-11 07:29:18 +00:00
HAS_DNF = True
except ImportError :
HAS_DNF = False
2016-10-27 16:28:22 +00:00
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . six import PY2
2015-07-11 07:29:18 +00:00
2016-10-27 16:28:22 +00:00
def _ensure_dnf ( module ) :
2015-07-11 07:29:18 +00:00
if not HAS_DNF :
2016-10-27 16:28:22 +00:00
if PY2 :
package = ' python2-dnf '
else :
package = ' python3-dnf '
if module . check_mode :
module . fail_json ( msg = " ` {0} ` is not installed, but it is required "
" for the Ansible dnf module. " . format ( package ) )
module . run_command ( [ ' dnf ' , ' install ' , ' -y ' , package ] , check_rc = True )
global dnf
try :
import dnf
import dnf . cli
import dnf . const
import dnf . exceptions
import dnf . subject
import dnf . util
except ImportError :
module . fail_json ( msg = " Could not import the dnf python module. "
" Please install ` {0} ` package. " . format ( package ) )
2015-07-11 07:29:18 +00:00
2017-01-05 08:24:20 +00:00
def _configure_base ( module , base , conf_file , disable_gpg_check , installroot = ' / ' ) :
2015-07-11 07:29:18 +00:00
""" Configure the dnf Base object. """
conf = base . conf
# Turn off debug messages in the output
conf . debuglevel = 0
# Set whether to check gpg signatures
conf . gpgcheck = not disable_gpg_check
# Don't prompt for user confirmations
conf . assumeyes = True
2017-01-05 08:24:20 +00:00
# Set installroot
conf . installroot = installroot
2015-07-11 07:29:18 +00:00
# Change the configuration file path if provided
if conf_file :
# Fail if we can't read the configuration file.
if not os . access ( conf_file , os . R_OK ) :
module . fail_json (
msg = " cannot read configuration file " , conf_file = conf_file )
else :
conf . config_file_path = conf_file
# Read the configuration file
conf . read ( )
def _specify_repositories ( base , disablerepo , enablerepo ) :
""" Enable and disable repositories matching the provided patterns. """
base . read_all_repos ( )
repos = base . repos
# Disable repositories
for repo_pattern in disablerepo :
for repo in repos . get_matching ( repo_pattern ) :
repo . disable ( )
# Enable repositories
for repo_pattern in enablerepo :
for repo in repos . get_matching ( repo_pattern ) :
repo . enable ( )
2017-01-05 08:24:20 +00:00
def _base ( module , conf_file , disable_gpg_check , disablerepo , enablerepo , installroot ) :
2015-07-11 07:29:18 +00:00
""" Return a fully configured dnf Base object. """
base = dnf . Base ( )
2017-01-05 08:24:20 +00:00
_configure_base ( module , base , conf_file , disable_gpg_check , installroot )
2015-07-11 07:29:18 +00:00
_specify_repositories ( base , disablerepo , enablerepo )
2016-10-30 02:08:37 +00:00
base . fill_sack ( load_system_repo = ' auto ' )
2015-07-11 07:29:18 +00:00
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
2014-09-30 08:25:40 +00:00
else :
2016-10-27 16:28:22 +00:00
packages = dnf . subject . Subject ( command ) . get_best_query ( base . sack )
2015-07-11 07:29:18 +00:00
results = [ _package_dict ( package ) for package in packages ]
module . exit_json ( results = results )
2014-09-30 08:25:40 +00:00
2015-07-09 21:00:22 +00:00
2015-07-11 07:29:18 +00:00
def _mark_package_install ( module , base , pkg_spec ) :
2015-07-09 21:00:22 +00:00
""" Mark the package for install. """
try :
2015-07-11 07:29:18 +00:00
base . install ( pkg_spec )
2016-10-27 16:28:22 +00:00
except dnf . exceptions . MarkingError :
2015-09-22 04:50:52 +00:00
module . fail_json ( msg = " No package {} available. " . format ( pkg_spec ) )
2015-07-11 07:29:18 +00:00
2016-10-31 16:24:26 +00:00
def _parse_spec_group_file ( names ) :
pkg_specs , grp_specs , filenames = [ ] , [ ] , [ ]
for name in names :
if name . endswith ( " .rpm " ) :
filenames . append ( name )
elif name . startswith ( " @ " ) :
grp_specs . append ( name [ 1 : ] )
else :
pkg_specs . append ( name )
return pkg_specs , grp_specs , filenames
def _install_remote_rpms ( base , filenames ) :
if int ( dnf . __version__ . split ( " . " ) [ 0 ] ) > = 2 :
pkgs = list ( sorted ( base . add_remote_rpms ( list ( filenames ) ) , reverse = True ) )
else :
pkgs = [ ]
for filename in filenames :
pkgs . append ( base . add_remote_rpm ( filename ) )
for pkg in pkgs :
base . package_install ( pkg )
2015-07-11 07:29:18 +00:00
def ensure ( module , base , state , names ) :
2016-10-30 02:08:37 +00:00
# Accumulate failures. Package management modules install what they can
# and fail with a message about what they can't.
failures = [ ]
2016-02-02 18:39:19 +00:00
allow_erasing = False
2015-07-11 07:29:18 +00:00
if names == [ ' * ' ] and state == ' latest ' :
base . upgrade_all ( )
2015-07-09 21:00:22 +00:00
else :
2016-10-31 16:24:26 +00:00
pkg_specs , group_specs , filenames = _parse_spec_group_file ( names )
2015-07-11 07:29:18 +00:00
if group_specs :
base . read_comps ( )
2016-10-30 02:08:37 +00:00
pkg_specs = [ p . strip ( ) for p in pkg_specs ]
filenames = [ f . strip ( ) for f in filenames ]
2015-07-11 07:29:18 +00:00
groups = [ ]
2016-10-30 02:08:37 +00:00
environments = [ ]
2016-11-04 15:37:21 +00:00
for group_spec in ( g . strip ( ) for g in group_specs ) :
2015-07-11 07:29:18 +00:00
group = base . comps . group_by_pattern ( group_spec )
if group :
2016-11-04 15:37:21 +00:00
groups . append ( group )
2015-07-11 07:29:18 +00:00
else :
2016-11-04 15:37:21 +00:00
environment = base . comps . environment_by_pattern ( group_spec )
2016-10-30 02:08:37 +00:00
if environment :
2016-11-04 15:37:21 +00:00
environments . append ( environment . id )
2016-10-30 02:08:37 +00:00
else :
module . fail_json (
msg = " No group {} available. " . format ( group_spec ) )
2015-07-11 07:29:18 +00:00
2015-07-09 21:00:22 +00:00
if state in [ ' installed ' , ' present ' ] :
# Install files.
2016-10-30 02:08:37 +00:00
_install_remote_rpms ( base , filenames )
2015-07-09 21:00:22 +00:00
# Install groups.
2016-10-30 02:08:37 +00:00
for group in groups :
try :
base . group_install ( group , dnf . const . GROUP_PACKAGE_TYPES )
except dnf . exceptions . Error as e :
# In dnf 2.0 if all the mandatory packages in a group do
# not install, an error is raised. We want to capture
# this but still install as much as possible.
failures . append ( ( group , e ) )
for environment in environments :
try :
base . environment_install ( environment , dnf . const . GROUP_PACKAGE_TYPES )
except dnf . exceptions . Error as e :
failures . append ( ( group , e ) )
2015-07-09 21:00:22 +00:00
# Install packages.
2016-10-30 02:08:37 +00:00
for pkg_spec in pkg_specs :
2015-07-11 07:29:18 +00:00
_mark_package_install ( module , base , pkg_spec )
2015-07-09 21:00:22 +00:00
elif state == ' latest ' :
2015-07-11 07:29:18 +00:00
# "latest" is same as "installed" for filenames.
2016-10-31 16:24:26 +00:00
_install_remote_rpms ( base , filenames )
2016-10-30 02:08:37 +00:00
2015-07-11 07:29:18 +00:00
for group in groups :
try :
2016-10-30 02:08:37 +00:00
try :
base . group_upgrade ( group )
except dnf . exceptions . CompsError :
# If not already installed, try to install.
base . group_install ( group , dnf . const . GROUP_PACKAGE_TYPES )
except dnf . exceptions . Error as e :
failures . append ( ( group , e ) )
for environment in environments :
try :
try :
base . environment_upgrade ( environment )
except dnf . exceptions . CompsError :
# If not already installed, try to install.
base . environment_install ( group , dnf . const . GROUP_PACKAGE_TYPES )
except dnf . exceptions . Error as e :
failures . append ( ( group , e ) )
2015-07-09 21:00:22 +00:00
for pkg_spec in pkg_specs :
2016-02-12 14:51:48 +00:00
# best effort causes to install the latest package
# even if not previously installed
base . conf . best = True
base . install ( pkg_spec )
2015-07-09 21:00:22 +00:00
2015-07-11 07:29:18 +00:00
else :
2016-02-02 18:39:19 +00:00
# state == absent
2015-07-11 07:29:18 +00:00
if filenames :
module . fail_json (
msg = " Cannot remove paths -- please specify package name. " )
2015-05-22 20:04:16 +00:00
2015-07-11 07:29:18 +00:00
for group in groups :
2016-11-04 15:37:21 +00:00
try :
2015-07-11 07:29:18 +00:00
base . group_remove ( group )
2016-11-04 15:37:21 +00:00
except dnf . exceptions . CompsError :
# Group is already uninstalled.
pass
2016-10-30 02:08:37 +00:00
2016-11-04 15:37:21 +00:00
for envioronment in environments :
try :
base . environment_remove ( environment )
except dnf . exceptions . CompsError :
# Environment is already uninstalled.
pass
installed = base . sack . query ( ) . installed ( )
2015-07-11 07:29:18 +00:00
for pkg_spec in pkg_specs :
if installed . filter ( name = pkg_spec ) :
base . remove ( pkg_spec )
2016-10-30 02:08:37 +00:00
2016-02-02 18:39:19 +00:00
# Like the dnf CLI we want to allow recursive removal of dependent
# packages
allow_erasing = True
2015-05-22 20:04:16 +00:00
2016-02-02 18:39:19 +00:00
if not base . resolve ( allow_erasing = allow_erasing ) :
2016-10-30 02:08:37 +00:00
if failures :
module . fail_json ( msg = ' Failed to install some of the specified packages ' ,
failures = failures )
2015-07-11 07:29:18 +00:00
module . exit_json ( msg = " Nothing to do " )
else :
if module . check_mode :
2016-10-30 02:08:37 +00:00
if failures :
module . fail_json ( msg = ' Failed to install some of the specified packages ' ,
failures = failures )
2015-07-11 07:29:18 +00:00
module . exit_json ( changed = True )
2016-10-30 02:08:37 +00:00
2015-07-11 07:29:18 +00:00
base . download_packages ( base . transaction . install_set )
base . do_transaction ( )
response = { ' changed ' : True , ' results ' : [ ] }
for package in base . transaction . install_set :
2016-10-27 19:36:46 +00:00
response [ ' results ' ] . append ( " Installed: {0} " . format ( package ) )
2015-07-11 07:29:18 +00:00
for package in base . transaction . remove_set :
2016-10-27 19:36:46 +00:00
response [ ' results ' ] . append ( " Removed: {0} " . format ( package ) )
2014-09-30 08:25:40 +00:00
2016-10-30 02:08:37 +00:00
if failures :
module . fail_json ( msg = ' Failed to install some of the specified packages ' ,
failures = failures )
2015-07-11 07:29:18 +00:00
module . exit_json ( * * response )
2014-09-30 08:25:40 +00:00
2015-07-11 07:29:18 +00:00
def main ( ) :
""" The main function. """
2014-09-30 08:25:40 +00:00
module = AnsibleModule (
2015-07-11 07:29:18 +00:00
argument_spec = dict (
name = dict ( aliases = [ ' pkg ' ] , type = ' list ' ) ,
state = dict (
default = ' installed ' ,
choices = [
' absent ' , ' present ' , ' installed ' , ' removed ' , ' latest ' ] ) ,
enablerepo = dict ( type = ' list ' , default = [ ] ) ,
disablerepo = dict ( type = ' list ' , default = [ ] ) ,
2014-09-30 08:25:40 +00:00
list = dict ( ) ,
2016-04-14 19:15:07 +00:00
conf_file = dict ( default = None , type = ' path ' ) ,
2015-07-11 07:29:18 +00:00
disable_gpg_check = dict ( default = False , type = ' bool ' ) ,
2017-01-05 08:24:20 +00:00
installroot = dict ( default = ' / ' , type = ' path ' ) ,
2014-09-30 08:25:40 +00:00
) ,
2015-07-11 07:29:18 +00:00
required_one_of = [ [ ' name ' , ' list ' ] ] ,
mutually_exclusive = [ [ ' name ' , ' list ' ] ] ,
supports_check_mode = True )
2014-09-30 08:25:40 +00:00
params = module . params
2016-03-04 17:42:38 +00:00
2016-10-27 16:28:22 +00:00
_ensure_dnf ( module )
2014-09-30 08:25:40 +00:00
if params [ ' list ' ] :
2015-11-01 20:58:20 +00:00
base = _base (
module , params [ ' conf_file ' ] , params [ ' disable_gpg_check ' ] ,
2017-01-05 08:24:20 +00:00
params [ ' disablerepo ' ] , params [ ' enablerepo ' ] , params [ ' installroot ' ] )
2015-07-11 07:29:18 +00:00
list_items ( module , base , params [ ' list ' ] )
2014-09-30 08:25:40 +00:00
else :
2015-11-01 20:58:20 +00:00
# Note: base takes a long time to run so we want to check for failure
# before running it.
2016-10-27 16:28:22 +00:00
if not dnf . util . am_i_root ( ) :
2015-11-01 20:58:20 +00:00
module . fail_json ( msg = " This command has to be run under the root user. " )
base = _base (
module , params [ ' conf_file ' ] , params [ ' disable_gpg_check ' ] ,
2017-01-05 08:24:20 +00:00
params [ ' disablerepo ' ] , params [ ' enablerepo ' ] , params [ ' installroot ' ] )
2015-11-01 20:58:20 +00:00
2015-07-11 07:29:18 +00:00
ensure ( module , base , params [ ' state ' ] , params [ ' name ' ] )
2014-09-30 08:25:40 +00:00
2015-05-22 19:44:32 +00:00
if __name__ == ' __main__ ' :
main ( )