Initial commit adding client-side support for 'apple_update_metadata'

Provided that 'AppleSoftwareUpdatesOnly' preference is False, primary
client manifest's catalogs are parsed for matching
'apple_update_metadata' and applied to pending Apple updates.

Please note that not ALL keys presented by an apple_update_metadata
item are applied as to not either clobber or complicate the listing of
items provided in 'AppleUpdates.plist'.  The current list of excluded
keys is as follows:

catalogs
installed_size
installer_type
name
version
version_to_install
This commit is contained in:
Heig Gregorian
2013-02-12 13:23:45 -08:00
parent e25b5aacf7
commit 9f8cd3a975
3 changed files with 106 additions and 7 deletions
+2 -2
View File
@@ -702,7 +702,7 @@ def main():
try:
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(
forcecheck=force_update_check)
forcecheck=force_update_check, client_id=options.id)
except:
munkicommon.display_error('Unexpected error in appleupdates:')
munkicommon.log(traceback.format_exc())
@@ -715,7 +715,7 @@ def main():
try:
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(
suppresscheck=True)
suppresscheck=True, client_id=options.id)
except:
munkicommon.display_error('Unexpected error in appleupdates:')
munkicommon.log(traceback.format_exc())
+72 -1
View File
@@ -47,6 +47,7 @@ import fetch
import launchd
import munkicommon
import munkistatus
import updatecheck
# Apple Software Update Catalog URLs.
@@ -172,6 +173,10 @@ class AppleUpdates(object):
self.local_catalog_dir, LOCAL_DOWNLOAD_CATALOG_NAME)
self._update_list_cache = None
# apple_update_metadata support
self.apple_md = {}
self.client_id = CLIENT_ID
def _ResetMunkiStatusAndDisplayMessage(self, message):
"""Resets MunkiStatus detail/percent, logs and msgs GUI.
@@ -825,8 +830,33 @@ class AppleUpdates(object):
Returns:
Boolean. True if apple updates was updated, False otherwise.
"""
metadata_exclusions = ['catalogs',
'installed_size',
'installer_type',
'name',
'version',
'version_to_install']
apple_updates = self.GetSoftwareUpdateInfo()
if apple_updates:
# Process update metadata only if AppleSoftwareUpdatesOnly is false
if not munkicommon.pref('AppleSoftwareUpdatesOnly'):
self.apple_md = updatecheck.check(
self.client_id, apple_update_md_only=True)
for update in apple_updates:
matching_items = self.getAllItemsWithProductKey(
update['productKey'])
if matching_items:
update_metadata = matching_items[0]
for key in update_metadata:
# Don't overwrite items in exclusions list
if key in metadata_exclusions:
continue
else:
# Apply non-empty metadata
if update_metadata[key]:
munkicommon.display_debug2(
'Applying %s to %s...' % (key, update['display_name']))
update[key] = update_metadata[key]
plist = {'AppleUpdates': apple_updates}
FoundationPlist.writePlist(plist, self.apple_updates_plist)
return True
@@ -1297,6 +1327,45 @@ class AppleUpdates(object):
return updates
def getAllItemsWithProductKey(self, productKey):
"""Searches apple_md for all items matching a given Apple productKey
Returns:
list of pkginfo items; sorted with newest version first. No precedence
is given to catalog order.
"""
def compare_item_versions(a, b):
"""Internal comparison function for use with sorting"""
return cmp(munkicommon.MunkiLooseVersion(b['version']),
munkicommon.MunkiLooseVersion(a['version']))
itemlist = []
munkicommon.display_debug1(
'Looking for metadata matching: %s...' % productKey)
# is productKey in the catalog name table?
for catalogname in self.apple_md.keys():
if productKey in self.apple_md[catalogname]['named']:
versionsmatchingproductKey = \
self.apple_md[catalogname]['named'][productKey]
for vers in versionsmatchingproductKey.keys():
if vers != 'latest':
indexlist = \
self.apple_md[catalogname]['named'][productKey][vers]
for index in indexlist:
thisitem = self.apple_md[catalogname]['items'][index]
if not thisitem in itemlist:
munkicommon.display_debug1(
'Adding item %s, version %s from catalog %s...' %
(productKey, thisitem['version'], catalogname))
itemlist.append(thisitem)
if itemlist:
# sort so latest version is first
itemlist.sort(compare_item_versions)
return itemlist
# Make the new appleupdates module easily dropped in with exposed funcs for now.
@@ -1326,8 +1395,10 @@ def installAppleUpdates():
return getAppleUpdatesInstance().InstallAppleUpdates()
def appleSoftwareUpdatesAvailable(forcecheck=False, suppresscheck=False):
def appleSoftwareUpdatesAvailable(forcecheck=False, suppresscheck=False, client_id=''):
"""Method for drop-in appleupdates replacement; see primary method docs."""
global CLIENT_ID
CLIENT_ID = client_id
return getAppleUpdatesInstance().AppleSoftwareUpdatesAvailable(
force_check=forcecheck, suppress_check=suppresscheck)
+32 -4
View File
@@ -44,12 +44,21 @@ from Foundation import NSDate, NSPredicate, NSTimeZone
# This many hours before a force install deadline, start notifying the user.
FORCE_INSTALL_WARNING_HOURS = 4
# Flag denoting a check from appleupdates for apple_update_metadata
APPLE_UPDATE_MD_ONLY = False
def makeCatalogDB(catalogitems):
"""Takes an array of catalog items and builds some indexes so we can
get our common data faster. Returns a dict we can use like a database"""
name_table = {}
pkgid_table = {}
# Filter catalogitems so that they only contain apple_update_metadata items
if APPLE_UPDATE_MD_ONLY:
catalogitems = [item for item in catalogitems
if item.get('installer_type') ==
'apple_update_metadata']
itemindex = -1
for item in catalogitems:
itemindex = itemindex + 1
@@ -2609,11 +2618,14 @@ def getDownloadCachePath(destinationpathprefix, url):
MACHINE = {}
CONDITIONS = {}
def check(client_id='', localmanifestpath=None):
def check(client_id='', localmanifestpath=None, apple_update_md_only=False):
"""Checks for available new or updated managed software, downloading
installer items if needed. Returns 1 if there are available updates,
0 if there are no available updates, and -1 if there were errors."""
global APPLE_UPDATE_MD_ONLY
APPLE_UPDATE_MD_ONLY = apple_update_md_only
global MACHINE
munkicommon.getMachineFacts()
MACHINE = munkicommon.getMachineFacts()
@@ -2627,9 +2639,6 @@ def check(client_id='', localmanifestpath=None):
if munkicommon.munkistatusoutput:
munkistatus.activate()
munkicommon.log('### Beginning managed software check ###')
munkicommon.display_status_major('Checking for available updates...')
if localmanifestpath:
mainmanifestpath = localmanifestpath
else:
@@ -2639,6 +2648,14 @@ def check(client_id='', localmanifestpath=None):
installinfo = {}
if APPLE_UPDATE_MD_ONLY:
munkicommon.display_detail('**Checking for Apple Update Metadata**')
print munkicommon.pref('AppleSoftwareUpdatesOnly')
return getAppleUpdateMetaData(mainmanifestpath)
munkicommon.log('### Beginning managed software check ###')
munkicommon.display_status_major('Checking for available updates...')
if mainmanifestpath:
# initialize our installinfo record
installinfo['processed_installs'] = []
@@ -3123,6 +3140,17 @@ def getResourceIfChangedAtomically(url,
verify=verify)
def getAppleUpdateMetaData(manifest):
global CATALOG
CATALOG = {}
if manifest:
manifestdata = getManifestData(manifest)
cataloglist = manifestdata.get('catalogs')
if cataloglist:
getCatalogs(cataloglist)
return CATALOG
def main():
"""Placeholder"""
pass