#!/usr/bin/python DOCUMENTATION = ''' --- module: rhn_register short_description: Manage Red Hat Network registration using the C(rhnreg_ks) command description: - Manage registration to the Red Hat Network. version_added: "1.2" author: James Laska notes: - In order to register a system, rhnreg_ks requires either a username and password, or an activationkey. requirements: - rhnreg_ks options: state: description: - whether to register (C(present)), or unregister (C(absent)) a system required: false choices: [ "present", "absent" ] default: "present" username: description: - Red Hat Network username required: False default: null password: description: - Red Hat Network password required: False default: null server_url: description: - Specify an alternative Red Hat Network server URL required: False default: Current value of I(serverURL) from C(/etc/sysconfig/rhn/up2date) is the default activationkey: description: - supply an activation key for use with registration required: False default: null channels: description: - Optionally specify a list of comma-separated channels to subscribe to upon successful registration. required: false default: [] examples: - code: rhn_register state=absent username=joe_user password=somepass description: Unregister system from RHN. - code: rhn_register state=present username=joe_user password=somepass description: Register as user I(joe_user) with password I(somepass) and auto-subscribe to available content. - code: rhn_register state=present activationkey=1-222333444 enable_eus=true description: Register with activationkey I(1-222333444) and enable extended update support. - code: rhn_register state=present username=joe_user password=somepass server_url=https://xmlrpc.my.satellite/XMLRPC description: Register as user I(joe_user) with password I(somepass) against a satellite server specified by I(server_url). - code: rhn_register state=present username=joe_user password=somepass channels=rhel-x86_64-server-6-foo-1,rhel-x86_64-server-6-bar-1 description: Register as user I(joe_user) with password I(somepass) and enable channels I(rhel-x86_64-server-6-foo-1) and I(rhel-x86_64-server-6-bar-1). ''' import sys import os import re import types import subprocess import ConfigParser import shlex import xmlrpclib import urlparse # Attempt to import rhn client tools sys.path.insert(0, '/usr/share/rhn') try: import up2date_client import up2date_client.config except ImportError, e: module.fail_json(msg="Unable to import up2date_client. Is 'rhn-client-tools' installed?\n%s" % e) class CommandException(Exception): pass def run_command(args): ''' Convenience method to run a command, specified as a list of arguments. Returns: * tuple - (stdout, stder, retcode) ''' # Coerce into a string if isinstance(args, str): args = shlex.split(args) # Run desired command proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (stdout, stderr) = proc.communicate() returncode = proc.poll() if returncode != 0: cmd = ' '.join(args) raise CommandException("Command failed (%s): %s\n%s" % (returncode, cmd, stdout)) return (stdout, stderr, returncode) class RegistrationBase (object): def __init__(self, username=None, password=None): self.username = username self.password = password def configure(self): raise NotImplementedError("Must be implemented by a sub-class") def enable(self): # Remove any existing redhat.repo redhat_repo = '/etc/yum.repos.d/redhat.repo' if os.path.isfile(redhat_repo): os.unlink(redhat_repo) def register(self): raise NotImplementedError("Must be implemented by a sub-class") def unregister(self): raise NotImplementedError("Must be implemented by a sub-class") def unsubscribe(self): raise NotImplementedError("Must be implemented by a sub-class") def update_plugin_conf(self, plugin, enabled=True): plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin if os.path.isfile(plugin_conf): cfg = ConfigParser.ConfigParser() cfg.read([plugin_conf]) if enabled: cfg.set('main', 'enabled', 1) else: cfg.set('main', 'enabled', 0) fd = open(plugin_conf, 'rwa+') cfg.write(fd) fd.close() def subscribe(self, **kwargs): raise NotImplementedError("Must be implemented by a sub-class") class Rhn(RegistrationBase): def __init__(self, username=None, password=None, filename=None): RegistrationBase.__init__(self, username, password) self.config = self._read_config(filename) def _read_config(self, filename=None): ''' Read configuration from /etc/sysconfig/rhn/up2date ''' self.config = up2date_client.config.initUp2dateConfig(filename) # Add support for specifying a default value w/o having to standup some # configuration. Yeah, I know this should be subclassed ... but, oh # well def get_option_default(self, key, default=''): if self.has_key(key): return self[key] else: return default self.config.get_option = types.MethodType(get_option_default, self.config, up2date_client.config.Config) return self.config @property def hostname(self): ''' Return the non-xmlrpc RHN hostname. This is a convenience method used for displaying a more readable RHN hostname. Returns: str ''' url = urlparse.urlparse(self.config['serverURL']) return url.netloc.replace('xmlrpc.','') @property def systemid(self): systemid = None xpath_str = "//member[name='system_id']/value/string" if os.path.isfile(self.config['systemIdPath']): fd = open(self.config['systemIdPath'], 'r') xml_data = fd.read() fd.close() # Ugh, xml parsing time ... # First, try parsing with libxml2 ... if systemid is None: try: import libxml2 doc = libxml2.parseDoc(xml_data) ctxt = doc.xpathNewContext() systemid = ctxt.xpathEval(xpath_str)[0].content doc.freeDoc() ctxt.xpathFreeContext() except ImportError: pass # m-kay, let's try with lxml now ... if systemid is None: try: from lxml import etree root = etree.fromstring(xml_data) systemid = root.xpath(xpath_str)[0].text except ImportError: pass # Strip the 'ID-' prefix if systemid is not None and systemid.startswith('ID-'): systemid = systemid[3:] return int(systemid) @property def is_registered(self): ''' Determine whether the current system is registered. Returns: True|False ''' return os.path.isfile(self.config['systemIdPath']) def configure(self, server_url): ''' Configure system for registration ''' self.config.set('serverURL', server_url) self.config.save() def enable(self): ''' Prepare the system for RHN registration. This includes ... * enabling the rhnplugin yum plugin * disabling the subscription-manager yum plugin ''' RegistrationBase.enable(self) self.update_plugin_conf('rhnplugin', True) self.update_plugin_conf('subscription-manager', False) def register(self, enable_eus=False, activationkey=None): ''' Register system to RHN. If enable_eus=True, extended update support will be requested. ''' register_cmd = "rhnreg_ks --username '%s' --password '%s' --force" % (self.username, self.password) if enable_eus: register_cmd += " --use-eus-channel" if activationkey is not None: register_cmd += " --activationkey '%s'" % activationkey # FIXME - support --profilename # FIXME - support --systemorgid run_command(register_cmd) def api(self, method, *args): ''' Convenience RPC wrapper ''' if not hasattr(self, 'server') or self.server is None: url = "https://xmlrpc.%s/rpc/api" % self.hostname self.server = xmlrpclib.Server(url, verbose=0) self.session = self.server.auth.login(self.username, self.password) func = getattr(self.server, method) return func(self.session, *args) def unregister(self): ''' Unregister a previously registered system ''' # Initiate RPC connection self.api('system.deleteSystems', [self.systemid]) # Remove systemid file os.unlink(self.config['systemIdPath']) def subscribe(self, channels=[]): if len(channels) <= 0: return current_channels = self.api('channel.software.listSystemChannels', self.systemid) new_channels = [item['channel_label'] for item in current_channels] new_channels.extend(channels) return self.api('channel.software.setSystemChannels', self.systemid, new_channels) def _subscribe(self, channels=[]): ''' Subscribe to requested yum repositories using 'rhn-channel' command ''' rhn_channel_cmd = "rhn-channel --user='%s' --password='%s'" % (self.username, self.password) (stdout, stderr, rc) = run_command(rhn_channel_cmd + " --available-channels") # Enable requested repoid's for wanted_channel in channels: # Each inserted repo regexp will be matched. If no match, no success. for availaible_channel in stdout.rstrip().split('\n'): # .rstrip() because of \n at the end -> empty string at the end if re.search(wanted_repo, available_channel): run_command(rhn_channel_cmd + " --add --channel=%s" % available_channel) def main(): # Read system RHN configuration rhn = Rhn() module = AnsibleModule( argument_spec = dict( state = dict(default='present', choices=['present', 'absent']), username = dict(default=None, required=False), password = dict(default=None, required=False), server_url = dict(default=rhn.config.get_option('serverURL'), required=False), activationkey = dict(default=None, required=False), enable_eus = dict(default=False, type='bool'), channels = dict(default=[], type='list'), ) ) state = module.params['state'] rhn.username = module.params['username'] rhn.password = module.params['password'] rhn.configure(module.params['server_url']) activationkey = module.params['activationkey'] channels = module.params['channels'] # Ensure system is registered if state == 'present': # Check for missing parameters ... if not (activationkey or rhn.username or rhn.password): module.fail_json(msg="Missing arguments, must supply an activationkey (%s) or username (%s) and password (%s)" % (activationkey, rhn.username, rhn.password)) if not activationkey and not (rhn.username and rhn.password): module.fail_json(msg="Missing arguments, If registering without an activationkey, must supply username or password") # Register system if rhn.is_registered: module.exit_json(changed=False, msg="System already registered.") else: try: rhn.enable() rhn.register(module.params['enable_eus'] == True, activationkey) rhn.subscribe(channels) except CommandException, e: module.fail_json(msg="Failed to register with '%s': %s" % (rhn.hostname, e)) else: module.exit_json(changed=True, msg="System successfully registered to '%s'." % rhn.hostname) # Ensure system is *not* registered if state == 'absent': if not rhn.is_registered: module.exit_json(changed=False, msg="System already unregistered.") else: try: rhn.unregister() except CommandException, e: module.fail_json(msg="Failed to unregister: %s" % e) else: module.exit_json(changed=True, msg="System successfully unregistered from %s." % rhn.hostname) # include magic from lib/ansible/module_common.py #<> main()