Skip to content

Commit

Permalink
Merge pull request #45 from salopensource/bail_offline
Browse files Browse the repository at this point in the history
Bail if we think the device was offline during the last Munki run
  • Loading branch information
grahamgilbert authored Apr 29, 2019
2 parents da85654 + 19620cf commit f0fe26d
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 45 deletions.
60 changes: 59 additions & 1 deletion payload/usr/local/munki/postflight.d/sal-postflight
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,74 @@

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'
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)
Expand All @@ -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
Expand Down
53 changes: 10 additions & 43 deletions payload/usr/local/sal/bin/sal-submit
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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. '
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -174,45 +178,14 @@ 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 = {}
if os.path.exists(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.')
Expand All @@ -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.
Expand All @@ -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

Expand Down
40 changes: 39 additions & 1 deletion payload/usr/local/sal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down Expand Up @@ -65,6 +65,7 @@ def pref(pref_name):
'BasicAuth': True,
'GetGrains': False,
'GetOhai': False,
'LastRunWasOffline': False,
}

pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID)
Expand All @@ -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
Expand Down

0 comments on commit f0fe26d

Please sign in to comment.