This repository has been archived by the owner on Oct 3, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
satprep_patch_freeze.py
executable file
·324 lines (281 loc) · 14.9 KB
/
satprep_patch_freeze.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# satprep_patch_freeze.py - a script for freezing patches
# and errata for managed systems.
#
# 2015 By Christian Stankowic
# <info at stankowic hyphen development dot net>
# https://github.com/stdevel
#
import logging
import pprint
import sys
import xmlrpclib
from optparse import OptionParser, OptionGroup
from satprep_shared import check_if_api_is_supported, get_credentials, is_blacklisted
import datetime
#define globale variables
LOGGER = logging.getLogger('satprep_patch_freeze')
mySystems=[]
myChannels={}
def getChannels(client, key):
#get _all_ the hosts
satGroups=[]
global myChannels
global mySystems
for item in client.systemgroup.listAllGroups(key):
satGroups.append(item["name"])
LOGGER.debug("This Satellite server's groups: '{0}'".format(satGroups))
tempHosts=[]
for host in options.targetSystems:
if len(client.system.getId(key, host)) != 0: tempHosts.append(host)
else: LOGGER.error("System '{0}' appears not to be a valid host".format(host))
for group in options.targetGroups:
if group in satGroups:
groupHosts = client.systemgroup.listSystems(key, group)
for host in groupHosts:
tempHosts.append(host["profile_name"])
LOGGER.debug("Adding system '{0}'".format(host["profile_name"]))
else: LOGGER.error("Group '{0}' appears not to be a valid group".format(group))
#removing blacklisted or hosts without base channel
for host in tempHosts:
hostId = client.system.getId(key, host)
if is_blacklisted(host, options.exclude):
LOGGER.debug("System '{0}' is blacklisted".format(host))
elif len(client.system.getSubscribedBaseChannel(key, hostId[0]["id"])) < 1:
LOGGER.error("System '{0}' has no base channel".format(host))
else:
LOGGER.debug("Adding valid system '{0}'".format(host))
mySystems.append(host)
#list hosts or die in a fire
if len(mySystems) == 0:
LOGGER.info("Nothing to do, giving up!")
sys.exit(1)
LOGGER.debug("Validated hosts:")
for host in mySystems: LOGGER.debug(host)
#get _all_ the software channels
for host in mySystems:
#adding base-channel
LOGGER.debug("Check base-channel for system '{0}'".format(host))
hostId = client.system.getId(key, host)
try:
LOGGER.debug("This system's profile ID: {0}".format(hostId))
baseChannel = client.system.getSubscribedBaseChannel(key, hostId[0]["id"])
cleanBase = baseChannel["label"]
if "." in cleanBase: cleanBase = cleanBase[cleanBase.find(".")+1:]
if cleanBase not in myChannels:
#channel non-present
LOGGER.debug("Adding channel '{0}'".format(cleanBase))
myChannels[cleanBase]=[]
#adding child channels
childChannels = client.system.listSubscribedChildChannels(key, hostId[0]["id"])
for channel in childChannels:
cleanChild = channel["label"]
if "." in cleanChild: cleanChild = cleanChild[cleanChild.find(".")+1:]
if cleanChild not in myChannels[cleanBase]:
LOGGER.debug("Adding child-channel '{0}'".format(cleanChild))
myChannels[cleanBase].append(cleanChild)
#also list non-subscribed channels if wanted
if options.allSubchannels:
childChannels = client.system.listSubscribableChildChannels(key, hostId[0]["id"])
for channel in childChannels:
if cleanChild not in myChannels[cleanBase]:
LOGGER.debug("Adding non-subscribed child-channel '{0}'".format(cleanChild))
myChannels[cleanBase].append(cleanChild)
except:
LOGGER.error("Unable to scan system '{0}', check hostname, profile name and whether a base channel was set!".format(host))
#print channel information
LOGGER.debug("Software channel tree: {0}".format(str(myChannels)))
def cloneChannels(client, key, date, label, unfreeze=False):
if unfreeze:
#remove clones
for channel in myChannels:
#remove child-channels
for child in myChannels[channel]:
if options.dryrun: LOGGER.info("I'd like to remove cloned child-channel '{0}'".format(options.targetLabel+"-"+options.targetDate+"."+child))
else:
try:
LOGGER.info("Deleting child-channel '{0}'".format(options.targetLabel+"-"+options.targetDate+"."+child))
result = client.channel.software.delete(key, options.targetLabel+"-"+options.targetDate+"."+child)
except xmlrpclib.Fault as e:
LOGGER.error("Unable to remove child-channel '{0}': '{1}'".format(options.targetLabel+"-"+options.targetDate+"."+child, e.faultString))
except xmlrpclib.ProtocolError as e:
LOGGER.error("Unable to remove child-channel '{0}': '{1}'".format(options.targetLabel+"-"+options.targetDate+"."+child, e.errmsg))
except:
LOGGER.error("Unable to remove child-channel '{0}'!".format(options.targetLabel+"-"+options.targetDate+"."+child))
#remove base-channel
if options.dryrun: LOGGER.info("I'd like to remove cloned base-channel '{0}'".format(options.targetLabel+"-"+options.targetDate+"."+channel))
else:
try:
LOGGER.info("Deleting base-channel '{0}'".format(options.targetLabel+"-"+options.targetDate+"."+channel))
result = client.channel.software.delete(key, options.targetLabel+"-"+options.targetDate+"."+channel)
except: LOGGER.error("Unable to remove base-channel '{0}'!".format(channel))
return True
#clone channels
for channel in myChannels:
#clone base-channels
myargs={"name" : "Cloned " + channel + " from "+options.targetDate+" ("+options.targetLabel+")", "label" : options.targetLabel+"-"+options.targetDate+"."+channel, "summary" : "Software channel cloned by Satprep"}
if options.dryrun:
LOGGER.info("I'd like to clone base-channel '{0}' as '{1}'".format(channel, options.targetLabel+"-"+options.targetDate+"."+channel))
else:
LOGGER.info("Cloning base-channel '{0}' as '{1}'".format(channel, options.targetLabel+"-"+options.targetDate+"."+channel))
try:
result = client.channel.software.clone(key, channel, myargs, False)
if result != 0: LOGGER.debug("Cloned base-channel")
except xmlrpclib.Fault as e:
LOGGER.error("Unable to clone base-channel: " + e.faultString)
except xmlrpclib.ProtocolError as e:
LOGGER.error("Unable to clone base-channel: " + e.errmsg)
except:
LOGGER.error("Unable to clone base-channel: " + str(sys.exc_info()[0]))
#clone child-channels
for child in myChannels[channel]:
myargs={"name" : "Cloned " + child + " from "+options.targetDate, "label" : options.targetLabel+"-"+options.targetDate+"."+child, "summary" : "Software channel cloned by Satprep", "parent_label": options.targetLabel+"-"+options.targetDate+"."+channel}
if options.dryrun:
LOGGER.info("I'd like to clone child-channel '{0}' as '{1}'".format(child, options.targetLabel+"-"+options.targetDate+"."+child))
else:
LOGGER.info("Cloning child-channel '{0}' as '{1}'".format(child, options.targetLabel+"-"+options.targetDate+"."+child))
try:
result = client.channel.software.clone(key, child, myargs, False)
if result != 0: LOGGER.debug("Cloned child-channel")
except xmlrpclib.Fault as e:
LOGGER.error("Unable to clone base-channel: " + e.faultString)
except xmlrpclib.ProtocolError as e:
LOGGER.error("Unable to clone base-channel: " + e.errmsg)
except:
LOGGER.error("Unable to clone child-channel: " + str(sys.exc_info()[0]))
def remapSystems(client, key, unfreeze=False):
#remap systems
if options.noRemap: LOGGER.info("Not remapping system's channels")
else:
for system in mySystems:
#remap base-channel
hostId = client.system.getId(key, system)
myBase = client.system.getSubscribedBaseChannel(key, hostId[0]["id"])
if options.unfreeze:
myNewBase = myBase["label"]
myNewBase = myNewBase[myNewBase.find(".")+1:]
else: myNewBase = options.targetLabel+"-"+options.targetDate+"."+myBase["label"]
if options.dryrun: LOGGER.info("I'd like to remap {0}'s base-channel from {1} to {2}".format(system, myBase["label"], myNewBase))
else:
try:
LOGGER.debug("Remapping {0}'s base-channel from {1} to {2}".format(system, myBase["label"], myNewBase))
result = client.system.setBaseChannel(key, hostId[0]["id"], myNewBase)
if result == 1: LOGGER.debug("Remapped system")
except xmlrpclib.Fault as e:
LOGGER.error("Unable to change base-channel for system '{0}' - '{1} - {2}'".format(system, e.faultCode, e.faultString))
except: LOGGER.error("Unable to change base-channel for system '{0}' - '{1}'".format(system, str(sys.exc_info()[0])))
#remap child-channels
childChannels = client.system.listSubscribedChildChannels(key, hostId[0]["id"])
tmpChannels=[]
for channel in childChannels:
myNewChannel = channel["label"]
if options.unfreeze:
#switch back to non-cloned
myNewChannel = myNewChannel[myNewChannel.find(".")+1:]
else:
#switch to cloned
myNewChannel = options.targetLabel+"-"+options.targetDate+"."+channel["label"]
tmpChannels.append(myNewChannel)
if options.dryrun: LOGGER.info("I'd like to set the following child-channels for {0}: {1}".format(system, str(tmpChannels)))
else:
try:
LOGGER.debug("Setting child-channels for {0}: {1}".format(system, str(tmpChannels)))
result = client.system.setChildChannels(key, hostId[0]["id"], tmpChannels)
except xmlrpclib.Fault as e:
#ignore retarded xmlrpclib.Fault as it works like a charm
pass
except:
LOGGER.error("Unable to set child-channels ({0}) for '{1}' - '{2}'".format(str(tmpChannels), system, sys.exc_info()[0]))
del tmpChannels
def main(options):
#check/set some necessary information
if len(options.targetSystems) == 0 and len(options.targetGroups) == 0:
LOGGER.error("You need to specify at least one system or system group!")
exit(1)
if options.targetDate == "wingardiumleviosa":
#set current date
now = datetime.datetime.now()
options.targetDate = now.strftime("%Y-%m-%d")
LOGGER.debug("Flicked date to: " + now.strftime("%Y-%m-%d"))
#split label, systems and groups
options.targetLabel = ''.join(options.targetLabel.split()).strip("-").lower()
if not "sp" in options.targetLabel: options.targetLabel = "sp-"+options.targetLabel
if len(options.targetSystems) == 1: options.targetSystems = str(options.targetSystems).strip("[]'").split(",")
if len(options.targetGroups) == 1: options.targetGroups = str(options.targetGroups).strip("[]'").split(",")
if len(options.exclude) == 1: options.exclude = str(options.exclude).strip("[]'").split(",")
LOGGER.debug("Options: {0}".format(options))
LOGGER.debug("Args: {0}".format(args))
#authenticate against Satellite and check whether supported API found
(username, password) = get_credentials("Satellite", options.authfile)
satellite_url = "http://{0}/rpc/api".format(options.server)
client = xmlrpclib.Server(satellite_url, verbose=options.debug)
key = client.auth.login(username, password)
check_if_api_is_supported(client)
#get channels
getChannels(client, key)
if options.unfreeze:
remapSystems(client, key, True)
cloneChannels(client, key, options.targetDate, options.targetLabel, True)
else:
cloneChannels(client, key, options.targetDate, options.targetLabel)
remapSystems(client, key)
def parse_options(args=None):
if args is None:
args = sys.argv
#define description, version and load parser
desc = '''%prog is used to clone software channels managed with Spacewalk, Red Hat Satellite 5.x and SUSE Manager to freeze system updates. It automatically clones appropriate software channels for particular systems or system groups and also remaps software channels to affected hosts. Login credentials are assigned using the following shell variables:
SATELLITE_LOGIN username
SATELLITE_PASSWORD password
It is also possible to create an authfile (permissions 0600) for usage with this script. The first line needs to contain the username, the second line should consist of the appropriate password.
If you're not defining variables or an authfile you will be prompted to enter your login information.
Checkout the GitHub page for updates: https://github.com/stdevel/satprep'''
parser = OptionParser(description=desc, version="%prog version 0.3.6")
#define option groups
genOpts = OptionGroup(parser, "Generic Options")
srvOpts = OptionGroup(parser, "Server Options")
sysOpts = OptionGroup(parser, "System Options")
chnOpts = OptionGroup(parser, "Channel Options")
parser.add_option_group(genOpts)
parser.add_option_group(srvOpts)
parser.add_option_group(sysOpts)
parser.add_option_group(chnOpts)
#GENERIC OPTIONS
#-d / --debug
genOpts.add_option("-d", "--debug", dest="debug", default=False, action="store_true", help="enable debugging outputs (default: no)")
#-n / --dry-run
genOpts.add_option("-n", "--dry-run", action="store_true", dest="dryrun", default=False, help="only simulates the creation of custom keys (default: no)")
#-u / --unfreeze
genOpts.add_option("-u", "--unfreeze", action="store_true", dest="unfreeze", default=False, help="removes clones and remaps systems (default: no)")
#SERVER OPTIONS
#-a / --authfile
srvOpts.add_option("-a", "--authfile", dest="authfile", metavar="FILE", default="", help="defines an auth file to use instead of shell variables")
#-s / --server
srvOpts.add_option("-s", "--server", dest="server", metavar="SERVER", default="localhost", help="defines the server to use (default: localhost)")
#SYSTEM OPTIONS
#-S / --system
sysOpts.add_option("-S", "--system", action="append", dest="targetSystems", metavar="SYSTEM", type="string", default=[], help="specifies a system to use for freezing patches")
#-g / --group
sysOpts.add_option("-g", "--group", action="append", dest="targetGroups", metavar="GROUP", type="string", default=[], help="specifies a system group to use for freezing patches")
#-e / --exclude
sysOpts.add_option("-e", "--exclude", action="append", dest="exclude", metavar="SYSTEM", type="string", default=[], help="defines hosts that should be excluded for freezing patches")
#-i / --no-remap
sysOpts.add_option("-i", "--no-remap", action="store_true", dest="noRemap", default=False, help="disables remapping affected systems to cloned channels (default: no)")
#CHANNEL OPTIONS
#-A / --all-subchannels
chnOpts.add_option("-A", "--all-subchannels", action="store_true", dest="allSubchannels", default=False, help="clones all sub-channels instead of only required ones (default: no)")
#-l / --label
chnOpts.add_option("-l", "--label", action="store", dest="targetLabel", metavar="LABEL", default="sp", help="defines a label for the cloned channel (e.g. application name)")
#-D / --date
chnOpts.add_option("-D", "--date", action="store", dest="targetDate", metavar="DATE", default="wingardiumleviosa", help="defines the date patches should be freezed (default: current date)")
(options, args) = parser.parse_args(args)
return (options, args)
if __name__ == "__main__":
(options, args) = parse_options()
if options.debug:
logging.basicConfig(level=logging.DEBUG)
LOGGER.setLevel(logging.DEBUG)
else:
logging.basicConfig()
LOGGER.setLevel(logging.INFO)
main(options)