Skip to content

Commit

Permalink
Merge pull request #562 from munki/automaticunattended
Browse files Browse the repository at this point in the history
Automatic Apple Updates
  • Loading branch information
gregneagle committed Jan 29, 2016
2 parents 39cf5e8 + cc315f7 commit 7c3ba0f
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 35 deletions.
80 changes: 45 additions & 35 deletions code/client/munkilib/appleupdates.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def __init__(self):
% time.strftime('%Y.%m.%d.%H.%M.%S')))
os.rename(self.cache_dir, new_name)
os.symlink(real_cache_dir, self.cache_dir)
except (OSError, IOError), err:
except (OSError, IOError) as err:
# error in setting up the cache directories
raise Error('Could not configure cache directory: %s' % err)

Expand Down Expand Up @@ -253,10 +253,12 @@ def RewriteProductURLs(self, product, rewrite_pkg_urls=False):
if rewrite_pkg_urls and 'URL' in package:
package['URL'] = self.RewriteURL(package['URL'])
if 'MetadataURL' in package:
package['MetadataURL'] = self.RewriteURL(package['MetadataURL'])
package['MetadataURL'] = self.RewriteURL(
package['MetadataURL'])
distributions = product['Distributions']
for dist_lang in distributions.keys():
distributions[dist_lang] = self.RewriteURL(distributions[dist_lang])
distributions[dist_lang] = self.RewriteURL(
distributions[dist_lang])

def RewriteCatalogURLs(self, catalog, rewrite_pkg_urls=False):
"""Rewrites URLs in a catalog to point to our local replica.
Expand Down Expand Up @@ -291,12 +293,12 @@ def RetrieveURLToCacheDir(self, full_url, copy_only_if_missing=False):
if not os.path.exists(local_dir_path):
try:
os.makedirs(local_dir_path)
except OSError, oserr:
except OSError as oserr:
raise ReplicationError(oserr)
try:
self.GetSoftwareUpdateResource(
full_url, local_file_path, resume=True)
except fetch.MunkiDownloadError, err:
except fetch.MunkiDownloadError as err:
raise ReplicationError(err)
return local_file_path

Expand Down Expand Up @@ -353,7 +355,7 @@ def CacheUpdateMetadata(self):
product_key)
self.RetrieveURLToCacheDir(
package['MetadataURL'], copy_only_if_missing=True)
#if 'URL' in package:
# if 'URL' in package:
# munkicommon.display_status_minor(
# 'Caching package for product ID %s',
# product_key)
Expand Down Expand Up @@ -382,7 +384,7 @@ def CacheUpdateMetadata(self):
if not os.path.exists(self.local_catalog_dir):
try:
os.makedirs(self.local_catalog_dir)
except OSError, oserr:
except OSError as oserr:
raise ReplicationError(oserr)

# rewrite metadata URLs to point to local caches.
Expand Down Expand Up @@ -470,12 +472,11 @@ def GetFirmwareAlertText(self, product_key):
html = readmes[0].firstChild.data
html_data = buffer(html.encode('utf-8'))
attributed_string, attributes = NSAttributedString.alloc(
).initWithHTML_documentAttributes_(html_data, None)
).initWithHTML_documentAttributes_(html_data, None)
firmware_alert_text = attributed_string.string()
return firmware_alert_text
return ''


def GetBlockingApps(self, product_key):
'''Given a product key, finds the cached softwareupdate dist file,
then parses it, looking for must-close apps and converting them to
Expand Down Expand Up @@ -653,7 +654,7 @@ def ExtractAndCopyDownloadedCatalog(self, _open=open):
if not os.path.exists(self.local_catalog_dir):
try:
os.makedirs(self.local_catalog_dir)
except OSError, oserr:
except OSError as oserr:
raise ReplicationError(oserr)

local_apple_sus_catalog = os.path.join(
Expand Down Expand Up @@ -719,13 +720,13 @@ def CacheAppleCatalog(self):
"""
try:
catalog_url = self._GetAppleCatalogURL()
except CatalogNotFoundError, err:
except CatalogNotFoundError as err:
munkicommon.display_error(str(err))
raise
if not os.path.exists(self.temp_cache_dir):
try:
os.makedirs(self.temp_cache_dir)
except OSError, oserr:
except OSError as oserr:
raise ReplicationError(oserr)
msg = 'Checking Apple Software Update catalog...'
self._ResetMunkiStatusAndDisplayMessage(msg)
Expand Down Expand Up @@ -803,8 +804,9 @@ def CheckForSoftwareUpdates(self, force_check=True):
self.CacheAppleCatalog()
except CatalogNotFoundError:
return False
except (ReplicationError, fetch.MunkiDownloadError), err:
munkicommon.display_warning('Could not download Apple SUS catalog:')
except (ReplicationError, fetch.MunkiDownloadError) as err:
munkicommon.display_warning(
'Could not download Apple SUS catalog:')
munkicommon.display_warning('\t%s', str(err))
return False

Expand All @@ -830,7 +832,7 @@ def CheckForSoftwareUpdates(self, force_check=True):
self._WriteFilteredCatalog(product_ids, self.filtered_catalog_path)
try:
self.CacheUpdateMetadata()
except ReplicationError, err:
except ReplicationError as err:
munkicommon.display_warning(
'Could not replicate software update metadata:')
munkicommon.display_warning('\t%s', str(err))
Expand Down Expand Up @@ -1003,7 +1005,7 @@ def _SetCustomCatalogURL(self, catalog_url):
software_update_key_list = CFPreferencesCopyKeyList(
APPLE_SOFTWARE_UPDATE_PREFS_DOMAIN,
kCFPreferencesAnyUser, kCFPreferencesCurrentHost) or []
if not self.ORIGINAL_CATALOG_URL_KEY in software_update_key_list:
if self.ORIGINAL_CATALOG_URL_KEY not in software_update_key_list:
# store the original CatalogURL
original_catalog_url = self._GetCatalogURL()
if not original_catalog_url:
Expand Down Expand Up @@ -1043,7 +1045,7 @@ def _ResetOriginalCatalogURL(self):
software_update_key_list = CFPreferencesCopyKeyList(
APPLE_SOFTWARE_UPDATE_PREFS_DOMAIN,
kCFPreferencesAnyUser, kCFPreferencesCurrentHost) or []
if not self.ORIGINAL_CATALOG_URL_KEY in software_update_key_list:
if self.ORIGINAL_CATALOG_URL_KEY not in software_update_key_list:
# do nothing
return
original_catalog_url = CFPreferencesCopyValue(
Expand Down Expand Up @@ -1127,7 +1129,7 @@ def _LeopardDownloadAvailableUpdates(self, catalog_url):
# set mode of Software Update.app executable so it won't launch
# yes, this is a hack. So sue me.
os.chmod(softwareupdateappbin, 0)
except OSError, err:
except OSError as err:
munkicommon.display_warning(
'Error with os.stat(Softare Update.app): %s', str(err))
munkicommon.display_warning('Skipping Apple SUS check.')
Expand All @@ -1146,7 +1148,7 @@ def _LeopardDownloadAvailableUpdates(self, catalog_url):
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except OSError, err:
except OSError as err:
munkicommon.display_warning('Error with Popen(%s): %s', cmd, err)
munkicommon.display_warning('Skipping Apple SUS check.')
# safely revert the chmod from above.
Expand All @@ -1160,7 +1162,7 @@ def _LeopardDownloadAvailableUpdates(self, catalog_url):
while True:
output = proc.stdout.readline().decode('UTF-8')
if munkicommon.stopRequested():
os.kill(proc.pid, 15) #15 is SIGTERM
os.kill(proc.pid, 15) # 15 is SIGTERM
break
if not output and (proc.poll() != None):
break
Expand Down Expand Up @@ -1210,7 +1212,7 @@ def _RunSoftwareUpdate(
Returns:
Integer softwareupdate exit code.
"""
if results == None:
if results is None:
# we're not interested in the results,
# but need to create a temporary dict anyway
results = {}
Expand All @@ -1222,7 +1224,9 @@ def _RunSoftwareUpdate(
# Try to find our ptyexec tool
# first look in the parent directory of this file's directory
# (../)
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
parent_dir = os.path.dirname(
os.path.dirname(
os.path.abspath(__file__)))
ptyexec_path = os.path.join(parent_dir, 'ptyexec')
if not os.path.exists(ptyexec_path):
# try absolute path in munki's normal install dir
Expand Down Expand Up @@ -1254,7 +1258,7 @@ def _RunSoftwareUpdate(
try:
job = launchd.Job(cmd)
job.start()
except launchd.LaunchdJobException, err:
except launchd.LaunchdJobException as err:
munkicommon.display_warning(
'Error with launchd job (%s): %s', cmd, str(err))
munkicommon.display_warning('Skipping softwareupdate run.')
Expand Down Expand Up @@ -1368,7 +1372,8 @@ def _RunSoftwareUpdate(
retcode = job.returncode()
if retcode == 0:
# get SoftwareUpdate's LastResultCode
last_result_code = self.GetSoftwareUpdatePref('LastResultCode') or 0
last_result_code = self.GetSoftwareUpdatePref(
'LastResultCode') or 0
if last_result_code > 2:
retcode = last_result_code

Expand Down Expand Up @@ -1403,7 +1408,7 @@ def InstallAppleUpdates(self, only_unattended=False):
# ensure that we don't restart for unattended installations
restartneeded = False
if not unattended_install_items:
return False # didn't find any unattended installs
return False # didn't find any unattended installs
else:
msg = 'Installing available Apple Software Updates...'
restartneeded = self.IsRestartNeeded()
Expand All @@ -1418,7 +1423,7 @@ def InstallAppleUpdates(self, only_unattended=False):
return False # didn't do anything, so no restart needed

installlist = self.GetSoftwareUpdateInfo()
installresults = {'installed':[], 'download':[]}
installresults = {'installed': [], 'download': []}

catalog_url = 'file://localhost' + urllib2.quote(
self.local_catalog_path)
Expand Down Expand Up @@ -1537,7 +1542,8 @@ def AppleSoftwareUpdatesAvailable(
'LastAppleSoftwareUpdateCheck')
if last_su_check_string:
try:
last_su_check = NSDate.dateWithString_(last_su_check_string)
last_su_check = NSDate.dateWithString_(
last_su_check_string)
# dateWithString_ returns None if invalid date string.
if not last_su_check:
raise ValueError
Expand Down Expand Up @@ -1591,12 +1597,12 @@ def copyUpdateMetadata(self, item, metadata):
# Mapping of supported RestartActions to
# equal or greater auxiliary actions
RestartActions = {
'RequireRestart' : ['RequireRestart', 'RecommendRestart'],
'RequireRestart': ['RequireRestart', 'RecommendRestart'],
'RecommendRestart': ['RequireRestart', 'RecommendRestart'],
'RequireLogout' : ['RequireRestart', 'RecommendRestart',
'RequireLogout'],
'None' : ['RequireRestart', 'RecommendRestart',
'RequireLogout']
'RequireLogout': ['RequireRestart', 'RecommendRestart',
'RequireLogout'],
'None': ['RequireRestart', 'RecommendRestart',
'RequireLogout']
}

for key in metadata:
Expand Down Expand Up @@ -1651,8 +1657,12 @@ def GetUnattendedInstalls(self):
'Error reading: %s', self.apple_updates_plist)
return item_list, product_ids
apple_updates = pl_dict.get('AppleUpdates', [])
os_version_tuple = munkicommon.getOsVersion(as_tuple=True)
for item in apple_updates:
if item.get('unattended_install'):
if (item.get('unattended_install') or
(munkicommon.pref('UnattendedAppleUpdates') and
item.get('RestartAction', 'None') is 'None' and
os_version_tuple >= (10, 10))):
if munkicommon.blockingApplicationsRunning(item):
munkicommon.display_detail(
'Skipping unattended install of %s because '
Expand All @@ -1669,8 +1679,8 @@ def GetUnattendedInstalls(self):
return item_list, product_ids



# Make the new appleupdates module easily dropped in with exposed funcs for now.
# Make the new appleupdates module easily dropped in with exposed funcs
# for now.

apple_updates_object = None

Expand Down
1 change: 1 addition & 0 deletions code/client/munkilib/munkicommon.py
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ def pref(pref_name):
'SuppressStopButtonOnInstall': False,
'PackageVerificationMode': 'hash',
'FollowHTTPRedirects': 'none',
'UnattendedAppleUpdates': False,
}
pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID)
if pref_value == None:
Expand Down

0 comments on commit 7c3ba0f

Please sign in to comment.