diff --git a/packaging/rhnreg_ks b/packaging/rhnreg_ks new file mode 100644 index 00000000000..8ca8de8bcc6 --- /dev/null +++ b/packaging/rhnreg_ks @@ -0,0 +1,371 @@ +#!/usr/bin/python + +DOCUMENTATION = ''' +--- +module: rhnreg_ks +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: rhnreg_ks state=absent username=joe_user password=somepass + description: Unregister system from RHN. + + - code: rhnreg_ks 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: rhnreg_ks state=present activationkey=1-222333444 enable_eus=true + description: Register with activationkey I(1-222333444) and enable extended update support. + + - code: rhnreg_ks 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: rhnreg_ks 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() diff --git a/packaging/subscription_manager b/packaging/subscription_manager index c51ae65ca69..f0dbe025c69 100644 --- a/packaging/subscription_manager +++ b/packaging/subscription_manager @@ -100,7 +100,183 @@ def run_command(args): 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 Rhsm(RegistrationBase): + def __init__(self, username=None, password=None): + RegistrationBase.__init__(self, username, password) + self.config = self._read_config() + + def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'): + ''' + Load RHSM configuration from /etc/rhsm/rhsm.conf. + Returns: + * ConfigParser object + ''' + + # Read RHSM defaults ... + cp = ConfigParser.ConfigParser() + cp.read(rhsm_conf) + + # 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=''): + sect, opt = key.split('.', 1) + if self.has_section(sect) and self.has_option(sect, opt): + return self.get(sect, opt) + else: + return default + + cp.get_option = types.MethodType(get_option_default, cp, ConfigParser.ConfigParser) + + return cp + + def enable(self): + ''' + Enable the system to receive updates from subscription-manager. + This involves updating affected yum plugins and removing any + conflicting yum repositories. + ''' + RegistrationBase.enable(self) + self.update_plugin_conf('rhnplugin', False) + self.update_plugin_conf('subscription-manager', True) + + def configure(self, **kwargs): + ''' + Configure the system as directed for registration with RHN + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'config'] + + # Pass supplied **kwargs as parameters to subscription-manager. Ignore + # non-configuration parameters and replace '_' with '.'. For example, + # 'server_hostname' becomes '--system.hostname'. + for k,v in kwargs.items(): + if re.search(r'^(system|rhsm)_', k): + args.append('--%s=%s' % (k.replace('_','.'), v)) + + run_command(args) + + @property + def is_registered(self): + ''' + Determine whether the current system + Returns: + * Boolean - whether the current system is currently registered to + RHN. + ''' + # Quick version... + if False: + return os.path.isfile('/etc/pki/consumer/cert.pem') and \ + os.path.isfile('/etc/pki/consumer/key.pem') + + args = ['subscription-manager', 'identity'] + try: + (stdout, stderr, retcode) = run_command(args) + except CommandException, e: + return False + else: + # Display some debug output + return True + + def register(self, username, password, autosubscribe, activationkey): + ''' + Register the current system to the provided RHN server + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'register'] + + # Generate command arguments + if activationkey: + args.append('--activationkey "%s"' % activationkey) + else: + if autosubscribe: + args.append('--autosubscribe') + if username: + args.extend(['--username', username]) + if password: + args.extend(['--password', password]) + + # Do the needful... + run_command(args) + + def unsubscribe(self): + ''' + Unsubscribe a system from all subscribed channels + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'unsubscribe', '--all'] + run_command(args) + + def unregister(self): + ''' + Unregister a currently registered system + Raises: + * Exception - if error occurs while running command + ''' + args = ['subscription-manager', 'unregister'] + run_command(args) + + def subscribe(self, regexp): + ''' + Subscribe current system to available pools matching the specified + regular expression + Raises: + * Exception - if error occurs while running command + ''' + + # Available pools ready for subscription + available_pools = RhsmPools() + + for pool in available_pools.filter(regexp): + pool.subscribe() + + class RhsmPool(object): + ''' + Convenience class for housing subscription information + ''' def __init__(self, **kwargs): for k,v in kwargs.items(): setattr(self, k, v) @@ -160,133 +336,19 @@ class RhsmPools(object): yield product -def read_rhsm_config(rhsm_conf='/etc/rhsm/rhsm.conf'): - ''' - Load RHSM configuration from /etc/rhsm/rhsm.conf. - Returns: - * ConfigParser object - ''' - - # Read RHSM defaults ... - cp = ConfigParser.ConfigParser() - if os.path.isfile(rhsm_conf): - cp.read(rhsm_conf) - - # 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=''): - sect, opt = key.split('.', 1) - if self.has_section(sect) and self.has_option(sect, opt): - return self.get(sect, opt) - else: - return default - - cp.get_option = types.MethodType(get_option_default, cp, ConfigParser.ConfigParser) - - return cp - - -def is_registered(): - ''' - Determine whether the current system - Returns: - * Boolean - whether the current system is currently registered to - RHN. - ''' - # Quick version... - if False: - return os.path.isfile('/etc/pki/consumer/cert.pem') and \ - os.path.isfile('/etc/pki/consumer/key.pem') - - args = ['subscription-manager', 'identity'] - try: - (stdout, stderr, retcode) = run_command(args) - except CommandException, e: - return False - else: - # Display some debug output - return True - - -def configure(**kwargs): - ''' - Configure the system as directed for registration with RHN - Raises: - * Exception - if error occurs while running command - ''' - args = ['subscription-manager', 'config'] - - # Pass supplied **kwargs as parameters to subscription-manager. Ignore - # non-configuration parameters and replace '_' with '.'. For example, - # 'server_hostname' becomes '--system.hostname'. - for k,v in kwargs.items(): - if re.search(r'^(system|rhsm)_', k): - args.append('--%s=%s' % (k.replace('_','.'), v)) - - run_command(args) - - -def register(username, password, autosubscribe, activationkey): - ''' - Register the current system to the provided RHN server - Raises: - * Exception - if error occurs while running command - ''' - args = ['subscription-manager', 'register'] - - # Generate command arguments - if activationkey: - args.append('--activationkey "%s"' % activationkey) - else: - if autosubscribe: - args.append('--autosubscribe') - if username: - args.extend(['--username', username]) - if password: - args.extend(['--password', password]) - - # Do the needful... - run_command(args) - - -def subscribe(regexp): - ''' - Subscribe current system to available pools matching the specified - regular expression - Raises: - * Exception - if error occurs while running command - ''' - - # Available pools ready for subscription - available_pools = RhsmPools() - - for pool in available_pools.filter(regexp): - pool.subscribe() - - -def unregister(): - ''' - Unregister a currently registered system - Raises: - * Exception - if error occurs while running command - ''' - args = ['subscription-manager', 'unregister'] - return run_command(args) - - def main(): # Load RHSM configuration from file - cp = read_rhsm_config() + rhn = Rhsm() 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_hostname = dict(default=cp.get_option('server.hostname'), required=False), - server_insecure = dict(default=cp.get_option('server.insecure'), required=False), - rhsm_baseurl = dict(default=cp.get_option('rhsm.baseurl'), required=False), + server_hostname = dict(default=rhn.config.get_option('server.hostname'), required=False), + server_insecure = dict(default=rhn.config.get_option('server.insecure'), required=False), + rhsm_baseurl = dict(default=rhn.config.get_option('rhsm.baseurl'), required=False), autosubscribe = dict(default=False, type='bool'), activationkey = dict(default=None, required=False), pool = dict(default='^$', required=False, type='str'), @@ -313,13 +375,14 @@ def main(): module.fail_json(msg="Missing arguments, If registering without an activationkey, must supply username or password") # Register system - if is_registered(): + if rhn.is_registered: module.exit_json(changed=False, msg="System already registered.") else: try: - configure(**module.params) - register(username, password, autosubscribe, activationkey) - subscribe(pool) + rhn.enable() + rhn.configure(**module.params) + rhn.register(username, password, autosubscribe, activationkey) + rhn.subscribe(pool) except CommandException, e: module.fail_json(msg="Failed to register with '%s': %s" % (server_hostname, e)) else: @@ -327,11 +390,12 @@ def main(): # Ensure system is *not* registered if state == 'absent': - if not is_registered(): + if not rhn.is_registered: module.exit_json(changed=False, msg="System already unregistered.") else: try: - unregister() + rhn.unsubscribe() + rhn.unregister() except CommandException, e: module.fail_json(msg="Failed to unregister: %s" % e) else: