Major rewrite and refactoring of the core tools.

installcheck replaces catalogcheck.py.  installcheck supports the new catalog format and the new dependencies.  Cleaned up output and logging.
ManagedInstaller and removepackages tweaked for better logging and MunkiStatus output.
Removed the logout hook examples (for now)
makecatalogitem is now makepkginfo
New makecatalogs tool.

git-svn-id: http://munki.googlecode.com/svn/trunk@50 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2009-05-11 18:03:40 +00:00
parent 3f2b50ccea
commit 59f69cf162
11 changed files with 1513 additions and 1112 deletions

View File

@@ -1,30 +0,0 @@
#!/bin/sh
#
# Copyright 2009 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
#
# http://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.
MIDIR=`defaults read /Library/Preferences/ManagedInstalls managed_install_dir`
IHOOK="/Applications/Utilities/radmind/iHook.app/Contents/MacOS/iHook"
SCRIPT="/Users/gneagle/Documents/managedinstalls/code/client/ManagedInstaller"
ARGS="-i"
if [ "$MIDIR" != "" ]; then
CACHEDIR="$MIDIR/Cache"
if [ -d "$CACHEDIR" ]; then
CACHECOUNT=`ls -1 "$CACHEDIR" | wc -l`
if [ $CACHECOUNT -gt 0 ]; then
"$IHOOK" --script="$SCRIPT" "$ARGS"
fi
fi
fi

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env python
#
# Copyright 2009 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
#
# http://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.
import os
import subprocess
import managedinstalls
managedinstalldir = managedinstalls.managed_install_dir()
installinfo = os.path.join(managedinstalldir, "InstallInfo.plist")
ihook = "/Applications/Utilities/radmind/iHook.app/Contents/MacOS/iHook"
script = "/Users/gneagle/Documents/managedinstalls/code/client/ManagedInstaller"
args = "-i"
if os.path.exists(installinfo):
retcode = subprocess.call([ihook, "--script=" + script, args])

View File

@@ -59,24 +59,13 @@ def createDirsIfNeeded(dirlist):
def log(message):
global logdir
createDirsIfNeeded([logdir])
logfile = os.path.join(logdir,'autoinstall.log')
f = open(logfile, mode='a', buffering=1)
if f:
logfile = os.path.join(logdir,'ManagedInstaller.log')
try:
f = open(logfile, mode='a', buffering=1)
print >>f, time.ctime(), message
f.close()
def countinstallcandidates(dirpath):
"""
Get the count of items to be installed
"""
candidatecount = 0
items = os.listdir(dirpath)
for item in items:
if (item.endswith(".pkg") or item.endswith(".mpkg") or item.endswith(".dmg")):
candidatecount += 1
return candidatecount
except:
pass
def install(pkgpath):
@@ -95,9 +84,10 @@ def install(pkgpath):
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
packagename = output.splitlines()[0]
print >>sys.stderr, "Package name is", packagename
if options.munkistatusoutput:
munkistatus.message("Installing %s..." % packagename)
munkistatus.detail("")
# clear indeterminate progress bar
munkistatus.percent(0)
@@ -162,7 +152,7 @@ def install(pkgpath):
print >>sys.stderr, msg
log(msg)
else:
print >>sys.stderr, msg
log(msg)
retcode = p.poll()
if retcode:
@@ -278,29 +268,32 @@ def getRemovalCount(removalList):
count +=1
return count
def processRemovals(removalList):
global logdir
restartFlag = False
for item in removalList:
if stopRequested():
return restartFlag
if 'installed' in item:
if item['installed']:
name = ""
if 'name' in item:
name = item['name']
elif 'catalogitem' in item:
name = item['catalogitem']
name = item.get('name','')
if 'uninstall_method' in item:
uninstallmethod = item['uninstall_method'].split(' ')
if uninstallmethod[0] == "removepackages":
if 'packages' in item:
if item.get('RestartAction') == "RequireRestart":
restartFlag = True
if options.munkistatusoutput:
munkistatus.message("Removing %s..." % name)
munkistatus.detail("")
# clear indeterminate progress bar
munkistatus.percent(0)
else:
print "Removing %s..." % name
cmd = [pathtoremovepackages, '-f']
log("Removing %s..." % name)
cmd = [pathtoremovepackages, '-f', '--logfile', os.path.join(logdir,'ManagedInstaller.log')]
if options.munkistatusoutput:
cmd.append('-m')
cmd.append('-d')
@@ -354,9 +347,13 @@ def processRemovals(removalList):
# it's a script or program to uninstall
if options.munkistatusoutput:
munkistatus.message("Running uninstall script for %s..." % name)
munkistatus.detail("")
# set indeterminate progress bar
munkistatus.percent(-1)
if item.get('RestartAction') == "RequireRestart":
restartFlag = True
cmd = uninstallmethod
uninstalleroutput = []
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
@@ -391,7 +388,7 @@ def processRemovals(removalList):
else:
log("Uninstall of %s was successful." % name)
if options.munkstatusoutput:
if options.munkistatusoutput:
# clear indeterminate progress bar
munkistatus.percent(0)
@@ -409,7 +406,8 @@ def mountdmg(dmgpath):
"""
mountpoints = []
dmgname = os.path.basename(dmgpath)
print "Mounting disk image %s" % dmgname
if not options.munkistatusoutput:
print "Mounting disk image %s" % dmgname
log("Mounting disk image %s" % dmgname)
p = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-mountRandom', '/tmp', '-nobrowse', '-plist'],
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -451,65 +449,61 @@ def main():
global installdir
needtorestart = False
log("### Beginning automated install session ###")
if os.path.exists(installdir):
installinfo = os.path.join(managedinstallbase, 'InstallInfo.plist')
if os.path.exists(installinfo):
try:
pl = plistlib.readPlist(installinfo)
except:
print >>sys.stderr, "Invalid %s" % installinfo
exit(-1)
# remove the install info file
# it's no longer valid once we start running
os.unlink(installinfo)
if "removals" in pl:
removalcount = getRemovalCount(pl['removals'])
if removalcount:
if options.munkistatusoutput:
if removalcount == 1:
munkistatus.message("Removing 1 item...")
else:
munkistatus.message("Removing %i items..." % removalcount)
# set indeterminate progress bar
munkistatus.percent(-1)
log("Processing removals")
needtorestart = processRemovals(pl['removals'])
if "managed_installs" in pl:
if not stopRequested():
installcount = getInstallCount(pl['managed_installs'])
if installcount:
if options.munkistatusoutput:
if installcount == 1:
munkistatus.message("Installing 1 item...")
else:
munkistatus.message("Installing %i items..." % installcount)
# set indeterminate progress bar
munkistatus.percent(-1)
log("Processing installs")
needtorestart = installWithInfo(installdir, pl['managed_installs'])
else:
log("No %s found." % installinfo)
#log("Installing everything in the cache.")
# install all pkgs and mpkgs
#needtorestart = installall(installdir)
createDirsIfNeeded([logdir])
log("### Beginning managed installer session ###")
installinfo = os.path.join(managedinstallbase, 'InstallInfo.plist')
if os.path.exists(installinfo):
try:
pl = plistlib.readPlist(installinfo)
except:
print >>sys.stderr, "Invalid %s" % installinfo
exit(-1)
if needtorestart:
log("Software installed or removed requires a restart.")
if options.munkistatusoutput:
munkistatus.message("Software installed or removed requires a restart.")
munkistatus.detail("")
munkistatus.percent(-1)
else:
print "Software installed or removed requires a restart."
sys.stdout.flush()
# remove the install info file
# it's no longer valid once we start running
os.unlink(installinfo)
if "removals" in pl:
removalcount = getRemovalCount(pl['removals'])
if removalcount:
if options.munkistatusoutput:
if removalcount == 1:
munkistatus.message("Removing 1 item...")
else:
munkistatus.message("Removing %i items..." % removalcount)
# set indeterminate progress bar
munkistatus.percent(-1)
log("Processing removals")
needtorestart = processRemovals(pl['removals'])
if "managed_installs" in pl:
if not stopRequested():
installcount = getInstallCount(pl['managed_installs'])
if installcount:
if options.munkistatusoutput:
if installcount == 1:
munkistatus.message("Installing 1 item...")
else:
munkistatus.message("Installing %i items..." % installcount)
# set indeterminate progress bar
munkistatus.percent(-1)
log("Processing installs")
needtorestart = installWithInfo(installdir, pl['managed_installs'])
else:
log("No %s found." % installinfo)
if needtorestart:
log("Software installed or removed requires a restart.")
if options.munkistatusoutput:
munkistatus.message("Software installed or removed requires a restart.")
munkistatus.detail("")
munkistatus.percent(-1)
else:
print "Software installed or removed requires a restart."
sys.stdout.flush()
log("### End managed installer session ###")
log("### End automated install session ###")
if needtorestart:
time.sleep(5)
cleanup()

View File

@@ -1,10 +1,14 @@
The actual tools are:
catalogcheck.py - gets the catalog for the client and processes it to determine what, if anything, needs to be installed.
ManagedInstaller - meant to be run as part of a logout hook. Does the actual installs.
makecatalogitem.py: Helper tool to help create catalogitem plists for each installer item.
installcheck - gets the manifest for the client and processes it to determine what, if anything, needs to be installed.
ManagedInstaller - meant to be run as part of a logout hook or as a launchd job. Does the actual installs and removals.
ManagedSoftwareUpdate.app - user notification tool.
MunkiStatus - used by ManagedInstaller to provide user feedback on the installation and removal process.
makepkginfo: Helper tool to help create info files for each installer item.
makecatalogs: Creates the software catalogs from the pkginfo files.
removepackages: used by ManagedInstaller to do package removals.
Supporting libraries:
managedinstalls.py - shared functions
munkistatus.py - functions to display status using MunkiStatus.app

View File

@@ -1,945 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009 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
#
# http://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.
"""
catalogcheck.py
Created by Greg Neagle on 2008-11-13.
"""
#standard libs
import sys
import os
import plistlib
import tempfile
import subprocess
from distutils import version
import urlparse
import optparse
import hashlib
#our lib
import managedinstalls
# appdict is a global so we don't call system_profiler more than once per session
appdict = {}
def getAppData():
"""
Queries system_profiler and returns a dict
of app info items
"""
global appdict
if appdict == {}:
if options.verbose:
print "Getting info on currently installed applications..."
cmd = ['/usr/sbin/system_profiler', '-XML', 'SPApplicationsDataType']
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(plist, err) = p.communicate()
if p.returncode == 0:
pl = plistlib.readPlistFromString(plist)
# top level is an array instead of a dict, so get dict
spdict = pl[0]
if '_items' in spdict:
appdict = spdict['_items']
return appdict
def getAppBundleID(path):
"""
Returns CFBundleIdentifier if available
for application at path
"""
infopath = os.path.join(path, "Contents", "Info.plist")
if os.path.exists(infopath):
try:
pl = plistlib.readPlist(infopath)
if 'CFBundleIdentifier' in pl:
return pl['CFBundleIdentifier']
except:
pass
return None
def compareVersions(thisvers, thatvers):
"""
Returns -1 if thisvers is older than thatvers
Returns 1 if thisvers is the same as thatvers
Returns 2 if thisvers is newer than thatvers
"""
if version.LooseVersion(thisvers) < version.LooseVersion(thatvers):
if options.verbose:
print "\tInstalled version is older (%s)." % thisvers
return -1
elif version.LooseVersion(thisvers) == version.LooseVersion(thatvers):
if options.verbose:
print "\tThis version is currently installed."
return 1
else:
if options.verbose:
print "\tA newer version is currently installed (%s)." % thisvers
return 2
def isSameOrNewerApplicationInstalled(app):
return compareApplicationVersion(app, newer_ok=True)
def compareApplicationVersion(app, newer_ok=False):
"""
app is a dict with application
bundle info
uses system profiler data to look for
an app that is the same version
(or newer version if newer_ok is True)
"""
name = bundleid = ''
if 'CFBundleName' in app:
name = app['CFBundleName']
if 'CFBundleIdentifier' in app:
bundleid = app['CFBundleIdentifier']
if 'CFBundleShortVersionString' in app:
versionstring = app['CFBundleShortVersionString']
if name == '' and bundleid == '':
print >>sys.stderr,"No application name or bundleid was specified!"
# return True so we don't install
return True
if options.verbose:
print "Looking for application %s with bundleid: %s, version %s..." % (name, bundleid, versionstring)
appinfo = []
appdata = getAppData()
if appdata:
for item in appdata:
if bundleid:
if 'path' in item:
if getAppBundleID(item['path']) == bundleid:
appinfo.append(item)
elif name:
if '_name' in item:
if item['_name'] == name:
appinfo.append(item)
for item in appinfo:
if '_name' in item:
if options.verbose:
print "\tName: \t %s" % item['_name'].encode("UTF-8")
if 'path' in item:
if options.verbose:
print "\tPath: \t %s" % item['path'].encode("UTF-8")
print "\tCFBundleIdentifier: \t %s" % getAppBundleID(item['path'])
if 'version' in item:
if options.verbose:
print "\tVersion: \t %s" % item['version'].encode("UTF-8")
if compareVersions(item['version'], versionstring) == 1:
# version is the same
return True
if newer_ok and compareVersions(item['version'], versionstring) == 2:
# version is newer
return True
# if we got this far, we didn't find the same or newer
if options.verbose:
print "Did not find the same or newer application on the startup disk."
return False
def compareBundleVersion(item):
"""
Returns 0 if the bundle isn't installed
or doesn't have valid Info.plist
-1 if it's older
1 if the version is the same
2 if the version is newer
-2 if there's an error in the input
"""
if 'path' in item and 'CFBundleShortVersionString' in item:
filepath = os.path.join(item['path'], 'Contents', 'Info.plist')
vers = item['CFBundleShortVersionString']
else:
print >>sys.stderr, "Missing bundle path or version!"
return -2
if options.verbose:
print "Checking %s for version %s..." % (filepath, vers)
if not os.path.exists(filepath):
if options.verbose:
print "\tNo Info.plist found at %s" % filepath
return 0
try:
pl = plistlib.readPlist(filepath)
except:
if options.verbose:
print "\t%s may not be a plist!" % filepath
return 0
if 'CFBundleShortVersionString' in pl:
installedvers = pl['CFBundleShortVersionString']
return compareVersions(installedvers, vers)
else:
if options.verbose:
print "\tNo version info in %s." % filepath
return 0
def comparePlistVersion(item):
"""
Gets the CFBundleShortVersionString from the plist
at filepath and compares versions.
Returns 0 if the plist isn't installed
-1 if it's older
1 if the version is the same
2 if the version is newer
-2 if there's an error in the input
"""
if 'path' in item and 'CFBundleShortVersionString' in item:
filepath = item['path']
vers = item['CFBundleShortVersionString']
else:
print >>sys.stderr, "Missing plist path or version!"
return -2
if options.verbose:
print "Checking %s for version %s..." % (filepath, vers)
if not os.path.exists(filepath):
if options.verbose:
print "\tNo plist found at %s" % filepath
return 0
try:
pl = plistlib.readPlist(filepath)
except:
if options.verbose:
print "\t%s may not be a plist!" % filepath
return 0
if 'CFBundleShortVersionString' in pl:
installedvers = pl['CFBundleShortVersionString']
return compareVersions(installedvers, vers)
else:
if options.verbose:
print "\tNo version info in %s." % filepath
return 0
def getmd5hash(filename):
if not os.path.isfile(filename):
return "NOT A FILE"
f = open(filename, 'rb')
m = hashlib.md5()
while 1:
chunk = f.read(2**16)
if not chunk:
break
m.update(chunk)
f.close()
return m.hexdigest()
def filesystemItemExists(item):
"""
Checks to see if a filesystem item exists
To do: add checksum support
"""
if 'path' in item:
filepath = item['path']
if options.verbose:
print "Checking existence of %s..." % filepath
if os.path.exists(filepath):
if options.verbose:
print "\tExists."
if 'md5checksum' in item:
storedchecksum = item['md5checksum']
ondiskchecksum = getmd5hash(filepath)
if options.verbose:
print "Comparing checksums..."
if storedchecksum == ondiskchecksum:
if options.verbose:
print "Checksums match."
return 1
else:
if options.verbose:
print "Checksums differ: expected %s, got %s" % (storedchecksum, ondiskchecksum)
return 0
return 1
else:
if options.verbose:
print "\tDoes not exist."
return 0
else:
print >>sys.stderr, "No path specified!"
return -2
def compareReceiptVersion(item):
"""
Determines if the given package is already installed.
packageid is a 'com.apple.pkg.ServerAdminTools' style id
Returns 0 if the receipt isn't present
-1 if it's older
1 if the version is the same
2 if the version is newer
-2 if there's an error in the input
"""
if 'packageid' in item and 'version' in item:
pkgid = item['packageid']
vers = item['version']
else:
print "Missing packageid or version info!"
return -2
if options.verbose:
print "Looking for package %s, version %s" % (pkgid, vers)
installedvers = managedinstalls.getInstalledPackageVersion(pkgid)
if installedvers:
return compareVersions(installedvers, vers)
else:
if options.verbose:
print "\tThis package is not currently installed."
return 0
def download_installeritem(pkgurl):
"""
Downloads a installer item from pkgurl.
"""
global mytmpdir
managed_install_dir = managedinstalls.managed_install_dir()
mycachedir = os.path.join(managed_install_dir, "Cache")
pkgname = os.path.basename(urlparse.urlsplit(pkgurl)[2])
destinationpath = os.path.join(mycachedir, pkgname)
if os.path.exists(destinationpath):
itemmodtime = os.stat(destinationpath).st_mtime
else:
itemmodtime = None
print "Downloading %s from %s" % (pkgname, pkgurl)
tempfilepath = os.path.join(mytmpdir, pkgname)
result = managedinstalls.getfilefromhttpurl(pkgurl, tempfilepath, showprogress=True, ifmodifiedsince=itemmodtime)
if result == 0:
os.rename(tempfilepath, destinationpath)
return True
elif result == 304:
# not modified
print "Installer item %s is already in the install cache." % pkgname
return True
else:
print >>sys.stderr, "Error code: %s" % result
if os.path.exists(tempfilepath):
os.remove(tempfilepath)
print "Couldn't get %s: %s" % (pkgname, result)
return False
def isItemInInstallList(catalogitem_pl, thelist):
"""
Returns True if the filename from the download location
for catalogitem is already in the install list,
and therefore already scheduled to be installed.
"""
if 'installer_item_location' in catalogitem_pl:
location = catalogitem_pl['installer_item_location']
filename = os.path.split(location)[1]
for item in thelist:
if "installer_item" in item:
if filename == item['installer_item']:
return True
return False
def getCatalogItemDetail(item, defaultbranch=''):
"""
Retrieves detailed info for a catalogitem
"""
managedinstallprefs = managedinstalls.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
managed_install_dir = managedinstallprefs['managed_install_dir']
catalogitempath = os.path.join(managed_install_dir, "catalogitems")
catalogbaseurl = sw_repo_baseurl + "/catalogitems"
if defaultbranch == '':
defaultbranch = "production"
itemname = os.path.split(item)[1]
itempath = os.path.join(catalogitempath, itemname)
if os.path.exists(itempath):
itemmodtime = os.stat(itempath).st_mtime
else:
itemmodtime = None
if item.startswith("/"):
# branch in item name
itemurl = catalogbaseurl + item
else:
# use default branch
itemurl = catalogbaseurl + "/" + defaultbranch + "/" + item
tempfilepath = os.path.join(mytmpdir, itemname)
if options.verbose:
print "Getting detail for %s from %s..." % (item, itemurl)
result = managedinstalls.getfilefromhttpurl(itemurl, tempfilepath, showprogress=True, ifmodifiedsince=itemmodtime)
if result == 0:
os.rename(tempfilepath, itempath)
elif result == 304:
# not modified, just return existing item
if options.verbose:
print "Item %s in local cache is up-to-date." % item
return itempath
else:
print >>sys.stderr, "Error code: %s" % result
if os.path.exists(tempfilepath):
os.remove(tempfilepath)
if os.path.exists(itempath):
return itempath
else:
print "Couldn't get detail for item %s: %s" % (item, result)
return ""
def enoughDiskSpace(catalogitem_pl):
"""
Used to determine if there is enough disk space
to be able to download and install the catalogitem
"""
# fudgefactor is set to 100MB
fudgefactor = 100000
installeritemsize = 0
installedsize = 0
if 'installer_item_size' in catalogitem_pl:
installeritemsize = catalogitem_pl['installer_item_size']
if 'installed_size' in catalogitem_pl:
installedsize = catalogitem_pl['installed_size']
diskspaceneeded = installeritemsize + installedsize + fudgefactor
availablediskspace = managedinstalls.getAvailableDiskSpace()
if availablediskspace > diskspaceneeded:
return True
else:
print "There is insufficient disk space to download and install. %sMB needed; %sMB available" % (int(diskspaceneeded/1024), int(availablediskspace/1024))
return False
def processInstalls(catalogitem, defaultbranch, installlist):
"""
Processes a catalog item. Determines if it needs to be
installed, and if so, if any items it is dependent on need to
be installed first. Items to be installed are added to the
installlist (a list of filenames)
Calls itself recursively as it processes dependencies.
Returns a boolean; when processing dependencies, a false return
will stop the installation of a dependent item
"""
managedinstallprefs = managedinstalls.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
managed_install_dir = managedinstallprefs['managed_install_dir']
catalogitempath = os.path.join(managed_install_dir, "catalogitems")
downloadbaseurl = sw_repo_baseurl + "/pkgs/"
catalogitemname = os.path.split(catalogitem)[1]
print "\nProcessing catalog item %s..." % catalogitemname
catalogitemdetail = getCatalogItemDetail(catalogitem, defaultbranch)
if not catalogitemdetail:
return False
try:
pl = plistlib.readPlist(catalogitemdetail)
except:
print >>sys.stderr, "%s is not a valid plist!" % catalogitem
return False
# check to see if item is already in the installlist:
if isItemInInstallList(pl, installlist):
print "%s is already scheduled to be installed." % catalogitemname
return True
# check dependencies
dependenciesMet = True
if 'dependencies' in pl:
dependencies = pl['dependencies']
for item in dependencies:
print "%s is dependent on %s. Getting info on %s..." % (catalogitemname, item, item)
success = processInstalls(item, defaultbranch, installlist)
if not success:
dependenciesMet = False
if not dependenciesMet:
print "Didn't attempt to install %s because could not resolve all dependencies." % catalogitemname
return False
needToInstall = False
if 'installs' in pl:
installitems = pl['installs']
for item in installitems:
if 'type' in item:
if item['type'] == 'application':
if not isSameOrNewerApplicationInstalled(item):
needToInstall = True
# once we know we need to install this one,
# no need to keep checking
break
if item['type'] == 'bundle':
comparisonResult = compareBundleVersion(item)
if comparisonResult == -1 or comparisonResult == 0 :
# not there or older
needToInstall = True
# once we know we need to install this one,
# no need to keep checking
break
if item['type'] == 'plist':
comparisonResult = comparePlistVersion(item)
if comparisonResult == -1 or comparisonResult == 0 :
# not there or older
needToInstall = True
# once we know we need to install this one,
# no need to keep checking
break
if item['type'] == 'file':
if filesystemItemExists(item) == 0 :
# not there, or wrong checksum
needToInstall = True
# once we know we need to install this one,
# no need to keep checking
break
elif 'receipts' in pl:
receipts = pl['receipts']
for item in receipts:
comparisonResult = compareReceiptVersion(item)
if comparisonResult == -1 or comparisonResult == 0 :
# not there or older
needToInstall = True
# once we know we need to install this one,
# no need to keep checking
break
name = description = ""
try:
name = pl['name']
description = pl['description']
except:
pass
iteminfo = {}
iteminfo["name"] = name
iteminfo["catalogitem"] = catalogitemname
iteminfo["description"] = description
if needToInstall:
print "Need to install %s" % catalogitemname
# check to see if there is enough free space to download and install
if not enoughDiskSpace(pl):
iteminfo["installed"] = False
installlist.append(iteminfo)
return False
if 'installer_item_location' in pl:
location = pl['installer_item_location']
url = downloadbaseurl + location
if download_installeritem(url):
filename = os.path.split(location)[1]
iteminfo["installer_item"] = filename
iteminfo["installed"] = False
installlist.append(iteminfo)
return True
else:
iteminfo["installed"] = False
installlist.append(iteminfo)
return False
else:
print "Can't install %s because there's no download info for the installer item" % catalogitemname
iteminfo["installed"] = False
installlist.append(iteminfo)
return False
else:
print "No need to install %s" % catalogitemname
iteminfo["installed"] = True
installlist.append(iteminfo)
return True
def processCatalogForInstalls(catalogpath, listofinstalls=[]):
"""
Processes catalogs. Can be recursive if catalogs inlcude other catalogs.
Probably doesn't handle circular catalog references well...
"""
defaultbranch = getCatalogValueForKey(catalogpath, 'default_branch')
nestedcatalogs = getCatalogValueForKey(catalogpath, "included_catalogs")
if nestedcatalogs:
for item in nestedcatalogs:
nestedcatalogpath = getcatalog(item)
if nestedcatalogpath:
listofinstalls = processCatalogForInstalls(nestedcatalogpath, listofinstalls)
installitems = getCatalogValueForKey(catalogpath, "managed_installs")
if installitems:
for item in installitems:
result = processInstalls(item, defaultbranch, listofinstalls)
return listofinstalls
def processRemovals(catalogitem, defaultbranch, removallist):
"""
Processes a catalog item. Determines if it needs to be
removed, (and if so, if any items dependent on it need to
be removed first.) Items to be removed are added to the
removallist
Calls itself recursively as it processes dependencies.
Returns a boolean; when processing dependencies, a false return
will stop the removal of a dependent item.
"""
managedinstallprefs = managedinstalls.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
managed_install_dir = managedinstallprefs['managed_install_dir']
catalogitempath = os.path.join(managed_install_dir, "catalogitems")
downloadbaseurl = sw_repo_baseurl + "/pkgs/"
catalogitemname = os.path.split(catalogitem)[1]
print "\nProcessing catalog item %s..." % catalogitemname
catalogitemdetail = getCatalogItemDetail(catalogitem, defaultbranch)
if not catalogitemdetail:
return False
try:
pl = plistlib.readPlist(catalogitemdetail)
except:
print >>sys.stderr, "%s is not a valid plist!" % catalogitem
return False
# check for uninstall info
if not 'uninstallable' in pl or not pl['uninstallable']:
print "%s is not uninstallable." % catalogitemname
return False
if not 'uninstall_method' in pl:
print "No uninstall info for %s." % catalogitemname
return False
# check for dependent items
# look at all the items in the local catalogitem cache
# and see if any depend on the current item; if so
# we should remove them as well
dependentitemsremoved = True
for item in os.listdir(catalogitempath):
if item != catalogitemname:
try:
itempath = os.path.join(catalogitempath, item)
itempl = plistlib.readPlist(itempath)
if 'dependencies' in itempl:
if catalogitemname in itempl['dependencies']:
print "%s is dependent on %s and must be removed as well" % (item, catalogitemname)
success = processRemovals(item, defaultbranch, removallist)
if not success:
dependentitemsremoved = False
except:
pass
if not dependentitemsremoved:
print "Didn't attempt to remove %s because could not remove all items dependent on it." % catalogitemname
return False
# check to see if item is already in the removallist:
if isItemInInstallList(pl, removallist):
print "%s is already scheduled to be removed." % catalogitemname
return True
needToRemove = False
if 'installs' in pl:
installitems = pl['installs']
for item in installitems:
if 'type' in item:
if item['type'] == 'application':
if compareApplicationVersion(item, newer_ok=False):
# exact version found
needToRemove = True
# once we know we need to remove this one,
# no need to keep checking
break
if item['type'] == 'bundle':
comparisonResult = compareBundleVersion(item)
if comparisonResult == 1:
# same version is installed
needToRemove = True
# once we know we need to remove this one,
# no need to keep checking
break
if item['type'] == 'plist':
comparisonResult = comparePlistVersion(item)
if comparisonResult == 1:
# same version is installed
needToRemove = True
# once we know we need to remove this one,
# no need to keep checking
break
if item['type'] == 'file':
if filesystemItemExists(item) == 1:
needToRemove = True
# once we know we need to remove this one,
# no need to keep checking
break
if 'receipts' in pl:
receipts = pl['receipts']
for item in receipts:
comparisonResult = compareReceiptVersion(item)
if comparisonResult == 1:
# same version is installed
needToRemove = True
# once we know we need to remove this one,
# no need to keep checking
break
name = description = ""
try:
name = pl['name']
description = pl['description']
except:
pass
iteminfo = {}
iteminfo["name"] = name
iteminfo["catalogitem"] = catalogitemname
iteminfo["description"] = description
if needToRemove:
print "Need to remove %s" % catalogitemname
uninstallmethod = pl['uninstall_method']
if uninstallmethod == 'removepackages':
# build list of packages based on receipts
if options.verbose:
print "Building list of packages to remove"
packages = []
if 'receipts' in pl:
for item in pl['receipts']:
if compareReceiptVersion(item) == 1:
packages.append(item['packageid'])
iteminfo['packages'] = packages
iteminfo["uninstall_method"] = uninstallmethod
iteminfo["installed"] = True
removallist.append(iteminfo)
return True
else:
print "No need to remove %s" % catalogitemname
iteminfo["installed"] = False
removallist.append(iteminfo)
return True
def processCatalogForRemovals(catalogpath, listofremovals=[]):
"""
Processes catalogs for removals. Can be recursive if catalogs inlcude other catalogs.
Probably doesn't handle circular catalog references well...
"""
defaultbranch = getCatalogValueForKey(catalogpath, 'default_branch')
nestedcatalogs = getCatalogValueForKey(catalogpath, "included_catalogs")
if nestedcatalogs:
for item in nestedcatalogs:
nestedcatalogpath = getcatalog(item)
if nestedcatalogpath:
listofremovals = processCatalogForRemovals(nestedcatalogpath, listofremovals)
removalitems = getCatalogValueForKey(catalogpath, "managed_uninstalls")
if removalitems:
for item in removalitems:
result = processRemovals(item, defaultbranch, listofremovals)
return listofremovals
def getCatalogValueForKey(catalogpath, keyname):
try:
pl = plistlib.readPlist(catalogpath)
except:
print >>sys.stderr, "Could not read plist %s" % catalogpath
return None
if keyname in pl:
return pl[keyname]
else:
return None
def createDirsIfNeeded(dirlist):
for dir in dirlist:
if not os.path.exists(dir):
try:
os.mkdir(dir)
except:
print >>sys.stderr, "Could not create %s" % dir
return False
return True
def getcatalog(partialurl):
"""
Gets a catalog from the server
"""
managedinstallprefs = managedinstalls.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
catalog_dir = os.path.join(managedinstallprefs['managed_install_dir'], "catalogs")
if not createDirsIfNeeded([catalog_dir]):
exit(-1)
if partialurl.startswith("http"):
# then it's really a request for the main catalog
catalogurl = partialurl
catalogname = "MainCatalog.plist"
else:
# request for nested catalog
catalogname = os.path.split(partialurl)[1]
catalogurl = sw_repo_baseurl + "/catalogs/" + partialurl
catalogpath = os.path.join(catalog_dir, catalogname)
if os.path.exists(catalogpath):
catalogmodtime = os.stat(catalogpath).st_mtime
else:
catalogmodtime = None
tempfilepath = os.path.join(mytmpdir, catalogname)
print "Getting catalog %s from %s..." % (catalogname, catalogurl)
result = managedinstalls.getfilefromhttpurl(catalogurl, tempfilepath, showprogress=True, ifmodifiedsince=catalogmodtime)
if result == 0:
try:
os.rename(tempfilepath, catalogpath)
return catalogpath
except:
print >>sys.stderr, "Could not write to %s" % catalogpath
return ""
elif result == 304:
# not modified, do nothing
print "Catalog %s in local cache is up-to-date." % catalogname
return catalogpath
else:
print >>sys.stderr, "Error code: %s retreiving catalog %s" % (result, catalogname)
if os.path.exists(tempfilepath):
os.remove(tempfilepath)
return ""
def getMainCatalog(alternate_id):
"""
Gets the main client catalog (aka manifest) from the server
"""
managedinstallprefs = managedinstalls.prefs()
manifesturl = managedinstallprefs['manifest_url']
clientidentifier = managedinstallprefs['client_identifier']
if not manifesturl.endswith('?') and not manifesturl.endswith('/'):
manifesturl = manifesturl + "/"
if alternate_id:
# use id passed in at command-line
manifesturl = manifesturl + alternate_id
elif clientidentifier:
# use client_identfier from /Library/Preferences/ManagedInstalls.plist
manifesturl = manifesturl + clientidentifier
else:
# no client identifier specified, so use the hostname
manifesturl = manifesturl + os.uname()[1]
return getcatalog(manifesturl)
# some globals
mytmpdir = tempfile.mkdtemp()
p = optparse.OptionParser()
p.add_option('--id', '-i', default='',
help='Alternate identifier for catalog retreival')
p.add_option('--verbose', '-v', action='store_true',
help='More verbose output.')
options, arguments = p.parse_args()
def main():
global mytmpdir, options
managedinstallprefs = managedinstalls.prefs()
managed_install_dir = managedinstallprefs['managed_install_dir']
catalogitemsdir = os.path.join(managed_install_dir, "catalogitems")
cachedir = os.path.join(managed_install_dir, "Cache")
if not createDirsIfNeeded([managed_install_dir, catalogitemsdir, cachedir]):
print >>sys.stderr, "No write access to managed install directory: %s" % managed_install_dir
exit(-1)
maincatalogpath = getMainCatalog(options.id)
if not maincatalogpath:
print >>sys.stderr, "Could not retreive managed install catalog."
exit(-1)
installlist = processCatalogForInstalls(maincatalogpath)
# clean up cache dir
# remove any item in the install cache that isn't scheduled
# to be installed --
# this allows us to 'pull back' an item before it is installed
# by removing it from the manifest
installer_item_list = []
for item in installlist:
if "installer_item" in item:
installer_item_list.append(item["installer_item"])
for item in os.listdir(cachedir):
if item not in installer_item_list:
if options.verbose:
print "Removing %s from cache" % item
os.unlink(os.path.join(cachedir, item))
# now generate a list of items to be uninstalled
removallist = processCatalogForRemovals(maincatalogpath)
# need to write out install list so the autoinstaller
# can use it to install things in the right order
pldict = {}
pldict['managed_installs'] = installlist
pldict['removals'] = removallist
plistlib.writePlist(pldict, os.path.join(managed_install_dir, "InstallInfo.plist"))
# now clean up catalogitem dir, removing items no longer needed
currentcatalogitems = []
for item in installlist:
currentcatalogitems.append(item['catalogitem'])
for item in removallist:
currentcatalogitems.append(item['catalogitem'])
for item in os.listdir(catalogitemsdir):
if item not in currentcatalogitems:
os.unlink(os.path.join(catalogitemsdir,item))
try:
# clean up our tmp dir
os.rmdir(mytmpdir)
except:
# not fatal if it fails
pass
if __name__ == '__main__':
main()

1239
code/client/installcheck Executable file

File diff suppressed because it is too large Load Diff

102
code/client/makecatalogs Executable file
View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009 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
#
# http://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.
"""
makecatalogs
Created by Greg Neagle on 2009-03-30.
Copyright (c) 2009 Walt Disney Animation Studios. All rights reserved.
Recursively scans a directory, looking for installer item info files. Builds a repo catalog from these files.
TO-DOs:
Maybe generate a checksum so we can verify the installer item is the right one
"""
import sys
import os
import optparse
import plistlib
import subprocess
def validPlist(path):
cmd = ['/usr/bin/plutil', '-lint', '-s' , path]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
if p.returncode == 0:
return True
else:
return False
def makeCatalogs(repopath):
pkgsinfopath = os.path.join(repopath, 'pkgsinfo')
if not os.path.exists(pkgsinfopath):
print >>sys.stderr, "pkgsinfo path %s doesn't exist!" % pkgsinfopath
exit(-1)
catalogs = {}
catalogs['all'] = []
for dirpath, dirnames, filenames in os.walk(pkgsinfopath):
subdir = dirpath[len(pkgsinfopath):]
for name in filenames:
filepath = os.path.join(dirpath,name)
if validPlist(filepath):
#if it's a valid plist, assume it's a pkginfo file
pkginfo = plistlib.readPlist(filepath)
#simple sanity checking
if 'installer_item_location' in pkginfo:
installeritempath = os.path.join(repopath, "pkgs", pkginfo['installer_item_location'])
if os.path.exists(installeritempath):
catalogs['all'].append(pkginfo)
for catalogname in pkginfo.get("catalogs",[]):
if not catalogname in catalogs:
catalogs[catalogname] = []
catalogs[catalogname].append(pkginfo)
else:
print >>sys.stderr, "WARNING: Info file %s refers to missing installer item: %s" % (filepath[len(pkgsinfopath)+1:], pkginfo['installer_item_location'])
# clear out old catalogs
path = os.path.join(repopath, "catalogs")
for item in os.listdir(path):
itempath = os.path.join(path,item)
if os.path.isfile(itempath):
os.remove(itempath)
# write the new catalogs
for key in catalogs.keys():
catalogpath = os.path.join(repopath, "catalogs", key)
plistlib.writePlist(catalogs[key], catalogpath)
def main():
usage = "usage: %prog [options] /path/to/repo_root"
p = optparse.OptionParser(usage=usage)
options, arguments = p.parse_args()
if len(arguments) == 0:
print >>sys.stderr, "Need to specify a path to the repo root!"
exit(-1)
repopath = arguments[0].rstrip("/")
if not os.path.exists(repopath):
print >>sys.stderr, "Repo root path %s doesn't exist!" % repopath
exit(-1)
makeCatalogs(repopath)
if __name__ == '__main__':
main()

View File

@@ -15,10 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
makecatalogitem.py
makepkginfo
Created by Greg Neagle on 2008-11-25.
Creates a managed install catalog item plist given an Installer item:
Creates a managed install pkg info plist given an Installer item:
a .pkg, a .mpkg, or a .dmg containing a .pkg or .mpkg
at the root of the mounted disk image.
@@ -29,7 +29,7 @@ reinstalled.
The generated plist is printed to STDOUT.
Usage: makecatalogitem /path/to/package_or_dmg [-f /path/to/item/it/installs ...]
Usage: makepkginfo /path/to/package_or_dmg [-f /path/to/item/it/installs ...]
"""
import sys
@@ -131,6 +131,9 @@ def getCatalogInfo(pkgitem):
metaversion = nameAndVersion(shortname)[1]
if not len(metaversion):
metaversion = highestpkgversion
elif highestpkgversion.startswith(metaversion):
#for example, highestpkgversion is 2.0.3124.0, version in filename is 2.0
metaversion = highestpkgversion
if 'installed_size' in installerinfo:
if installerinfo['installed_size'] > 0:
@@ -139,11 +142,10 @@ def getCatalogInfo(pkgitem):
cataloginfo = {}
cataloginfo['name'] = nameAndVersion(shortname)[0]
cataloginfo['version'] = metaversion
if 'RestartAction' in installerinfo:
cataloginfo['RestartAction'] = installerinfo['RestartAction']
cataloginfo['description'] = ""
if 'description' in installerinfo:
cataloginfo['description'] = installerinfo['description']
for key in ('display_name', 'RestartAction', 'description'):
if key in installerinfo:
cataloginfo[key] = installerinfo[key]
if installedsize > 0:
cataloginfo['installed_size'] = installedsize
@@ -230,6 +232,7 @@ def getiteminfo(itempath):
infodict = {}
if itempath.endswith('.app'):
infodict['type'] = 'application'
infodict['path'] = itempath
pl = getBundleInfo(itempath)
if 'CFBundleName' in pl:
infodict['CFBundleName'] = pl['CFBundleName']
@@ -241,12 +244,14 @@ def getiteminfo(itempath):
infodict['minosversion'] = pl['LSMinimumSystemVersion']
elif 'SystemVersionCheck:MinimumSystemVersion' in pl:
infodict['minosversion'] = pl['SystemVersionCheck:MinimumSystemVersion']
elif os.path.exists(os.path.join(itempath,'Contents','Info.plist')) or os.path.exists(os.path.join(itempath,'Resources','Info.plist')):
infodict['type'] = 'bundle'
infodict['path'] = itempath
pl = getBundleInfo(itempath)
if 'CFBundleShortVersionString' in pl:
infodict['CFBundleShortVersionString'] = pl['CFBundleShortVersionString']
elif itempath.endswith("Info.plist") or itempath.endswith("version.plist"):
infodict['type'] = 'plist'
infodict['path'] = itempath
@@ -307,6 +312,12 @@ def main():
if options.file:
installs = []
for fitem in options.file:
# no trailing slashes, please.
fitem = fitem.rstrip('/')
if fitem.startswith('/Library/Receipts'):
# no receipts, please!
print >>sys.stderr, "Item %s appears to be a receipt. Skipping." % fitem
continue
if os.path.exists(fitem):
iteminfodict = getiteminfo(fitem)
if 'minosversion' in iteminfodict:

View File

@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
managedinstallslib.py
managedinstalls
Created by Greg Neagle on 2008-11-18.
@@ -47,6 +47,7 @@ def getManagedInstallsPrefs():
prefs['manifest_url'] = "http:/managedinstalls/cgi-bin/getmanifest"
prefs['sw_repo_url'] = "http://managedinstalls/swrepo"
prefs['client_identifier'] = ""
prefs['logging_level'] = 1
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
if os.path.exists(prefsfile):
@@ -63,6 +64,9 @@ def getManagedInstallsPrefs():
prefs['sw_repo_url'] = pl['sw_repo_url']
if 'client_identifier' in pl:
prefs['client_identifier'] = pl['client_identifier']
if 'logging_level' in pl:
prefs['logging_level'] = pl['logging_level']
return prefs
@@ -113,6 +117,8 @@ def getInstallerPkgInfo(filename):
if 'Will Restart' in pl:
if pl['Will Restart'] == "YES":
installerinfo['RestartAction'] = "RequireRestart"
if "Title" in pl:
installerinfo['display_name'] = pl['Title']
return installerinfo
@@ -398,11 +404,13 @@ def display_percent_done(current,maximum):
sys.stdout.flush()
def httpDownload(url, filename, headers={}, postData=None, reporthook=None):
def httpDownload(url, filename, headers={}, postData=None, reporthook=None, message=None):
reqObj = urllib2.Request(url, postData, headers)
fp = urllib2.urlopen(reqObj)
headers = fp.info()
if message: print message
#read & write fileObj to filename
tfp = open(filename, 'wb')
result = filename, headers
@@ -438,7 +446,7 @@ def httpDownload(url, filename, headers={}, postData=None, reporthook=None):
def getfilefromhttpurl(url,filepath,showprogress=False,ifmodifiedsince=None):
def getfilefromhttpurl(url,filepath,showprogress=False,ifmodifiedsince=None, message=None):
"""
gets a file from a url.
If 'ifmodifiedsince' is specified, this header is set
@@ -455,7 +463,7 @@ def getfilefromhttpurl(url,filepath,showprogress=False,ifmodifiedsince=None):
if ifmodifiedsince:
modtimestr = time.strftime("%a, %d %b %Y %H:%M:%S GMT",time.gmtime(ifmodifiedsince))
request_headers["If-Modified-Since"] = modtimestr
(f,headers) = httpDownload(url, filename=filepath, headers=request_headers, reporthook=reporthook)
(f,headers) = httpDownload(url, filename=filepath, headers=request_headers, reporthook=reporthook, message=message)
if 'last-modified' in headers:
# set the modtime of the downloaded file to the modtime of the
# file on the server
@@ -472,8 +480,40 @@ def getfilefromhttpurl(url,filepath,showprogress=False,ifmodifiedsince=None):
return (-1, "Unexpected error")
return 0
def getHTTPfileIfNewerAtomically(url,destinationpath,showprogress=False, message=None):
"""
Gets file from HTTP URL, only if newer on web server.
Replaces pre-existing file only on success. (thus 'Atomically')
"""
mytmpdir = tempfile.mkdtemp()
mytemppath = os.path.join(mytmpdir,"TempDownload")
if os.path.exists(destinationpath):
modtime = os.stat(destinationpath).st_mtime
else:
modtime = None
result = getfilefromhttpurl(url, mytemppath, showprogress=True, ifmodifiedsince=modtime, message=message)
if result == 0:
try:
os.rename(mytemppath, destinationpath)
return destinationpath
except:
print >>sys.stderr, "Could not write to %s" % destinationpath
destinationpath = None
elif result == 304:
# not modified, return existing file
return destinationpath
else:
print >>sys.stderr, "Error code: %s retreiving %s" % (result, url)
destinationpath = None
if os.path.exists(mytemppath):
os.remove(mytemppath)
os.rmdir(mytmpdir)
return destinationpath
debug = False
def main():
pass

View File

@@ -150,8 +150,8 @@ def main():
help='Disable the stop button.')
p.add_option('--enableStopButton', action='store_true',
help='Enable the stop button.')
options, arguments = p.parse_args()
if options.quit:

View File

@@ -15,7 +15,7 @@
# limitations under the License.
"""
removePackages - a tool to analyze installed packages and remove
removepackages - a tool to analyze installed packages and remove
files unique to the packages given at the command line. No attempt
is made to revert to older versions of a file when uninstalling;
only file removals are done.
@@ -30,7 +30,7 @@ import plistlib
import sqlite3
import time
import munkistatus
#import applereceiptutils
##################################################################
# Schema of /Library/Receipts/db/a.receiptsdb:
@@ -143,8 +143,9 @@ def display_percent_done(current,maximum):
def display_status(msg):
"""
Displays major status messages, formatting as needed
for verbose/non-verbose and iHook-style output.
for verbose/non-verbose and munkistatus-style output.
"""
log(msg)
if options.munkistatusoutput:
munkistatus.detail(msg)
elif options.verbose:
@@ -157,7 +158,7 @@ def display_status(msg):
def display_info(msg):
"""
Displays minor info messages, formatting as needed
for verbose/non-verbose and iHook-style output.
for verbose/non-verbose and munkistatus-style output.
"""
if options.munkistatusoutput:
#munkistatus.detail(msg)
@@ -168,12 +169,22 @@ def display_info(msg):
def display_error(msg):
"""
Prints msg to stderr and eventually to the log
Prints msg to stderr and the log
"""
print >>sys.stderr, "ERROR: %s" % msg
print >>sys.stderr, msg
log(msg)
def log(msg):
try:
f = open(options.logfile, mode='a', buffering=1)
print >>f, time.ctime(), msg
f.close()
except:
pass
def shouldRebuildDB(pkgdbpath):
"""
Checks to see if our internal package DB should be rebuilt.
@@ -599,6 +610,7 @@ def removeReceipts(pkgkeylist):
receiptpath = os.path.join('/Library/Receipts/boms', pkgname)
if os.path.exists(receiptpath):
display_info("Removing %s..." % receiptpath)
log("Removing %s..." % receiptpath)
retcode = subprocess.call(["/bin/rm", "-rf", receiptpath])
# remove pkg info from our database
@@ -709,6 +721,7 @@ def removeFilesystemItems(removalpaths):
# use os.path.lexists so broken links return true so we can remove them
if os.path.lexists(pathtoremove):
display_info("Removing: " + pathtoremove.encode("UTF-8"))
log("Removing: " + pathtoremove.encode("UTF-8"))
if (os.path.isdir(pathtoremove) and not os.path.islink(pathtoremove)):
diritems = os.listdir(pathtoremove)
if diritems == ['.DS_Store']:
@@ -780,6 +793,8 @@ p.add_option('--dontquitmunkistatus', '-d', action='store_true',
help="Don't quit MunkiStatus on exit. (For example, when called by ManagedInstaller.)")
p.add_option('--verbose', '-v', action='store_true',
help='More verbose output.')
p.add_option('--logfile', default='',
help="Path to a log file.")
# Get our options and our package names
options, pkgnames = p.parse_args()
@@ -812,7 +827,6 @@ if stopRequested():
cleanup()
exit()
if removalpaths:
if options.listfiles:
removalpaths.sort()