1# -*- coding: utf-8 -*-
2# This code is part of Ansible, but is an independent component.
3# This particular file snippet, and this file snippet only, is BSD licensed.
4# Modules you write using this snippet, which is embedded dynamically by Ansible
5# still belong to the author of the module, and may assign their own license
6# to the complete work.
7#
8# Copyright (c), James Laska
9#
10# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
11
12from __future__ import (absolute_import, division, print_function)
13__metaclass__ = type
14
15
16import os
17import re
18import shutil
19import tempfile
20import types
21
22from ansible.module_utils.six.moves import configparser
23
24
25class RegistrationBase(object):
26    def __init__(self, module, username=None, password=None):
27        self.module = module
28        self.username = username
29        self.password = password
30
31    def configure(self):
32        raise NotImplementedError("Must be implemented by a sub-class")
33
34    def enable(self):
35        # Remove any existing redhat.repo
36        redhat_repo = '/etc/yum.repos.d/redhat.repo'
37        if os.path.isfile(redhat_repo):
38            os.unlink(redhat_repo)
39
40    def register(self):
41        raise NotImplementedError("Must be implemented by a sub-class")
42
43    def unregister(self):
44        raise NotImplementedError("Must be implemented by a sub-class")
45
46    def unsubscribe(self):
47        raise NotImplementedError("Must be implemented by a sub-class")
48
49    def update_plugin_conf(self, plugin, enabled=True):
50        plugin_conf = '/etc/yum/pluginconf.d/%s.conf' % plugin
51
52        if os.path.isfile(plugin_conf):
53            tmpfd, tmpfile = tempfile.mkstemp()
54            shutil.copy2(plugin_conf, tmpfile)
55            cfg = configparser.ConfigParser()
56            cfg.read([tmpfile])
57
58            if enabled:
59                cfg.set('main', 'enabled', 1)
60            else:
61                cfg.set('main', 'enabled', 0)
62
63            fd = open(tmpfile, 'w+')
64            cfg.write(fd)
65            fd.close()
66            self.module.atomic_move(tmpfile, plugin_conf)
67
68    def subscribe(self, **kwargs):
69        raise NotImplementedError("Must be implemented by a sub-class")
70
71
72class Rhsm(RegistrationBase):
73    def __init__(self, module, username=None, password=None):
74        RegistrationBase.__init__(self, module, username, password)
75        self.config = self._read_config()
76        self.module = module
77
78    def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'):
79        '''
80            Load RHSM configuration from /etc/rhsm/rhsm.conf.
81            Returns:
82             * ConfigParser object
83        '''
84
85        # Read RHSM defaults ...
86        cp = configparser.ConfigParser()
87        cp.read(rhsm_conf)
88
89        # Add support for specifying a default value w/o having to standup some configuration
90        # Yeah, I know this should be subclassed ... but, oh well
91        def get_option_default(self, key, default=''):
92            sect, opt = key.split('.', 1)
93            if self.has_section(sect) and self.has_option(sect, opt):
94                return self.get(sect, opt)
95            else:
96                return default
97
98        cp.get_option = types.MethodType(get_option_default, cp, configparser.ConfigParser)
99
100        return cp
101
102    def enable(self):
103        '''
104            Enable the system to receive updates from subscription-manager.
105            This involves updating affected yum plugins and removing any
106            conflicting yum repositories.
107        '''
108        RegistrationBase.enable(self)
109        self.update_plugin_conf('rhnplugin', False)
110        self.update_plugin_conf('subscription-manager', True)
111
112    def configure(self, **kwargs):
113        '''
114            Configure the system as directed for registration with RHN
115            Raises:
116              * Exception - if error occurs while running command
117        '''
118        args = ['subscription-manager', 'config']
119
120        # Pass supplied **kwargs as parameters to subscription-manager.  Ignore
121        # non-configuration parameters and replace '_' with '.'.  For example,
122        # 'server_hostname' becomes '--system.hostname'.
123        for k, v in kwargs.items():
124            if re.search(r'^(system|rhsm)_', k):
125                args.append('--%s=%s' % (k.replace('_', '.'), v))
126
127        self.module.run_command(args, check_rc=True)
128
129    @property
130    def is_registered(self):
131        '''
132            Determine whether the current system
133            Returns:
134              * Boolean - whether the current system is currently registered to
135                          RHN.
136        '''
137        args = ['subscription-manager', 'identity']
138        rc, stdout, stderr = self.module.run_command(args, check_rc=False)
139        if rc == 0:
140            return True
141        else:
142            return False
143
144    def register(self, username, password, autosubscribe, activationkey):
145        '''
146            Register the current system to the provided RHN server
147            Raises:
148              * Exception - if error occurs while running command
149        '''
150        args = ['subscription-manager', 'register']
151
152        # Generate command arguments
153        if activationkey:
154            args.append('--activationkey "%s"' % activationkey)
155        else:
156            if autosubscribe:
157                args.append('--autosubscribe')
158            if username:
159                args.extend(['--username', username])
160            if password:
161                args.extend(['--password', password])
162
163        # Do the needful...
164        rc, stderr, stdout = self.module.run_command(args, check_rc=True)
165
166    def unsubscribe(self):
167        '''
168            Unsubscribe a system from all subscribed channels
169            Raises:
170              * Exception - if error occurs while running command
171        '''
172        args = ['subscription-manager', 'unsubscribe', '--all']
173        rc, stderr, stdout = self.module.run_command(args, check_rc=True)
174
175    def unregister(self):
176        '''
177            Unregister a currently registered system
178            Raises:
179              * Exception - if error occurs while running command
180        '''
181        args = ['subscription-manager', 'unregister']
182        rc, stderr, stdout = self.module.run_command(args, check_rc=True)
183        self.update_plugin_conf('rhnplugin', False)
184        self.update_plugin_conf('subscription-manager', False)
185
186    def subscribe(self, regexp):
187        '''
188            Subscribe current system to available pools matching the specified
189            regular expression
190            Raises:
191              * Exception - if error occurs while running command
192        '''
193
194        # Available pools ready for subscription
195        available_pools = RhsmPools(self.module)
196
197        for pool in available_pools.filter(regexp):
198            pool.subscribe()
199
200
201class RhsmPool(object):
202    '''
203        Convenience class for housing subscription information
204    '''
205
206    def __init__(self, module, **kwargs):
207        self.module = module
208        for k, v in kwargs.items():
209            setattr(self, k, v)
210
211    def __str__(self):
212        return str(self.__getattribute__('_name'))
213
214    def subscribe(self):
215        args = "subscription-manager subscribe --pool %s" % self.PoolId
216        rc, stdout, stderr = self.module.run_command(args, check_rc=True)
217        if rc == 0:
218            return True
219        else:
220            return False
221
222
223class RhsmPools(object):
224    """
225        This class is used for manipulating pools subscriptions with RHSM
226    """
227    def __init__(self, module):
228        self.module = module
229        self.products = self._load_product_list()
230
231    def __iter__(self):
232        return self.products.__iter__()
233
234    def _load_product_list(self):
235        """
236            Loads list of all available pools for system in data structure
237        """
238        args = "subscription-manager list --available"
239        rc, stdout, stderr = self.module.run_command(args, check_rc=True)
240
241        products = []
242        for line in stdout.split('\n'):
243            # Remove leading+trailing whitespace
244            line = line.strip()
245            # An empty line implies the end of an output group
246            if len(line) == 0:
247                continue
248            # If a colon ':' is found, parse
249            elif ':' in line:
250                (key, value) = line.split(':', 1)
251                key = key.strip().replace(" ", "")  # To unify
252                value = value.strip()
253                if key in ['ProductName', 'SubscriptionName']:
254                    # Remember the name for later processing
255                    products.append(RhsmPool(self.module, _name=value, key=value))
256                elif products:
257                    # Associate value with most recently recorded product
258                    products[-1].__setattr__(key, value)
259                # FIXME - log some warning?
260                # else:
261                    # warnings.warn("Unhandled subscription key/value: %s/%s" % (key,value))
262        return products
263
264    def filter(self, regexp='^$'):
265        '''
266            Return a list of RhsmPools whose name matches the provided regular expression
267        '''
268        r = re.compile(regexp)
269        for product in self.products:
270            if r.search(product._name):
271                yield product
272