1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) James Laska 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = r''' 15--- 16module: rhn_register 17short_description: Manage Red Hat Network registration using the C(rhnreg_ks) command 18description: 19 - Manage registration to the Red Hat Network. 20version_added: "1.2" 21author: 22- James Laska (@jlaska) 23notes: 24 - This is for older Red Hat products. You probably want the M(redhat_subscription) module instead. 25 - In order to register a system, C(rhnreg_ks) requires either a username and password, or an activationkey. 26requirements: 27 - rhnreg_ks 28 - either libxml2 or lxml 29options: 30 state: 31 description: 32 - Whether to register (C(present)), or unregister (C(absent)) a system. 33 type: str 34 choices: [ absent, present ] 35 default: present 36 username: 37 description: 38 - Red Hat Network username. 39 type: str 40 password: 41 description: 42 - Red Hat Network password. 43 type: str 44 server_url: 45 description: 46 - Specify an alternative Red Hat Network server URL. 47 - The default is the current value of I(serverURL) from C(/etc/sysconfig/rhn/up2date). 48 type: str 49 activationkey: 50 description: 51 - Supply an activation key for use with registration. 52 type: str 53 profilename: 54 description: 55 - Supply an profilename for use with registration. 56 type: str 57 version_added: "2.0" 58 ca_cert: 59 description: 60 - Supply a custom ssl CA certificate file for use with registration. 61 type: path 62 version_added: "2.1" 63 aliases: [ sslcacert ] 64 systemorgid: 65 description: 66 - Supply an organizational id for use with registration. 67 type: str 68 version_added: "2.1" 69 channels: 70 description: 71 - Optionally specify a list of channels to subscribe to upon successful registration. 72 type: list 73 default: [] 74 enable_eus: 75 description: 76 - If C(no), extended update support will be requested. 77 type: bool 78 default: no 79 nopackages: 80 description: 81 - If C(yes), the registered node will not upload its installed packages information to Satellite server. 82 type: bool 83 default: no 84 version_added: "2.5" 85''' 86 87EXAMPLES = r''' 88- name: Unregister system from RHN 89 rhn_register: 90 state: absent 91 username: joe_user 92 password: somepass 93 94- name: Register as user with password and auto-subscribe to available content 95 rhn_register: 96 state: present 97 username: joe_user 98 password: somepass 99 100- name: Register with activationkey and enable extended update support 101 rhn_register: 102 state: present 103 activationkey: 1-222333444 104 enable_eus: yes 105 106- name: Register with activationkey and set a profilename which may differ from the hostname 107 rhn_register: 108 state: present 109 activationkey: 1-222333444 110 profilename: host.example.com.custom 111 112- name: Register as user with password against a satellite server 113 rhn_register: 114 state: present 115 username: joe_user 116 password: somepass 117 server_url: https://xmlrpc.my.satellite/XMLRPC 118 119- name: Register as user with password and enable channels 120 rhn_register: 121 state: present 122 username: joe_user 123 password: somepass 124 channels: rhel-x86_64-server-6-foo-1,rhel-x86_64-server-6-bar-1 125''' 126 127RETURN = r''' 128# Default return values 129''' 130 131import os 132import sys 133 134# Attempt to import rhn client tools 135sys.path.insert(0, '/usr/share/rhn') 136try: 137 import up2date_client 138 import up2date_client.config 139 HAS_UP2DATE_CLIENT = True 140except ImportError: 141 HAS_UP2DATE_CLIENT = False 142 143# INSERT REDHAT SNIPPETS 144from ansible.module_utils import redhat 145from ansible.module_utils.basic import AnsibleModule 146from ansible.module_utils.six.moves import urllib, xmlrpc_client 147 148 149class Rhn(redhat.RegistrationBase): 150 151 def __init__(self, module=None, username=None, password=None): 152 redhat.RegistrationBase.__init__(self, module, username, password) 153 self.config = self.load_config() 154 self.server = None 155 self.session = None 156 157 def logout(self): 158 if self.session is not None: 159 self.server.auth.logout(self.session) 160 161 def load_config(self): 162 ''' 163 Read configuration from /etc/sysconfig/rhn/up2date 164 ''' 165 if not HAS_UP2DATE_CLIENT: 166 return None 167 168 config = up2date_client.config.initUp2dateConfig() 169 170 return config 171 172 @property 173 def server_url(self): 174 return self.config['serverURL'] 175 176 @property 177 def hostname(self): 178 ''' 179 Return the non-xmlrpc RHN hostname. This is a convenience method 180 used for displaying a more readable RHN hostname. 181 182 Returns: str 183 ''' 184 url = urllib.parse.urlparse(self.server_url) 185 return url[1].replace('xmlrpc.', '') 186 187 @property 188 def systemid(self): 189 systemid = None 190 xpath_str = "//member[name='system_id']/value/string" 191 192 if os.path.isfile(self.config['systemIdPath']): 193 fd = open(self.config['systemIdPath'], 'r') 194 xml_data = fd.read() 195 fd.close() 196 197 # Ugh, xml parsing time ... 198 # First, try parsing with libxml2 ... 199 if systemid is None: 200 try: 201 import libxml2 202 doc = libxml2.parseDoc(xml_data) 203 ctxt = doc.xpathNewContext() 204 systemid = ctxt.xpathEval(xpath_str)[0].content 205 doc.freeDoc() 206 ctxt.xpathFreeContext() 207 except ImportError: 208 pass 209 210 # m-kay, let's try with lxml now ... 211 if systemid is None: 212 try: 213 from lxml import etree 214 root = etree.fromstring(xml_data) 215 systemid = root.xpath(xpath_str)[0].text 216 except ImportError: 217 raise Exception('"libxml2" or "lxml" is required for this module.') 218 219 # Strip the 'ID-' prefix 220 if systemid is not None and systemid.startswith('ID-'): 221 systemid = systemid[3:] 222 223 return int(systemid) 224 225 @property 226 def is_registered(self): 227 ''' 228 Determine whether the current system is registered. 229 230 Returns: True|False 231 ''' 232 return os.path.isfile(self.config['systemIdPath']) 233 234 def configure_server_url(self, server_url): 235 ''' 236 Configure server_url for registration 237 ''' 238 239 self.config.set('serverURL', server_url) 240 self.config.save() 241 242 def enable(self): 243 ''' 244 Prepare the system for RHN registration. This includes ... 245 * enabling the rhnplugin yum plugin 246 * disabling the subscription-manager yum plugin 247 ''' 248 redhat.RegistrationBase.enable(self) 249 self.update_plugin_conf('rhnplugin', True) 250 self.update_plugin_conf('subscription-manager', False) 251 252 def register(self, enable_eus=False, activationkey=None, profilename=None, sslcacert=None, systemorgid=None, nopackages=False): 253 ''' 254 Register system to RHN. If enable_eus=True, extended update 255 support will be requested. 256 ''' 257 register_cmd = ['/usr/sbin/rhnreg_ks', '--force'] 258 if self.username: 259 register_cmd.extend(['--username', self.username, '--password', self.password]) 260 if self.server_url: 261 register_cmd.extend(['--serverUrl', self.server_url]) 262 if enable_eus: 263 register_cmd.append('--use-eus-channel') 264 if nopackages: 265 register_cmd.append('--nopackages') 266 if activationkey is not None: 267 register_cmd.extend(['--activationkey', activationkey]) 268 if profilename is not None: 269 register_cmd.extend(['--profilename', profilename]) 270 if sslcacert is not None: 271 register_cmd.extend(['--sslCACert', sslcacert]) 272 if systemorgid is not None: 273 register_cmd.extend(['--systemorgid', systemorgid]) 274 rc, stdout, stderr = self.module.run_command(register_cmd, check_rc=True) 275 276 def api(self, method, *args): 277 ''' 278 Convenience RPC wrapper 279 ''' 280 if self.server is None: 281 if self.hostname != 'rhn.redhat.com': 282 url = "https://%s/rpc/api" % self.hostname 283 else: 284 url = "https://xmlrpc.%s/rpc/api" % self.hostname 285 self.server = xmlrpc_client.ServerProxy(url) 286 self.session = self.server.auth.login(self.username, self.password) 287 288 func = getattr(self.server, method) 289 return func(self.session, *args) 290 291 def unregister(self): 292 ''' 293 Unregister a previously registered system 294 ''' 295 296 # Initiate RPC connection 297 self.api('system.deleteSystems', [self.systemid]) 298 299 # Remove systemid file 300 os.unlink(self.config['systemIdPath']) 301 302 def subscribe(self, channels): 303 if not channels: 304 return 305 306 if self._is_hosted(): 307 current_channels = self.api('channel.software.listSystemChannels', self.systemid) 308 new_channels = [item['channel_label'] for item in current_channels] 309 new_channels.extend(channels) 310 return self.api('channel.software.setSystemChannels', self.systemid, list(new_channels)) 311 312 else: 313 current_channels = self.api('channel.software.listSystemChannels', self.systemid) 314 current_channels = [item['label'] for item in current_channels] 315 new_base = None 316 new_childs = [] 317 for ch in channels: 318 if ch in current_channels: 319 continue 320 if self.api('channel.software.getDetails', ch)['parent_channel_label'] == '': 321 new_base = ch 322 else: 323 if ch not in new_childs: 324 new_childs.append(ch) 325 out_base = 0 326 out_childs = 0 327 328 if new_base: 329 out_base = self.api('system.setBaseChannel', self.systemid, new_base) 330 331 if new_childs: 332 out_childs = self.api('system.setChildChannels', self.systemid, new_childs) 333 334 return out_base and out_childs 335 336 def _is_hosted(self): 337 ''' 338 Return True if we are running against Hosted (rhn.redhat.com) or 339 False otherwise (when running against Satellite or Spacewalk) 340 ''' 341 return 'rhn.redhat.com' in self.hostname 342 343 344def main(): 345 346 module = AnsibleModule( 347 argument_spec=dict( 348 state=dict(type='str', default='present', choices=['absent', 'present']), 349 username=dict(type='str'), 350 password=dict(type='str', no_log=True), 351 server_url=dict(type='str'), 352 activationkey=dict(type='str', no_log=True), 353 profilename=dict(type='str'), 354 ca_cert=dict(type='path', aliases=['sslcacert']), 355 systemorgid=dict(type='str'), 356 enable_eus=dict(type='bool', default=False), 357 nopackages=dict(type='bool', default=False), 358 channels=dict(type='list', default=[]), 359 ), 360 # username/password is required for state=absent, or if channels is not empty 361 # (basically anything that uses self.api requires username/password) but it doesn't 362 # look like we can express that with required_if/required_together/mutually_exclusive 363 364 # only username+password can be used for unregister 365 required_if=[['state', 'absent', ['username', 'password']]], 366 ) 367 368 if not HAS_UP2DATE_CLIENT: 369 module.fail_json(msg="Unable to import up2date_client. Is 'rhn-client-tools' installed?") 370 371 server_url = module.params['server_url'] 372 username = module.params['username'] 373 password = module.params['password'] 374 375 state = module.params['state'] 376 activationkey = module.params['activationkey'] 377 profilename = module.params['profilename'] 378 sslcacert = module.params['ca_cert'] 379 systemorgid = module.params['systemorgid'] 380 channels = module.params['channels'] 381 enable_eus = module.params['enable_eus'] 382 nopackages = module.params['nopackages'] 383 384 rhn = Rhn(module=module, username=username, password=password) 385 386 # use the provided server url and persist it to the rhn config. 387 if server_url: 388 rhn.configure_server_url(server_url) 389 390 if not rhn.server_url: 391 module.fail_json( 392 msg="No serverURL was found (from either the 'server_url' module arg or the config file option 'serverURL' in /etc/sysconfig/rhn/up2date)" 393 ) 394 395 # Ensure system is registered 396 if state == 'present': 397 398 # Check for missing parameters ... 399 if not (activationkey or rhn.username or rhn.password): 400 module.fail_json(msg="Missing arguments, must supply an activationkey (%s) or username (%s) and password (%s)" % (activationkey, rhn.username, 401 rhn.password)) 402 if not activationkey and not (rhn.username and rhn.password): 403 module.fail_json(msg="Missing arguments, If registering without an activationkey, must supply username or password") 404 405 # Register system 406 if rhn.is_registered: 407 module.exit_json(changed=False, msg="System already registered.") 408 409 try: 410 rhn.enable() 411 rhn.register(enable_eus, activationkey, profilename, sslcacert, systemorgid, nopackages) 412 rhn.subscribe(channels) 413 except Exception as exc: 414 module.fail_json(msg="Failed to register with '%s': %s" % (rhn.hostname, exc)) 415 finally: 416 rhn.logout() 417 418 module.exit_json(changed=True, msg="System successfully registered to '%s'." % rhn.hostname) 419 420 # Ensure system is *not* registered 421 if state == 'absent': 422 if not rhn.is_registered: 423 module.exit_json(changed=False, msg="System already unregistered.") 424 425 if not (rhn.username and rhn.password): 426 module.fail_json(msg="Missing arguments, the system is currently registered and unregistration requires a username and password") 427 428 try: 429 rhn.unregister() 430 except Exception as exc: 431 module.fail_json(msg="Failed to unregister: %s" % exc) 432 finally: 433 rhn.logout() 434 435 module.exit_json(changed=True, msg="System successfully unregistered from %s." % rhn.hostname) 436 437 438if __name__ == '__main__': 439 main() 440