Files
munki/code/client/munkilib/updatecheck/installationstate.py
2020-01-01 08:53:37 -08:00

299 lines
11 KiB
Python

# encoding: utf-8
#
# Copyright 2009-2020 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
updatecheck.installationstate
Created by Greg Neagle on 2017-01-01.
Utilities for determining installation status for Munki items.
"""
from __future__ import absolute_import, print_function
import os
from . import catalogs
from . import compare
from .. import display
from .. import osutils
from .. import profiles
from .. import scriptutils
from .. import utils
from ..wrappers import unicode_or_str
def installed_state(item_pl):
"""Checks to see if the item described by item_pl (or a newer version) is
currently installed
All tests must pass to be considered installed.
Returns 1 if it looks like this version is installed
Returns 2 if it looks like a newer version is installed.
Returns 0 otherwise.
"""
foundnewer = False
if item_pl.get('OnDemand'):
# always install these items -- retcode 0 means install is needed
display.display_debug1('This is an OnDemand item. Must install.')
return 0
if item_pl.get('installcheck_script'):
retcode = scriptutils.run_embedded_script(
'installcheck_script', item_pl, suppress_error=True)
display.display_debug1('installcheck_script returned %s', retcode)
# retcode 0 means install is needed
if retcode == 0:
return 0
# non-zero could be an error or successfully indicating
# that an install is not needed. We hope it's the latter.
# return 1 so we're marked as not needing to be installed
return 1
# this was deprecated a very long time ago. removing 02 Jan 2017
#if item_pl.get('softwareupdatename'):
# available_apple_updates = appleupdates.softwareUpdateList()
# display.display_debug2(
# 'Available Apple updates:\n%s', available_apple_updates)
# if item_pl['softwareupdatename'] in available_apple_updates:
# display.display_debug1(
# '%s is in available Apple Software Updates',
# item_pl['softwareupdatename'])
# # return 0 so we're marked as needing to be installed
# return 0
# else:
# display.display_debug1(
# '%s is not in available Apple Software Updates',
# item_pl['softwareupdatename'])
# # return 1 so we're marked as not needing to be installed
# return 1
if item_pl.get('installer_type') == 'startosinstall':
current_os_vers = osutils.getOsVersion()
item_os_vers = item_pl.get('version')
# need just major.minor part of the version -- 10.12 and not 10.12.4
item_os_vers = '.'.join(item_os_vers.split('.')[0:2])
comparison = compare.compare_versions(current_os_vers, item_os_vers)
if comparison == compare.VERSION_IS_LOWER:
return 0
if comparison == compare.VERSION_IS_HIGHER:
return 2
# version is the same
return 1
if item_pl.get('installer_type') == 'profile':
identifier = item_pl.get('PayloadIdentifier')
hash_value = item_pl.get('installer_item_hash')
if profiles.profile_needs_to_be_installed(identifier, hash_value):
return 0
# does not need to be installed
return 1
# does 'installs' exist and is it non-empty?
if item_pl.get('installs', None):
installitems = item_pl['installs']
for item in installitems:
try:
comparison = compare.compare_item_version(item)
if comparison in (-1, 0):
return 0
elif comparison == 2:
# this item is newer
foundnewer = True
except utils.Error as err:
# some problem with the installs data
display.display_error(unicode_or_str(err))
# return 1 so we're marked as not needing to be installed
return 1
# if there is no 'installs' key, then we'll use receipt info
# to determine install status.
elif 'receipts' in item_pl:
receipts = item_pl['receipts']
for item in receipts:
try:
comparison = compare.compare_receipt_version(item)
if comparison in (-1, 0):
# not there or older
return 0
elif comparison == 2:
foundnewer = True
except utils.Error as err:
# some problem with the receipts data
display.display_error(unicode_or_str(err))
# return 1 so we're marked as not needing to be installed
return 1
# if we got this far, we passed all the tests, so the item
# must be installed (or we don't have enough info...)
if foundnewer:
return 2
# not newer
return 1
def some_version_installed(item_pl):
"""Checks to see if some version of an item is installed.
Args:
item_pl: item plist for the item to check for version of.
Returns a boolean.
"""
if item_pl.get('OnDemand'):
# These should never be counted as installed
display.display_debug1('This is an OnDemand item.')
return False
if item_pl.get('installcheck_script'):
retcode = scriptutils.run_embedded_script(
'installcheck_script', item_pl, suppress_error=True)
display.display_debug1(
'installcheck_script returned %s', retcode)
# retcode 0 means install is needed
# (ie, item is not installed)
if retcode == 0:
return False
# non-zero could be an error or successfully indicating
# that an install is not needed. We hope it's the latter.
return True
if item_pl.get('installer_type') == 'startosinstall':
# Some version of macOS is always installed!
return True
if item_pl.get('installer_type') == 'profile':
identifier = item_pl.get('PayloadIdentifier')
return profiles.profile_is_installed(identifier)
# does 'installs' exist and is it non-empty?
if item_pl.get('installs'):
installitems = item_pl['installs']
# check each item for existence
for item in installitems:
try:
if compare.compare_item_version(item) == 0:
# not there
return False
except utils.Error as err:
# some problem with the installs data
display.display_error(unicode_or_str(err))
return False
# if there is no 'installs' key, then we'll use receipt info
# to determine install status.
elif 'receipts' in item_pl:
receipts = item_pl['receipts']
for item in receipts:
try:
if compare.compare_receipt_version(item) == 0:
# not there
return False
except utils.Error as err:
# some problem with the installs data
display.display_error(unicode_or_str(err))
return False
# if we got this far, we passed all the tests, so the item
# must be installed (or we don't have enough info...)
return True
def evidence_this_is_installed(item_pl):
"""Checks to see if there is evidence that the item described by item_pl
(any version) is currently installed.
If any tests pass, the item might be installed.
This is used when determining if we can remove the item, thus
the attention given to the uninstall method.
Returns a boolean.
"""
if item_pl.get('OnDemand'):
# These should never be counted as installed
display.display_debug1('This is an OnDemand item.')
return False
if item_pl.get('uninstallcheck_script'):
retcode = scriptutils.run_embedded_script(
'uninstallcheck_script', item_pl, suppress_error=True)
display.display_debug1(
'uninstallcheck_script returned %s', retcode)
# retcode 0 means uninstall is needed
# (ie, item is installed)
if retcode == 0:
return True
# non-zero could be an error or successfully indicating
# that an uninstall is not needed
return False
if item_pl.get('installcheck_script'):
retcode = scriptutils.run_embedded_script(
'installcheck_script', item_pl, suppress_error=True)
display.display_debug1(
'installcheck_script returned %s', retcode)
# retcode 0 means install is needed
# (ie, item is not installed)
if retcode == 0:
return False
# non-zero could be an error or successfully indicating
# that an install is not needed
return True
if item_pl.get('installer_type') == 'startosinstall':
# Some version of macOS is always installed!
return True
if item_pl.get('installer_type') == 'profile':
identifier = item_pl.get('PayloadIdentifier')
return profiles.profile_is_installed(identifier)
foundallinstallitems = False
if ('installs' in item_pl and
item_pl.get('uninstall_method') != 'removepackages'):
display.display_debug2("Checking 'installs' items...")
installitems = item_pl['installs']
if installitems:
foundallinstallitems = True
for item in installitems:
if 'path' in item:
# we can only check by path; if the item has been moved
# we're not clever enough to find it, and our removal
# methods are currently even less clever
if not os.path.exists(item['path']):
# this item isn't on disk
display.display_debug2(
'%s not found on disk.', item['path'])
foundallinstallitems = False
if (foundallinstallitems and
item_pl.get('uninstall_method') != 'removepackages'):
return True
if item_pl.get('receipts'):
display.display_debug2("Checking receipts...")
pkgdata = catalogs.analyze_installed_pkgs()
if item_pl['name'] in pkgdata['installed_names']:
return True
else:
display.display_debug2("Installed receipts don't match.")
# if we got this far, we failed all the tests, so the item
# must not be installed (or we don't have the right info...)
return False
if __name__ == '__main__':
print('This is a library of support tools for the Munki Suite.')