unattended_install in metadata no longer applied if a RestartAction exists (in original item or metadata); unattended Apple updates are skipped if a blocking application is running; installlist, holding Apple update installation results, only holds items which are eligible for unattended installation

Mostly, this commit involves improving upon expected functionality of
an unattend Apple update installation.

Munki performs a "suppressed" Apple software update check post
installation run if a GUI user is logged in such that the user will be
prompted to install any remaining updates.  This is inline with munki's
behavior for munki packages.

blocking_applications can now accept items which are full paths in case
an admin wishes to be specific regarding the location of a running,
blocking application and/or executable.

Also of note is a fix for previously broken blocking_application
checking for Apple updates, both in MSU and managaedsoftwareupdate.
This commit is contained in:
Heig Gregorian
2013-02-19 13:32:09 -08:00
parent a52acd73f8
commit 53fc136d28
4 changed files with 89 additions and 15 deletions

View File

@@ -442,7 +442,11 @@ def getRunningBlockingApps(appnames):
filemanager = NSFileManager.alloc().init()
for appname in appnames:
matching_items = []
if appname.endswith('.app'):
if appname.startswith('/'):
# search by exact path
matching_items = [item for item in proc_list
if item == appname]
elif appname.endswith('.app'):
# search by filename
matching_items = [item for item in proc_list
if '/'+ appname + '/Contents/MacOS/' in item]

View File

@@ -796,9 +796,17 @@ def main():
mustlogout = True
# it's possible that we no longer have any available updates
# so we need to check InstallInfo.plist again
# however Apple Updates have not been affected by the
# unattended install tasks (so that check is still valid).
# so we need to check InstallInfo.plist and
# AppleUpdates.plist again
try:
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(
suppresscheck=True, client_id=options.id)
except:
munkicommon.display_error('Unexpected error in appleupdates:')
munkicommon.log(traceback.format_exc())
munkicommon.savereport()
raise
if appleupdatesavailable or munkiUpdatesAvailable():
# set a flag to notify the user of available updates
# after we conclude this run.

View File

@@ -443,7 +443,9 @@ class AppleUpdates(object):
if fileurl.startswith('file://localhost'):
fileurl = fileurl[len('file://localhost'):]
pathname = urllib2.unquote(fileurl).rstrip('/')
dirname = os.path.dirname(pathname)
executable = munkicommon.getAppBundleExecutable(pathname)
executable = executable[len(dirname + '/'):]
blocking_apps.append(executable or pathname)
return blocking_apps
@@ -1174,15 +1176,19 @@ class AppleUpdates(object):
# Creating an 'unattended_install' filtered catalog
# against the existing filtered catalog is not an option as
# cached downloads are purged if they do not exist in the
# filtered catalog. Instead, get a list of updates
# that are eligible for unattended_install.
unattended_install_items = self.GetUnattendedInstalls()
# filtered catalog. Instead, get a list of updates, and their
# product_ids, that are eligible for unattended_install.
unattended_install_items, unattended_install_product_ids = \
self.GetUnattendedInstalls()
restartneeded = False
if not unattended_install_items:
return False
else:
msg = 'Installing available Apple Software Updates...'
restartneeded = self.IsRestartNeeded()
self._ResetMunkiStatusAndDisplayMessage(msg)
restartneeded = self.IsRestartNeeded()
# use our filtered local catalog
if not os.path.exists(self.local_catalog_path):
munkicommon.display_error(
@@ -1200,6 +1206,11 @@ class AppleUpdates(object):
if only_unattended:
# Append list of unattended_install items
su_options.extend(unattended_install_items)
# Filter installist to only include items
# which we're attempting to install
installlist = [item for item in installlist
if item.get('productKey') in
unattended_install_product_ids]
else:
# We're installing all available updates
su_options.extend(['-a'])
@@ -1373,28 +1384,50 @@ class AppleUpdates(object):
'\tSkipping %s \'%s\', \'%s\' is preferred.'
% (key, metadata[key], item[key]))
continue
elif key == 'unattended_install':
# Don't apply unattended_install if a RestartAction exists
# in either the original item or metadata
if metadata.get('RestartAction', 'None') != 'None' or \
item.get('RestartAction', 'None') != 'None':
munkicommon.display_warning(
'\tIgnoring unattended_install key '
'because RestartAction is %s.'
% metadata.get('RestartAction'))
continue
munkicommon.display_debug2('\tApplying %s...' % key)
item[key] = metadata[key]
return item
def GetUnattendedInstalls(self):
"""Processes AppleUpdates.plist to return a list
consisting of NAME-VERSION formatted items
which have been marked as unattended_installs.
of NAME-VERSION formatted items and a list of product_ids
which are elgible for unattended installation.
"""
item_list = []
product_ids = []
try:
pl_dict = FoundationPlist.readPlist(self.apple_updates_plist)
except FoundationPlist.FoundationPlistException:
munkicommon.display_error(
'Error reading: %s', self.apple_updates_plist)
return item_list
return item_list, product_ids
apple_updates = pl_dict.get('AppleUpdates', [])
for item in apple_updates:
if item.get('unattended_install'):
install_item = item['name'] + '-' + item['version_to_install']
item_list.append(install_item)
return item_list
if munkicommon.blockingApplicationsRunning(item):
munkicommon.display_detail(
'Skipping unattended install of %s because '
'blocking application(s) running.'
% item['display_name'])
continue
install_item = item['name'] + '-' + item['version_to_install']
item_list.append(install_item)
product_ids.append(item['productKey'])
else:
munkicommon.display_detail(
'Skipping install of %s because it\'s not unattended.'
% item['display_name'])
return item_list, product_ids

31
code/client/munkilib/munkicommon.py Normal file → Executable file
View File

@@ -2240,7 +2240,11 @@ def isAppRunning(appname):
display_detail('Checking if %s is running...' % appname)
proc_list = getRunningProcesses()
matching_items = []
if appname.endswith('.app'):
if appname.startswith('/'):
# search by exact path
matching_items = [item for item in proc_list
if item == appname]
elif appname.endswith('.app'):
# search by filename
matching_items = [item for item in proc_list
if '/'+ appname + '/Contents/MacOS/' in item]
@@ -2494,6 +2498,31 @@ def forceLogoutNow():
display_error('Exception in forceLogoutNow(): %s' % str(e))
def blockingApplicationsRunning(pkginfoitem):
"""Returns true if any application in the blocking_applications list
is running or, if there is no blocking_applications list, if any
application in the installs list is running."""
if 'blocking_applications' in pkginfoitem:
appnames = pkginfoitem['blocking_applications']
else:
# if no blocking_applications specified, get appnames
# from 'installs' list if it exists
appnames = [os.path.basename(item.get('path'))
for item in pkginfoitem.get('installs', [])
if item['type'] == 'application']
display_debug1("Checking for %s" % appnames)
running_apps = [appname for appname in appnames
if isAppRunning(appname)]
if running_apps:
display_detail(
"Blocking apps for %s are running:" % pkginfoitem['name'])
display_detail(" %s" % running_apps)
return True
return False
# module globals
#debug = False
verbose = 1