3
# Copyright (C) 2015 Red Hat, Inc.
5
# Author: Brent Baude <bbaude@redhat.com>
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.
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.
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/>.
19
from cloudinit import util
22
def handle(name, cfg, _cloud, log, _args):
23
sm = SubscriptionManager(cfg)
25
if not sm.is_configured():
26
log.debug("%s: module not configured.", name)
29
if not sm.is_registered():
31
verify, verify_msg = sm._verify_keys()
32
if verify is not True:
33
raise SubscriptionError(verify_msg)
34
cont = sm.rhn_register()
36
raise SubscriptionError("Registration failed or did not "
39
# Splitting up the registration, auto-attach, and servicelevel
40
# commands because the error codes, messages from subman are not
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 "
49
sm.log.debug("Completed auto-attach with service level")
51
if not sm._set_auto_attach():
52
raise SubscriptionError("Setting auto-attach failed")
54
sm.log.debug("Completed auto-attach")
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)
61
return_stat = sm.addPool(sm.pools)
63
raise SubscriptionError("Unable to attach pools {0}"
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)
68
raise SubscriptionError("Unable to add or remove repos")
69
sm.log_success("rh_subscription plugin completed successfully")
70
except SubscriptionError as e:
72
sm.log_warn("rh_subscription plugin did not complete successfully")
74
sm.log_success("System is already registered")
77
class SubscriptionError(Exception):
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']
87
def __init__(self, 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']
103
def log_success(self, msg):
104
'''Simple wrapper for logging info messages. Useful for unittests'''
107
def log_warn(self, msg):
108
'''Simple wrapper for logging warning messages. Useful for unittests'''
111
def _verify_keys(self):
113
Checks that the keys in the rh_subscription dict from the user-data
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. "\
121
"{1}".format(k, ', '.join(self.valid_rh_keys))
122
return False, bad_key
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 "\
130
return False, not_bool
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 "
137
return False, no_auto
140
def is_registered(self):
142
Checks if the system is already registered and returns
143
True if so, else False
148
self._sub_man_cli(cmd)
149
except util.ProcessExecutionError:
154
def _sub_man_cli(self, cmd, logstring_val=False):
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
160
cmd = self.subman + cmd
161
return util.subp(cmd, logstring=logstring_val)
163
def rhn_register(self):
165
Registers the system by userid and password or activation key
166
and org. Returns True when successful False when not.
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)]
174
# If the baseurl and/or server url are passed in, we register
177
if self.rhsm_baseurl is not None:
178
cmd.append("--baseurl={0}".format(self.rhsm_baseurl))
180
if self.server_hostname is not None:
181
cmd.append("--serverurl={0}".format(self.server_hostname))
184
return_out, return_err = self._sub_man_cli(cmd,
186
except util.ProcessExecutionError as e:
188
self.log_warn("Registration failed due "
189
"to: {0}".format(e.stderr))
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)]
197
# If the baseurl and/or server url are passed in, we register
200
if self.rhsm_baseurl is not None:
201
cmd.append("--baseurl={0}".format(self.rhsm_baseurl))
203
if self.server_hostname is not None:
204
cmd.append("--serverurl={0}".format(self.server_hostname))
206
# Attempting to register the system only
208
return_out, return_err = self._sub_man_cli(cmd,
210
except util.ProcessExecutionError as e:
212
self.log_warn("Registration failed due "
213
"to: {0}".format(e.stderr))
217
self.log_warn("Unable to register system due to incomplete "
219
self.log_warn("Use either activationkey and org *or* userid "
223
reg_id = return_out.split("ID: ")[1].rstrip()
224
self.log.debug("Registered successfully with ID {0}".format(reg_id))
227
def _set_service_level(self):
228
cmd = ['attach', '--auto', '--servicelevel={0}'
229
.format(self.servicelevel)]
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"):
239
self.log_warn("Setting the service level failed with: "
240
"{0}".format(e.stderr.strip()))
242
for line in return_out.split("\n"):
247
def _set_auto_attach(self):
248
cmd = ['attach', '--auto']
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()))
255
for line in return_out.split("\n"):
262
Gets the list pools for the active subscription and returns them
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")
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")
278
return available, consumed
282
Obtains the current list of active yum repositories and returns
286
cmd = ['repos', '--list-enabled']
287
return_out, return_err = self._sub_man_cli(cmd)
289
for repo in return_out.split("\n"):
290
if "Repo ID:" in repo:
291
active_repos.append((repo.split(':')[1]).strip())
293
cmd = ['repos', '--list-disabled']
294
return_out, return_err = self._sub_man_cli(cmd)
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
302
def addPool(self, pools):
304
Takes a list of subscription pools and "attaches" them to the
308
# An empty list was passed
310
self.log.debug("No pools to attach")
313
pool_available, pool_consumed = self._getPools()
317
if (pool not in pool_consumed) and (pool in pool_available):
318
pool_list.append('--pool={0}'.format(pool))
320
self.log_warn("Pool {0} is not available".format(pool))
321
if len(pool_list) > 0:
322
cmd.extend(pool_list)
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=', ''))
329
except util.ProcessExecutionError as e:
330
self.log_warn("Unable to attach pool {0} "
331
"due to {1}".format(pool, e))
334
def update_repos(self, erepos, drepos):
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
341
if (erepos is not None) and (not isinstance(erepos, list)):
342
self.log_warn("Repo IDs must in the format of a list.")
345
if (drepos is not None) and (not isinstance(drepos, list)):
346
self.log_warn("Repo IDs must in the format of a list.")
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")
354
active_repos, inactive_repos = self._getRepos()
355
# Creating a list of repoids to be enabled
357
enable_list_fail = []
358
for repoid in erepos:
359
if (repoid in inactive_repos):
360
enable_list.append("--enable={0}".format(repoid))
362
enable_list_fail.append(repoid)
364
# Creating a list of repoids to be disabled
366
disable_list_fail = []
367
for repoid in drepos:
368
if repoid in active_repos:
369
disable_list.append("--disable={0}".format(repoid))
371
disable_list_fail.append(repoid)
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))
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))
388
if len(enable_list) > 0:
389
cmd.extend(enable_list)
390
if len(disable_list) > 0:
391
cmd.extend(disable_list)
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))
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=', ''))
407
def is_configured(self):
408
return bool((self.userid and self.password) or self.activation_key)