1# -*- coding: utf-8 -*- 2# This code is part of Ansible, but is an independent component. 3# This particular file snippet, and this file snippet only, is BSD licensed. 4# Modules you write using this snippet, which is embedded dynamically by Ansible 5# still belong to the author of the module, and may assign their own license 6# to the complete work. 7# 8# Copyright (c), James Laska 9# 10# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) 11 12from __future__ import (absolute_import, division, print_function) 13__metaclass__ = type 14 15 16import os 17import re 18import shutil 19import tempfile 20import types 21 22from ansible.module_utils.six.moves import configparser 23 24 25class RegistrationBase(object): 26 def __init__(self, module, username=None, password=None): 27 self.module = module 28 self.username = username 29 self.password = password 30 31 def configure(self): 32 raise NotImplementedError("Must be implemented by a sub-class") 33 34 def enable(self): 35 # Remove any existing redhat.repo 36 redhat_repo = '/etc/yum.repos.d/redhat.repo' 37 if os.path.isfile(redhat_repo): 38 os.unlink(redhat_repo) 39 40 def register(self): 41 raise NotImplementedError("Must be implemented by a sub-class") 42 43 def unregister(self): 44 raise NotImplementedError("Must be implemented by a sub-class") 45 46 def unsubscribe(self): 47 raise NotImplementedError("Must be implemented by a sub-class") 48 49 def update_plugin_conf(self, plugin, enabled=True): 50 plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin 51 52 if os.path.isfile(plugin_conf): 53 tmpfd, tmpfile = tempfile.mkstemp() 54 shutil.copy2(plugin_conf, tmpfile) 55 cfg = configparser.ConfigParser() 56 cfg.read([tmpfile]) 57 58 if enabled: 59 cfg.set('main', 'enabled', 1) 60 else: 61 cfg.set('main', 'enabled', 0) 62 63 fd = open(tmpfile, 'w+') 64 cfg.write(fd) 65 fd.close() 66 self.module.atomic_move(tmpfile, plugin_conf) 67 68 def subscribe(self, **kwargs): 69 raise NotImplementedError("Must be implemented by a sub-class") 70 71 72class Rhsm(RegistrationBase): 73 def __init__(self, module, username=None, password=None): 74 RegistrationBase.__init__(self, module, username, password) 75 self.config = self._read_config() 76 self.module = module 77 78 def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'): 79 ''' 80 Load RHSM configuration from /etc/rhsm/rhsm.conf. 81 Returns: 82 * ConfigParser object 83 ''' 84 85 # Read RHSM defaults ... 86 cp = configparser.ConfigParser() 87 cp.read(rhsm_conf) 88 89 # Add support for specifying a default value w/o having to standup some configuration 90 # Yeah, I know this should be subclassed ... but, oh well 91 def get_option_default(self, key, default=''): 92 sect, opt = key.split('.', 1) 93 if self.has_section(sect) and self.has_option(sect, opt): 94 return self.get(sect, opt) 95 else: 96 return default 97 98 cp.get_option = types.MethodType(get_option_default, cp, configparser.ConfigParser) 99 100 return cp 101 102 def enable(self): 103 ''' 104 Enable the system to receive updates from subscription-manager. 105 This involves updating affected yum plugins and removing any 106 conflicting yum repositories. 107 ''' 108 RegistrationBase.enable(self) 109 self.update_plugin_conf('rhnplugin', False) 110 self.update_plugin_conf('subscription-manager', True) 111 112 def configure(self, **kwargs): 113 ''' 114 Configure the system as directed for registration with RHN 115 Raises: 116 * Exception - if error occurs while running command 117 ''' 118 args = ['subscription-manager', 'config'] 119 120 # Pass supplied **kwargs as parameters to subscription-manager. Ignore 121 # non-configuration parameters and replace '_' with '.'. For example, 122 # 'server_hostname' becomes '--system.hostname'. 123 for k, v in kwargs.items(): 124 if re.search(r'^(system|rhsm)_', k): 125 args.append('--%s=%s' % (k.replace('_', '.'), v)) 126 127 self.module.run_command(args, check_rc=True) 128 129 @property 130 def is_registered(self): 131 ''' 132 Determine whether the current system 133 Returns: 134 * Boolean - whether the current system is currently registered to 135 RHN. 136 ''' 137 args = ['subscription-manager', 'identity'] 138 rc, stdout, stderr = self.module.run_command(args, check_rc=False) 139 if rc == 0: 140 return True 141 else: 142 return False 143 144 def register(self, username, password, autosubscribe, activationkey): 145 ''' 146 Register the current system to the provided RHN server 147 Raises: 148 * Exception - if error occurs while running command 149 ''' 150 args = ['subscription-manager', 'register'] 151 152 # Generate command arguments 153 if activationkey: 154 args.append('--activationkey "%s"' % activationkey) 155 else: 156 if autosubscribe: 157 args.append('--autosubscribe') 158 if username: 159 args.extend(['--username', username]) 160 if password: 161 args.extend(['--password', password]) 162 163 # Do the needful... 164 rc, stderr, stdout = self.module.run_command(args, check_rc=True) 165 166 def unsubscribe(self): 167 ''' 168 Unsubscribe a system from all subscribed channels 169 Raises: 170 * Exception - if error occurs while running command 171 ''' 172 args = ['subscription-manager', 'unsubscribe', '--all'] 173 rc, stderr, stdout = self.module.run_command(args, check_rc=True) 174 175 def unregister(self): 176 ''' 177 Unregister a currently registered system 178 Raises: 179 * Exception - if error occurs while running command 180 ''' 181 args = ['subscription-manager', 'unregister'] 182 rc, stderr, stdout = self.module.run_command(args, check_rc=True) 183 self.update_plugin_conf('rhnplugin', False) 184 self.update_plugin_conf('subscription-manager', False) 185 186 def subscribe(self, regexp): 187 ''' 188 Subscribe current system to available pools matching the specified 189 regular expression 190 Raises: 191 * Exception - if error occurs while running command 192 ''' 193 194 # Available pools ready for subscription 195 available_pools = RhsmPools(self.module) 196 197 for pool in available_pools.filter(regexp): 198 pool.subscribe() 199 200 201class RhsmPool(object): 202 ''' 203 Convenience class for housing subscription information 204 ''' 205 206 def __init__(self, module, **kwargs): 207 self.module = module 208 for k, v in kwargs.items(): 209 setattr(self, k, v) 210 211 def __str__(self): 212 return str(self.__getattribute__('_name')) 213 214 def subscribe(self): 215 args = "subscription-manager subscribe --pool %s" % self.PoolId 216 rc, stdout, stderr = self.module.run_command(args, check_rc=True) 217 if rc == 0: 218 return True 219 else: 220 return False 221 222 223class RhsmPools(object): 224 """ 225 This class is used for manipulating pools subscriptions with RHSM 226 """ 227 def __init__(self, module): 228 self.module = module 229 self.products = self._load_product_list() 230 231 def __iter__(self): 232 return self.products.__iter__() 233 234 def _load_product_list(self): 235 """ 236 Loads list of all available pools for system in data structure 237 """ 238 args = "subscription-manager list --available" 239 rc, stdout, stderr = self.module.run_command(args, check_rc=True) 240 241 products = [] 242 for line in stdout.split('\n'): 243 # Remove leading+trailing whitespace 244 line = line.strip() 245 # An empty line implies the end of an output group 246 if len(line) == 0: 247 continue 248 # If a colon ':' is found, parse 249 elif ':' in line: 250 (key, value) = line.split(':', 1) 251 key = key.strip().replace(" ", "") # To unify 252 value = value.strip() 253 if key in ['ProductName', 'SubscriptionName']: 254 # Remember the name for later processing 255 products.append(RhsmPool(self.module, _name=value, key=value)) 256 elif products: 257 # Associate value with most recently recorded product 258 products[-1].__setattr__(key, value) 259 # FIXME - log some warning? 260 # else: 261 # warnings.warn("Unhandled subscription key/value: %s/%s" % (key,value)) 262 return products 263 264 def filter(self, regexp='^$'): 265 ''' 266 Return a list of RhsmPools whose name matches the provided regular expression 267 ''' 268 r = re.compile(regexp) 269 for product in self.products: 270 if r.search(product._name): 271 yield product 272