Make all munkicommon.display_* methods accept a message and optional arguments

to concatenate to the message (using Python concat ala logging.* methods); drop unicode chars.

Fix problem (line 338 of updatecheck.py) where attempting to string-concat
unicode characters before sending to munkicommon.display_debug1() causes
UnicodeDecodeError.



git-svn-id: http://munki.googlecode.com/svn/trunk@921 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Justin McWilliams
2010-11-18 21:03:01 +00:00
parent c1c9ce2ac7
commit e190a2e9d5
2 changed files with 73 additions and 38 deletions
+49 -14
View File
@@ -34,6 +34,7 @@ import sys
import tempfile
import time
import urllib2
import warnings
from distutils import version
from xml.dom import minidom
from Foundation import NSDate
@@ -133,11 +134,39 @@ def display_percent_done(current, maximum):
sys.stdout.flush()
def display_status(msg):
def str_to_ascii(s):
"""Given str (unicode, latin-1, or not) return ascii.
Args:
s: str, likely in Unicode-16BE, UTF-8, or Latin-1 charset
Returns:
str, ascii form, no >7bit chars
"""
try:
return unicode(s).encode('ascii', 'ignore')
except UnicodeDecodeError:
return s.decode('ascii', 'ignore')
def concat_log_message(msg, *args):
"""Concatenates a string with any additional arguments; drops unicode."""
if args:
args = [str_to_ascii(arg) for arg in args]
try:
msg = msg % tuple(args)
except TypeError, e:
warnings.warn(
'String format does not match concat args: %s' % (
str(sys.exc_info())))
return msg
def display_status(msg, *args):
"""
Displays major status messages, formatting as needed
for verbose/non-verbose and munkistatus-style output.
"""
msg = concat_log_message(msg, *args)
log(msg)
if munkistatusoutput:
munkistatus.detail(msg)
@@ -149,11 +178,12 @@ def display_status(msg):
sys.stdout.flush()
def display_info(msg):
def display_info(msg, *args):
"""
Displays info messages.
Not displayed in MunkiStatus.
"""
msg = concat_log_message(msg, *args)
log(msg)
if munkistatusoutput:
pass
@@ -162,13 +192,14 @@ def display_info(msg):
sys.stdout.flush()
def display_detail(msg):
def display_detail(msg, *args):
"""
Displays minor info messages, formatting as needed
for verbose/non-verbose and munkistatus-style output.
These are usually logged only, but can be printed to
stdout if verbose is set to 2 or higher
"""
msg = concat_log_message(msg, *args)
if munkistatusoutput:
pass
elif verbose > 1:
@@ -178,11 +209,12 @@ def display_detail(msg):
log(msg)
def display_debug1(msg):
def display_debug1(msg, *args):
"""
Displays debug messages, formatting as needed
for verbose/non-verbose and munkistatus-style output.
"""
msg = concat_log_message(msg, *args)
if munkistatusoutput:
pass
elif verbose > 2:
@@ -192,11 +224,12 @@ def display_debug1(msg):
log('DEBUG1: %s' % msg)
def display_debug2(msg):
def display_debug2(msg, *args):
"""
Displays debug messages, formatting as needed
for verbose/non-verbose and munkistatus-style output.
"""
msg = concat_log_message(msg, *args)
if munkistatusoutput:
pass
elif verbose > 3:
@@ -213,10 +246,11 @@ def reset_warnings():
rotatelog(warningsfile)
def display_warning(msg):
def display_warning(msg, *args):
"""
Prints warning msgs to stderr and the log
"""
msg = concat_log_message(msg, *args)
warning = 'WARNING: %s' % msg
print >> sys.stderr, warning.encode('UTF-8')
log(warning)
@@ -233,10 +267,11 @@ def reset_errors():
rotatelog(errorsfile)
def display_error(msg):
def display_error(msg, *args):
"""
Prints msg to stderr and the log
"""
msg = concat_log_message(msg, *args)
errmsg = 'ERROR: %s' % msg
print >> sys.stderr, errmsg.encode('UTF-8')
log(errmsg)
@@ -247,7 +282,7 @@ def display_error(msg):
def format_time(timestamp=None):
"""Return timestamp as an ISO 8601 formatted string, in the current
"""Return timestamp as an ISO 8601 formatted string, in the current
timezone.
If timestamp isn't given the current time is used."""
if timestamp is None:
@@ -710,10 +745,10 @@ def getInstallerPkgInfo(filename):
stderr=subprocess.PIPE)
(out, err) = proc.communicate()
if proc.returncode:
display_error("installer -query failed: %s %s" %
display_error("installer -query failed: %s %s" %
(out.decode('UTF-8'), err.decode('UTF-8')))
return None
if out:
restartAction = str(out).rstrip('\n')
if restartAction != 'None':
@@ -1449,7 +1484,7 @@ def getAppData():
except Exception:
pass
return APPDATA
def getRunningProcesses():
"""Returns a list of paths of running processes"""
@@ -1471,7 +1506,7 @@ def getRunningProcesses():
stderr=subprocess.PIPE)
(output, unused_err) = proc.communicate()
if proc.returncode == 0:
carbon_apps = [item[len(LaunchCFMApp)+1:]
carbon_apps = [item[len(LaunchCFMApp)+1:]
for item in output.splitlines()
if item.startswith(LaunchCFMApp)]
if carbon_apps:
@@ -1484,7 +1519,7 @@ def getRunningProcesses():
# some utility functions
def isAppRunning(appname):
"""Tries to determine if the application in appname is currently
"""Tries to determine if the application in appname is currently
running"""
display_detail('Checking if %s is running...' % appname)
proc_list = getRunningProcesses()
@@ -1501,7 +1536,7 @@ def isAppRunning(appname):
# try adding '.app' to the name and check again
matching_items = [item for item in proc_list
if '/'+ appname + '.app/' in item]
if matching_items:
# it's running!
display_debug1('Matching process list: %s' % matching_items)
+24 -24
View File
@@ -54,7 +54,7 @@ def makeCatalogDB(catalogitems):
if name == 'NO NAME' or vers == 'NO VERSION':
munkicommon.display_warning('Bad pkginfo: %s' % item)
# normalize the version number
vers = munkicommon.padVersionString(vers, 5)
@@ -333,9 +333,9 @@ def compareApplicationVersion(app):
if 'path' in item:
if item['path'].startswith('/Users/') and \
not item['path'].startswith('/Users/Shared/'):
munkicommon.display_debug2(('Skipped '
'app %s with path %s') % (
item['name'], item['path']))
munkicommon.display_debug2(
'Skipped app %s with path %s',
item['name'], item['path'])
continue
if bundleid and item['bundleid'] == bundleid:
appinfo.append(item)
@@ -622,15 +622,15 @@ class PackageVerificationError(MunkiDownloadError):
pass
def download_installeritem(item_pl, uninstalling=False):
"""Downloads an (un)installer item.
"""Downloads an (un)installer item.
Raises an error if there are issues..."""
download_item_key = 'installer_item_location'
item_hash_key = 'installer_item_hash'
if uninstalling and 'uninstaller_item_location' in item_pl:
download_item_key = 'uninstaller_item_location'
item_hash_key = 'uninstaller_item_hash'
location = item_pl.get(download_item_key)
if not location:
raise MunkiDownloadError("No %s in item info." % download_item_key)
@@ -682,7 +682,7 @@ def isItemInInstallInfo(manifestitem_pl, thelist, vers=''):
"""
for item in thelist:
try:
if (item.get('name', item['manifestitem']) ==
if (item.get('name', item['manifestitem']) ==
manifestitem_pl['name']):
if not vers:
return True
@@ -898,11 +898,11 @@ def getItemDetail(name, cataloglist, vers=''):
if rejected_items:
for reason in rejected_items:
munkicommon.display_warning(reason)
return None
def enoughDiskSpace(manifestitem_pl, installlist=None,
def enoughDiskSpace(manifestitem_pl, installlist=None,
uninstalling=False, warn=True):
"""Determine if there is enough disk space to download the manifestitem."""
# fudgefactor is set to 100MB
@@ -1223,7 +1223,7 @@ def processManagedUpdate(manifestitem, cataloglist, installinfo):
manifestitemname = os.path.split(manifestitem)[1]
munkicommon.display_debug1(
'* Processing manifest item %s for update' % manifestitemname)
item_pl = getItemDetail(manifestitem, cataloglist)
if not item_pl:
@@ -1277,7 +1277,7 @@ def processOptionalInstall(manifestitem, cataloglist, installinfo):
'No pkginfo for %s found in catalogs: %s' %
(manifestitem, ', '.join(cataloglist)))
return
# check to see if item (any version) is already in the
# optional_install list:
for item in installinfo['optional_installs']:
@@ -1291,7 +1291,7 @@ def processOptionalInstall(manifestitem, cataloglist, installinfo):
# unless it was added because it's a managed_update
if not manifestitemname in installinfo['managed_updates']:
munkicommon.display_debug1(
'%s has already been processed for install.' %
'%s has already been processed for install.' %
manifestitemname)
return
# check to see if item (any version) is already in the removallist:
@@ -1322,7 +1322,7 @@ def processOptionalInstall(manifestitem, cataloglist, installinfo):
warn=False):
iteminfo['note'] = \
'Insufficient disk space to download and install.'
munkicommon.display_debug1(
"Adding %s to the optional install list" % iteminfo['name'])
installinfo['optional_installs'].append(iteminfo)
@@ -1415,7 +1415,7 @@ def processInstall(manifestitem, cataloglist, installinfo):
iteminfo['installer_item_size'] = item_pl.get('installer_item_size', 0)
iteminfo['installed_size'] = item_pl.get('installer_item_size',
iteminfo['installer_item_size'])
# currently we will ignore the forced_install and forced_uninstall key if
# the item is part of a dependency graph or needs a restart or logout...
if (not item_pl.get('requires') and not item_pl.get('update_for') and
@@ -1526,9 +1526,9 @@ def processManifestForKey(manifestpath, manifest_key, installinfo,
Probably doesn't handle circular manifest references well.
"""
munkicommon.display_debug1(
"** Processing manifest %s for %s" %
"** Processing manifest %s for %s" %
(os.path.basename(manifestpath), manifest_key))
cataloglist = getManifestValueForKey(manifestpath, 'catalogs')
if cataloglist:
getCatalogs(cataloglist)
@@ -1599,9 +1599,9 @@ def processRemoval(manifestitem, cataloglist, installinfo):
"""
manifestitemname_withversion = os.path.split(manifestitem)[1]
munkicommon.display_debug1(
'* Processing manifest item %s for removal' %
'* Processing manifest item %s for removal' %
manifestitemname_withversion)
(manifestitemname, includedversion) = nameAndVersion(
manifestitemname_withversion)
infoitems = []
@@ -1748,21 +1748,21 @@ def processRemoval(manifestitem, cataloglist, installinfo):
iteminfo['display_name'] = uninstall_item.get('display_name', '')
iteminfo['manifestitem'] = manifestitemname_withversion
iteminfo['description'] = 'Will be removed.'
# currently we will ignore the forced_install and forced_uninstall key if
# the item is part of a dependency graph or needs a restart or logout...
if (not uninstall_item.get('requires')
and not uninstall_item.get('update_for')
if (not uninstall_item.get('requires')
and not uninstall_item.get('update_for')
and not uninstall_item.get('RestartAction')):
iteminfo['forced_uninstall'] = uninstall_item.get(
'forced_uninstall', False)
if 'blocking_applications' in uninstall_item:
iteminfo['blocking_applications'] = \
uninstall_item['blocking_applications']
if 'installs' in uninstall_item:
iteminfo['installs'] = uninstall_item['installs']
if packagesToRemove:
# remove references for each package
packagesToReallyRemove = []