From 3ba19bf369fe122db5e7787ee28165adfaf6d993 Mon Sep 17 00:00:00 2001 From: James Laska Date: Thu, 6 Jun 2013 14:54:35 -0400 Subject: [PATCH] Add RHN subscription_manager module The subscription_manager module acts as a wrapper to the command 'subscription-manager'. The subscription-manager utility allows users to manage RHN registration, subscription and yum repositories from the Red Hat Entitlement platform. Currently, this module supports the following sub-commands: * config * [un]register * subscribe --- library/packaging/subscription_manager | 335 +++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 library/packaging/subscription_manager diff --git a/library/packaging/subscription_manager b/library/packaging/subscription_manager new file mode 100644 index 00000000000..44cb90007eb --- /dev/null +++ b/library/packaging/subscription_manager @@ -0,0 +1,335 @@ +#!/usr/bin/python + +DOCUMENTATION = ''' +--- +module: subscription-manager +short_description: Define interface to subscription-manager +description: + - Adds or removes Red Hat software channels on a system +version_added: 1.0 +author: James Laska +notes: + - this module fetches the systemid from rhn. A function + to use the local systemid is provided (get_localsystem) + but not integrated +requirements: + - none +options: + username: + description + - RHN username + required: false + default: null + password: + description: + - RHN password + required: false + default: null + server_hostname: + description: + - Specify an alternative RHN server + required: false + server_insecure: + description: + - Allow RHN traffic over insecure http + required: false + default: false + rhsm_baseurl: + description: + - Specify CDN baseurl + required: false + default: false + autosubscribe: + description: + - Upon successful registration, auto-consume available subscriptions + required: false + default: false + activationkey: + description: + - supply an activation key for use with registration + required: false + default: null + pool: + description: + - A pool to subscribe to (accepts regular expression syntax) + required: false + default: '^$' +examples: + - code: subscription_manager action=register username=rhsm_user password=somepass autosubscribe=true +''' + +import os +import re +import types +import subprocess +import ConfigParser +import shlex + + +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 RhsmPool(object): + def __init__(self, **kwargs): + for k,v in kwargs.items(): + setattr(self, k, v) + def __str__(self): + return str(self.__getattribute__('_name')) + def subscribe(self): + (stdout, stderr, retcode) = run_command("subscription-manager subscribe --pool %s" % self.PoolId) + return True + + +class RhsmPools(object): + """ + This class is used for manipulating pools subscriptions with RHSM + """ + def __init__(self): + self.products = self._load_product_list() + + def __iter__(self): + return self.products.__iter__() + + def _load_product_list(self): + """ + Loads list of all availaible pools for system in data structure + """ + (stdout, stderr, retval) = run_command("subscription-manager list --available") + + products = [] + for line in stdout.split('\n'): + # Remove leading+trailing whitespace + line = line.strip() + # An empty line implies the end of a output group + if len(line) == 0: + continue + # If a colon ':' is found, parse + elif ':' in line: + (key, value) = line.split(':',1) + key = key.strip().replace(" ", "") # To unify + value = value.strip() + if key in ['ProductName', 'SubscriptionName']: + # Remember the name for later processing + products.append(RhsmPool(_name=value, key=value)) + elif products: + # Associate value with most recently recorded product + products[-1].__setattr__(key, value) + # FIXME - log some warning? + #else: + # warnings.warn("Unhandled subscription key/value: %s/%s" % (key,value)) + return products + + def filter(self, regexp='^$'): + ''' + Return a list of RhsmPools whose name matches the provided regular expression + ''' + r = re.compile(regexp) + for product in self.products: + if r.search(product._name): + 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() + + 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), + autosubscribe = dict(default=False, type='bool'), + activationkey = dict(default=None, required=False), + pool = dict(default='^$', required=False, type='str'), + ) + ) + + state = module.params['state'] + username = module.params['username'] + password = module.params['password'] + server_hostname = module.params['server_hostname'] + server_insecure = module.params['server_insecure'] + rhsm_baseurl = module.params['rhsm_baseurl'] + autosubscribe = module.params['autosubscribe'] == True + activationkey = module.params['activationkey'] + pool = module.params['pool'] + + # Ensure system is registered + if state == 'present': + + # Check for missing parameters ... + if not (activationkey or username or password): + module.fail_json(msg="Missing arguments, must supply an activationkey (%s) or username (%s) and password (%s)" % (activationkey, username, password)) + if not activationkey and not (username and password): + module.fail_json(msg="Missing arguments, If registering without an activationkey, must supply username or password") + + # Register system + if is_registered(): + module.exit_json(changed=False, msg="System already registered.") + else: + try: + configure(**module.params) + register(username, password, autosubscribe, activationkey) + subscribe(pool) + except CommandException, e: + module.fail_json(msg="Failed to register with '%s': %s" % (server_hostname, e)) + else: + module.exit_json(changed=True, msg="System successfully registered to '%s'." % server_hostname) + + # Ensure system is *not* registered + if state == 'absent': + if not is_registered(): + module.exit_json(changed=False, msg="System already unregistered.") + else: + try: + 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." % server_hostname) + + +# include magic from lib/ansible/module_common.py +#<> +main()