mirror of
https://github.com/munki/munki.git
synced 2026-04-30 01:11:01 -05:00
Initial import
git-svn-id: http://munki.googlecode.com/svn/trunk@2 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
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
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
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])
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
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])
|
||||
Executable
+412
@@ -0,0 +1,412 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
installomatic
|
||||
Tool to automatically install pkgs, mpkgs, and dmgs
|
||||
(containing pkgs and mpkgs) from a defined folder. Intended
|
||||
to be run as part of a logout hook, but can be run manually
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import plistlib
|
||||
import optparse
|
||||
import managedinstalls
|
||||
|
||||
|
||||
def log(message):
|
||||
global logdir
|
||||
logfile = os.path.join(logdir,'autoinstall.log')
|
||||
f = open(logfile, mode='a', buffering=1)
|
||||
if f:
|
||||
print >>f, time.ctime(), message
|
||||
f.close()
|
||||
|
||||
|
||||
def countinstallcandidates(dirpath):
|
||||
"""
|
||||
Counts the number of pkgs, mpkgs, and dmgs
|
||||
in dirpath
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
def install(pkgpath):
|
||||
"""
|
||||
Uses the apple installer to install the package or metapackage
|
||||
at pkgpath. Prints status messages to STDOUT.
|
||||
Returns the installer return code and true if a restart is needed.
|
||||
"""
|
||||
global installablecount
|
||||
global currentinstallable
|
||||
global options
|
||||
|
||||
currentinstallable += 1
|
||||
restartneeded = False
|
||||
installeroutput = []
|
||||
|
||||
cmd = ['/usr/sbin/installer', '-pkginfo', '-pkg', pkgpath]
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
packagename = output.splitlines()[0]
|
||||
print >>sys.stderr, "Package name is", packagename
|
||||
if options.ihookoutput:
|
||||
print "%TITLE Installing " + packagename + "..."
|
||||
print "%%%s Item %s of %s" % (0, currentinstallable, installablecount)
|
||||
log("Installing %s from %s" % (packagename, os.path.basename(pkgpath)))
|
||||
cmd = ['/usr/sbin/installer', '-query', 'RestartAction', '-pkg', pkgpath]
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
restartaction = output.rstrip("\n")
|
||||
if restartaction == "RequireRestart":
|
||||
message = "%s requires a restart after installation." % packagename
|
||||
print message
|
||||
sys.stdout.flush()
|
||||
log(message)
|
||||
restartneeded = True
|
||||
|
||||
cmd = ['/usr/sbin/installer', '-verboseR', '-pkg', pkgpath, '-target', '/']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
while (p.poll() == None):
|
||||
installinfo = p.stdout.readline()
|
||||
if installinfo.startswith("installer:"):
|
||||
# save all installer output in case there is
|
||||
# an error so we can dump it to the log
|
||||
installeroutput.append(installinfo)
|
||||
msg = installinfo[10:].rstrip("\n")
|
||||
if msg.startswith("PHASE:"):
|
||||
phase = msg[6:]
|
||||
if phase:
|
||||
print phase
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("STATUS:"):
|
||||
status = msg[7:]
|
||||
if status:
|
||||
print status
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("%"):
|
||||
if options.ihookoutput:
|
||||
percent = float(msg[1:])
|
||||
percent = int(percent * 100)
|
||||
print "%%%s Item %s of %s" % (percent, currentinstallable, installablecount)
|
||||
if percent == 100:
|
||||
overallpercentage = min(100,int(currentinstallable/installablecount * 100))
|
||||
print "%%%s Item %s of %s" % (overallpercentage, currentinstallable, installablecount)
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith(" Error"):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
print >>sys.stderr, msg
|
||||
log(msg)
|
||||
elif msg.startswith(" Cannot install"):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
print >>sys.stderr, msg
|
||||
log(msg)
|
||||
else:
|
||||
print >>sys.stderr, msg
|
||||
|
||||
retcode = p.poll()
|
||||
if retcode:
|
||||
message = "Install of %s failed." % packagename
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
for line in installeroutput:
|
||||
print >>sys.stderr, " ", line.rstrip("\n")
|
||||
log(line.rstrip("\n"))
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
restartneeded = False
|
||||
else:
|
||||
log("Install of %s was successful." % packagename)
|
||||
|
||||
return (retcode, restartneeded)
|
||||
|
||||
|
||||
def installall(dirpath):
|
||||
"""
|
||||
Attempts to install all pkgs and mpkgs in a given directory.
|
||||
Will mount dmg files and install pkgs and mpkgs found at the
|
||||
root of any mountpoints.
|
||||
"""
|
||||
restartflag = False
|
||||
installitems = os.listdir(dirpath)
|
||||
for item in installitems:
|
||||
itempath = os.path.join(dirpath, item)
|
||||
if (item.endswith(".pkg") or item.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath)
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if item.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
# of the mountpoint -- call us recursively!
|
||||
needtorestart = installall(mountpoint)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
unmountdmg(mountpoint)
|
||||
|
||||
return restartflag
|
||||
|
||||
|
||||
def installWithInfo(dirpath, installlist):
|
||||
"""
|
||||
Uses the installlist to install items in the
|
||||
correct order.
|
||||
"""
|
||||
restartflag = False
|
||||
for item in installlist:
|
||||
if "installer_item" in item:
|
||||
itempath = os.path.join(dirpath, item["installer_item"])
|
||||
if not os.path.exists(itempath):
|
||||
#can't install, so we should stop
|
||||
return restartFlag
|
||||
if (itempath.endswith(".pkg") or itempath.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath)
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if itempath.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
# of the mountpoint -- call us recursively!
|
||||
needtorestart = installall(mountpoint)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
unmountdmg(mountpoint)
|
||||
|
||||
# now remove the item from the install cache
|
||||
# (using rm -f in case it's a bundle pkg)
|
||||
retcode = subprocess.call(["/bin/rm", "-rf", itempath])
|
||||
|
||||
return restartflag
|
||||
|
||||
|
||||
def processRemovals(removalList):
|
||||
restartFlag = False
|
||||
for item in removalList:
|
||||
name = ""
|
||||
if 'name' in item:
|
||||
name = item['name']
|
||||
elif 'catalogitem' in item:
|
||||
name = item['catalogitem']
|
||||
if 'uninstall_method' in item:
|
||||
uninstallmethod = item['uninstall_method'].split(' ')
|
||||
if uninstallmethod[0] == "removepackages":
|
||||
if 'packages' in item:
|
||||
if options.ihookoutput:
|
||||
print "%TITLE Removing " + name + "..."
|
||||
#print "%%%s Item %s of %s" % (0, currentinstallable, installablecount)
|
||||
cmd = ['/Users/Shared/bin/removepackages', '-f']
|
||||
if options.ihookoutput:
|
||||
cmd.append('-i')
|
||||
for package in item['packages']:
|
||||
cmd.append(package)
|
||||
uninstalleroutput = []
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
while (p.poll() == None):
|
||||
msg = p.stdout.readline()
|
||||
# save all uninstaller output in case there is
|
||||
# an error so we can dump it to the log
|
||||
uninstalleroutput.append(msg)
|
||||
msg = msg.rstrip("\n")
|
||||
if msg.startswith("STATUS: "):
|
||||
status = msg[8:]
|
||||
if status:
|
||||
print status
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("INFO: "):
|
||||
info = msg[6:]
|
||||
if info:
|
||||
print >>sys.stderr, info
|
||||
elif msg.startswith("ERROR: "):
|
||||
error = msg[7:]
|
||||
if error:
|
||||
print >>sys.stderr, error
|
||||
else:
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
|
||||
retcode = p.poll()
|
||||
if retcode:
|
||||
message = "Uninstall of %s failed." % name
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
for line in uninstalleroutput:
|
||||
print >>sys.stderr, " ", line.rstrip("\n")
|
||||
log(line.rstrip("\n"))
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
else:
|
||||
log("Uninstall of %s was successful." % name)
|
||||
|
||||
elif os.path.exists(uninstallmethod[0]) and os.access(uninstallmethod[0], os.X_OK):
|
||||
# it's a script or program to uninstall
|
||||
if options.ihookoutput:
|
||||
print "%TITLE Running uninstall script for " + name + "..."
|
||||
print "%BEGINPOLE"
|
||||
sys.stdout.flush()
|
||||
|
||||
cmd = uninstallmethod
|
||||
uninstalleroutput = []
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
while (p.poll() == None):
|
||||
msg = p.stdout.readline()
|
||||
# save all uninstaller output in case there is
|
||||
# an error so we can dump it to the log
|
||||
uninstalleroutput.append(msg)
|
||||
msg = msg.rstrip("\n")
|
||||
if options.ihookoutput:
|
||||
# redirect output to the iHook drawer
|
||||
print >>sys.stderr, msg
|
||||
else:
|
||||
print msg
|
||||
|
||||
retcode = p.poll()
|
||||
if retcode:
|
||||
message = "Uninstall of %s failed." % name
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
for line in uninstalleroutput:
|
||||
print >>sys.stderr, " ", line.rstrip("\n")
|
||||
log(line.rstrip("\n"))
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
else:
|
||||
log("Uninstall of %s was successful." % name)
|
||||
|
||||
else:
|
||||
log("Uninstall of %s failed because there was no valid uninstall method." % name)
|
||||
|
||||
return restartFlag
|
||||
|
||||
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
"""
|
||||
Attempts to mount the dmg at dmgpath
|
||||
and returns a list of mountpoints
|
||||
"""
|
||||
mountpoints = []
|
||||
dmgname = os.path.basename(dmgpath)
|
||||
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)
|
||||
(plist, err) = p.communicate()
|
||||
if plist:
|
||||
pl = plistlib.readPlistFromString(plist)
|
||||
for entity in pl['system-entities']:
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
|
||||
return mountpoints
|
||||
|
||||
|
||||
def unmountdmg(mountpoint):
|
||||
"""
|
||||
Unmounts the dmg at mountpoint
|
||||
"""
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, err
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint, '-force'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
|
||||
|
||||
# module (global) variables
|
||||
managedinstallbase = managedinstalls.managed_install_dir()
|
||||
installdir = os.path.join(managedinstallbase , 'Cache')
|
||||
logdir = os.path.join(managedinstallbase, 'Logs')
|
||||
installablecount = 0
|
||||
currentinstallable = 0
|
||||
|
||||
p = optparse.OptionParser()
|
||||
p.add_option('--ihookoutput', '-i', action='store_true')
|
||||
options, arguments = p.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
global installdir
|
||||
global installablecount
|
||||
|
||||
if options.ihookoutput:
|
||||
print '%WINDOWSIZE 512 232'
|
||||
print '%BACKGROUND /Users/Shared/Installer.png'
|
||||
print '%BECOMEKEY'
|
||||
print '%BEGINPOLE'
|
||||
sys.stdout.flush()
|
||||
|
||||
needtorestart = False
|
||||
|
||||
#installablecount = countinstallcandidates(installdir)
|
||||
#if installablecount:
|
||||
installablecount = 5
|
||||
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)
|
||||
if "removals" in pl:
|
||||
log("Processing removals")
|
||||
needtorestart = processRemovals(pl['removals'])
|
||||
if "managed_installs" in pl:
|
||||
log("Processing installs")
|
||||
needtorestart = installWithInfo(installdir, pl['managed_installs'])
|
||||
# remove the install info
|
||||
os.unlink(installinfo)
|
||||
else:
|
||||
log("No %s found." % installinfo)
|
||||
log("Installing everything in the cache.")
|
||||
# install all pkgs and mpkgs
|
||||
needtorestart = installall(installdir)
|
||||
|
||||
if needtorestart:
|
||||
print "Software installed requires a restart."
|
||||
log("Software installed requires a restart.")
|
||||
sys.stdout.flush()
|
||||
|
||||
log("### End automated install session ###")
|
||||
if needtorestart:
|
||||
time.sleep(5)
|
||||
# uncomment this when testing is done so it will restart.
|
||||
#retcode = subprocess.call(["/sbin/shutdown", "-r", "now"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
The actual tools are:
|
||||
|
||||
catalogcheck.py - gets the catalog for the client and processes it to determine what, if anything, needs to be installed.
|
||||
installAtLogout.py - 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.
|
||||
|
||||
|
||||
Supporting library:
|
||||
managedinstalls.py
|
||||
Executable
+842
@@ -0,0 +1,842 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
catalogcheck.py
|
||||
|
||||
Created by Greg Neagle on 2008-11-13.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import plistlib
|
||||
import tempfile
|
||||
import subprocess
|
||||
from distutils import version
|
||||
import urlparse
|
||||
import optparse
|
||||
|
||||
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 == {}:
|
||||
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):
|
||||
print "\tInstalled version is older (%s)." % thisvers
|
||||
return -1
|
||||
elif version.LooseVersion(thisvers) == version.LooseVersion(thatvers):
|
||||
print "\tThis version is currently installed."
|
||||
return 1
|
||||
else:
|
||||
print "\tA newer version is currently installed (%s)." % thisvers
|
||||
return 2
|
||||
|
||||
|
||||
def isSameOrNewerApplicationInstalled(app):
|
||||
"""
|
||||
app is a dict with application
|
||||
bundle info
|
||||
uses system profiler data to look for
|
||||
an app that is the same or newer version
|
||||
"""
|
||||
|
||||
name = bundleid = ''
|
||||
versionstring = '0.0.0'
|
||||
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 "No application name or bundleid was specified!"
|
||||
# return True so we don't install
|
||||
return True
|
||||
|
||||
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:
|
||||
print "\tName: \t %s" % item['_name'].encode("UTF-8")
|
||||
if 'path' in item:
|
||||
print "\tPath: \t %s" % item['path'].encode("UTF-8")
|
||||
print "\tCFBundleIdentifier: \t %s" % getAppBundleID(item['path'])
|
||||
if 'version' in item:
|
||||
print "\tVersion: \t %s" % item['version'].encode("UTF-8")
|
||||
if compareVersions(item['version'], versionstring) > 0:
|
||||
return True
|
||||
|
||||
# if we got this far, we didn't find the same or newer
|
||||
print "Did not find the same or newer application on the startup disk."
|
||||
return False
|
||||
|
||||
|
||||
def compareBundleVersion(item):
|
||||
"""
|
||||
Gets the CFBundleShortVersionString from the Info.plist
|
||||
in bundlepath/Contents and compares versions.
|
||||
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 "Missing bundle path or version!"
|
||||
return -2
|
||||
|
||||
print "Checking %s for version %s..." % (filepath, vers)
|
||||
if not os.path.exists(filepath):
|
||||
print "\tNo Info.plist found at %s" % filepath
|
||||
return 0
|
||||
|
||||
try:
|
||||
pl = plistlib.readPlist(filepath)
|
||||
except:
|
||||
print "\t%s may not be a plist!" % filepath
|
||||
return 0
|
||||
|
||||
if 'CFBundleShortVersionString' in pl:
|
||||
installedvers = pl['CFBundleShortVersionString']
|
||||
return compareVersions(installedvers, vers)
|
||||
else:
|
||||
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 "Missing plist path or version!"
|
||||
return -2
|
||||
|
||||
print "Checking %s for version %s..." % (filepath, vers)
|
||||
if not os.path.exists(filepath):
|
||||
print "\tNo plist found at %s" % filepath
|
||||
return 0
|
||||
|
||||
try:
|
||||
pl = plistlib.readPlist(filepath)
|
||||
except:
|
||||
print "\t%s may not be a plist!" % filepath
|
||||
return 0
|
||||
|
||||
if 'CFBundleShortVersionString' in pl:
|
||||
installedvers = pl['CFBundleShortVersionString']
|
||||
return compareVersions(installedvers, vers)
|
||||
else:
|
||||
print "\tNo version info in %s." % filepath
|
||||
return 0
|
||||
|
||||
|
||||
def filesystemItemExists(item):
|
||||
"""
|
||||
Checks to see if a filesystem item exists
|
||||
To do: add checksum support
|
||||
"""
|
||||
if 'path' in item:
|
||||
filepath = item['path']
|
||||
print "Checking existence of %s..." % filepath
|
||||
if os.path.exists(filepath):
|
||||
print "\tExists."
|
||||
return 1
|
||||
else:
|
||||
print "\tDoes not exist."
|
||||
return 0
|
||||
else:
|
||||
print "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
|
||||
|
||||
print "Looking for package %s, version %s" % (pkgid, vers)
|
||||
installedvers = managedinstalls.getInstalledPackageVersion(pkgid)
|
||||
if installedvers:
|
||||
return compareVersions(installedvers, vers)
|
||||
else:
|
||||
print "\tThis package is not currently installed."
|
||||
return 0
|
||||
|
||||
|
||||
def download_installeritem(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=''):
|
||||
|
||||
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)
|
||||
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
|
||||
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 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 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
|
||||
|
||||
# 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
|
||||
|
||||
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):
|
||||
print "Need to install %s" % catalogitemname
|
||||
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
|
||||
print "Need to install %s" % catalogitemname
|
||||
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
|
||||
print "Need to install %s" % catalogitemname
|
||||
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
|
||||
print "Need to install %s" % catalogitemname
|
||||
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
|
||||
print "Need to install %s" % catalogitemname
|
||||
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:
|
||||
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 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 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 isSameOrNewerApplicationInstalled(item):
|
||||
print "Need to remove %s" % catalogitemname
|
||||
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
|
||||
print "Need to remove %s" % catalogitemname
|
||||
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
|
||||
print "Need to remove %s" % catalogitemname
|
||||
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:
|
||||
print "Need to remove %s" % catalogitemname
|
||||
needToRemove = True
|
||||
# once we know we need to remove 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:
|
||||
# same version is installed
|
||||
print "Need to remove %s" % catalogitemname
|
||||
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:
|
||||
uninstallmethod = pl['uninstall_method']
|
||||
|
||||
if uninstallmethod == 'removepackages':
|
||||
# build list of packages based on receipts
|
||||
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)
|
||||
|
||||
|
||||
mytmpdir = ""
|
||||
def main():
|
||||
global mytmpdir
|
||||
mytmpdir = tempfile.mkdtemp()
|
||||
|
||||
p = optparse.OptionParser()
|
||||
p.add_option('--id', '-i', default='',
|
||||
help='Alternate identifier for catalog retreival')
|
||||
options, arguments = p.parse_args()
|
||||
|
||||
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:
|
||||
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()
|
||||
|
||||
Executable
+294
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
installomatic
|
||||
Tool to automatically install pkgs, mpkgs, and dmgs
|
||||
(containing pkgs and mpkgs) from a defined folder. Intended
|
||||
to be run as part of a logout hook, but can be run manually
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import plistlib
|
||||
import optparse
|
||||
import managedinstalls
|
||||
|
||||
|
||||
def log(message):
|
||||
global logdir
|
||||
logfile = os.path.join(logdir,'install.log')
|
||||
f = open(logfile, mode='a', buffering=1)
|
||||
if f:
|
||||
print >>f, time.ctime(), message
|
||||
f.close()
|
||||
|
||||
|
||||
def countinstallcandidates(dirpath):
|
||||
"""
|
||||
Counts the number of pkgs, mpkgs, and dmgs
|
||||
in dirpath
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
def install(pkgpath):
|
||||
"""
|
||||
Uses the apple installer to install the package or metapackage
|
||||
at pkgpath. Prints status messages to STDOUT.
|
||||
Returns the installer return code and true if a restart is needed.
|
||||
"""
|
||||
global installablecount
|
||||
global currentinstallable
|
||||
global options
|
||||
|
||||
currentinstallable += 1
|
||||
restartneeded = False
|
||||
installeroutput = []
|
||||
|
||||
cmd = ['/usr/sbin/installer', '-pkginfo', '-pkg', pkgpath]
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
packagename = output.splitlines()[0]
|
||||
print >>sys.stderr, "Package name is", packagename
|
||||
if options.ihookoutput:
|
||||
print "%TITLE Installing " + packagename + "..."
|
||||
print "%%%s Item %s of %s" % (0, currentinstallable, installablecount)
|
||||
log("Installing %s from %s" % (packagename, os.path.basename(pkgpath)))
|
||||
cmd = ['/usr/sbin/installer', '-query', 'RestartAction', '-pkg', pkgpath]
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
restartaction = output.rstrip("\n")
|
||||
if restartaction == "RequireRestart":
|
||||
message = "%s requires a restart after installation." % packagename
|
||||
print message
|
||||
sys.stdout.flush()
|
||||
log(message)
|
||||
restartneeded = True
|
||||
|
||||
cmd = ['/usr/sbin/installer', '-verboseR', '-pkg', pkgpath, '-target', '/']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
while (p.poll() == None):
|
||||
installinfo = p.stdout.readline()
|
||||
if installinfo.startswith("installer:"):
|
||||
# save all installer output in case there is
|
||||
# an error so we can dump it to the log
|
||||
installeroutput.append(installinfo)
|
||||
msg = installinfo[10:].rstrip("\n")
|
||||
if msg.startswith("PHASE:"):
|
||||
phase = msg[6:]
|
||||
if phase:
|
||||
print phase
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("STATUS:"):
|
||||
status = msg[7:]
|
||||
if status:
|
||||
print status
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("%"):
|
||||
if options.ihookoutput:
|
||||
percent = float(msg[1:])
|
||||
percent = int(percent * 100)
|
||||
print "%%%s Item %s of %s" % (percent, currentinstallable, installablecount)
|
||||
if percent == 100:
|
||||
overallpercentage = min(100,int(currentinstallable/installablecount * 100))
|
||||
print "%%%s Item %s of %s" % (overallpercentage, currentinstallable, installablecount)
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith(" Error"):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
print >>sys.stderr, msg
|
||||
log(msg)
|
||||
elif msg.startswith(" Cannot install"):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
print >>sys.stderr, msg
|
||||
log(msg)
|
||||
else:
|
||||
print >>sys.stderr, msg
|
||||
|
||||
retcode = p.poll()
|
||||
if retcode:
|
||||
message = "Install of %s failed." % packagename
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
for line in installeroutput:
|
||||
print >>sys.stderr, " ", line.rstrip("\n")
|
||||
log(line.rstrip("\n"))
|
||||
message = "-------------------------------------------------"
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
restartneeded = False
|
||||
else:
|
||||
log("Install of %s was successful." % packagename)
|
||||
|
||||
return (retcode, restartneeded)
|
||||
|
||||
|
||||
def installall(dirpath):
|
||||
"""
|
||||
Attempts to install all pkgs and mpkgs in a given directory.
|
||||
Will mount dmg files and install pkgs and mpkgs found at the
|
||||
root of any mountpoints.
|
||||
"""
|
||||
restartflag = False
|
||||
installitems = os.listdir(dirpath)
|
||||
for item in installitems:
|
||||
itempath = os.path.join(dirpath, item)
|
||||
if (item.endswith(".pkg") or item.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath)
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if item.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
# of the mountpoint -- call us recursively!
|
||||
needtorestart = installall(mountpoint)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
unmountdmg(mountpoint)
|
||||
|
||||
return restartflag
|
||||
|
||||
|
||||
def installWithInfo(dirpath, installlist):
|
||||
"""
|
||||
Uses the installlist to install items in the
|
||||
correct order.
|
||||
"""
|
||||
restartflag = False
|
||||
for item in installlist:
|
||||
itempath = os.path.join(dirpath, item)
|
||||
if not os.path.exists(itempath):
|
||||
#can't install, so we should stop
|
||||
return restartFlag
|
||||
if (item.endswith(".pkg") or item.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath)
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if item.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
# of the mountpoint -- call us recursively!
|
||||
needtorestart = installall(mountpoint)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
unmountdmg(mountpoint)
|
||||
|
||||
# now remove the item from the install cache
|
||||
# (using rm -f in case it's a bundle pkg)
|
||||
retcode = subprocess.call(["/bin/rm", "-rf", itempath])
|
||||
|
||||
return restartflag
|
||||
|
||||
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
"""
|
||||
Attempts to mount the dmg at dmgpath
|
||||
and returns a list of mountpoints
|
||||
"""
|
||||
mountpoints = []
|
||||
dmgname = os.path.basename(dmgpath)
|
||||
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)
|
||||
(plist, err) = p.communicate()
|
||||
if plist:
|
||||
pl = plistlib.readPlistFromString(plist)
|
||||
for entity in pl['system-entities']:
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
|
||||
return mountpoints
|
||||
|
||||
|
||||
def unmountdmg(mountpoint):
|
||||
"""
|
||||
Unmounts the dmg at mountpoint
|
||||
"""
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, err
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint, '-force'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
|
||||
|
||||
# module (global) variables
|
||||
managedinstallbase = managedinstalls.managed_install_dir()
|
||||
installdir = os.path.join(managedinstallbase , 'Cache')
|
||||
logdir = os.path.join(managedinstallbase, 'Logs')
|
||||
installablecount = 0
|
||||
currentinstallable = 0
|
||||
|
||||
p = optparse.OptionParser()
|
||||
p.add_option('--ihookoutput', '-i', action='store_true')
|
||||
options, arguments = p.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
global installdir
|
||||
global installablecount
|
||||
|
||||
if options.ihookoutput:
|
||||
print '%WINDOWSIZE 512 232'
|
||||
print '%BACKGROUND /Users/Shared/Installer.png'
|
||||
print '%BECOMEKEY'
|
||||
print '%BEGINPOLE'
|
||||
sys.stdout.flush()
|
||||
|
||||
needtorestart = False
|
||||
|
||||
installablecount = countinstallcandidates(installdir)
|
||||
if installablecount:
|
||||
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(0)
|
||||
if "install" in pl:
|
||||
needtorestart = installWithInfo(installdir, pl['install'])
|
||||
# remove the install info
|
||||
os.unlink(installinfo)
|
||||
else:
|
||||
print "No %s found." % installinfo
|
||||
# install all pkgs and mpkgs
|
||||
needtorestart = installall(installdir)
|
||||
|
||||
if needtorestart:
|
||||
print "Software installed requires a restart."
|
||||
log("Software installed requires a restart.")
|
||||
sys.stdout.flush()
|
||||
|
||||
log("### End automated install session ###")
|
||||
if needtorestart:
|
||||
time.sleep(5)
|
||||
# uncomment this when testing is done so it will restart.
|
||||
#retcode = subprocess.call(["/sbin/shutdown", "-r", "now"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
Executable
+242
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
makecatalogitem.py
|
||||
|
||||
Created by Greg Neagle on 2008-11-25.
|
||||
Creates a managed install catalog item 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.
|
||||
|
||||
You may also pass additional items that are installed by the package. These
|
||||
are added to the 'installs' key of the catalog item plist and are used when
|
||||
processing the catalog to check if the package needs to be installed or
|
||||
reinstalled.
|
||||
|
||||
The generated plist is printed to STDOUT.
|
||||
|
||||
Usage: makecatalogitem /path/to/package_or_dmg [-f /path/to/item/it/installs ...]
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import optparse
|
||||
from distutils import version
|
||||
import plistlib
|
||||
import subprocess
|
||||
|
||||
import managedinstalls
|
||||
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
"""
|
||||
Attempts to mount the dmg at dmgpath
|
||||
and returns a list of mountpoints
|
||||
"""
|
||||
mountpoints = []
|
||||
dmgname = os.path.basename(dmgpath)
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-mountRandom', '/tmp', '-nobrowse', '-plist'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(plist, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, "Error %s mounting %s." % (err, dmgpath)
|
||||
if plist:
|
||||
pl = plistlib.readPlistFromString(plist)
|
||||
for entity in pl['system-entities']:
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
|
||||
return mountpoints
|
||||
|
||||
|
||||
def unmountdmg(mountpoint):
|
||||
"""
|
||||
Unmounts the dmg at mountpoint
|
||||
"""
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, err
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint, '-force'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
|
||||
|
||||
def nameAndVersion(s):
|
||||
"""
|
||||
Splits a string into the name and version numbers:
|
||||
'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1')
|
||||
'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1')
|
||||
'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1')
|
||||
"""
|
||||
index = 0
|
||||
for char in s:
|
||||
if char in "0123456789":
|
||||
possibleVersion = s[index:]
|
||||
if not (" " in possibleVersion or "_" in possibleVersion or "-" in possibleVersion or "v" in possibleVersion):
|
||||
return (s[0:index].rstrip(" .-_v"), possibleVersion)
|
||||
index += 1
|
||||
# no version number found, just return original string and empty string
|
||||
return (s, '')
|
||||
|
||||
|
||||
def getCatalogInfo(pkgitem):
|
||||
info = managedinstalls.getPkgInfo(pkgitem)
|
||||
highestpkgversion = "0.0"
|
||||
for infoitem in info:
|
||||
if version.LooseVersion(infoitem['version']) > version.LooseVersion(highestpkgversion):
|
||||
highestpkgversion = infoitem['version']
|
||||
|
||||
name = os.path.split(pkgitem)[1]
|
||||
shortname = os.path.splitext(name)[0]
|
||||
metaversion = nameAndVersion(shortname)[1]
|
||||
if not len(metaversion):
|
||||
metaversion = highestpkgversion
|
||||
|
||||
cataloginfo = {}
|
||||
cataloginfo['name'] = nameAndVersion(shortname)[0]
|
||||
cataloginfo['version'] = metaversion
|
||||
cataloginfo['description'] = ""
|
||||
cataloginfo['receipts'] = []
|
||||
for infoitem in info:
|
||||
pkginfo = {}
|
||||
pkginfo['packageid'] = infoitem['id']
|
||||
pkginfo['version'] = infoitem['version']
|
||||
cataloginfo['receipts'].append(pkginfo)
|
||||
return cataloginfo
|
||||
|
||||
|
||||
def getCatalogInfoFromDmg(dmgpath):
|
||||
cataloginfo = None
|
||||
mountpoints = mountdmg(dmgpath)
|
||||
for mountpoint in mountpoints:
|
||||
for fsitem in os.listdir(mountpoint):
|
||||
itempath = os.path.join(mountpoint, fsitem)
|
||||
if itempath.endswith('.pkg') or itempath.endswith('.mpkg'):
|
||||
cataloginfo = getCatalogInfo(itempath)
|
||||
# get out of fsitem loop
|
||||
break
|
||||
if cataloginfo:
|
||||
# get out of moutpoint loop
|
||||
break
|
||||
|
||||
#unmount all the mountpoints from the dmg
|
||||
for mountpoint in mountpoints:
|
||||
unmountdmg(mountpoint)
|
||||
return cataloginfo
|
||||
|
||||
|
||||
def getBundleInfo(path):
|
||||
"""
|
||||
Returns Info.plist data if available
|
||||
for bundle at path
|
||||
"""
|
||||
infopath = os.path.join(path, "Contents", "Info.plist")
|
||||
if not os.path.exists(infopath):
|
||||
infopath = os.path.join(path, "Resources", "Info.plist")
|
||||
|
||||
if os.path.exists(infopath):
|
||||
try:
|
||||
pl = plistlib.readPlist(infopath)
|
||||
return pl
|
||||
except:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def getiteminfo(itempath):
|
||||
infodict = {}
|
||||
if itempath.endswith('.app'):
|
||||
infodict['type'] = 'application'
|
||||
pl = getBundleInfo(itempath)
|
||||
if 'CFBundleName' in pl:
|
||||
infodict['CFBundleName'] = pl['CFBundleName']
|
||||
if 'CFBundleIdentifier' in pl:
|
||||
infodict['CFBundleIdentifier'] = pl['CFBundleIdentifier']
|
||||
if 'CFBundleShortVersionString' in pl:
|
||||
infodict['CFBundleShortVersionString'] = pl['CFBundleShortVersionString']
|
||||
if 'LSMinimumSystemVersion' in pl:
|
||||
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
|
||||
try:
|
||||
pl = plistlib.readPlist(itempath)
|
||||
if 'CFBundleShortVersionString' in pl:
|
||||
infodict['CFBundleShortVersionString'] = pl['CFBundleShortVersionString']
|
||||
except:
|
||||
pass
|
||||
|
||||
if not 'CFBundleShortVersionString' in infodict:
|
||||
infodict['type'] = 'file'
|
||||
infodict['path'] = itempath
|
||||
return infodict
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
usage = "usage: %prog [options] /path/to/installeritem"
|
||||
p = optparse.OptionParser(usage=usage)
|
||||
p.add_option('--file', '-f', action="append",
|
||||
help='Path to a filesystem item installed by this package. Can be specified multiple times.')
|
||||
options, arguments = p.parse_args()
|
||||
if len(arguments) == 0:
|
||||
print >>sys.stderr, "Need to specify an installer item (.pkg, .mpkg, .dmg)!"
|
||||
exit(-1)
|
||||
|
||||
if len(arguments) > 1:
|
||||
print >>sys.stderr, "Can process only one installer item at a time. Ignoring additional installer items."
|
||||
|
||||
item = arguments[0].rstrip("/")
|
||||
if os.path.exists(item):
|
||||
if item.endswith('.dmg'):
|
||||
catinfo = getCatalogInfoFromDmg(item)
|
||||
elif item.endswith('.pkg') or item.endswith('.mpkg'):
|
||||
catinfo = getCatalogInfo(item)
|
||||
else:
|
||||
print >>sys.stderr, "%s is not an installer package!" % item
|
||||
exit(-1)
|
||||
|
||||
if catinfo:
|
||||
minosversion = ""
|
||||
if options.file:
|
||||
installs = []
|
||||
for fitem in options.file:
|
||||
if os.path.exists(fitem):
|
||||
iteminfodict = getiteminfo(fitem)
|
||||
if 'minosversion' in iteminfodict:
|
||||
thisminosversion = iteminfodict.pop('minosversion')
|
||||
if not minosversion:
|
||||
minosversion = thisminosversion
|
||||
elif version.LooseVersion(thisminosversion) < version.LooseVersion(minosversion):
|
||||
minosversion = thisminosversion
|
||||
installs.append(iteminfodict)
|
||||
else:
|
||||
print >>sys.stderr, "Item %s doesn't exist. Skipping." % fitem
|
||||
catinfo['installs'] = installs
|
||||
|
||||
name = os.path.split(item)[1]
|
||||
catinfo['installer_item_location'] = name
|
||||
if minosversion:
|
||||
catinfo['minimum_os_version'] = minosversion
|
||||
else:
|
||||
catinfo['minimum_os_version'] = "10.4.0"
|
||||
|
||||
# and now, what we've all been waiting for...
|
||||
print plistlib.writePlistToString(catinfo)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""
|
||||
managedinstallslib.py
|
||||
|
||||
Created by Greg Neagle on 2008-11-18.
|
||||
Copyright (c) 2008 Walt Disney Animation Studios. All rights reserved.
|
||||
|
||||
Common functions used by the managedinstalls tools.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import plistlib
|
||||
import urllib2
|
||||
import urlparse
|
||||
import time
|
||||
import calendar
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
from xml.dom import minidom
|
||||
|
||||
|
||||
#####################################################
|
||||
# managed installs preferences/metadata
|
||||
#####################################################
|
||||
|
||||
|
||||
def getManagedInstallsPrefs():
|
||||
# define default values
|
||||
prefs = {}
|
||||
prefs['managed_install_dir'] = "/Library/Managed Installs"
|
||||
prefs['manifest_url'] = "http:/managedinstalls/cgi-bin/getmanifest"
|
||||
prefs['sw_repo_url'] = "http://managedinstalls/swrepo"
|
||||
prefs['client_identifier'] = ""
|
||||
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
|
||||
|
||||
if os.path.exists(prefsfile):
|
||||
try:
|
||||
pl = plistlib.readPlist(prefsfile)
|
||||
except:
|
||||
pass
|
||||
if pl:
|
||||
if 'managed_install_dir' in pl:
|
||||
prefs['managed_install_dir'] = pl['managed_install_dir']
|
||||
if 'manifest_url' in pl:
|
||||
prefs['manifest_url'] = pl['manifest_url']
|
||||
if 'sw_repo_url' in pl:
|
||||
prefs['sw_repo_url'] = pl['sw_repo_url']
|
||||
if 'client_identifier' in pl:
|
||||
prefs['client_identifier'] = pl['client_identifier']
|
||||
|
||||
return prefs
|
||||
|
||||
|
||||
def managed_install_dir():
|
||||
prefs = getManagedInstallsPrefs()
|
||||
return prefs['managed_install_dir']
|
||||
|
||||
|
||||
def manifest_url():
|
||||
prefs = getManagedInstallsPrefs()
|
||||
return prefs['manifest_url']
|
||||
|
||||
|
||||
def sw_repo_url():
|
||||
prefs = getManagedInstallsPrefs()
|
||||
return prefs['sw_repo_url']
|
||||
|
||||
|
||||
def pref(prefname):
|
||||
prefs = getManagedInstallsPrefs()
|
||||
if prefname in prefs:
|
||||
return prefs[prefname]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def prefs():
|
||||
return getManagedInstallsPrefs()
|
||||
|
||||
|
||||
#####################################################
|
||||
# Apple package utilities
|
||||
#####################################################
|
||||
|
||||
|
||||
def normalizeVersion(majorVersion, minorVersion="0"):
|
||||
majorVersionParts = majorVersion.split(".")
|
||||
if len(majorVersionParts) == 5 and minorVersion == "0":
|
||||
minorVersion = majorVersionParts[4]
|
||||
|
||||
while len(majorVersionParts) < 3:
|
||||
majorVersionParts.append("0")
|
||||
|
||||
version = majorVersionParts[0:3]
|
||||
version.append(str(minorVersion))
|
||||
return ".".join(version)
|
||||
|
||||
|
||||
def parsePkgRefs(filename):
|
||||
info = []
|
||||
dom = minidom.parse(filename)
|
||||
pkgrefs = dom.getElementsByTagName("pkg-ref")
|
||||
if pkgrefs:
|
||||
for ref in pkgrefs:
|
||||
keys = ref.attributes.keys()
|
||||
if 'id' in keys and 'version' in keys:
|
||||
if debug:
|
||||
for key in keys:
|
||||
print key, "=>", ref.attributes[key].value.encode('UTF-8')
|
||||
|
||||
pkginfo = {}
|
||||
pkginfo['id'] = ref.attributes['id'].value.encode('UTF-8')
|
||||
pkginfo['version'] = normalizeVersion(ref.attributes['version'].value.encode('UTF-8'))
|
||||
if not pkginfo in info:
|
||||
info.append(pkginfo)
|
||||
else:
|
||||
pkgrefs = dom.getElementsByTagName("pkg-info")
|
||||
if pkgrefs:
|
||||
for ref in pkgrefs:
|
||||
keys = ref.attributes.keys()
|
||||
if 'identifier' in keys and 'version' in keys:
|
||||
if debug:
|
||||
for key in keys:
|
||||
print key, "=>", ref.attributes[key].value.encode('UTF-8')
|
||||
|
||||
pkginfo = {}
|
||||
pkginfo['id'] = ref.attributes['identifier'].value.encode('UTF-8')
|
||||
pkginfo['version'] = normalizeVersion(ref.attributes['version'].value.encode('UTF-8'))
|
||||
if not pkginfo in info:
|
||||
info.append(pkginfo)
|
||||
return info
|
||||
|
||||
|
||||
def getFlatPackageInfo(pkgpath):
|
||||
"""
|
||||
returns array of dictionaries with info on packages
|
||||
contained in the flat package
|
||||
"""
|
||||
|
||||
infoarray = []
|
||||
mytmpdir = tempfile.mkdtemp()
|
||||
os.chdir(mytmpdir)
|
||||
p = subprocess.Popen(["/usr/bin/xar", "-xf", pkgpath, "--exclude", "Payload"])
|
||||
returncode = p.wait()
|
||||
if returncode == 0:
|
||||
currentdir = mytmpdir
|
||||
packageinfofile = os.path.join(currentdir, "PackageInfo")
|
||||
if os.path.exists(packageinfofile):
|
||||
infoarray = parsePkgRefs(packageinfofile)
|
||||
|
||||
else:
|
||||
distributionfile = os.path.join(currentdir, "Distribution")
|
||||
if os.path.exists(distributionfile):
|
||||
infoarray = parsePkgRefs(distributionfile)
|
||||
|
||||
shutil.rmtree(mytmpdir)
|
||||
return infoarray
|
||||
|
||||
|
||||
def getBundlePackageInfo(pkgpath):
|
||||
infoarray = []
|
||||
pkginfo = {}
|
||||
|
||||
if pkgpath.endswith(".pkg"):
|
||||
plistpath = os.path.join(pkgpath, "Contents", "Info.plist")
|
||||
if os.path.exists(plistpath):
|
||||
pl = plistlib.readPlist(plistpath)
|
||||
if debug:
|
||||
for key in pl:
|
||||
print key, "=>", pl[key]
|
||||
if "CFBundleIdentifier" in pl:
|
||||
pkginfo['id'] = pl["CFBundleIdentifier"];
|
||||
if "CFBundleShortVersionString" in pl:
|
||||
majorVersion = pl["CFBundleShortVersionString"]
|
||||
minorVersion = "0"
|
||||
if "IFMinorVersion" in pl:
|
||||
minorVersion = str(pl["IFMinorVersion"])
|
||||
pkginfo['version'] = normalizeVersion(majorVersion, minorVersion)
|
||||
infoarray.append(pkginfo)
|
||||
return infoarray
|
||||
|
||||
bundlecontents = os.path.join(pkgpath, "Contents")
|
||||
if os.path.exists(bundlecontents):
|
||||
for item in os.listdir(bundlecontents):
|
||||
if item.endswith(".dist"):
|
||||
filename = os.path.join(bundlecontents, item)
|
||||
infoarray = parsePkgRefs(filename)
|
||||
return infoarray
|
||||
|
||||
return infoarray
|
||||
|
||||
|
||||
def getPkgInfo(p):
|
||||
info = []
|
||||
if p.endswith(".pkg") or p.endswith(".mpkg"):
|
||||
if debug:
|
||||
print "Examining %s" % p
|
||||
if os.path.isfile(p): # new flat package
|
||||
info = getFlatPackageInfo(p)
|
||||
|
||||
if os.path.isdir(p): # bundle-style package?
|
||||
info = getBundlePackageInfo(p)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def examinePackage(p):
|
||||
info = []
|
||||
if p.endswith(".pkg") or p.endswith(".mpkg"):
|
||||
if debug:
|
||||
print "Examining %s" % p
|
||||
if os.path.isfile(p): # new flat package
|
||||
info = getFlatPackageInfo(p)
|
||||
|
||||
if os.path.isdir(p): # bundle-style package?
|
||||
info = getBundlePackageInfo(p)
|
||||
|
||||
if len(info) == 0:
|
||||
print >>sys.stderr, "Can't determine bundle ID of %s." % p
|
||||
return
|
||||
|
||||
# print info
|
||||
for pkg in info:
|
||||
#print pkg
|
||||
pkg_id = pkg['id']
|
||||
vers = pkg['version']
|
||||
print "packageid: %s \t version: %s" % (pkg_id, vers)
|
||||
|
||||
else:
|
||||
print >>sys.stderr, "%s doesn't appear to be an Installer package." % p
|
||||
|
||||
|
||||
def getInstalledPackageVersion(pkgid):
|
||||
"""
|
||||
Checks a package id against the receipts to
|
||||
determine if a package is already installed.
|
||||
Returns the version string of the installed pkg
|
||||
if it exists, or an empty string if it does not
|
||||
"""
|
||||
|
||||
# Check /Library/Receipts
|
||||
receiptsdir = "/Library/Receipts"
|
||||
if os.path.exists(receiptsdir):
|
||||
installitems = os.listdir(receiptsdir)
|
||||
for item in installitems:
|
||||
if item.endswith(".pkg"):
|
||||
info = getBundlePackageInfo(os.path.join(receiptsdir, item))
|
||||
if len(info):
|
||||
infoitem = info[0]
|
||||
foundbundleid = infoitem['id']
|
||||
foundvers = infoitem['version']
|
||||
if pkgid == foundbundleid:
|
||||
return foundvers
|
||||
|
||||
# If we got to this point, we haven't found the pkgid yet.
|
||||
# Now check new (Leopard) package database
|
||||
p = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid], bufsize=1,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
|
||||
if out:
|
||||
pl = plistlib.readPlistFromString(out)
|
||||
|
||||
if "pkgid" in pl:
|
||||
foundbundleid = pl["pkgid"];
|
||||
if "pkg-version" in pl:
|
||||
foundvers = pl["pkg-version"]
|
||||
if pkgid == foundbundleid:
|
||||
return normalizeVersion(foundvers)
|
||||
|
||||
# This package does not appear to be currently installed
|
||||
return ""
|
||||
|
||||
#
|
||||
# Handles http downloads for the managed installer tools.
|
||||
#
|
||||
# Supports Last-modified and If-modified-since headers so
|
||||
# we download from the server only if we don't have it in the
|
||||
# local cache, or the locally cached item is older than the
|
||||
# one on the server.
|
||||
#
|
||||
# Possible failure mode: if client's main catalog gets pointed
|
||||
# to a different, older, catalog, we'll fail to retreive it.
|
||||
# Need to check content length as well, and if it changes, retreive
|
||||
# it anyway.
|
||||
#
|
||||
|
||||
|
||||
def getsteps(num_of_steps, limit):
|
||||
"""
|
||||
Helper function for display_percent_done
|
||||
"""
|
||||
steps = []
|
||||
current = 0.0
|
||||
for i in range(0,num_of_steps):
|
||||
if i == num_of_steps-1:
|
||||
steps.append(int(round(limit)))
|
||||
else:
|
||||
steps.append(int(round(current)))
|
||||
current += float(limit)/float(num_of_steps-1)
|
||||
return steps
|
||||
|
||||
|
||||
def display_percent_done(current,maximum):
|
||||
"""
|
||||
Mimics the command-line progress meter seen in some
|
||||
of Apple's tools (like softwareupdate)
|
||||
"""
|
||||
step = getsteps(16, maximum)
|
||||
output = ''
|
||||
indicator = ['\t0','.','.','20','.','.','40','.','.',
|
||||
'60','.','.','80','.','.','100\n']
|
||||
for i in range(0,16):
|
||||
if current == step[i]:
|
||||
output += indicator[i]
|
||||
if output:
|
||||
sys.stdout.write(output)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def httpDownload(url, filename, headers={}, postData=None, reporthook=None):
|
||||
reqObj = urllib2.Request(url, postData, headers)
|
||||
fp = urllib2.urlopen(reqObj)
|
||||
headers = fp.info()
|
||||
|
||||
#read & write fileObj to filename
|
||||
tfp = open(filename, 'wb')
|
||||
result = filename, headers
|
||||
bs = 1024*8
|
||||
size = -1
|
||||
read = 0
|
||||
blocknum = 0
|
||||
|
||||
if reporthook:
|
||||
if "content-length" in headers:
|
||||
size = int(headers["Content-Length"])
|
||||
reporthook(blocknum, bs, size)
|
||||
|
||||
while 1:
|
||||
block = fp.read(bs)
|
||||
if block == "":
|
||||
break
|
||||
read += len(block)
|
||||
tfp.write(block)
|
||||
blocknum += 1
|
||||
if reporthook:
|
||||
reporthook(blocknum, bs, size)
|
||||
|
||||
fp.close()
|
||||
tfp.close()
|
||||
|
||||
# raise exception if actual size does not match content-length header
|
||||
if size >= 0 and read < size:
|
||||
raise ContentTooShortError("retrieval incomplete: got only %i out "
|
||||
"of %i bytes" % (read, size), result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def getfilefromhttpurl(url,filepath,showprogress=False,ifmodifiedsince=None):
|
||||
"""
|
||||
gets a file from a url.
|
||||
If 'ifmodifiedsince' is specified, this header is set
|
||||
and the file is not retreived if it hasn't changed on the server.
|
||||
Returns 0 if successful, or HTTP error code
|
||||
"""
|
||||
def reporthook(block_count, block_size, file_size):
|
||||
if showprogress and (file_size > 0):
|
||||
max_blocks = file_size/block_size
|
||||
display_percent_done(block_count, max_blocks)
|
||||
|
||||
try:
|
||||
request_headers = {}
|
||||
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)
|
||||
if 'last-modified' in headers:
|
||||
# set the modtime of the downloaded file to the modtime of the
|
||||
# file on the server
|
||||
modtimestr = headers['last-modified']
|
||||
modtimetuple = time.strptime(modtimestr, "%a, %d %b %Y %H:%M:%S %Z")
|
||||
modtimeint = calendar.timegm(modtimetuple)
|
||||
os.utime(filepath, (time.time(), modtimeint))
|
||||
|
||||
except urllib2.HTTPError, err:
|
||||
return err.code
|
||||
except IOError, err:
|
||||
return err
|
||||
except:
|
||||
return (-1, "Unexpected error")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
debug = False
|
||||
def main():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user