mirror of
https://github.com/munki/munki.git
synced 2026-05-12 23:38:50 -05:00
More changes to support showing optional item updates that require user action to install -- like updating macOS version or making disk space available.
This commit is contained in:
@@ -465,6 +465,52 @@ def unmanage(item):
|
||||
user_removal_selections.discard(item['name'])
|
||||
|
||||
|
||||
def getLocalizedShortNoteForItem(item, is_update=False):
|
||||
'''Attempt to localize a note. Currently handle only two types.'''
|
||||
note = item.get('note')
|
||||
if is_update:
|
||||
return NSLocalizedString(u"Update available",
|
||||
u"Update available display text")
|
||||
if note.startswith('Insufficient disk space to download and install'):
|
||||
return NSLocalizedString(u"Not enough disk space",
|
||||
u"Not Enough Disk Space display text")
|
||||
if note.startswith('Requires macOS version '):
|
||||
return NSLocalizedString(u"macOS update required",
|
||||
u"macOS update required text")
|
||||
# we don't know how to localize this note, return None
|
||||
return None
|
||||
|
||||
|
||||
def getLocalizedLongNoteForItem(item, is_update=False):
|
||||
'''Attempt to localize a note. Currently handle only two types.'''
|
||||
note = item.get('note')
|
||||
if note.startswith('Insufficient disk space to download and install'):
|
||||
if is_update:
|
||||
return NSLocalizedString(
|
||||
u"An older version is currently installed. There is not enough "
|
||||
"disk space to download and install this update.",
|
||||
u"Long Not Enough Disk Space For Update display text")
|
||||
else:
|
||||
return NSLocalizedString(
|
||||
u"There is not enough disk space to download and install this "
|
||||
"item.",
|
||||
u"Long Not Enough Disk Space display text")
|
||||
if note.startswith('Requires macOS version '):
|
||||
if is_update:
|
||||
base_string = NSLocalizedString(
|
||||
u"An older version is currently installed. You must upgrade to "
|
||||
"macOS version %s or higher to be able to install this update.",
|
||||
u"Long update requires a higher OS version text")
|
||||
else:
|
||||
base_string = NSLocalizedString(
|
||||
u"You must upgrade to macOS version %s to be able to "
|
||||
"install this item.",
|
||||
u"Long item requires a higher OS version text")
|
||||
os_version = item.get('minimum_os_version', 'UNKNOWN')
|
||||
return base_string % os_version
|
||||
# we don't know how to localize this note, return None
|
||||
return None
|
||||
|
||||
class GenericItem(dict):
|
||||
'''Base class for our types of Munki items'''
|
||||
|
||||
@@ -591,24 +637,16 @@ class GenericItem(dict):
|
||||
# use the Generic package icon
|
||||
return 'static/Generic.png'
|
||||
|
||||
def unavailable_reason_text(self):
|
||||
def unavailable_reason_text(self, is_update=False):
|
||||
'''There are several reasons an item might be unavailable for install.
|
||||
Return the relevent reason'''
|
||||
if ('licensed_seats_available' in self
|
||||
and not self['licensed_seats_available']):
|
||||
return NSLocalizedString(u"No licenses available",
|
||||
u"No Licenses Available display text")
|
||||
if (self.get('note') ==
|
||||
'Insufficient disk space to download and install.'):
|
||||
return NSLocalizedString(u"Not enough disk space",
|
||||
u"Not Enough Disk Space display text")
|
||||
if self.get('note', '').startswith('Requires macOS version '):
|
||||
base_string = NSLocalizedString(
|
||||
u"Requires macOS version %s",
|
||||
u"Item requires a higher OS version text")
|
||||
# this is a bit of a cheat; we should probably store the
|
||||
# minimum_os_version with the install info
|
||||
return base_string % self['note'].split()[-1]
|
||||
localizedNote = getLocalizedShortNoteForItem(self, is_update=is_update)
|
||||
if localizedNote:
|
||||
return '<span class="warning">' + localizedNote + '</span>'
|
||||
# return generic reason
|
||||
return NSLocalizedString(u"Not currently available",
|
||||
u"Not Currently Available display text")
|
||||
@@ -617,6 +655,9 @@ class GenericItem(dict):
|
||||
'''Return localized status display text'''
|
||||
if self['status'] == 'unavailable':
|
||||
return self.unavailable_reason_text()
|
||||
if (self['status'] in ['installed', 'installed-not-removable'] and
|
||||
self.get('note')):
|
||||
return self.unavailable_reason_text(is_update=True)
|
||||
text_map = {
|
||||
'install-error':
|
||||
NSLocalizedString(u"Installation Error",
|
||||
@@ -1008,9 +1049,12 @@ class OptionalItem(GenericItem):
|
||||
start_text += ('<span class="warning">%s</span><br/><br/>'
|
||||
% filtered_html(warning_text))
|
||||
if self.get('note'):
|
||||
# some other note. Probably won't be localized, but we can try
|
||||
warning_text = NSBundle.mainBundle().localizedStringForKey_value_table_(
|
||||
self['note'], self['note'], None)
|
||||
is_update = self['status'] in ['installed', 'installed-not-removable']
|
||||
warning_text = getLocalizedLongNoteForItem(self, is_update=is_update)
|
||||
if not warning_text:
|
||||
# some other note. Probably won't be localized, but we can try
|
||||
warning_text = NSBundle.mainBundle().localizedStringForKey_value_table_(
|
||||
self['note'], self['note'], None)
|
||||
start_text += ('<span class="warning">%s</span><br/><br/>'
|
||||
% filtered_html(warning_text))
|
||||
if self.get('dependent_items'):
|
||||
|
||||
@@ -1243,7 +1243,8 @@ span.removal-error,
|
||||
span.will-be-installed,
|
||||
span.will-be-removed,
|
||||
span.update-will-be-installed,
|
||||
span.update-available {
|
||||
span.update-available,
|
||||
span.warning {
|
||||
visibility: visible !important;
|
||||
color: #CC0000 !important;
|
||||
}
|
||||
|
||||
@@ -534,10 +534,22 @@ def build_updates_page():
|
||||
|
||||
item_list = MunkiItems.getEffectiveUpdateList()
|
||||
|
||||
# find any optional installs with update available
|
||||
other_updates = [
|
||||
item for item in MunkiItems.getOptionalInstallItems()
|
||||
if item['status'] == 'update-available']
|
||||
|
||||
# find any listed optional install updates that require a higher OS
|
||||
# or have insufficent disk space or other blockers (because they have a
|
||||
# note)
|
||||
higher_os_updates = [
|
||||
item for item in MunkiItems.getOptionalInstallItems()
|
||||
if item['status'] == 'installed' and item.get('note')]
|
||||
for item in higher_os_updates:
|
||||
item['hide_cancel_button'] = u'hidden'
|
||||
|
||||
other_updates.extend(higher_os_updates)
|
||||
|
||||
page = {}
|
||||
page['update_rows'] = u''
|
||||
page['hide_progress_spinner'] = u'hidden'
|
||||
|
||||
@@ -157,19 +157,65 @@ def process_optional_install(manifestitem, cataloglist, installinfo):
|
||||
manifestitemname)
|
||||
return
|
||||
|
||||
item_pl = catalogs.get_item_detail(manifestitem, cataloglist,
|
||||
is_optional_item=True)
|
||||
item_pl = catalogs.get_item_detail(manifestitem, cataloglist)
|
||||
if not item_pl:
|
||||
display.display_warning(
|
||||
'Could not process item %s for optional install. No pkginfo found '
|
||||
'in catalogs: %s ', manifestitem, ', '.join(cataloglist))
|
||||
return
|
||||
# could not find an item valid for the current OS and hardware
|
||||
# try again to see if there is an item for a higher OS
|
||||
item_pl = catalogs.get_item_detail(
|
||||
manifestitem, cataloglist, skip_min_os_check=True)
|
||||
if item_pl:
|
||||
# found an item that requires a higher OS version
|
||||
display.display_debug1(
|
||||
'Found %s, version %s that requires a higher os version',
|
||||
item_pl['name'], item_pl['version'])
|
||||
# insert a note about the OS version requirement
|
||||
item_pl['note'] = ('Requires macOS version %s.'
|
||||
% item_pl['minimum_os_version'])
|
||||
item_pl['update_available'] = True
|
||||
else:
|
||||
# could not find anything!
|
||||
display.display_warning(
|
||||
'Could not process item %s for optional install. No pkginfo '
|
||||
'found in catalogs: %s ', manifestitem, ', '.join(cataloglist))
|
||||
return
|
||||
|
||||
is_currently_installed = installationstate.some_version_installed(item_pl)
|
||||
if is_currently_installed and unused_software.should_be_removed(item_pl):
|
||||
process_removal(manifestitem, cataloglist, installinfo)
|
||||
installer.remove_from_selfserve_installs(manifestitem)
|
||||
return
|
||||
needs_update = False
|
||||
if is_currently_installed:
|
||||
if unused_software.should_be_removed(item_pl):
|
||||
process_removal(manifestitem, cataloglist, installinfo)
|
||||
installer.remove_from_selfserve_installs(manifestitem)
|
||||
return
|
||||
if not item_pl.get('OnDemand') and 'installcheck_script' not in item_pl:
|
||||
# installcheck_scripts can be expensive and only tell us if
|
||||
# an item is installed or not. So if iteminfo['installed'] is
|
||||
# True, and we're using an installcheck_script,
|
||||
# installationstate.installed_state is going to return 1
|
||||
# (which does not equal 0), so we can avoid running it again.
|
||||
# We should really revisit all of this in the future to avoid
|
||||
# repeated checks of the same data.
|
||||
needs_update = False
|
||||
else:
|
||||
needs_update = installationstate.installed_state(item_pl) == 0
|
||||
if not needs_update:
|
||||
# the version we have installed is the newest for the current OS.
|
||||
# check again to see if there is a newer version for a higher OS
|
||||
display.display_debug1(
|
||||
'Checking for versions of %s that require a higher OS version',
|
||||
manifestitem)
|
||||
another_item_pl = catalogs.get_item_detail(
|
||||
manifestitem, cataloglist, skip_min_os_check=True)
|
||||
if another_item_pl != item_pl:
|
||||
# we found a different item. Replace the one we found
|
||||
# previously with this one.
|
||||
item_pl = another_item_pl
|
||||
display.display_debug1(
|
||||
'Found %s, version %s that requires a higher os version',
|
||||
item_pl['name'], item_pl['version'])
|
||||
# insert a note about the OS version requirement
|
||||
item_pl['note'] = ('Requires macOS version %s.'
|
||||
% item_pl['minimum_os_version'])
|
||||
item_pl['update_available'] = True
|
||||
|
||||
# if we get to this point we can add this item
|
||||
# to the list of optional installs
|
||||
@@ -183,19 +229,7 @@ def process_optional_install(manifestitem, cataloglist, installinfo):
|
||||
if key in item_pl:
|
||||
iteminfo[key] = item_pl[key]
|
||||
iteminfo['installed'] = is_currently_installed
|
||||
if iteminfo['installed']:
|
||||
if not item_pl.get('OnDemand') and 'installcheck_script' in item_pl:
|
||||
# installcheck_scripts can be expensive and only tell us if
|
||||
# an item is installed or not. So if iteminfo['installed'] is
|
||||
# True, and we're using an installcheck_script,
|
||||
# installationstate.installed_state is going to return 1
|
||||
# (which does not equal 0), so we can avoid running it again.
|
||||
# We should really revisit all of this in the future to avoid
|
||||
# repeated checks of the same data.
|
||||
iteminfo['needs_update'] = False
|
||||
else:
|
||||
iteminfo['needs_update'] = (
|
||||
installationstate.installed_state(item_pl) == 0)
|
||||
iteminfo['needs_update'] = needs_update
|
||||
iteminfo['licensed_seat_info_available'] = item_pl.get(
|
||||
'licensed_seat_info_available', False)
|
||||
iteminfo['uninstallable'] = (
|
||||
@@ -209,15 +243,20 @@ def process_optional_install(manifestitem, cataloglist, installinfo):
|
||||
# catalogs.get_item_detail() passed us a note about this item;
|
||||
# pass it along
|
||||
iteminfo['note'] = item_pl['note']
|
||||
elif (not iteminfo['installed']) or (iteminfo.get('needs_update')):
|
||||
elif needs_update or not is_currently_installed:
|
||||
if not download.enough_disk_space(
|
||||
item_pl, installinfo.get('managed_installs', []), warn=False):
|
||||
iteminfo['note'] = (
|
||||
'Insufficient disk space to download and install.')
|
||||
if needs_update:
|
||||
iteminfo['needs_update'] = False
|
||||
iteminfo['update_available'] = True
|
||||
optional_keys = ['preinstall_alert',
|
||||
'preuninstall_alert',
|
||||
'preupgrade_alert',
|
||||
'OnDemand']
|
||||
'OnDemand',
|
||||
'minimum_os_version',
|
||||
'update_available']
|
||||
for key in optional_keys:
|
||||
if key in item_pl:
|
||||
iteminfo[key] = item_pl[key]
|
||||
|
||||
@@ -414,9 +414,10 @@ def analyze_installed_pkgs():
|
||||
return pkgdata
|
||||
|
||||
|
||||
def get_item_detail(name, cataloglist, vers='', is_optional_item=False):
|
||||
def get_item_detail(name, cataloglist, vers='', skip_min_os_check=False):
|
||||
"""Searches the catalogs in list for an item matching the given name that
|
||||
can be installed on the current hardware/OS
|
||||
can be installed on the current hardware/OS (optionally skipping the
|
||||
minimum OS check so we can return an item that requires a higher OS)
|
||||
|
||||
If no version is supplied, but the version is appended to the name
|
||||
('TextWrangler--2.3.0.0.0') that version is used.
|
||||
@@ -580,11 +581,12 @@ def get_item_detail(name, cataloglist, vers='', is_optional_item=False):
|
||||
(len(indexlist), name, catalogname))
|
||||
for index in indexlist:
|
||||
# iterate through list of items with matching name, highest
|
||||
# version first, looking for one that passes all the conditional
|
||||
# tests (if any)
|
||||
# version first, looking for first one that passes all the
|
||||
# conditional tests (if any)
|
||||
item = _CATALOG[catalogname]['items'][index]
|
||||
if (munki_version_ok(item) and
|
||||
os_version_ok(item) and
|
||||
os_version_ok(item,
|
||||
skip_min_os_check=skip_min_os_check) and
|
||||
cpu_arch_ok(item) and
|
||||
installable_condition_ok(item)):
|
||||
display.display_debug1(
|
||||
@@ -592,29 +594,6 @@ def get_item_detail(name, cataloglist, vers='', is_optional_item=False):
|
||||
item['name'], item['version'], catalogname)
|
||||
return item
|
||||
|
||||
# if we're here, we didn't find an item that matches our machine
|
||||
# restraints. if we are checking for optional installs, check again,
|
||||
# this time returning an item that requires a higher os version
|
||||
# that we can use to incentivize people to update their os
|
||||
if is_optional_item:
|
||||
for index in indexlist:
|
||||
# iterate through list of items with matching name, highest
|
||||
# version first, looking for one that passes all the
|
||||
# conditional tests (if any)
|
||||
item = _CATALOG[catalogname]['items'][index]
|
||||
if (munki_version_ok(item) and
|
||||
os_version_ok(item, skip_min_os_check=True) and
|
||||
cpu_arch_ok(item) and
|
||||
installable_condition_ok(item)):
|
||||
display.display_debug1(
|
||||
'Found %s, version %s in catalog %s that requires '
|
||||
'a higher os version',
|
||||
item['name'], item['version'], catalogname)
|
||||
# insert a note
|
||||
item['note'] = ('Requires macOS version %s.'
|
||||
% item['minimum_os_version'])
|
||||
return item
|
||||
|
||||
# if we got this far, we didn't find it.
|
||||
display.display_debug1('Not found')
|
||||
for reason in rejected_items:
|
||||
|
||||
@@ -237,11 +237,13 @@ def check(client_id='', localmanifestpath=None):
|
||||
|
||||
# build list of items in the optional_installs list
|
||||
# that have not exceeded available seats
|
||||
# and don't have notes (indicating why they can't be installed)
|
||||
available_optional_installs = [
|
||||
item['name']
|
||||
for item in installinfo.get('optional_installs', [])
|
||||
if (not 'licensed_seats_available' in item
|
||||
or item['licensed_seats_available'])]
|
||||
if (not 'note' in item and
|
||||
(not 'licensed_seats_available' in item or
|
||||
item['licensed_seats_available']))]
|
||||
if selfserveinstalls:
|
||||
# filter the list, removing any items not in the current list
|
||||
# of available self-serve installs
|
||||
|
||||
Reference in New Issue
Block a user