1# -*- coding: utf-8 -*- 2# 3# # Copyright: (c) 2012, Red Hat, Inc 4# Written by Seth Vidal <skvidal at fedoraproject.org> 5# Contributing Authors: 6# - Ansible Core Team 7# - Eduard Snesarev (@verm666) 8# - Berend De Schouwer (@berenddeschouwer) 9# - Abhijeet Kasurde (@Akasurde) 10# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 11 12import os 13import time 14import glob 15import tempfile 16from abc import ABCMeta, abstractmethod 17 18from ansible.module_utils._text import to_native 19from ansible.module_utils.six import with_metaclass 20 21yumdnf_argument_spec = dict( 22 argument_spec=dict( 23 allow_downgrade=dict(type='bool', default=False), 24 autoremove=dict(type='bool', default=False), 25 bugfix=dict(required=False, type='bool', default=False), 26 conf_file=dict(type='str'), 27 disable_excludes=dict(type='str', default=None), 28 disable_gpg_check=dict(type='bool', default=False), 29 disable_plugin=dict(type='list', default=[]), 30 disablerepo=dict(type='list', default=[]), 31 download_only=dict(type='bool', default=False), 32 download_dir=dict(type='str', default=None), 33 enable_plugin=dict(type='list', default=[]), 34 enablerepo=dict(type='list', default=[]), 35 exclude=dict(type='list', default=[]), 36 installroot=dict(type='str', default="/"), 37 install_repoquery=dict(type='bool', default=True), 38 install_weak_deps=dict(type='bool', default=True), 39 list=dict(type='str'), 40 name=dict(type='list', elements='str', aliases=['pkg'], default=[]), 41 releasever=dict(default=None), 42 security=dict(type='bool', default=False), 43 skip_broken=dict(type='bool', default=False), 44 # removed==absent, installed==present, these are accepted as aliases 45 state=dict(type='str', default=None, choices=['absent', 'installed', 'latest', 'present', 'removed']), 46 update_cache=dict(type='bool', default=False, aliases=['expire-cache']), 47 update_only=dict(required=False, default="no", type='bool'), 48 validate_certs=dict(type='bool', default=True), 49 lock_timeout=dict(type='int', default=30), 50 ), 51 required_one_of=[['name', 'list', 'update_cache']], 52 mutually_exclusive=[['name', 'list']], 53 supports_check_mode=True, 54) 55 56 57class YumDnf(with_metaclass(ABCMeta, object)): 58 """ 59 Abstract class that handles the population of instance variables that should 60 be identical between both YUM and DNF modules because of the feature parity 61 and shared argument spec 62 """ 63 64 def __init__(self, module): 65 66 self.module = module 67 68 self.allow_downgrade = self.module.params['allow_downgrade'] 69 self.autoremove = self.module.params['autoremove'] 70 self.bugfix = self.module.params['bugfix'] 71 self.conf_file = self.module.params['conf_file'] 72 self.disable_excludes = self.module.params['disable_excludes'] 73 self.disable_gpg_check = self.module.params['disable_gpg_check'] 74 self.disable_plugin = self.module.params['disable_plugin'] 75 self.disablerepo = self.module.params.get('disablerepo', []) 76 self.download_only = self.module.params['download_only'] 77 self.download_dir = self.module.params['download_dir'] 78 self.enable_plugin = self.module.params['enable_plugin'] 79 self.enablerepo = self.module.params.get('enablerepo', []) 80 self.exclude = self.module.params['exclude'] 81 self.installroot = self.module.params['installroot'] 82 self.install_repoquery = self.module.params['install_repoquery'] 83 self.install_weak_deps = self.module.params['install_weak_deps'] 84 self.list = self.module.params['list'] 85 self.names = [p.strip() for p in self.module.params['name']] 86 self.releasever = self.module.params['releasever'] 87 self.security = self.module.params['security'] 88 self.skip_broken = self.module.params['skip_broken'] 89 self.state = self.module.params['state'] 90 self.update_only = self.module.params['update_only'] 91 self.update_cache = self.module.params['update_cache'] 92 self.validate_certs = self.module.params['validate_certs'] 93 self.lock_timeout = self.module.params['lock_timeout'] 94 95 # It's possible someone passed a comma separated string since it used 96 # to be a string type, so we should handle that 97 self.names = self.listify_comma_sep_strings_in_list(self.names) 98 self.disablerepo = self.listify_comma_sep_strings_in_list(self.disablerepo) 99 self.enablerepo = self.listify_comma_sep_strings_in_list(self.enablerepo) 100 self.exclude = self.listify_comma_sep_strings_in_list(self.exclude) 101 102 # Fail if someone passed a space separated string 103 # https://github.com/ansible/ansible/issues/46301 104 for name in self.names: 105 if ' ' in name and not any(spec in name for spec in ['@', '>', '<', '=']): 106 module.fail_json( 107 msg='It appears that a space separated string of packages was passed in ' 108 'as an argument. To operate on several packages, pass a comma separated ' 109 'string of packages or a list of packages.' 110 ) 111 112 # Sanity checking for autoremove 113 if self.state is None: 114 if self.autoremove: 115 self.state = "absent" 116 else: 117 self.state = "present" 118 119 if self.autoremove and (self.state != "absent"): 120 self.module.fail_json( 121 msg="Autoremove should be used alone or with state=absent", 122 results=[], 123 ) 124 125 # This should really be redefined by both the yum and dnf module but a 126 # default isn't a bad idea 127 self.lockfile = '/var/run/yum.pid' 128 129 @abstractmethod 130 def is_lockfile_pid_valid(self): 131 return 132 133 def _is_lockfile_present(self): 134 return (os.path.isfile(self.lockfile) or glob.glob(self.lockfile)) and self.is_lockfile_pid_valid() 135 136 def wait_for_lock(self): 137 '''Poll until the lock is removed if timeout is a positive number''' 138 139 if not self._is_lockfile_present(): 140 return 141 142 if self.lock_timeout > 0: 143 for iteration in range(0, self.lock_timeout): 144 time.sleep(1) 145 if not self._is_lockfile_present(): 146 return 147 148 self.module.fail_json(msg='{0} lockfile is held by another process'.format(self.pkg_mgr_name)) 149 150 def listify_comma_sep_strings_in_list(self, some_list): 151 """ 152 method to accept a list of strings as the parameter, find any strings 153 in that list that are comma separated, remove them from the list and add 154 their comma separated elements to the original list 155 """ 156 new_list = [] 157 remove_from_original_list = [] 158 for element in some_list: 159 if ',' in element: 160 remove_from_original_list.append(element) 161 new_list.extend([e.strip() for e in element.split(',')]) 162 163 for element in remove_from_original_list: 164 some_list.remove(element) 165 166 some_list.extend(new_list) 167 168 if some_list == [""]: 169 return [] 170 171 return some_list 172 173 @abstractmethod 174 def run(self): 175 raise NotImplementedError 176