Blame cloudinit/config/cc_rh_subscription.py

Packit Service a04d08
# Copyright (C) 2015 Red Hat, Inc.
Packit Service a04d08
#
Packit Service a04d08
# Author: Brent Baude <bbaude@redhat.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
"""
Packit Service a04d08
RedHat Subscription
Packit Service a04d08
-------------------
Packit Service a04d08
**Summary:** register red hat enterprise linux based system
Packit Service a04d08
Packit Service a04d08
Register a RedHat system either by username and password *or* activation and
Packit Service a04d08
org. Following a sucessful registration, you can auto-attach subscriptions, set
Packit Service a04d08
the service level, add subscriptions based on pool id, enable/disable yum
Packit Service a04d08
repositories based on repo id, and alter the rhsm_baseurl and server-hostname
Packit Service a04d08
in ``/etc/rhsm/rhs.conf``. For more details, see the ``Register RedHat
Packit Service a04d08
Subscription`` example config.
Packit Service a04d08
Packit Service a04d08
**Internal name:** ``cc_rh_subscription``
Packit Service a04d08
Packit Service a04d08
**Module frequency:** per instance
Packit Service a04d08
Packit Service a04d08
**Supported distros:** rhel, fedora
Packit Service a04d08
Packit Service a04d08
**Config keys**::
Packit Service a04d08
Packit Service a04d08
    rh_subscription:
Packit Service a04d08
        username: <username>
Packit Service a04d08
        password: <password>
Packit Service a04d08
        activation-key: <activation key>
Packit Service a04d08
        org: <org number>
Packit Service a04d08
        auto-attach: <true/false>
Packit Service a04d08
        service-level: <service level>
Packit Service a04d08
        add-pool: <list of pool ids>
Packit Service a04d08
        enable-repo: <list of yum repo ids>
Packit Service a04d08
        disable-repo: <list of yum repo ids>
Packit Service a04d08
        rhsm-baseurl: <url>
Packit Service a04d08
        server-hostname: <hostname>
Packit Service a04d08
"""
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service 751c4a
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
distros = ['fedora', 'rhel']
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def handle(name, cfg, _cloud, log, _args):
Packit Service a04d08
    sm = SubscriptionManager(cfg, log=log)
Packit Service a04d08
    if not sm.is_configured():
Packit Service a04d08
        log.debug("%s: module not configured.", name)
Packit Service a04d08
        return None
Packit Service a04d08
Packit Service a04d08
    if not sm.is_registered():
Packit Service a04d08
        try:
Packit Service a04d08
            verify, verify_msg = sm._verify_keys()
Packit Service a04d08
            if verify is not True:
Packit Service a04d08
                raise SubscriptionError(verify_msg)
Packit Service a04d08
            cont = sm.rhn_register()
Packit Service a04d08
            if not cont:
Packit Service a04d08
                raise SubscriptionError("Registration failed or did not "
Packit Service a04d08
                                        "run completely")
Packit Service a04d08
Packit Service a04d08
            # Splitting up the registration, auto-attach, and servicelevel
Packit Service a04d08
            # commands because the error codes, messages from subman are not
Packit Service a04d08
            # specific enough.
Packit Service a04d08
Packit Service a04d08
            # Attempt to change the service level
Packit Service a04d08
            if sm.auto_attach and sm.servicelevel is not None:
Packit Service a04d08
                if not sm._set_service_level():
Packit Service a04d08
                    raise SubscriptionError("Setting of service-level "
Packit Service a04d08
                                            "failed")
Packit Service a04d08
                else:
Packit Service a04d08
                    sm.log.debug("Completed auto-attach with service level")
Packit Service a04d08
            elif sm.auto_attach:
Packit Service a04d08
                if not sm._set_auto_attach():
Packit Service a04d08
                    raise SubscriptionError("Setting auto-attach failed")
Packit Service a04d08
                else:
Packit Service a04d08
                    sm.log.debug("Completed auto-attach")
Packit Service a04d08
Packit Service a04d08
            if sm.pools is not None:
Packit Service a04d08
                if not isinstance(sm.pools, list):
Packit Service a04d08
                    pool_fail = "Pools must in the format of a list"
Packit Service a04d08
                    raise SubscriptionError(pool_fail)
Packit Service a04d08
Packit Service a04d08
                return_stat = sm.addPool(sm.pools)
Packit Service a04d08
                if not return_stat:
Packit Service a04d08
                    raise SubscriptionError("Unable to attach pools {0}"
Packit Service a04d08
                                            .format(sm.pools))
Packit Service a04d08
            return_stat = sm.update_repos()
Packit Service a04d08
            if not return_stat:
Packit Service a04d08
                raise SubscriptionError("Unable to add or remove repos")
Packit Service a04d08
            sm.log_success("rh_subscription plugin completed successfully")
Packit Service a04d08
        except SubscriptionError as e:
Packit Service a04d08
            sm.log_warn(str(e))
Packit Service a04d08
            sm.log_warn("rh_subscription plugin did not complete successfully")
Packit Service a04d08
    else:
Packit Service a04d08
        sm.log_success("System is already registered")
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class SubscriptionError(Exception):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class SubscriptionManager(object):
Packit Service a04d08
    valid_rh_keys = ['org', 'activation-key', 'username', 'password',
Packit Service a04d08
                     'disable-repo', 'enable-repo', 'add-pool',
Packit Service a04d08
                     'rhsm-baseurl', 'server-hostname',
Packit Service a04d08
                     'auto-attach', 'service-level']
Packit Service a04d08
Packit Service a04d08
    def __init__(self, cfg, log=None):
Packit Service a04d08
        if log is None:
Packit Service a04d08
            log = LOG
Packit Service a04d08
        self.log = log
Packit Service a04d08
        self.cfg = cfg
Packit Service a04d08
        self.rhel_cfg = self.cfg.get('rh_subscription', {})
Packit Service a04d08
        self.rhsm_baseurl = self.rhel_cfg.get('rhsm-baseurl')
Packit Service a04d08
        self.server_hostname = self.rhel_cfg.get('server-hostname')
Packit Service a04d08
        self.pools = self.rhel_cfg.get('add-pool')
Packit Service a04d08
        self.activation_key = self.rhel_cfg.get('activation-key')
Packit Service a04d08
        self.org = self.rhel_cfg.get('org')
Packit Service a04d08
        self.userid = self.rhel_cfg.get('username')
Packit Service a04d08
        self.password = self.rhel_cfg.get('password')
Packit Service a04d08
        self.auto_attach = self.rhel_cfg.get('auto-attach')
Packit Service a04d08
        self.enable_repo = self.rhel_cfg.get('enable-repo')
Packit Service a04d08
        self.disable_repo = self.rhel_cfg.get('disable-repo')
Packit Service a04d08
        self.servicelevel = self.rhel_cfg.get('service-level')
Packit Service a04d08
Packit Service a04d08
    def log_success(self, msg):
Packit Service a04d08
        '''Simple wrapper for logging info messages. Useful for unittests'''
Packit Service a04d08
        self.log.info(msg)
Packit Service a04d08
Packit Service a04d08
    def log_warn(self, msg):
Packit Service a04d08
        '''Simple wrapper for logging warning messages. Useful for unittests'''
Packit Service a04d08
        self.log.warning(msg)
Packit Service a04d08
Packit Service a04d08
    def _verify_keys(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Checks that the keys in the rh_subscription dict from the user-data
Packit Service a04d08
        are what we expect.
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        for k in self.rhel_cfg:
Packit Service a04d08
            if k not in self.valid_rh_keys:
Packit Service a04d08
                bad_key = "{0} is not a valid key for rh_subscription. "\
Packit Service a04d08
                          "Valid keys are: "\
Packit Service a04d08
                          "{1}".format(k, ', '.join(self.valid_rh_keys))
Packit Service a04d08
                return False, bad_key
Packit Service a04d08
Packit Service a04d08
        # Check for bad auto-attach value
Packit Service a04d08
        if (self.auto_attach is not None) and \
Packit Service a04d08
                not (util.is_true(self.auto_attach) or
Packit Service a04d08
                     util.is_false(self.auto_attach)):
Packit Service a04d08
            not_bool = "The key auto-attach must be a boolean value "\
Packit Service a04d08
                       "(True/False "
Packit Service a04d08
            return False, not_bool
Packit Service a04d08
Packit Service a04d08
        if (self.servicelevel is not None) and ((not self.auto_attach) or
Packit Service a04d08
           (util.is_false(str(self.auto_attach)))):
Packit Service a04d08
            no_auto = ("The service-level key must be used in conjunction "
Packit Service a04d08
                       "with the auto-attach key.  Please re-run with "
Packit Service a04d08
                       "auto-attach: True")
Packit Service a04d08
            return False, no_auto
Packit Service a04d08
        return True, None
Packit Service a04d08
Packit Service a04d08
    def is_registered(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Checks if the system is already registered and returns
Packit Service a04d08
        True if so, else False
Packit Service a04d08
        '''
Packit Service a04d08
        cmd = ['identity']
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            _sub_man_cli(cmd)
Packit Service 751c4a
        except subp.ProcessExecutionError:
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def rhn_register(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Registers the system by userid and password or activation key
Packit Service a04d08
        and org.  Returns True when successful False when not.
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        if (self.activation_key is not None) and (self.org is not None):
Packit Service a04d08
            # register by activation key
Packit Service a04d08
            cmd = ['register', '--activationkey={0}'.
Packit Service a04d08
                   format(self.activation_key), '--org={0}'.format(self.org)]
Packit Service a04d08
Packit Service a04d08
            # If the baseurl and/or server url are passed in, we register
Packit Service a04d08
            # with them.
Packit Service a04d08
Packit Service a04d08
            if self.rhsm_baseurl is not None:
Packit Service a04d08
                cmd.append("--baseurl={0}".format(self.rhsm_baseurl))
Packit Service a04d08
Packit Service a04d08
            if self.server_hostname is not None:
Packit Service a04d08
                cmd.append("--serverurl={0}".format(self.server_hostname))
Packit Service a04d08
Packit Service a04d08
            try:
Packit Service a04d08
                return_out = _sub_man_cli(cmd, logstring_val=True)[0]
Packit Service 751c4a
            except subp.ProcessExecutionError as e:
Packit Service a04d08
                if e.stdout == "":
Packit Service a04d08
                    self.log_warn("Registration failed due "
Packit Service a04d08
                                  "to: {0}".format(e.stderr))
Packit Service a04d08
                return False
Packit Service a04d08
Packit Service a04d08
        elif (self.userid is not None) and (self.password is not None):
Packit Service a04d08
            # register by username and password
Packit Service a04d08
            cmd = ['register', '--username={0}'.format(self.userid),
Packit Service a04d08
                   '--password={0}'.format(self.password)]
Packit Service a04d08
Packit Service a04d08
            # If the baseurl and/or server url are passed in, we register
Packit Service a04d08
            # with them.
Packit Service a04d08
Packit Service a04d08
            if self.rhsm_baseurl is not None:
Packit Service a04d08
                cmd.append("--baseurl={0}".format(self.rhsm_baseurl))
Packit Service a04d08
Packit Service a04d08
            if self.server_hostname is not None:
Packit Service a04d08
                cmd.append("--serverurl={0}".format(self.server_hostname))
Packit Service a04d08
Packit Service a04d08
            # Attempting to register the system only
Packit Service a04d08
            try:
Packit Service a04d08
                return_out = _sub_man_cli(cmd, logstring_val=True)[0]
Packit Service 751c4a
            except subp.ProcessExecutionError as e:
Packit Service a04d08
                if e.stdout == "":
Packit Service a04d08
                    self.log_warn("Registration failed due "
Packit Service a04d08
                                  "to: {0}".format(e.stderr))
Packit Service a04d08
                return False
Packit Service a04d08
Packit Service a04d08
        else:
Packit Service a04d08
            self.log_warn("Unable to register system due to incomplete "
Packit Service a04d08
                          "information.")
Packit Service a04d08
            self.log_warn("Use either activationkey and org *or* userid "
Packit Service a04d08
                          "and password")
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        reg_id = return_out.split("ID: ")[1].rstrip()
Packit Service a04d08
        self.log.debug("Registered successfully with ID %s", reg_id)
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def _set_service_level(self):
Packit Service a04d08
        cmd = ['attach', '--auto', '--servicelevel={0}'
Packit Service a04d08
               .format(self.servicelevel)]
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            return_out = _sub_man_cli(cmd)[0]
Packit Service 751c4a
        except subp.ProcessExecutionError as e:
Packit Service a04d08
            if e.stdout.rstrip() != '':
Packit Service a04d08
                for line in e.stdout.split("\n"):
Packit Service a04d08
                    if line != '':
Packit Service a04d08
                        self.log_warn(line)
Packit Service a04d08
            else:
Packit Service a04d08
                self.log_warn("Setting the service level failed with: "
Packit Service a04d08
                              "{0}".format(e.stderr.strip()))
Packit Service a04d08
            return False
Packit Service a04d08
        for line in return_out.split("\n"):
Packit Service a04d08
            if line != "":
Packit Service a04d08
                self.log.debug(line)
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def _set_auto_attach(self):
Packit Service a04d08
        cmd = ['attach', '--auto']
Packit Service a04d08
        try:
Packit Service a04d08
            return_out = _sub_man_cli(cmd)[0]
Packit Service 751c4a
        except subp.ProcessExecutionError as e:
Packit Service a04d08
            self.log_warn("Auto-attach failed with: {0}".format(e))
Packit Service a04d08
            return False
Packit Service a04d08
        for line in return_out.split("\n"):
Packit Service a04d08
            if line != "":
Packit Service a04d08
                self.log.debug(line)
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def _getPools(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Gets the list pools for the active subscription and returns them
Packit Service a04d08
        in list form.
Packit Service a04d08
        '''
Packit Service a04d08
        available = []
Packit Service a04d08
        consumed = []
Packit Service a04d08
Packit Service a04d08
        # Get all available pools
Packit Service a04d08
        cmd = ['list', '--available', '--pool-only']
Packit Service a04d08
        results = _sub_man_cli(cmd)[0]
Packit Service a04d08
        available = (results.rstrip()).split("\n")
Packit Service a04d08
Packit Service a04d08
        # Get all consumed pools
Packit Service a04d08
        cmd = ['list', '--consumed', '--pool-only']
Packit Service a04d08
        results = _sub_man_cli(cmd)[0]
Packit Service a04d08
        consumed = (results.rstrip()).split("\n")
Packit Service a04d08
Packit Service a04d08
        return available, consumed
Packit Service a04d08
Packit Service a04d08
    def _getRepos(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Obtains the current list of active yum repositories and returns
Packit Service a04d08
        them in list form.
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        cmd = ['repos', '--list-enabled']
Packit Service a04d08
        return_out = _sub_man_cli(cmd)[0]
Packit Service a04d08
        active_repos = []
Packit Service a04d08
        for repo in return_out.split("\n"):
Packit Service a04d08
            if "Repo ID:" in repo:
Packit Service a04d08
                active_repos.append((repo.split(':')[1]).strip())
Packit Service a04d08
Packit Service a04d08
        cmd = ['repos', '--list-disabled']
Packit Service a04d08
        return_out = _sub_man_cli(cmd)[0]
Packit Service a04d08
Packit Service a04d08
        inactive_repos = []
Packit Service a04d08
        for repo in return_out.split("\n"):
Packit Service a04d08
            if "Repo ID:" in repo:
Packit Service a04d08
                inactive_repos.append((repo.split(':')[1]).strip())
Packit Service a04d08
        return active_repos, inactive_repos
Packit Service a04d08
Packit Service a04d08
    def addPool(self, pools):
Packit Service a04d08
        '''
Packit Service a04d08
        Takes a list of subscription pools and "attaches" them to the
Packit Service a04d08
        current subscription
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        # An empty list was passed
Packit Service a04d08
        if len(pools) == 0:
Packit Service a04d08
            self.log.debug("No pools to attach")
Packit Service a04d08
            return True
Packit Service a04d08
Packit Service a04d08
        pool_available, pool_consumed = self._getPools()
Packit Service a04d08
        pool_list = []
Packit Service a04d08
        cmd = ['attach']
Packit Service a04d08
        for pool in pools:
Packit Service a04d08
            if (pool not in pool_consumed) and (pool in pool_available):
Packit Service a04d08
                pool_list.append('--pool={0}'.format(pool))
Packit Service a04d08
            else:
Packit Service a04d08
                self.log_warn("Pool {0} is not available".format(pool))
Packit Service a04d08
        if len(pool_list) > 0:
Packit Service a04d08
            cmd.extend(pool_list)
Packit Service a04d08
            try:
Packit Service a04d08
                _sub_man_cli(cmd)
Packit Service a04d08
                self.log.debug("Attached the following pools to your "
Packit Service a04d08
                               "system: %s", (", ".join(pool_list))
Packit Service a04d08
                               .replace('--pool=', ''))
Packit Service a04d08
                return True
Packit Service 751c4a
            except subp.ProcessExecutionError as e:
Packit Service a04d08
                self.log_warn("Unable to attach pool {0} "
Packit Service a04d08
                              "due to {1}".format(pool, e))
Packit Service a04d08
                return False
Packit Service a04d08
Packit Service a04d08
    def update_repos(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Takes a list of yum repo ids that need to be disabled or enabled; then
Packit Service a04d08
        it verifies if they are already enabled or disabled and finally
Packit Service a04d08
        executes the action to disable or enable
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        erepos = self.enable_repo
Packit Service a04d08
        drepos = self.disable_repo
Packit Service a04d08
        if erepos is None:
Packit Service a04d08
            erepos = []
Packit Service a04d08
        if drepos is None:
Packit Service a04d08
            drepos = []
Packit Service a04d08
        if not isinstance(erepos, list):
Packit Service a04d08
            self.log_warn("Repo IDs must in the format of a list.")
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        if not isinstance(drepos, list):
Packit Service a04d08
            self.log_warn("Repo IDs must in the format of a list.")
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        # Bail if both lists are not populated
Packit Service a04d08
        if (len(erepos) == 0) and (len(drepos) == 0):
Packit Service a04d08
            self.log.debug("No repo IDs to enable or disable")
Packit Service a04d08
            return True
Packit Service a04d08
Packit Service a04d08
        active_repos, inactive_repos = self._getRepos()
Packit Service a04d08
        # Creating a list of repoids to be enabled
Packit Service a04d08
        enable_list = []
Packit Service a04d08
        enable_list_fail = []
Packit Service a04d08
        for repoid in erepos:
Packit Service a04d08
            if (repoid in inactive_repos):
Packit Service a04d08
                enable_list.append("--enable={0}".format(repoid))
Packit Service a04d08
            else:
Packit Service a04d08
                enable_list_fail.append(repoid)
Packit Service a04d08
Packit Service a04d08
        # Creating a list of repoids to be disabled
Packit Service a04d08
        disable_list = []
Packit Service a04d08
        disable_list_fail = []
Packit Service a04d08
        for repoid in drepos:
Packit Service a04d08
            if repoid in active_repos:
Packit Service a04d08
                disable_list.append("--disable={0}".format(repoid))
Packit Service a04d08
            else:
Packit Service a04d08
                disable_list_fail.append(repoid)
Packit Service a04d08
Packit Service a04d08
        # Logging any repos that are already enabled or disabled
Packit Service a04d08
        if len(enable_list_fail) > 0:
Packit Service a04d08
            for fail in enable_list_fail:
Packit Service a04d08
                # Check if the repo exists or not
Packit Service a04d08
                if fail in active_repos:
Packit Service a04d08
                    self.log.debug("Repo %s is already enabled", fail)
Packit Service a04d08
                else:
Packit Service a04d08
                    self.log_warn("Repo {0} does not appear to "
Packit Service a04d08
                                  "exist".format(fail))
Packit Service a04d08
        if len(disable_list_fail) > 0:
Packit Service a04d08
            for fail in disable_list_fail:
Packit Service a04d08
                self.log.debug("Repo %s not disabled "
Packit Service a04d08
                               "because it is not enabled", fail)
Packit Service a04d08
Packit Service a04d08
        cmd = ['repos']
Packit Service a04d08
        if len(disable_list) > 0:
Packit Service a04d08
            cmd.extend(disable_list)
Packit Service a04d08
Packit Service a04d08
        if len(enable_list) > 0:
Packit Service a04d08
            cmd.extend(enable_list)
Packit Service a04d08
Packit Service a04d08
        try:
Packit Service a04d08
            _sub_man_cli(cmd)
Packit Service 751c4a
        except subp.ProcessExecutionError as e:
Packit Service a04d08
            self.log_warn("Unable to alter repos due to {0}".format(e))
Packit Service a04d08
            return False
Packit Service a04d08
Packit Service a04d08
        if len(enable_list) > 0:
Packit Service a04d08
            self.log.debug("Enabled the following repos: %s",
Packit Service a04d08
                           (", ".join(enable_list)).replace('--enable=', ''))
Packit Service a04d08
        if len(disable_list) > 0:
Packit Service a04d08
            self.log.debug("Disabled the following repos: %s",
Packit Service a04d08
                           (", ".join(disable_list)).replace('--disable=', ''))
Packit Service a04d08
        return True
Packit Service a04d08
Packit Service a04d08
    def is_configured(self):
Packit Service a04d08
        return bool((self.userid and self.password) or self.activation_key)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _sub_man_cli(cmd, logstring_val=False):
Packit Service a04d08
    '''
Packit Service 751c4a
    Uses the prefered cloud-init subprocess def of subp.subp
Packit Service a04d08
    and runs subscription-manager.  Breaking this to a
Packit Service a04d08
    separate function for later use in mocking and unittests
Packit Service a04d08
    '''
Packit Service 751c4a
    return subp.subp(['subscription-manager'] + cmd,
Packit Service a04d08
                     logstring=logstring_val)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab