diff --git a/payload/usr/local/munki/postflight.d/sal-postflight b/payload/usr/local/munki/postflight.d/sal-postflight index c623084..20c5b1a 100644 --- a/payload/usr/local/munki/postflight.d/sal-postflight +++ b/payload/usr/local/munki/postflight.d/sal-postflight @@ -2,6 +2,13 @@ import os import subprocess +import sys +import socket +import urllib2 + +sys.path[:0] = ['/usr/local/munki', '/usr/local/sal'] +import utils +from munkilib import FoundationPlist, munkicommon TOUCH_FILE_PATH = '/Users/Shared/.com.salopensource.sal.run' LAUNCHD = 'com.salopensource.sal.runner' @@ -9,10 +16,60 @@ LAUNCHD_PATH = '/Library/LaunchDaemons/{}.plist'.format(LAUNCHD) SUBMIT_SCRIPT = '/usr/local/sal/bin/sal-submit' +def check_for_errors(report): + """Checks if the device was offline for last Munki run.""" + errors = report.get("Errors", []) + warnings = report.get("Warnings", []) + target_errors = ["Could not retrieve managed install primary manifest."] + target_warnings = [ + "Could not download Apple SUS catalog:", + "\t(-1009, u'The Internet connection appears to be offline.')" + ] + + using_sus = munkicommon.pref('InstallAppleSoftwareUpdates') + + if using_sus: + if warnings == target_warnings and errors == target_errors: + return True + else: + if errors == target_errors: + return True + + return False + + +def check_server_connection(): + host = munkicommon.pref('SoftwareRepoURL') + try: + urllib2.urlopen(host, timeout=5).getcode() + except urllib2.HTTPError as e: + # If we get a http error, the server is returning _something_ + return True + except urllib2.URLError as e: + return False + except socket.timeout as e: + return False + return True + +def check_server_online(): + # read report + report = utils.get_managed_install_report() + # check for errors and warnings + if not check_for_errors(report): + utils.set_pref('LastRunWasOffline', False) + return + # if they're there check is server is really offline + if not check_server_connection(): + utils.set_pref('LastRunWasOffline', True) + return + + # If we get here, it's online + utils.set_pref('LastRunWasOffline', False) + def write_touch_file(): if os.path.exists(TOUCH_FILE_PATH): os.remove(TOUCH_FILE_PATH) - + if not os.path.exists(TOUCH_FILE_PATH): with open(TOUCH_FILE_PATH, 'a'): os.utime(TOUCH_FILE_PATH, None) @@ -33,6 +90,7 @@ def ensure_launchd_loaded(): subprocess.check_call(cmd) def main(): + check_server_online() write_touch_file() ensure_launchd_loaded() # If the launchd isn't present, call the submit script old school diff --git a/payload/usr/local/sal/bin/sal-submit b/payload/usr/local/sal/bin/sal-submit index e0d1de9..cd0dae6 100755 --- a/payload/usr/local/sal/bin/sal-submit +++ b/payload/usr/local/sal/bin/sal-submit @@ -34,7 +34,7 @@ import yaml BUNDLE_ID = 'com.github.salopensource.sal' -VERSION = '2.1.3' +VERSION = '2.1.4' # To resolve unicode write errors set our default encoding to utf8 reload(sys) @@ -47,12 +47,16 @@ def main(): other_sal_pid = utils.pythonScriptRunning('sal-submit') if other_sal_pid: sys.exit('Another instance of sal-submit is already running. Exiting.') - + time.sleep(1) munki_pid = utils.pythonScriptRunning('managedsoftwareupdate') if munki_pid: sys.exit('managedsoftwareupdate is running. Exiting.') - report = get_managed_install_report() + report = utils.get_managed_install_report() + + if utils.pref('LastRunWasOffline'): + sys.exit('Device appears to have been offline during ' + 'last Munki run') serial = report['MachineInfo'].get('serial_number') if not serial: sys.exit('Unable to get MachineInfo from ManagedInstallReport.plist. ' @@ -114,7 +118,7 @@ def main(): send_install(ServerURL, copy.copy(submission)) send_catalogs(ServerURL, copy.copy(submission)) send_profiles(ServerURL, copy.copy(submission)) - + touchfile = '/Users/Shared/.com.salopensource.sal.run' if os.path.exists(touchfile): os.remove(touchfile) @@ -174,37 +178,6 @@ def get_server_prefs(): return (required_prefs["ServerURL"], name_type, required_prefs["key"]) -def get_managed_install_report(): - """Return Munki ManagedInstallsReport.plist as a plist dict. - - Returns: - ManagedInstalls report for last Munki run as a plist - dict, or an empty dict. - """ - # Checks munki preferences to see where the install directory is set to. - managed_install_dir = munkicommon.pref('ManagedInstallDir') - - # set the paths based on munki's configuration. - managed_install_report = os.path.join( - managed_install_dir, 'ManagedInstallReport.plist') - - munkicommon.display_debug2( - "Looking for munki's ManagedInstallReport.plist at {} ...".format( - managed_install_report)) - try: - munki_report = FoundationPlist.readPlist(managed_install_report) - except FoundationPlist.FoundationPlistException: - munki_report = {} - - if 'MachineInfo' not in munki_report: - munki_report['MachineInfo'] = {} - - munkicommon.display_debug2('ManagedInstallReport.plist:') - munkicommon.display_debug2(format_plist(munki_report)) - - return munki_report - - def get_plugin_results(plugin_results_plist): """ Read external data plist if it exists and return a dict.""" result = {} @@ -212,7 +185,7 @@ def get_plugin_results(plugin_results_plist): try: plist_data = FoundationPlist.readPlist(plugin_results_plist) munkicommon.display_debug2('External data plist:') - munkicommon.display_debug2(format_plist(plist_data)) + munkicommon.display_debug2(utils.format_plist(plist_data)) result = plist_data except FoundationPlist.NSPropertyListSerializationException: munkicommon.display_debug2('Could not read external data plist.') @@ -222,12 +195,6 @@ def get_plugin_results(plugin_results_plist): return result -def format_plist(plist): - """Format a plist as a string for debug output.""" - # For now, just dump it. - return FoundationPlist.writePlistToString(plist) - - def get_sys_profile(): """Get sysprofiler info. @@ -252,7 +219,7 @@ def get_sys_profile(): munkicommon.display_debug2( 'System Profiler SPNetworkDataType and SPHardwareDataType:') - munkicommon.display_debug2(format_plist(system_profile)) + munkicommon.display_debug2(utils.format_plist(system_profile)) return system_profile diff --git a/payload/usr/local/sal/utils.py b/payload/usr/local/sal/utils.py index af022dc..4c8fbfb 100644 --- a/payload/usr/local/sal/utils.py +++ b/payload/usr/local/sal/utils.py @@ -9,7 +9,7 @@ import time sys.path.insert(0, '/usr/local/munki') -from munkilib import FoundationPlist +from munkilib import FoundationPlist, munkicommon from Foundation import kCFPreferencesAnyUser, \ kCFPreferencesCurrentHost, \ CFPreferencesSetValue, \ @@ -65,6 +65,7 @@ def pref(pref_name): 'BasicAuth': True, 'GetGrains': False, 'GetOhai': False, + 'LastRunWasOffline': False, } pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID) @@ -82,6 +83,43 @@ def pref(pref_name): return pref_value +def get_managed_install_report(): + """Return Munki ManagedInstallsReport.plist as a plist dict. + + Returns: + ManagedInstalls report for last Munki run as a plist + dict, or an empty dict. + """ + # Checks munki preferences to see where the install directory is set to. + managed_install_dir = munkicommon.pref('ManagedInstallDir') + + # set the paths based on munki's configuration. + managed_install_report = os.path.join( + managed_install_dir, 'ManagedInstallReport.plist') + + munkicommon.display_debug2( + "Looking for munki's ManagedInstallReport.plist at {} ...".format( + managed_install_report)) + try: + munki_report = FoundationPlist.readPlist(managed_install_report) + except FoundationPlist.FoundationPlistException: + munki_report = {} + + if 'MachineInfo' not in munki_report: + munki_report['MachineInfo'] = {} + + munkicommon.display_debug2('ManagedInstallReport.plist:') + munkicommon.display_debug2(format_plist(munki_report)) + + return munki_report + + +def format_plist(plist): + """Format a plist as a string for debug output.""" + # For now, just dump it. + return FoundationPlist.writePlistToString(plist) + + def pythonScriptRunning(scriptname): """ Tests if a script is running. If it is found running, it will try