Skip to content
This repository was archived by the owner on Oct 3, 2020. It is now read-only.

Commit 9e478d5

Browse files
Christian StankowicChristian Stankowic
authored andcommitted
Release 0.3.5
Fixed several bugs: - custom info key values are now escaped to avoid aborts (added shared function escape_string; see issue #49) - when cloning channels, labels are added to the name (see issue #47) - changed name format to „label-date.channel“ when cloning channels to avoid using protected names (see issues #46 and #48) - fixed a bug to avoid scheduling downtimes for non-monitored hosts - fixed a bug where VM snapshots were not created - fixed a bug where wrong reboot_required information were inserted into patch reports - fixed a bug where diff reports could not be created (see issue #44) - verification log files are created if non-existent - added support for Spacewalk 2.4
1 parent 2995a0d commit 9e478d5

7 files changed

+76
-70
lines changed

satprep_diff.py

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
#define logger
2525
LOGGER = logging.getLogger('satprep_diff')
26-
vlogInvalid=False
2726

2827
#TODO: delete original snapshots after creating delta option
2928
#TODO: use pre-existing delta CSV instead of creating one (e.g. for testing purposes)
@@ -42,7 +41,7 @@ def parse_options(args=None):
4241
desc='''%prog is used to create patch diff reports of systems managed with Spacewalk, Red Hat Satellite and SUSE Manager. The script needs TeXlive/LaTeX to create PDF reports. Defining your own templates is possible - the default template needs to be located in the same directory like this script.
4342
4443
Checkout the GitHub page for updates: https://github.com/stdevel/satprep'''
45-
parser = OptionParser(usage=usage, description=desc, version="%prog version 0.3.4")
44+
parser = OptionParser(usage=usage, description=desc, version="%prog version 0.3.5")
4645
#define option groups
4746
genOpts = OptionGroup(parser, "Generic Options")
4847
repOpts = OptionGroup(parser, "Report Options")
@@ -86,8 +85,6 @@ def parse_options(args=None):
8685

8786

8887
def main(options):
89-
global vlogInvalid
90-
9188
#define folder of this script
9289
thisFolder = os.path.dirname(os.path.realpath(__file__))
9390

@@ -161,14 +158,7 @@ def main(options):
161158
this_date = datetime.datetime.fromtimestamp(os.path.getmtime(args[1])).strftime('%Y-%m-%d')
162159
#set vlog
163160
if options.verificationLog == "":
164-
if os.access(datetime.datetime.fromtimestamp(os.path.getmtime(args[0])).strftime('%Y%m%d')+"_satprep.vlog", os.W_OK):
165-
options.verificationLog = datetime.datetime.fromtimestamp(os.path.getmtime(args[0])).strftime('%Y%m%d')+"_satprep.vlog"
166-
elif os.access(options.verificationLog, os.W_OK) != True:
167-
if os.access(datetime.datetime.fromtimestamp(os.path.getmtime(args[0])).strftime('%Y%m%d')+"_satprep.vlog", os.W_OK):
168-
options.verificationLog = datetime.datetime.fromtimestamp(os.path.getmtime(args[0])).strftime('%Y%m%d')+"_satprep.vlog"
169-
else: options.verificationLog = ""
170-
if options.verificationLog != "": LOGGER.info(options.verificationLog + " seems to be our verification log")
171-
else: LOGGER.info("Snapshot and monitoring checkboxes won't be pre-selected as we don't have a valid .vlog!")
161+
options.verificationLog = datetime.datetime.fromtimestamp(os.path.getmtime(args[0])).strftime('%Y%m%d')+"_satprep.vlog"
172162
else:
173163
#file2 is bigger
174164
LOGGER.info("Assuming file2 ('"+args[1]+"') is the first snapshot.")
@@ -180,23 +170,15 @@ def main(options):
180170
this_date = datetime.datetime.fromtimestamp(os.path.getmtime(args[0])).strftime('%Y-%m-%d')
181171
#set vlog
182172
if options.verificationLog == "":
183-
if os.access(datetime.datetime.fromtimestamp(os.path.getmtime(args[1])).strftime('%Y%m%d')+"_satprep.vlog", os.W_OK):
184-
options.verificationLog = datetime.datetime.fromtimestamp(os.path.getmtime(args[1])).strftime('%Y%m%d')+"_satprep.vlog"
185-
elif os.access(options.verificationLog, os.W_OK) != True:
186-
if os.access(datetime.datetime.fromtimestamp(os.path.getmtime(args[1])).strftime('%Y%m%d')+"_satprep.vlog", os.W_OK):
187-
options.verificationLog = datetime.datetime.fromtimestamp(os.path.getmtime(args[1])).strftime('%Y%m%d')+"_satprep.vlog"
188-
else: options.verificationLog = ""
189-
if options.verificationLog != "": LOGGER.info(options.verificationLog + " seems to be our verification log")
190-
else:
191-
LOGGER.info("Snapshot and monitoring checkboxes won't be pre-selected as we don't have a valid .vlog!")
192-
vlogInvalid=True
173+
options.verificationLog = datetime.datetime.fromtimestamp(os.path.getmtime(args[1])).strftime('%Y%m%d')+"_satprep.vlog"
193174

194175
#read vlog
195-
if vlogInvalid == False:
176+
if os.path.exists(options.verificationLog):
196177
f_log = open(options.verificationLog, 'r')
197-
vlog = f_log.read()
198-
LOGGER.debug("vlog is:\n" + vlog)
199-
else: vlog=""
178+
else:
179+
f_log = open(options.verificationLog, 'w+')
180+
vlog = f_log.read()
181+
LOGGER.debug("vlog is:\n" + vlog)
200182

201183
#create delta
202184
delta = ''.join(x[2:] for x in diff if x.startswith('- '))
@@ -249,9 +231,11 @@ def main(options):
249231
for host in hosts:
250232
#scan imported data
251233

252-
#flag for NoReboot Box and notes
234+
#pre-setting some variables
253235
this_NoReboot="$\Box$"
254236
this_RebootNotes=""
237+
this_monYes="$\Box$"
238+
this_monNo="$\Box$"
255239

256240
#LaTeX line including the the errata-relevant columns for this host
257241
thisColDescriptor=""
@@ -338,14 +322,13 @@ def main(options):
338322
if "@" in tempHost: tempHost = tempHost[:tempHost.find("@")]
339323
else: tempHost = host
340324
if "MONOK;"+host in vlog:
341-
LOGGER.debug("MONOK;"+tempHost + " in vlog!")
325+
LOGGER.debug("MONOK;"+tempHost+" in vlog!")
342326
this_monYes = "$\CheckedBox$"
343327
this_monNo = "$\Box$"
344328
else:
345-
LOGGER.debug("MONOK;"+tempHost + " NOT in vlog!")
329+
LOGGER.debug("MONOK;"+tempHost+" NOT in vlog!")
346330
this_monYes = "$\Box$"
347-
if vlogInvalid == False: this_monNo = "$\CheckedBox$"
348-
else: this_monNo = "$\Box$"
331+
this_monNo = "$\CheckedBox$"
349332
if repcols["system_monitoring_notes"] < 666 and len(line[repcols["system_monitoring_notes"]]) > 1:
350333
this_monNotes = line[repcols["system_monitoring_notes"]]
351334
else: this_monNotes = ""
@@ -392,19 +375,14 @@ def main(options):
392375
else:
393376
LOGGER.debug("SNAPOK;"+tempHost + " NOT in vlog!")
394377
this_vmSnapYes = "$\Box$"
395-
if vlogInvalid == False: this_vmSnapNo = "$\CheckedBox$"
378+
this_vmSnapNo = "$\CheckedBox$"
396379
else:
397380
#set boxes and notes
398381
this_hwCheckNo = "$\Box$"
399382
this_vmSnapNo = "$\CheckedBox$"
400383
this_hwCheckNotes = ""
401384
this_vmSnapNotes ="not a virtual machine"
402385

403-
#set reboot box if specified and present in report
404-
if repcols["errata_reboot"] < 666 and line[repcols["errata_reboot"]] != "reboot_suggested":
405-
this_NoReboot="$\CheckedBox$"
406-
this_RebootNotes="no reboot required"
407-
408386
#set errata information
409387
if repcols["errata_name"] < 666 and line[repcols['errata_name']] != "":
410388
this_errata_name.append(line[repcols["errata_name"]])
@@ -416,6 +394,13 @@ def main(options):
416394
this_errata_type.append(line[repcols["errata_type"]])
417395
if repcols["errata_reboot"] < 666 and line[repcols["errata_reboot"]] != "":
418396
this_errata_reboot.append(line[repcols["errata_reboot"]])
397+
398+
#set reboot box if specified and present in report
399+
rebootErrata = [s for e in this_errata_reboot if e == "1"]
400+
print len(rebootErrata)
401+
if len(rebootErrata) == 0:
402+
this_NoReboot="$\CheckedBox$"
403+
this_RebootNotes="no reboot required"
419404

420405
#add errata
421406
tempRow=""

satprep_install_custominfos.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def parse_options(args=None):
111111
If you're not defining variables or an authfile you will be prompted to enter your login information.
112112
113113
Checkout the GitHub page for updates: https://github.com/stdevel/satprep'''
114-
parser = OptionParser(description=desc, version="%prog version 0.3.4")
114+
parser = OptionParser(description=desc, version="%prog version 0.3.5")
115115
#define option groups
116116
genOpts = OptionGroup(parser, "Generic Options")
117117
srvOpts = OptionGroup(parser, "Server Options")

satprep_patch_freeze.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def getChannels(client, key):
6161
LOGGER.debug("This system's profile ID: {0}".format(hostId))
6262
baseChannel = client.system.getSubscribedBaseChannel(key, hostId[0]["id"])
6363
cleanBase = baseChannel["label"]
64-
if ".sp" in cleanBase: cleanBase = cleanBase[:cleanBase.find(".sp")]
64+
if "." in cleanBase: cleanBase = cleanBase[cleanBase.find(".")+1:]
6565
if cleanBase not in myChannels:
6666
#channel non-present
6767
LOGGER.debug("Adding channel '{0}'".format(cleanBase))
@@ -70,7 +70,7 @@ def getChannels(client, key):
7070
childChannels = client.system.listSubscribedChildChannels(key, hostId[0]["id"])
7171
for channel in childChannels:
7272
cleanChild = channel["label"]
73-
if ".sp" in cleanChild: cleanChild = cleanChild[:cleanChild.find(".sp")]
73+
if "." in cleanChild: cleanChild = cleanChild[:cleanChild.find(".")+1:]
7474
if cleanChild not in myChannels[cleanBase]:
7575
LOGGER.debug("Adding child-channel '{0}'".format(cleanChild))
7676
myChannels[cleanBase].append(cleanChild)
@@ -94,34 +94,34 @@ def cloneChannels(client, key, date, label, unfreeze=False):
9494
for channel in myChannels:
9595
#remove child-channels
9696
for child in myChannels[channel]:
97-
if options.dryrun: LOGGER.info("I'd like to remove cloned child-channel '{0}'".format(child+"."+options.targetLabel+"-"+options.targetDate))
97+
if options.dryrun: LOGGER.info("I'd like to remove cloned child-channel '{0}'".format(options.targetLabel+"-"+options.targetDate+"."+child))
9898
else:
9999
try:
100-
LOGGER.info("Deleting child-channel '{0}'".format(child+"-"+options.targetLabel+"-"+options.targetDate))
101-
result = client.channel.software.delete(key, child+"."+options.targetLabel+"-"+options.targetDate)
100+
LOGGER.info("Deleting child-channel '{0}'".format(options.targetLabel+"-"+options.targetDate+"."+child))
101+
result = client.channel.software.delete(key, options.targetLabel+"-"+options.targetDate+"."+child)
102102
except xmlrpclib.Fault as e:
103-
LOGGER.error("Unable to remove child-channel '{0}': '{1}'".format(child+"-"+options.targetLabel+"-"+options.targetDate, e.faultString))
103+
LOGGER.error("Unable to remove child-channel '{0}': '{1}'".format(options.targetLabel+"-"+options.targetDate+"."+child, e.faultString))
104104
except xmlrpclib.ProtocolError as e:
105-
LOGGER.error("Unable to remove child-channel '{0}': '{1}'".format(child+"-"+options.targetLabel+"-"+options.targetDate, e.errmsg))
105+
LOGGER.error("Unable to remove child-channel '{0}': '{1}'".format(options.targetLabel+"-"+options.targetDate+"."+child, e.errmsg))
106106
except:
107-
LOGGER.error("Unable to remove child-channel '{0}'!".format(child+"-"+options.targetLabel+"-"+options.targetDate))
107+
LOGGER.error("Unable to remove child-channel '{0}'!".format(options.targetLabel+"-"+options.targetDate+"."+child))
108108
#remove base-channel
109109
if options.dryrun: LOGGER.info("I'd like to remove cloned base-channel '{0}'".format(channel+"."+options.targetLabel+"-"+options.targetDate))
110110
else:
111111
try:
112112
LOGGER.info("Deleting base-channel '{0}'".format(channel+"."+options.targetLabel+"-"+options.targetDate))
113-
result = client.channel.software.delete(key, channel+"."+options.targetLabel+"-"+options.targetDate)
113+
result = client.channel.software.delete(key, options.targetLabel+"-"+options.targetDate+"."+channel)
114114
except: LOGGER.error("Unable to remove base-channel '{0}'!".format(channel))
115115
return True
116116

117117
#clone channels
118118
for channel in myChannels:
119119
#clone base-channels
120-
myargs={"name" : channel+" clone from "+options.targetDate, "label" : channel+"."+options.targetLabel+"-"+options.targetDate, "summary" : "Software channel cloned by Satprep"}
120+
myargs={"name" : channel+" clone from "+options.targetDate+" ("+options.targetLabel+")", "label" : options.targetLabel+"-"+options.targetDate+"."+channel, "summary" : "Software channel cloned by Satprep"}
121121
if options.dryrun:
122-
LOGGER.info("I'd like to clone base-channel '{0}' as '{1}'".format(channel, channel+"."+options.targetLabel+"-"+options.targetDate))
122+
LOGGER.info("I'd like to clone base-channel '{0}' as '{1}'".format(channel, options.targetLabel+"-"+options.targetDate+"."+channel))
123123
else:
124-
LOGGER.info("Cloning base-channel '{0}' as '{1}'".format(channel, channel+"."+options.targetLabel+"-"+options.targetDate))
124+
LOGGER.info("Cloning base-channel '{0}' as '{1}'".format(channel, options.targetLabel+"-"+options.targetDate+"."+channel))
125125
try:
126126
result = client.channel.software.clone(key, channel, myargs, False)
127127
if result != 0: LOGGER.debug("Cloned base-channel")
@@ -134,11 +134,11 @@ def cloneChannels(client, key, date, label, unfreeze=False):
134134

135135
#clone child-channels
136136
for child in myChannels[channel]:
137-
myargs={"name" : child+" clone from "+options.targetDate, "label" : child+"."+options.targetLabel+"-"+options.targetDate, "summary" : "Software channel cloned by Satprep", "parent_label": channel+"."+options.targetLabel+"-"+options.targetDate}
137+
myargs={"name" : child+" clone from "+options.targetDate, "label" : options.targetLabel+"-"+options.targetDate+"."+child, "summary" : "Software channel cloned by Satprep", "parent_label": options.targetLabel+"-"+options.targetDate+"."+channel}
138138
if options.dryrun:
139-
LOGGER.info("I'd like to clone child-channel '{0}' as '{1}'".format(child, child+"."+options.targetLabel+"-"+options.targetDate))
139+
LOGGER.info("I'd like to clone child-channel '{0}' as '{1}'".format(child, options.targetLabel+"-"+options.targetDate+"."+child))
140140
else:
141-
LOGGER.info("Cloning child-channel '{0}' as '{1}'".format(child, child+"."+options.targetLabel+"-"+options.targetDate))
141+
LOGGER.info("Cloning child-channel '{0}' as '{1}'".format(child, options.targetLabel+"-"+options.targetDate+"."+child))
142142
try:
143143
result = client.channel.software.clone(key, child, myargs, False)
144144
if result != 0: LOGGER.debug("Cloned child-channel")
@@ -161,8 +161,8 @@ def remapSystems(client, key, unfreeze=False):
161161
myBase = client.system.getSubscribedBaseChannel(key, hostId[0]["id"])
162162
if options.unfreeze:
163163
myNewBase = myBase["label"]
164-
myNewBase = myNewBase[:myNewBase.find(".sp")]
165-
else: myNewBase = myBase["label"]+"."+options.targetLabel+"-"+options.targetDate
164+
myNewBase = myNewBase[myNewBase.find(".")+1:]
165+
else: myNewBase = options.targetLabel+"-"+options.targetDate+"."+myBase["label"]
166166

167167
if options.dryrun: LOGGER.info("I'd like to remap {0}'s base-channel from {1} to {2}".format(system, myBase["label"], myNewBase))
168168
else:
@@ -181,10 +181,10 @@ def remapSystems(client, key, unfreeze=False):
181181
myNewChannel = channel["label"]
182182
if options.unfreeze:
183183
#switch back to non-cloned
184-
myNewChannel = myNewChannel[:myNewChannel.find(".sp")]
184+
myNewChannel = myNewChannel[myNewChannel.find(".")+1:]
185185
else:
186186
#switch to cloned
187-
myNewChannel = channel["label"]+"."+options.targetLabel+"-"+options.targetDate
187+
myNewChannel = options.targetLabel+"-"+options.targetDate+"."+channel["label"]
188188
tmpChannels.append(myNewChannel)
189189
if options.dryrun: LOGGER.info("I'd like to set the following child-channels for {0}: {1}".format(system, str(tmpChannels)))
190190
else:
@@ -252,7 +252,7 @@ def parse_options(args=None):
252252
If you're not defining variables or an authfile you will be prompted to enter your login information.
253253
254254
Checkout the GitHub page for updates: https://github.com/stdevel/satprep'''
255-
parser = OptionParser(description=desc, version="%prog version 0.3.4")
255+
parser = OptionParser(description=desc, version="%prog version 0.3.5")
256256
#define option groups
257257
genOpts = OptionGroup(parser, "Generic Options")
258258
srvOpts = OptionGroup(parser, "Server Options")

satprep_prepare_maintenance.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ def verify():
4848
#check whether the output directory/file is writable
4949
if os.access(os.getcwd(), os.W_OK):
5050
LOGGER.debug("Output file/directory writable!")
51-
myLog = open(myPrefix+"_satprep.vlog", "r+")
52-
#myFile = myLog.readlines()
51+
if os.path.exists(myPrefix+"_satprep.vlog"):
52+
myLog = open(myPrefix+"_satprep.vlog", "r+")
53+
else:
54+
myLog = open(myPrefix+"_satprep.vlog", "w+")
5355
myFile = myLog.read().splitlines()
5456
LOGGER.debug("vlog before customization: ***\n" + str(myFile))
5557
else:
@@ -326,16 +328,17 @@ def readFile(file):
326328
LOGGER.debug("Script parameters are avoiding creating snapshot for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")")
327329
else:
328330
#add host to downtimeHosts if reboot required and monitoring flag set, add custom names if defined
329-
if row[repcols["system_monitoring"]] == "1" and row[repcols["errata_reboot"]] == "1":
331+
if repcols["errata_reboot"] < 666 and row[repcols["errata_reboot"]] == "1":
330332
#handle custom name
331333
if row[repcols["system_monitoring"]] == "1" and row[repcols["system_monitoring_name"]] != "":
332334
this_name = row[repcols["system_monitoring_name"]]
333-
else: this_name = row[repcols["hostname"]]
335+
elif row[repcols["system_monitoring"]] == "1":
336+
this_name = row[repcols["hostname"]]
334337
#only add if prod/nonprod modes aren't avoiding it
335338
if (row[repcols["system_prod"]] == "1" and options.nonprodOnly == False) \
336339
or (row[repcols["system_prod"]] != "1" and options.prodOnly == False) \
337340
or (options.prodOnly == False and options.nonprodOnly == False):
338-
if is_blacklisted(row[repcols["hostname"]], options.exclude) == False: downtimeHosts.append(this_name)
341+
if is_blacklisted(row[repcols["hostname"]], options.exclude) == False and row[repcols["system_monitoring"]] == "1": downtimeHosts.append(this_name)
339342
LOGGER.debug("Downtime will be scheduled for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")")
340343
else: LOGGER.debug("Script parameters are avoiding scheduling downtime for '" + this_name + "' (P:" + row[repcols["system_prod"]] + ")")
341344

@@ -404,7 +407,7 @@ def parse_options(args=None):
404407
405408
Check-out the GitHub documentation (https://github.com/stdevel/satprep) for further information.
406409
'''
407-
parser = OptionParser(usage=usage, description=desc, version="%prog version 0.3.4")
410+
parser = OptionParser(usage=usage, description=desc, version="%prog version 0.3.5")
408411
#define option groups
409412
genOpts = OptionGroup(parser, "Generic Options")
410413
monOpts = OptionGroup(parser, "Monitoring Options")

satprep_shared.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from datetime import datetime, timedelta
1313
import libvirt
1414
from fnmatch import fnmatch
15+
import string
1516

1617

1718

@@ -20,7 +21,7 @@
2021
LIBVIRT_PASSWORD=""
2122

2223
LOGGER = logging.getLogger('satprep-shared')
23-
SUPPORTED_API_LEVELS = ["11.1", "12", "13", "13.0", "14", "14.0", "15", "15.0", "16", "16.0"]
24+
SUPPORTED_API_LEVELS = ["11.1", "12", "13", "13.0", "14", "14.0", "15", "15.0", "16", "16.0", "17", "17.0"]
2425

2526

2627

@@ -301,3 +302,9 @@ def get_systems_by_systemgroup(client, key, group):
301302
#get systems by system group
302303
systems = client.systemgroup.listSystems(key, group)
303304
LOGGER.debug(systems)
305+
306+
307+
308+
def escape_string(str):
309+
temp=filter(string.printable.__contains__,str)
310+
return ''.join([c for c in temp if ord(c) > 31 or ord(c) == 9])

satprep_snapshot.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import time
1818
import xmlrpclib
1919
from optparse import OptionParser, OptionGroup
20-
from satprep_shared import check_if_api_is_supported, get_credentials
20+
from satprep_shared import check_if_api_is_supported, get_credentials, escape_string
2121
from unidecode import unidecode
2222

2323

@@ -47,7 +47,7 @@ def parse_options(args=None):
4747
If you're not defining variables or an authfile you will be prompted to enter your login information.
4848
4949
Checkout the GitHub page for updates: https://github.com/stdevel/satprep'''
50-
parser = OptionParser(description=desc, version="%prog version 0.3.4")
50+
parser = OptionParser(description=desc, version="%prog version 0.3.5")
5151
#define option groups
5252
genOpts = OptionGroup(parser, "Generic Options")
5353
parser.add_option_group(genOpts)
@@ -349,6 +349,8 @@ def process_errata(client, key, writer, system):
349349
ascii=unidecode(field)
350350
))
351351
valueSet[i] = str(unidecode(field))
352+
#remove crap
353+
valueSet[i] = escape_string(str(valueSet[i]))
352354

353355
writer.writerow(valueSet)
354356

@@ -517,8 +519,17 @@ def process_patches(client, key, writer, system):
517519
else:
518520
valueSet.append("")
519521

520-
if valueSet:
521-
writer.writerow(valueSet)
522+
#replace unicodes
523+
for i,field in enumerate(valueSet):
524+
if type(field) is unicode:
525+
LOGGER.debug("Converted to ascii: {ascii}".format(
526+
ascii=unidecode(field)
527+
))
528+
valueSet[i] = str(unidecode(field))
529+
#remove crap
530+
valueSet[i] = escape_string(str(valueSet[i]))
531+
532+
writer.writerow(valueSet)
522533

523534

524535

0 commit comments

Comments
 (0)