Merge branch 'master' into installer-copy_as

This commit is contained in:
Heig Gregorian
2012-06-29 11:04:07 -07:00
6 changed files with 282 additions and 126 deletions

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.5.0</string>
<string>3.5.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -425,7 +425,7 @@ def getRunningBlockingApps(appnames):
if appname.endswith('.app'):
# search by filename
matching_items = [item for item in proc_list
if '/'+ appname + '/' in item]
if '/'+ appname + '/Contents/MacOS/' in item]
else:
# check executable name
matching_items = [item for item in proc_list
@@ -434,7 +434,7 @@ def getRunningBlockingApps(appnames):
if not matching_items:
# try adding '.app' to the name and check again
matching_items = [item for item in proc_list
if '/' + appname + '.app/' in item]
if '/' + appname + '.app/Contents/MacOS/' in item]
matching_items = set(matching_items)
for path in matching_items:

View File

@@ -612,6 +612,14 @@ def main():
itemhash = munkicommon.getsha256hash(item)
if item.endswith('.dmg'):
if munkicommon.DMGisWritable(item):
print >> sys.stderr, ("WARNING: %s is a writable disk "
"image. Checksum verification is not supported."
% item)
print >> sys.stderr, ("WARNING: Consider converting "
"%s to a read-only disk image."
% item)
itemhash = "N/A"
catinfo = getCatalogInfoFromDmg(item, options)
if (catinfo and
catinfo.get('installer_type') == "AdobeCS5Installer"):
@@ -668,7 +676,8 @@ def main():
catinfo['version'] = options.pkgvers
catinfo['installer_item_size'] = int(itemsize/1024)
catinfo['installer_item_hash'] = itemhash
if itemhash != "N/A":
catinfo['installer_item_hash'] = itemhash
# try to generate the correct item location
temppath = item

View File

@@ -596,7 +596,7 @@ def installWithInfo(
retcode = 0
if 'preinstall_script' in item:
retcode = runEmbeddedScript('preinstall_script', item)
retcode = munkicommon.runEmbeddedScript('preinstall_script', item)
if retcode == 0 and 'installer_item' in item:
display_name = item.get('display_name') or item.get('name')
@@ -722,7 +722,8 @@ def installWithInfo(
if retcode == 0 and 'postinstall_script' in item:
# only run embedded postinstall script if the install did not
# return a failure code
retcode = runEmbeddedScript('postinstall_script', item)
retcode = munkicommon.runEmbeddedScript(
'postinstall_script', item)
if retcode:
# we won't consider postinstall script failures as fatal
# since the item has been installed via package/disk image
@@ -820,98 +821,6 @@ def installWithInfo(
return (restartflag, skipped_installs)
def writefile(stringdata, path):
'''Writes string data to path.
Returns the path on success, empty string on failure.'''
try:
fileobject = open(path, mode='w', buffering=1)
print >> fileobject, stringdata.encode('UTF-8')
fileobject.close()
return path
except (OSError, IOError):
munkicommon.display_error("Couldn't write %s" % stringdata)
return ""
def runEmbeddedScript(scriptname, pkginfo_item):
'''Runs a script embedded in the pkginfo.
Returns the result code.'''
# get the script text from the pkginfo
script_text = pkginfo_item.get(scriptname)
itemname = pkginfo_item.get('name')
if not script_text:
munkicommon.display_error(
'Missing script %s for %s' % (scriptname, itemname))
return -1
# write the script to a temp file
scriptpath = os.path.join(munkicommon.tmpdir, scriptname)
if writefile(script_text, scriptpath):
cmd = ['/bin/chmod', '-R', 'o+x', scriptpath]
retcode = subprocess.call(cmd)
if retcode:
munkicommon.display_error(
'Error setting script mode in %s for %s'
% (scriptname, itemname))
return -1
else:
munkicommon.display_error(
'Cannot write script %s for %s' % (scriptname, itemname))
return -1
# now run the script
return runScript(itemname, scriptpath, scriptname)
def runScript(itemname, path, scriptname):
'''Runs a script, Returns return code.'''
munkicommon.display_status_minor(
'Running %s for %s ' % (scriptname, itemname))
if munkicommon.munkistatusoutput:
# set indeterminate progress bar
munkistatus.percent(-1)
scriptoutput = []
try:
proc = subprocess.Popen(path, shell=False, bufsize=1,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except OSError, e:
munkicommon.display_error(
'Error executing script %s: %s' % (scriptname, str(e)))
return -1
while True:
msg = proc.stdout.readline().decode('UTF-8')
if not msg and (proc.poll() != None):
break
# save all script output in case there is
# an error so we can dump it to the log
scriptoutput.append(msg)
msg = msg.rstrip("\n")
munkicommon.display_info(msg)
retcode = proc.poll()
if retcode:
munkicommon.display_error(
'Running %s for %s failed.' % (scriptname, itemname))
munkicommon.display_error("-"*78)
for line in scriptoutput:
munkicommon.display_error("\t%s" % line.rstrip("\n"))
munkicommon.display_error("-"*78)
else:
munkicommon.log(
'Running %s for %s was successful.' % (scriptname, itemname))
if munkicommon.munkistatusoutput:
# clear indeterminate progress bar
munkistatus.percent(0)
return retcode
def skippedItemsThatRequireThisItem(item, skipped_items):
'''Looks for items in the skipped_items that require or are update_for
the current item. Returns a list of matches.'''
@@ -977,7 +886,7 @@ def processRemovals(removallist, only_unattended=False):
retcode = 0
# run preuninstall_script if it exists
if 'preuninstall_script' in item:
retcode = runEmbeddedScript('preuninstall_script', item)
retcode = munkicommon.runEmbeddedScript('preuninstall_script', item)
if retcode == 0 and 'uninstall_method' in item:
uninstallmethod = item['uninstall_method']
@@ -1022,7 +931,8 @@ def processRemovals(removallist, only_unattended=False):
name)
elif uninstallmethod == 'uninstall_script':
retcode = runEmbeddedScript('uninstall_script', item)
retcode = munkicommon.runEmbeddedScript(
'uninstall_script', item)
if (retcode == 0 and
item.get('RestartAction') == "RequireRestart"):
restartFlag = True
@@ -1030,7 +940,7 @@ def processRemovals(removallist, only_unattended=False):
elif os.path.exists(uninstallmethod) and \
os.access(uninstallmethod, os.X_OK):
# it's a script or program to uninstall
retcode = runScript(
retcode = munkicommon.runScript(
name, uninstallmethod, 'uninstall script')
if (retcode == 0 and
item.get('RestartAction') == "RequireRestart"):
@@ -1043,7 +953,8 @@ def processRemovals(removallist, only_unattended=False):
retcode = -99
if retcode == 0 and item.get('postuninstall_script'):
retcode = runEmbeddedScript('postuninstall_script', item)
retcode = munkicommon.runEmbeddedScript(
'postuninstall_script', item)
if retcode:
# we won't consider postuninstall script failures as fatal
# since the item has been uninstalled

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2011 Greg Neagle.
# Copyright 2009-2012 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -44,10 +44,12 @@ from types import StringType
from xml.dom import minidom
from Foundation import NSArray, NSDate, NSMetadataQuery, NSPredicate, NSRunLoop
from Foundation import CFPreferencesCopyAppValue
from Foundation import CFPreferencesSetValue
from Foundation import CFPreferencesAppSynchronize
from Foundation import CFPreferencesCopyAppValue
from Foundation import CFPreferencesCopyKeyList
from Foundation import CFPreferencesSetValue
from Foundation import kCFPreferencesAnyUser
from Foundation import kCFPreferencesCurrentUser
from Foundation import kCFPreferencesCurrentHost
import munkistatus
@@ -469,7 +471,7 @@ def validateDateFormat(datetime_string):
formatted_datetime_string = ''
try:
formatted_datetime_string = time.strftime(
'%Y-%m-%dT%H:%M:%SZ', time.strptime(datetime_string,
'%Y-%m-%dT%H:%M:%SZ', time.strptime(datetime_string,
'%Y-%m-%dT%H:%M:%SZ'))
except:
pass
@@ -547,7 +549,7 @@ def saveappdata():
os.path.join(
pref('ManagedInstallDir'), 'ApplicationInventory.plist'))
except FoundationPlist.NSPropertyListSerializationException, err:
munkicommon.display_warning(
display_warning(
'Unable to save inventory report: %s' % err)
@@ -755,6 +757,27 @@ def getFirstPlist(textString):
# dmg helpers
def DMGisWritable(dmgpath):
'''Attempts to determine if the given disk image is writable'''
proc = subprocess.Popen(
['/usr/bin/hdiutil', 'imageinfo', dmgpath, '-plist'],
bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate()
if err:
print >> sys.stderr, (
'hdiutil error %s with image %s.' % (err, dmgpath))
(pliststr, out) = getFirstPlist(out)
if pliststr:
try:
plist = FoundationPlist.readPlistFromString(pliststr)
format = plist.get('Format')
if format in ['UDSB', 'UDSP', 'UDRW', 'RdWr']:
return True
except FoundationPlist.NSPropertyListSerializationException:
pass
return False
def DMGhasSLA(dmgpath):
'''Returns true if dmg has a Software License Agreement.
These dmgs normally cannot be attached without user intervention'''
@@ -907,6 +930,65 @@ def isApplication(pathname):
# managed installs preferences/metadata
#####################################################
class Preferences(object):
"""Class which directly reads/writes Apple CF preferences."""
def __init__(self, bundle_id, user=kCFPreferencesAnyUser):
"""Init.
Args:
bundle_id: str, like 'ManagedInstalls'
"""
if bundle_id.endswith('.plist'):
bundle_id = bundle_id[:-6]
self.bundle_id = bundle_id
self.user = user
def __iter__(self):
keys = CFPreferencesCopyKeyList(
self.bundle_id, self.user, kCFPreferencesCurrentHost)
if keys is not None:
for i in keys:
yield i
def __contains__(self, pref_name):
pref_value = CFPreferencesCopyAppValue(pref_name, self.bundle_id)
return pref_value is not None
def __getitem__(self, pref_name):
return CFPreferencesCopyAppValue(pref_name, self.bundle_id)
def __setitem__(self, pref_name, pref_value):
CFPreferencesSetValue(
pref_name, pref_value, self.bundle_id, self.user,
kCFPreferencesCurrentHost)
CFPreferencesAppSynchronize(self.bundle_id)
def __delitem__(self, pref_name):
self.__setitem__(pref_name, None)
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, self.bundle_id)
def get(self, pref_name, default=None):
if not pref_name in self:
return default
else:
return self.__getitem__(pref_name)
class ManagedInstallsPreferences(Preferences):
"""Preferences which read from /L/P/ManagedInstalls."""
def __init__(self):
Preferences.__init__(self, 'ManagedInstalls', kCFPreferencesAnyUser)
class SecureManagedInstallsPreferences(Preferences):
"""Preferences which read from /private/var/root/L/P/ManagedInstalls."""
def __init__(self):
Preferences.__init__(self, 'ManagedInstalls', kCFPreferencesCurrentUser)
def reload_prefs():
"""Uses CFPreferencesAppSynchronize(BUNDLE_ID)
to make sure we have the latest prefs. Call this
@@ -1530,10 +1612,10 @@ def getChoiceChangesXML(pkgitem):
(out, unused_err) = proc.communicate()
if out:
plist = FoundationPlist.readPlistFromString(out)
# list comprehension to populate choices with those items
# whose 'choiceAttribute' value is 'selected'
choices = [item for item in plist
choices = [item for item in plist
if 'selected' in item['choiceAttribute']]
except:
# No choices found or something went wrong
@@ -2024,7 +2106,7 @@ def getConditions():
which can be placed into /usr/local/munki/conditions"""
global CONDITIONS
if not CONDITIONS:
# define path to conditions directory which would contain
# define path to conditions directory which would contain
# admin created scripts
scriptdir = os.path.realpath(os.path.dirname(sys.argv[0]))
conditionalscriptdir = os.path.join(scriptdir, "conditions")
@@ -2058,13 +2140,13 @@ def getConditions():
else:
# /usr/local/munki/conditions does not exist
pass
if (os.path.exists(conditionalitemspath) and
if (os.path.exists(conditionalitemspath) and
validPlist(conditionalitemspath)):
# import conditions into CONDITIONS dict
CONDITIONS = FoundationPlist.readPlist(conditionalitemspath)
os.unlink(conditionalitemspath)
else:
# either ConditionalItems.plist does not exist
# either ConditionalItems.plist does not exist
# or does not pass validation
CONDITIONS = {}
return CONDITIONS
@@ -2079,7 +2161,7 @@ def isAppRunning(appname):
if appname.endswith('.app'):
# search by filename
matching_items = [item for item in proc_list
if '/'+ appname + '/' in item]
if '/'+ appname + '/Contents/MacOS/' in item]
else:
# check executable name
matching_items = [item for item in proc_list
@@ -2087,7 +2169,7 @@ def isAppRunning(appname):
if not matching_items:
# try adding '.app' to the name and check again
matching_items = [item for item in proc_list
if '/'+ appname + '.app/' in item]
if '/'+ appname + '.app/Contents/MacOS/' in item]
if matching_items:
# it's running!
@@ -2208,6 +2290,100 @@ def findProcesses(user=None, exe=None):
return pids
# utility functions for running scripts from pkginfo files
# used by updatecheck.py and installer.py
def writefile(stringdata, path):
'''Writes string data to path.
Returns the path on success, empty string on failure.'''
try:
fileobject = open(path, mode='w', buffering=1)
print >> fileobject, stringdata.encode('UTF-8')
fileobject.close()
return path
except (OSError, IOError):
display_error("Couldn't write %s" % stringdata)
return ""
def runEmbeddedScript(scriptname, pkginfo_item, suppress_error=False):
'''Runs a script embedded in the pkginfo.
Returns the result code.'''
# get the script text from the pkginfo
script_text = pkginfo_item.get(scriptname)
itemname = pkginfo_item.get('name')
if not script_text:
display_error(
'Missing script %s for %s' % (scriptname, itemname))
return -1
# write the script to a temp file
scriptpath = os.path.join(tmpdir, scriptname)
if writefile(script_text, scriptpath):
cmd = ['/bin/chmod', '-R', 'o+x', scriptpath]
retcode = subprocess.call(cmd)
if retcode:
display_error(
'Error setting script mode in %s for %s'
% (scriptname, itemname))
return -1
else:
display_error(
'Cannot write script %s for %s' % (scriptname, itemname))
return -1
# now run the script
return runScript(
itemname, scriptpath, scriptname, suppress_error=suppress_error)
def runScript(itemname, path, scriptname, suppress_error=False):
'''Runs a script, Returns return code.'''
display_status_minor(
'Running %s for %s ' % (scriptname, itemname))
if munkistatusoutput:
# set indeterminate progress bar
munkistatus.percent(-1)
scriptoutput = []
try:
proc = subprocess.Popen(path, shell=False, bufsize=1,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except OSError, e:
display_error(
'Error executing script %s: %s' % (scriptname, str(e)))
return -1
while True:
msg = proc.stdout.readline().decode('UTF-8')
if not msg and (proc.poll() != None):
break
# save all script output in case there is
# an error so we can dump it to the log
scriptoutput.append(msg)
msg = msg.rstrip("\n")
display_info(msg)
retcode = proc.poll()
if retcode and not suppress_error:
display_error(
'Running %s for %s failed.' % (scriptname, itemname))
display_error("-"*78)
for line in scriptoutput:
display_error("\t%s" % line.rstrip("\n"))
display_error("-"*78)
elif not suppress_error:
log('Running %s for %s was successful.' % (scriptname, itemname))
if munkistatusoutput:
# clear indeterminate progress bar
munkistatus.percent(0)
return retcode
def forceLogoutNow():
"""Force the logout of interactive GUI users and spawn MSU."""

View File

@@ -22,18 +22,12 @@ Created by Greg Neagle on 2008-11-13.
"""
# standard libs
#import calendar
#import errno
import datetime
import os
#import re
#import shutil
import subprocess
import socket
#import time
import urllib2
import urlparse
#import xattr
from OpenSSL.crypto import load_certificate, FILETYPE_PEM
# our libs
@@ -328,12 +322,14 @@ def analyzeInstalledPkgs():
if not name in references[pkgid]:
references[pkgid].append(name)
#PKGDATA['itemname_to_pkgid'] = itemname_to_pkgid
#PKGDATA['pkgid_to_itemname'] = pkgid_to_itemname
PKGDATA['receipts_for_name'] = installedpkgsmatchedtoname
PKGDATA['installed_names'] = installed
#PKGDATA['partiallyinstalled_names'] = partiallyinstalled
PKGDATA['pkg_references'] = references
# left here for future debugging/testing use....
#PKGDATA['itemname_to_pkgid'] = itemname_to_pkgid
#PKGDATA['pkgid_to_itemname'] = pkgid_to_itemname
#PKGDATA['partiallyinstalled_names'] = partiallyinstalled
#PKGDATA['orphans'] = orphans
#PKGDATA['matched_orphans'] = matched_orphans
#ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
@@ -656,6 +652,14 @@ def compareReceiptVersion(item):
Raises munkicommon.Error if there's an error in the input
"""
if item.get('optional'):
# receipt has been marked as optional, so it doesn't matter
# if it's installed or not. Return 1
# only check receipts not marked as optional
munkicommon.display_debug1(
'Skipping %s because it is marked as optional'
% item.get('packageid', item.get('name')))
return 1
if not INSTALLEDPKGS:
getInstalledPackages()
if 'packageid' in item and 'version' in item:
@@ -1149,7 +1153,20 @@ def installedState(item_pl):
Returns 0 otherwise.
"""
foundnewer = False
if item_pl.get('installcheck_script'):
retcode = munkicommon.runEmbeddedScript(
'installcheck_script', item_pl, suppress_error=True)
munkicommon.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
if item_pl.get('softwareupdatename'):
availableAppleUpdates = appleupdates.softwareUpdateList()
munkicommon.display_debug2(
@@ -1166,8 +1183,8 @@ def installedState(item_pl):
item_pl['softwareupdatename'])
# return 1 so we're marked as not needing to be installed
return 1
# does 'installs' exist and is it non-empty?
# does 'installs' exist and is it non-empty?
if item_pl.get('installs', None):
installitems = item_pl['installs']
for item in installitems:
@@ -1215,7 +1232,22 @@ def someVersionInstalled(item_pl):
Args:
item_pl: item plist for the item to check for version of.
Returns a boolean.
"""
if item_pl.get('installcheck_script'):
retcode = munkicommon.runEmbeddedScript(
'installcheck_script', item_pl, suppress_error=True)
munkicommon.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
# does 'installs' exist and is it non-empty?
if item_pl.get('installs'):
installitems = item_pl['installs']
@@ -1256,7 +1288,35 @@ def evidenceThisIsInstalled(item_pl):
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('uninstallcheck_script'):
retcode = munkicommon.runEmbeddedScript(
'uninstallcheck_script', item_pl, suppress_error=True)
munkicommon.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 = munkicommon.runEmbeddedScript(
'installcheck_script', item_pl, suppress_error=True)
munkicommon.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
foundallinstallitems = False
if ('installs' in item_pl and
item_pl.get('uninstall_method') != 'removepackages'):