~cloud-init-dev/cloud-init/trunk

« back to all changes in this revision

Viewing changes to cloudinit/config/cc_rh_subscription.py

  • Committer: Scott Moser
  • Date: 2016-08-10 15:06:15 UTC
  • Revision ID: smoser@ubuntu.com-20160810150615-ma2fv107w3suy1ma
README: Mention move of revision control to git.

cloud-init development has moved its revision control to git.
It is available at 
  https://code.launchpad.net/cloud-init

Clone with 
  git clone https://git.launchpad.net/cloud-init
or
  git clone git+ssh://git.launchpad.net/cloud-init

For more information see
  https://git.launchpad.net/cloud-init/tree/HACKING.rst

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vi: ts=4 expandtab
2
 
#
3
 
#    Copyright (C) 2015 Red Hat, Inc.
4
 
#
5
 
#    Author: Brent Baude <bbaude@redhat.com>
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU General Public License version 3, as
9
 
#    published by the Free Software Foundation.
10
 
#
11
 
#    This program is distributed in the hope that it will be useful,
12
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
#    GNU General Public License for more details.
15
 
#
16
 
#    You should have received a copy of the GNU General Public License
17
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 
 
19
 
from cloudinit import util
20
 
 
21
 
 
22
 
def handle(name, cfg, _cloud, log, _args):
23
 
    sm = SubscriptionManager(cfg)
24
 
    sm.log = log
25
 
    if not sm.is_configured():
26
 
        log.debug("%s: module not configured.", name)
27
 
        return None
28
 
 
29
 
    if not sm.is_registered():
30
 
        try:
31
 
            verify, verify_msg = sm._verify_keys()
32
 
            if verify is not True:
33
 
                raise SubscriptionError(verify_msg)
34
 
            cont = sm.rhn_register()
35
 
            if not cont:
36
 
                raise SubscriptionError("Registration failed or did not "
37
 
                                        "run completely")
38
 
 
39
 
            # Splitting up the registration, auto-attach, and servicelevel
40
 
            # commands because the error codes, messages from subman are not
41
 
            # specific enough.
42
 
 
43
 
            # Attempt to change the service level
44
 
            if sm.auto_attach and sm.servicelevel is not None:
45
 
                if not sm._set_service_level():
46
 
                    raise SubscriptionError("Setting of service-level "
47
 
                                            "failed")
48
 
                else:
49
 
                    sm.log.debug("Completed auto-attach with service level")
50
 
            elif sm.auto_attach:
51
 
                if not sm._set_auto_attach():
52
 
                    raise SubscriptionError("Setting auto-attach failed")
53
 
                else:
54
 
                    sm.log.debug("Completed auto-attach")
55
 
 
56
 
            if sm.pools is not None:
57
 
                if not isinstance(sm.pools, list):
58
 
                    pool_fail = "Pools must in the format of a list"
59
 
                    raise SubscriptionError(pool_fail)
60
 
 
61
 
                return_stat = sm.addPool(sm.pools)
62
 
                if not return_stat:
63
 
                    raise SubscriptionError("Unable to attach pools {0}"
64
 
                                            .format(sm.pools))
65
 
            if (sm.enable_repo is not None) or (sm.disable_repo is not None):
66
 
                return_stat = sm.update_repos(sm.enable_repo, sm.disable_repo)
67
 
                if not return_stat:
68
 
                    raise SubscriptionError("Unable to add or remove repos")
69
 
            sm.log_success("rh_subscription plugin completed successfully")
70
 
        except SubscriptionError as e:
71
 
            sm.log_warn(str(e))
72
 
            sm.log_warn("rh_subscription plugin did not complete successfully")
73
 
    else:
74
 
        sm.log_success("System is already registered")
75
 
 
76
 
 
77
 
class SubscriptionError(Exception):
78
 
    pass
79
 
 
80
 
 
81
 
class SubscriptionManager(object):
82
 
    valid_rh_keys = ['org', 'activation-key', 'username', 'password',
83
 
                     'disable-repo', 'enable-repo', 'add-pool',
84
 
                     'rhsm-baseurl', 'server-hostname',
85
 
                     'auto-attach', 'service-level']
86
 
 
87
 
    def __init__(self, cfg):
88
 
        self.cfg = cfg
89
 
        self.rhel_cfg = self.cfg.get('rh_subscription', {})
90
 
        self.rhsm_baseurl = self.rhel_cfg.get('rhsm-baseurl')
91
 
        self.server_hostname = self.rhel_cfg.get('server-hostname')
92
 
        self.pools = self.rhel_cfg.get('add-pool')
93
 
        self.activation_key = self.rhel_cfg.get('activation-key')
94
 
        self.org = self.rhel_cfg.get('org')
95
 
        self.userid = self.rhel_cfg.get('username')
96
 
        self.password = self.rhel_cfg.get('password')
97
 
        self.auto_attach = self.rhel_cfg.get('auto-attach')
98
 
        self.enable_repo = self.rhel_cfg.get('enable-repo')
99
 
        self.disable_repo = self.rhel_cfg.get('disable-repo')
100
 
        self.servicelevel = self.rhel_cfg.get('service-level')
101
 
        self.subman = ['subscription-manager']
102
 
 
103
 
    def log_success(self, msg):
104
 
        '''Simple wrapper for logging info messages. Useful for unittests'''
105
 
        self.log.info(msg)
106
 
 
107
 
    def log_warn(self, msg):
108
 
        '''Simple wrapper for logging warning messages. Useful for unittests'''
109
 
        self.log.warn(msg)
110
 
 
111
 
    def _verify_keys(self):
112
 
        '''
113
 
        Checks that the keys in the rh_subscription dict from the user-data
114
 
        are what we expect.
115
 
        '''
116
 
 
117
 
        for k in self.rhel_cfg:
118
 
            if k not in self.valid_rh_keys:
119
 
                bad_key = "{0} is not a valid key for rh_subscription. "\
120
 
                          "Valid keys are: "\
121
 
                          "{1}".format(k, ', '.join(self.valid_rh_keys))
122
 
                return False, bad_key
123
 
 
124
 
        # Check for bad auto-attach value
125
 
        if (self.auto_attach is not None) and \
126
 
                not (util.is_true(self.auto_attach) or
127
 
                     util.is_false(self.auto_attach)):
128
 
            not_bool = "The key auto-attach must be a boolean value "\
129
 
                       "(True/False "
130
 
            return False, not_bool
131
 
 
132
 
        if (self.servicelevel is not None) and ((not self.auto_attach) or
133
 
           (util.is_false(str(self.auto_attach)))):
134
 
            no_auto = ("The service-level key must be used in conjunction "
135
 
                       "with the auto-attach key.  Please re-run with "
136
 
                       "auto-attach: True")
137
 
            return False, no_auto
138
 
        return True, None
139
 
 
140
 
    def is_registered(self):
141
 
        '''
142
 
        Checks if the system is already registered and returns
143
 
        True if so, else False
144
 
        '''
145
 
        cmd = ['identity']
146
 
 
147
 
        try:
148
 
            self._sub_man_cli(cmd)
149
 
        except util.ProcessExecutionError:
150
 
            return False
151
 
 
152
 
        return True
153
 
 
154
 
    def _sub_man_cli(self, cmd, logstring_val=False):
155
 
        '''
156
 
        Uses the prefered cloud-init subprocess def of util.subp
157
 
        and runs subscription-manager.  Breaking this to a
158
 
        separate function for later use in mocking and unittests
159
 
        '''
160
 
        cmd = self.subman + cmd
161
 
        return util.subp(cmd, logstring=logstring_val)
162
 
 
163
 
    def rhn_register(self):
164
 
        '''
165
 
        Registers the system by userid and password or activation key
166
 
        and org.  Returns True when successful False when not.
167
 
        '''
168
 
 
169
 
        if (self.activation_key is not None) and (self.org is not None):
170
 
            # register by activation key
171
 
            cmd = ['register', '--activationkey={0}'.
172
 
                   format(self.activation_key), '--org={0}'.format(self.org)]
173
 
 
174
 
            # If the baseurl and/or server url are passed in, we register
175
 
            # with them.
176
 
 
177
 
            if self.rhsm_baseurl is not None:
178
 
                cmd.append("--baseurl={0}".format(self.rhsm_baseurl))
179
 
 
180
 
            if self.server_hostname is not None:
181
 
                cmd.append("--serverurl={0}".format(self.server_hostname))
182
 
 
183
 
            try:
184
 
                return_out, return_err = self._sub_man_cli(cmd,
185
 
                                                           logstring_val=True)
186
 
            except util.ProcessExecutionError as e:
187
 
                if e.stdout == "":
188
 
                    self.log_warn("Registration failed due "
189
 
                                  "to: {0}".format(e.stderr))
190
 
                return False
191
 
 
192
 
        elif (self.userid is not None) and (self.password is not None):
193
 
            # register by username and password
194
 
            cmd = ['register', '--username={0}'.format(self.userid),
195
 
                   '--password={0}'.format(self.password)]
196
 
 
197
 
            # If the baseurl and/or server url are passed in, we register
198
 
            # with them.
199
 
 
200
 
            if self.rhsm_baseurl is not None:
201
 
                cmd.append("--baseurl={0}".format(self.rhsm_baseurl))
202
 
 
203
 
            if self.server_hostname is not None:
204
 
                cmd.append("--serverurl={0}".format(self.server_hostname))
205
 
 
206
 
            # Attempting to register the system only
207
 
            try:
208
 
                return_out, return_err = self._sub_man_cli(cmd,
209
 
                                                           logstring_val=True)
210
 
            except util.ProcessExecutionError as e:
211
 
                if e.stdout == "":
212
 
                    self.log_warn("Registration failed due "
213
 
                                  "to: {0}".format(e.stderr))
214
 
                return False
215
 
 
216
 
        else:
217
 
            self.log_warn("Unable to register system due to incomplete "
218
 
                          "information.")
219
 
            self.log_warn("Use either activationkey and org *or* userid "
220
 
                          "and password")
221
 
            return False
222
 
 
223
 
        reg_id = return_out.split("ID: ")[1].rstrip()
224
 
        self.log.debug("Registered successfully with ID {0}".format(reg_id))
225
 
        return True
226
 
 
227
 
    def _set_service_level(self):
228
 
        cmd = ['attach', '--auto', '--servicelevel={0}'
229
 
               .format(self.servicelevel)]
230
 
 
231
 
        try:
232
 
            return_out, return_err = self._sub_man_cli(cmd)
233
 
        except util.ProcessExecutionError as e:
234
 
            if e.stdout.rstrip() != '':
235
 
                for line in e.stdout.split("\n"):
236
 
                    if line is not '':
237
 
                        self.log_warn(line)
238
 
            else:
239
 
                self.log_warn("Setting the service level failed with: "
240
 
                              "{0}".format(e.stderr.strip()))
241
 
            return False
242
 
        for line in return_out.split("\n"):
243
 
            if line is not "":
244
 
                self.log.debug(line)
245
 
        return True
246
 
 
247
 
    def _set_auto_attach(self):
248
 
        cmd = ['attach', '--auto']
249
 
        try:
250
 
            return_out, return_err = self._sub_man_cli(cmd)
251
 
        except util.ProcessExecutionError:
252
 
            self.log_warn("Auto-attach failed with: "
253
 
                          "{0}]".format(return_err.strip()))
254
 
            return False
255
 
        for line in return_out.split("\n"):
256
 
            if line is not "":
257
 
                self.log.debug(line)
258
 
        return True
259
 
 
260
 
    def _getPools(self):
261
 
        '''
262
 
        Gets the list pools for the active subscription and returns them
263
 
        in list form.
264
 
        '''
265
 
        available = []
266
 
        consumed = []
267
 
 
268
 
        # Get all available pools
269
 
        cmd = ['list', '--available', '--pool-only']
270
 
        results, errors = self._sub_man_cli(cmd)
271
 
        available = (results.rstrip()).split("\n")
272
 
 
273
 
        # Get all consumed pools
274
 
        cmd = ['list', '--consumed', '--pool-only']
275
 
        results, errors = self._sub_man_cli(cmd)
276
 
        consumed = (results.rstrip()).split("\n")
277
 
 
278
 
        return available, consumed
279
 
 
280
 
    def _getRepos(self):
281
 
        '''
282
 
        Obtains the current list of active yum repositories and returns
283
 
        them in list form.
284
 
        '''
285
 
 
286
 
        cmd = ['repos', '--list-enabled']
287
 
        return_out, return_err = self._sub_man_cli(cmd)
288
 
        active_repos = []
289
 
        for repo in return_out.split("\n"):
290
 
            if "Repo ID:" in repo:
291
 
                active_repos.append((repo.split(':')[1]).strip())
292
 
 
293
 
        cmd = ['repos', '--list-disabled']
294
 
        return_out, return_err = self._sub_man_cli(cmd)
295
 
 
296
 
        inactive_repos = []
297
 
        for repo in return_out.split("\n"):
298
 
            if "Repo ID:" in repo:
299
 
                inactive_repos.append((repo.split(':')[1]).strip())
300
 
        return active_repos, inactive_repos
301
 
 
302
 
    def addPool(self, pools):
303
 
        '''
304
 
        Takes a list of subscription pools and "attaches" them to the
305
 
        current subscription
306
 
        '''
307
 
 
308
 
        # An empty list was passed
309
 
        if len(pools) == 0:
310
 
            self.log.debug("No pools to attach")
311
 
            return True
312
 
 
313
 
        pool_available, pool_consumed = self._getPools()
314
 
        pool_list = []
315
 
        cmd = ['attach']
316
 
        for pool in pools:
317
 
            if (pool not in pool_consumed) and (pool in pool_available):
318
 
                pool_list.append('--pool={0}'.format(pool))
319
 
            else:
320
 
                self.log_warn("Pool {0} is not available".format(pool))
321
 
        if len(pool_list) > 0:
322
 
            cmd.extend(pool_list)
323
 
            try:
324
 
                self._sub_man_cli(cmd)
325
 
                self.log.debug("Attached the following pools to your "
326
 
                               "system: %s" % (", ".join(pool_list))
327
 
                               .replace('--pool=', ''))
328
 
                return True
329
 
            except util.ProcessExecutionError as e:
330
 
                self.log_warn("Unable to attach pool {0} "
331
 
                              "due to {1}".format(pool, e))
332
 
                return False
333
 
 
334
 
    def update_repos(self, erepos, drepos):
335
 
        '''
336
 
        Takes a list of yum repo ids that need to be disabled or enabled; then
337
 
        it verifies if they are already enabled or disabled and finally
338
 
        executes the action to disable or enable
339
 
        '''
340
 
 
341
 
        if (erepos is not None) and (not isinstance(erepos, list)):
342
 
            self.log_warn("Repo IDs must in the format of a list.")
343
 
            return False
344
 
 
345
 
        if (drepos is not None) and (not isinstance(drepos, list)):
346
 
            self.log_warn("Repo IDs must in the format of a list.")
347
 
            return False
348
 
 
349
 
        # Bail if both lists are not populated
350
 
        if (len(erepos) == 0) and (len(drepos) == 0):
351
 
            self.log.debug("No repo IDs to enable or disable")
352
 
            return True
353
 
 
354
 
        active_repos, inactive_repos = self._getRepos()
355
 
        # Creating a list of repoids to be enabled
356
 
        enable_list = []
357
 
        enable_list_fail = []
358
 
        for repoid in erepos:
359
 
            if (repoid in inactive_repos):
360
 
                enable_list.append("--enable={0}".format(repoid))
361
 
            else:
362
 
                enable_list_fail.append(repoid)
363
 
 
364
 
        # Creating a list of repoids to be disabled
365
 
        disable_list = []
366
 
        disable_list_fail = []
367
 
        for repoid in drepos:
368
 
            if repoid in active_repos:
369
 
                disable_list.append("--disable={0}".format(repoid))
370
 
            else:
371
 
                disable_list_fail.append(repoid)
372
 
 
373
 
        # Logging any repos that are already enabled or disabled
374
 
        if len(enable_list_fail) > 0:
375
 
            for fail in enable_list_fail:
376
 
                # Check if the repo exists or not
377
 
                if fail in active_repos:
378
 
                    self.log.debug("Repo {0} is already enabled".format(fail))
379
 
                else:
380
 
                    self.log_warn("Repo {0} does not appear to "
381
 
                                  "exist".format(fail))
382
 
        if len(disable_list_fail) > 0:
383
 
            for fail in disable_list_fail:
384
 
                self.log.debug("Repo {0} not disabled "
385
 
                               "because it is not enabled".format(fail))
386
 
 
387
 
        cmd = ['repos']
388
 
        if len(enable_list) > 0:
389
 
            cmd.extend(enable_list)
390
 
        if len(disable_list) > 0:
391
 
            cmd.extend(disable_list)
392
 
 
393
 
        try:
394
 
            self._sub_man_cli(cmd)
395
 
        except util.ProcessExecutionError as e:
396
 
            self.log_warn("Unable to alter repos due to {0}".format(e))
397
 
            return False
398
 
 
399
 
        if len(enable_list) > 0:
400
 
            self.log.debug("Enabled the following repos: %s" %
401
 
                           (", ".join(enable_list)).replace('--enable=', ''))
402
 
        if len(disable_list) > 0:
403
 
            self.log.debug("Disabled the following repos: %s" %
404
 
                           (", ".join(disable_list)).replace('--disable=', ''))
405
 
        return True
406
 
 
407
 
    def is_configured(self):
408
 
        return bool((self.userid and self.password) or self.activation_key)