commit e1d0e38aeb8416e2af976d92f88860071776c2a0 Author: Wesley Whetstone Date: Tue Dec 11 21:45:05 2018 -0800 add shutdown feature for some updates. (#889) * adding support for munki showdown to after apple updates instead of restarting if softwareupdate indicates a shutdown is required diff --git a/code/client/managedsoftwareupdate b/code/client/managedsoftwareupdate index c1b17ff5..a5db72c8 100755 --- a/code/client/managedsoftwareupdate +++ b/code/client/managedsoftwareupdate @@ -233,13 +233,13 @@ def doInstallTasks(do_apple_updates, only_unattended=False): # changes after this round of installs clearLastNotifiedDate() - munki_need_to_restart = False - apple_need_to_restart = False + munki_items_restart_action = constants.POSTACTION_NONE + apple_items_restart_action = constants.POSTACTION_NONE if munkiUpdatesAvailable(): # install munki updates try: - munki_need_to_restart = installer.run( + munki_items_restart_action = installer.run( only_unattended=only_unattended) except: display.display_error('Unexpected error in munkilib.installer:') @@ -277,7 +277,7 @@ def doInstallTasks(do_apple_updates, only_unattended=False): if do_apple_updates: # install Apple updates try: - apple_need_to_restart = appleupdates.installAppleUpdates( + apple_items_restart_action = appleupdates.installAppleUpdates( only_unattended=only_unattended) except: display.display_error( @@ -287,7 +287,7 @@ def doInstallTasks(do_apple_updates, only_unattended=False): raise reports.savereport() - return munki_need_to_restart or apple_need_to_restart + return max(apple_items_restart_action, munki_items_restart_action) def doFinishingTasks(runtype=None): @@ -327,8 +327,8 @@ def startLogoutHelper(): 'Could not start com.googlecode.munki.logouthelper') -def doRestart(): - """Handle the need for a restart.""" +def doRestart(shutdown=False): + """Handle the need for a restart or a possbile shutdown.""" restartMessage = 'Software installed or removed requires a restart.' munkilog.log(restartMessage) if display.munkistatusoutput: @@ -346,7 +346,9 @@ def doRestart(): time.sleep(5) # try to use authrestartd to do an auth restart; if that fails # do it directly - if not authrestartd.restart(): + if shutdown: + authrestart.do_authorized_or_normal_restart(shutdown=shutdown) + elif not authrestartd.restart(): authrestart.do_authorized_or_normal_restart() else: if display.munkistatusoutput: @@ -944,14 +946,14 @@ def main(): # send a notification event so MSC can update its display if needed sendUpdateNotification() - mustrestart = False + restart_action = constants.POSTACTION_NONE mustlogout = False notify_user = False force_action = None if updatesavailable or appleupdatesavailable: if options.installonly or options.logoutinstall: # just install - mustrestart = doInstallTasks(appleupdatesavailable) + restart_action = doInstallTasks(appleupdatesavailable) # reset our count of available updates (it might not actually # be zero, but we want to clear the badge on the Dock icon; # it can be updated to the "real" count on the next Munki run) @@ -1096,7 +1098,9 @@ def main(): if mustlogout: # not handling this currently pass - if mustrestart: + if restart_action == constants.POSTACTION_SHUTDOWN: + doRestart(shutdown=True) + elif restart_action == constants.POSTACTION_RESTART: doRestart() else: # tell status app we're done sending status diff --git a/code/client/munkilib/appleupdates/au.py b/code/client/munkilib/appleupdates/au.py index ec5893cf..8bb93f6d 100644 --- a/code/client/munkilib/appleupdates/au.py +++ b/code/client/munkilib/appleupdates/au.py @@ -39,6 +39,7 @@ from . import su_prefs from . import sync from ..updatecheck import catalogs +from ..constants import POSTACTION_NONE, POSTACTION_RESTART, POSTACTION_SHUTDOWN from .. import display from .. import fetch @@ -81,6 +82,7 @@ class AppleUpdates(object): of those updates. """ + SHUTDOWN_ACTIONS = ['RequireShutdown'] RESTART_ACTIONS = ['RequireRestart', 'RecommendRestart'] def __init__(self): @@ -97,6 +99,7 @@ class AppleUpdates(object): self.applesync = sync.AppleUpdateSync() self._update_list_cache = None + self.shutdown_instead_of_restart = False # apple_update_metadata support self.client_id = '' @@ -111,17 +114,20 @@ class AppleUpdates(object): # pylint: disable=no-self-use display.display_status_major(message) - def restart_needed(self): - """Returns True if any update requires an restart.""" + def restart_action_for_updates(self): + """Returns the most heavily weighted postaction""" try: apple_updates = FoundationPlist.readPlist(self.apple_updates_plist) except FoundationPlist.NSPropertyListSerializationException: - return True + return POSTACTION_RESTART + for item in apple_updates.get('AppleUpdates', []): + if item.get('RestartAction') in self.SHUTDOWN_ACTIONS: + return POSTACTION_SHUTDOWN for item in apple_updates.get('AppleUpdates', []): if item.get('RestartAction') in self.RESTART_ACTIONS: - return True + return POSTACTION_RESTART # if we get this far, there must be no items that require restart - return False + return POSTACTION_NONE def clear_apple_update_info(self): """Clears Apple update info. @@ -652,6 +658,11 @@ class AppleUpdates(object): pass elif output == '': pass + elif output.startswith('Please call halt'): + # This update requires we shutdown instead of a restart. + display.display_status_minor(output) + display.display_debug2('This update requires a shutdown...') + self.shutdown_instead_of_restart = True else: display.display_status_minor(output) @@ -683,18 +694,19 @@ class AppleUpdates(object): if display.munkistatusoutput: munkistatus.hideStopButton() + self.shutdown_instead_of_restart = False # Get list of unattended_installs if only_unattended: msg = 'Installing unattended Apple Software Updates...' unattended_install_items, unattended_install_product_ids = \ self.get_unattended_installs() # ensure that we don't restart for unattended installations - restartneeded = False + restart_action = POSTACTION_NONE if not unattended_install_items: return False # didn't find any unattended installs else: msg = 'Installing available Apple Software Updates...' - restartneeded = self.restart_needed() + restart_action = self.restart_action_for_updates() self._display_status_major(msg) @@ -843,7 +855,11 @@ class AppleUpdates(object): if display.munkistatusoutput: munkistatus.showStopButton() - return restartneeded + if self.shutdown_instead_of_restart: + display.display_debug2('Found shutdown flag...') + restart_action = POSTACTION_SHUTDOWN + + return restart_action def software_updates_available( self, force_check=False, suppress_check=False): @@ -920,11 +936,12 @@ class AppleUpdates(object): # Mapping of supported restart_actions to # equal or greater auxiliary actions restart_actions = { + 'RequireShutdown': ['RequireShutdown'], 'RequireRestart': ['RequireRestart', 'RecommendRestart'], 'RecommendRestart': ['RequireRestart', 'RecommendRestart'], 'RequireLogout': ['RequireRestart', 'RecommendRestart', 'RequireLogout'], - 'None': ['RequireRestart', 'RecommendRestart', + 'None': ['RequireShutdown', 'RequireRestart', 'RecommendRestart', 'RequireLogout', 'None'] } @@ -984,7 +1001,7 @@ class AppleUpdates(object): for item in apple_updates: if (item.get('unattended_install') or (prefs.pref('UnattendedAppleUpdates') and - item.get('RestartAction', 'None') is 'None' and + item.get('RestartAction', 'None') == 'None' and os_version_tuple >= (10, 10))): if processes.blocking_applications_running(item): display.display_detail( diff --git a/code/client/munkilib/authrestart/__init__.py b/code/client/munkilib/authrestart/__init__.py index 27859c8d..ff8dc0f1 100644 --- a/code/client/munkilib/authrestart/__init__.py +++ b/code/client/munkilib/authrestart/__init__.py @@ -181,8 +181,17 @@ def perform_auth_restart(username=None, password=None): return True -def do_authorized_or_normal_restart(username=None, password=None): - '''Do an authrestart if allowed/possible, else do a normal restart.''' +def do_authorized_or_normal_restart(username=None, + password=None, + shutdown=False): + '''Do a shutdown if needed, or an authrestart if allowed/possible, + else do a normal restart.''' + if shutdown: + # we need a shutdown here instead of any type of restart + display.display_info('Shutting down now.') + display.display_debug1('Performing a regular shutdown...') + dummy_retcode = subprocess.call(['/sbin/shutdown', '-h', '-o', 'now']) + return display.display_info('Restarting now.') os_version_tuple = osutils.getOsVersion(as_tuple=True) if (prefs.pref('PerformAuthRestarts') and diff --git a/code/client/munkilib/constants.py b/code/client/munkilib/constants.py index 5cb83145..c63f0954 100644 --- a/code/client/munkilib/constants.py +++ b/code/client/munkilib/constants.py @@ -51,6 +51,12 @@ CHECKANDINSTALLATSTARTUPFLAG = ( INSTALLATSTARTUPFLAG = '/Users/Shared/.com.googlecode.munki.installatstartup' INSTALLATLOGOUTFLAG = '/private/tmp/com.googlecode.munki.installatlogout' +# postinstall actions +POSTACTION_NONE = 0 +POSTACTION_LOGOUT = 1 +POSTACTION_RESTART = 2 +POSTACTION_SHUTDOWN = 4 + if __name__ == '__main__': print 'This is a library of support tools for the Munki Suite.' diff --git a/code/client/munkilib/installer/core.py b/code/client/munkilib/installer/core.py index 71f4f92d..d3e47478 100644 --- a/code/client/munkilib/installer/core.py +++ b/code/client/munkilib/installer/core.py @@ -35,6 +35,7 @@ from . import pkg from . import rmpkgs from .. import adobeutils +from .. import constants from .. import display from .. import dmgutils from .. import munkistatus @@ -752,7 +753,9 @@ def run(only_unattended=False): munkilog.log("### End managed installer session ###") reports.savereport() - return removals_need_restart or installs_need_restart + if removals_need_restart or installs_need_restart: + return constants.POSTACTION_RESTART + return constants.POSTACTION_NONE if __name__ == '__main__':