munkilib.py:

Workaround for broken ScriptingBridge to SystemConfiguration in an unreleased OS version.
removepackages.py: 
for an unreleased OS version, no longer manipulate the Apple receipts DB using sqlite3, but instead just use pkgutil (and other related changes)
munkistatus.py: 
munkistatus.message() now clears the detail field.
managedinstaller: 
removed explicit clearing of munkistatus detail
makecatalogs: 
more progress and error reporting when building catalogs
makepkginfo: 
add default catalogs, uninstallable, and uninstall_method info to dictionary



git-svn-id: http://munki.googlecode.com/svn/trunk@104 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2009-06-24 04:58:17 +00:00
parent 7bec452b3e
commit c435d139ba
6 changed files with 184 additions and 49 deletions
+4 -1
View File
@@ -65,9 +65,12 @@ def makeCatalogs(repopath):
if not catalogname in catalogs:
catalogs[catalogname] = []
catalogs[catalogname].append(pkginfo)
print "Adding %s to %s..." % (filepath[len(pkgsinfopath)+1:], catalogname)
else:
print >>sys.stderr, "WARNING: Info file %s refers to missing installer item: %s" % (filepath[len(pkgsinfopath)+1:], pkginfo['installer_item_location'])
else:
print >>sys.stderr, "WARNING: file %s is not a valid plist" % filepath
# clear out old catalogs
path = os.path.join(repopath, "catalogs")
for item in os.listdir(path):
+5
View File
@@ -223,6 +223,11 @@ def main():
catinfo['minimum_os_version'] = minosversion
else:
catinfo['minimum_os_version'] = "10.4.0"
# some metainfo
catinfo['catalogs'] = ['testing']
catinfo['uninstallable'] = True
catinfo['uninstall_method'] = "removepackages"
# and now, what we've all been waiting for...
print plistlib.writePlistToString(catinfo)
-4
View File
@@ -82,7 +82,6 @@ def install(pkgpath):
if options.munkistatusoutput:
munkistatus.message("Installing %s..." % packagename)
munkistatus.detail("")
# clear indeterminate progress bar
munkistatus.percent(0)
@@ -307,7 +306,6 @@ def processRemovals(removalList):
restartFlag = True
if options.munkistatusoutput:
munkistatus.message("Removing %s..." % name)
munkistatus.detail("")
# clear indeterminate progress bar
munkistatus.percent(0)
else:
@@ -332,7 +330,6 @@ 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)
@@ -473,7 +470,6 @@ def main():
if options.munkistatusoutput:
munkistatus.hideStopButton()
munkistatus.message("Software installed or removed requires a restart.")
munkistatus.detail("")
munkistatus.percent(-1)
else:
print "Software installed or removed requires a restart."
+12 -3
View File
@@ -35,7 +35,7 @@ import shutil
from distutils import version
from xml.dom import minidom
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
#from SystemConfiguration import SCDynamicStoreCopyConsoleUser
from Foundation import NSDictionary, NSDate
@@ -56,8 +56,17 @@ def readPlist(plistfile):
def getconsoleuser():
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
osvers = int(os.uname()[2].split('.')[0])
if osvers > 9:
cmd = ['/usr/bin/who | /usr/bin/grep console']
p = subprocess.Popen(cmd, shell=True, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
return output
else:
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
def pythonScriptRunning(scriptname):
+13 -10
View File
@@ -51,10 +51,10 @@ def quit():
# it's running, send it a quit message
result = osascript('tell application "MunkiStatus" to quit')
def activate():
osascript('tell application "MunkiStatus" to activate')
def title(titleText):
result = osascript('tell application "MunkiStatus" to set title of window "mainWindow" to "%s"' % titleText)
@@ -62,12 +62,14 @@ def title(titleText):
def message(messageText):
result = osascript('tell application "MunkiStatus" to set contents of text field "mainTextFld" of window "mainWindow" to "%s"' % messageText)
# when you change the message, the detail is no longer valid, so let's clear that
result = osascript('tell application "MunkiStatus" to set contents of text field "minorTextFld" of window "mainWindow" to "")
def detail(detailsText):
result = osascript('tell application "MunkiStatus" to set contents of text field "minorTextFld" of window "mainWindow" to "%s"' % detailsText)
def percent(percentage):
# Note: this is a relatively slow operation.
# If you are calling this on every iteration of a loop,
@@ -104,10 +106,11 @@ def getStopButtonState():
(out, err) = p.communicate()
if p.returncode == 0:
# it's running, ask it for the button state
result = osascript('tell application "MunkiStatus" to get the state of button "stopBtn" of window "mainWindow"')
return int(result)
else:
return 0
result = osascript('tell application "MunkiStatus" to get the state of button "stopBtn" of window "mainWindow"')
if result == "1":
return 1
return 0
def hideStopButton():
+150 -31
View File
@@ -199,6 +199,7 @@ def shouldRebuildDB(pkgdbpath):
"""
receiptsdir = "/Library/Receipts"
bomsdir = "/Library/Receipts/boms"
installhistory = "/Library/Receipts/InstallHistory.plist"
applepkgdb = "/Library/Receipts/db/a.receiptdb"
if not os.path.exists(pkgdbpath):
@@ -229,12 +230,19 @@ def shouldRebuildDB(pkgdbpath):
bom_modtime = os.stat(bompath).st_mtime
if (packagedb_modtime < bom_modtime):
return True
if os.path.exists(installhistory):
installhistory_modtime = os.stat(installhistory).st_mtime
if packagedb_modtime < installhistory_modtime:
return True
if os.path.exists(applepkgdb):
applepkgdb_modtime = os.stat(applepkgdb).st_mtime
if packagedb_modtime < applepkgdb_modtime:
return True
# if we got this far, we don't need to update the db
return False
def CreateTables(c):
@@ -351,12 +359,13 @@ def ImportBom(bompath, c):
Imports package data into our internal package database
using a combination of the bom file and data in Apple's
package database into our internal package database.
If we completely trusted the accuracy of Apple's database, we wouldn't
need the bom files, but in my enviroment at least, the bom files are
a better indicator of what flat packages have actually been installed
on the current machine. We still need to consult Apple's package database
because the bom files are missing metadata about the package.
"""
# If we completely trusted the accuracy of Apple's database, we wouldn't
# need the bom files, but in my enviroment at least, the bom files are
# a better indicator of what flat packages have actually been installed
# on the current machine. We still need to consult Apple's package database
# because the bom files are missing metadata about the package.
applepkgdb = "/Library/Receipts/db/a.receiptdb"
pkgname = os.path.basename(bompath)
@@ -416,6 +425,71 @@ def ImportBom(bompath, c):
c.execute('INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) values (?, ?, ?, ?, ?)', t)
def ImportFromPkgutil(pkgname, c):
"""
Imports package data from pkgutil into our internal package database.
"""
timestamp = 0
owner = 0
pkgid = pkgname
vers = "1.0"
ppath = "./"
#get metadata from applepkgdb
p = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid],
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(plist, err) = p.communicate()
if plist:
pl = plistlib.readPlistFromString(plist)
if "install-location" in pl:
ppath = pl["install-location"]
if "pkg-version" in pl:
vers = pl["pkg-version"]
if "install-time" in pl:
timestamp = pl["install-time"]
t = (timestamp, owner, pkgid, vers, ppath, pkgname)
c.execute('INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)', t)
pkgkey = c.lastrowid
cmd = ["/usr/sbin/pkgutil", "--files", pkgid]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
line = p.stdout.readline()
if not line and (p.poll() != None):
break
path = line.rstrip("\n")
# pkgutil --files pkgid only gives us path info. We don't
# really need perms, uid and gid, so we'll just fake them.
# if we needed them, we'd have to call
# pkgutil --export-plist pkgid and iterate through the
# plist. That would be slower, so we'll do things this way...
perms = "0000"
uid = "0"
gid = "0"
if path != ".":
#prepend the ppath so the paths match the actual install locations
path = path.lstrip("./")
path = ppath + path
path = path.lstrip("./")
t = (path, )
row = c.execute('SELECT path_key from paths where path = ?', t).fetchone()
if not row:
c.execute('INSERT INTO paths (path) values (?)', t)
pathkey = c.lastrowid
else:
pathkey = row[0]
t = (pkgkey, pathkey, uid, gid, perms)
c.execute('INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) values (?, ?, ?, ?, ?)', t)
def initDatabase(packagedb,forcerebuild=False):
"""
Builds or rebuilds our internal package database.
@@ -445,8 +519,22 @@ def initDatabase(packagedb,forcerebuild=False):
for item in bomslist:
if item.endswith(".bom"):
pkgcount += 1
else:
#no boms dir in some versions of OS X
pkglist = []
cmd = ['/usr/sbin/pkgutil', '--pkgs']
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
line = p.stdout.readline()
if not line and (p.poll() != None):
break
pkglist.append(line.rstrip('\n'))
pkgcount += 1
conn = sqlite3.connect(packagedb)
conn.text_factory = str
c = conn.cursor()
CreateTables(c)
@@ -488,7 +576,19 @@ def initDatabase(packagedb,forcerebuild=False):
ImportBom(bompath, c)
currentpkgindex += 1
display_percent_done(currentpkgindex, pkgcount)
else:
#no boms dir in some versions of OS X
for pkg in pkglist:
if stopRequested():
c.close()
conn.close()
#our package db isn't valid, so we should delete it
os.remove(packagedb)
display_info("Importing %s..." % pkg)
ImportFromPkgutil(pkg, c)
currentpkgindex += 1
display_percent_done(currentpkgindex, pkgcount)
# in case we didn't quite get to 100% for some reason
if currentpkgindex < pkgcount:
display_percent_done(pkgcount, pkgcount)
@@ -542,11 +642,17 @@ def getpathstoremove(pkgkeylist):
# set up some subqueries:
# all the paths that are referred to by the selected packages:
in_selected_packages = "select distinct path_key from pkgs_paths where pkg_key in %s" % str(pkgkeys)
if len(pkgkeys) > 1:
in_selected_packages = "select distinct path_key from pkgs_paths where pkg_key in %s" % str(pkgkeys)
else:
in_selected_packages = "select distinct path_key from pkgs_paths where pkg_key = %s" % str(pkgkeys[0])
# all the paths that are referred to by every package except the selected packages:
not_in_other_packages = "select distinct path_key from pkgs_paths where pkg_key not in %s" % str(pkgkeys)
if len(pkgkeys) > 1:
not_in_other_packages = "select distinct path_key from pkgs_paths where pkg_key not in %s" % str(pkgkeys)
else:
not_in_other_packages = "select distinct path_key from pkgs_paths where pkg_key != %s" % str(pkgkeys[0])
# every path that is used by the selected packages and no other packages:
combined_query = "select path from paths where (path_key in (%s) and path_key not in (%s))" % (in_selected_packages, not_in_other_packages)
@@ -577,10 +683,13 @@ def removeReceipts(pkgkeylist, noupdateapplepkgdb):
conn = sqlite3.connect(packagedb)
c = conn.cursor()
osvers = int(os.uname()[2].split('.')[0])
applepkgdb = '/Library/Receipts/db/a.receiptdb'
if not noupdateapplepkgdb:
aconn = sqlite3.connect(applepkgdb)
ac = aconn.cursor()
if osvers < 10:
aconn = sqlite3.connect(applepkgdb)
ac = aconn.cursor()
if not verbose:
display_percent_done(1,4)
@@ -592,11 +701,12 @@ def removeReceipts(pkgkeylist, noupdateapplepkgdb):
if row:
pkgname = row[0]
pkgid = row[1]
receiptpath = None
if pkgname.endswith('.pkg'):
receiptpath = os.path.join('/Library/Receipts', pkgname)
if pkgname.endswith('.bom'):
receiptpath = os.path.join('/Library/Receipts/boms', pkgname)
if os.path.exists(receiptpath):
if receiptpath and os.path.exists(receiptpath):
display_info("Removing %s..." % receiptpath)
log("Removing %s..." % receiptpath)
retcode = subprocess.call(["/bin/rm", "-rf", receiptpath])
@@ -610,31 +720,40 @@ def removeReceipts(pkgkeylist, noupdateapplepkgdb):
# then remove pkg info from Apple's database unless option is passed
if not noupdateapplepkgdb:
if pkgid:
t = (pkgid, )
row = ac.execute('SELECT pkg_key FROM pkgs where pkgid = ?', t).fetchone()
if row:
if osvers < 10:
t = (pkgid, )
row = ac.execute('SELECT pkg_key FROM pkgs where pkgid = ?', t).fetchone()
if row:
if verbose:
print "Removing package data from Apple package database..."
apple_pkg_key = row[0]
t = (apple_pkg_key, )
ac.execute('DELETE FROM pkgs where pkg_key = ?', t)
ac.execute('DELETE FROM pkgs_paths where pkg_key = ?', t)
ac.execute('DELETE FROM pkgs_groups where pkg_key = ?', t)
ac.execute('DELETE FROM acls where pkg_key = ?', t)
ac.execute('DELETE FROM taints where pkg_key = ?', t)
ac.execute('DELETE FROM sha1s where pkg_key = ?', t)
ac.execute('DELETE FROM oldpkgs where pkg_key = ?', t)
else:
cmd = ['/usr/sbin/pkgutil', '--forget', pkgid]
p = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
if verbose:
print "Removing package data from Apple package database..."
apple_pkg_key = row[0]
t = (apple_pkg_key, )
ac.execute('DELETE FROM pkgs where pkg_key = ?', t)
ac.execute('DELETE FROM pkgs_paths where pkg_key = ?', t)
ac.execute('DELETE FROM pkgs_groups where pkg_key = ?', t)
ac.execute('DELETE FROM acls where pkg_key = ?', t)
ac.execute('DELETE FROM taints where pkg_key = ?', t)
ac.execute('DELETE FROM sha1s where pkg_key = ?', t)
ac.execute('DELETE FROM oldpkgs where pkg_key = ?', t)
if output: print output.rstrip('\n')
display_percent_done(2,4)
# now remove orphaned paths from paths table
# first, Apple's database if option is passed
if not noupdateapplepkgdb:
display_info("Removing unused paths from Apple package database...")
ac.execute('DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)')
aconn.commit()
ac.close()
aconn.close()
if osvers < 10:
display_info("Removing unused paths from Apple package database...")
ac.execute('DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)')
aconn.commit()
ac.close()
aconn.close()
display_percent_done(3,4)