Renamed managedinstalls.py to munkilib.py.

Removed cfconsoleuser.py. Its function was added to munkilib.py.
Renamed ManagedInstaller to managedinstaller.py. Next commit it will be renamed to managedinstaller.
Renamed removepackages to removepackages.py
Refactored removepackages.py so it can be called as a Python module as well as a command-line tool.
managedinstaller.py now calls removepackages.py as a module instead of as a subprocess.
Cleaned up globals in installcheck, managedinstaller, and removepackages.
Removed bad copyright info from makcatalogs.


git-svn-id: http://munki.googlecode.com/svn/trunk@74 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2009-05-20 16:34:17 +00:00
parent bc8b3d2870
commit 9ab12d5a8f
8 changed files with 193 additions and 262 deletions
+3 -3
View File
@@ -6,10 +6,10 @@ ManagedSoftwareUpdate.app - user notification tool.
MunkiStatus - used by ManagedInstaller to provide user feedback on the installation and removal process.
makepkginfo: Helper tool to help create info files for each installer item.
makecatalogs: Creates the software catalogs from the pkginfo files.
removepackages: used by ManagedInstaller to do package removals.
Supporting libraries:
managedinstalls.py - shared functions
munkistatus.py - functions to display status using MunkiStatus.app
munkistatus.py - functions to display status using MunkiStatus.app. Can also be called directly as a command-line tool.
removepackages.py: used by ManagedInstaller to do package removals. Can also be called directly as a command-line tool.
-30
View File
@@ -1,30 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
"""
cfconsoleuser.py
Created by Greg Neagle on 2009-04-20.
"""
# Copyright 2009 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import os
from SystemConfiguration import *
def getConsoleUser():
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
+20 -20
View File
@@ -40,7 +40,7 @@ import time
import socket
#our lib
import managedinstalls
import munkilib
def log(message):
@@ -73,7 +73,7 @@ def reporterrors():
# this needs to be expanded to support error reporting
# via email and HTTP CGI.
global options
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
clientidentifier = managedinstallprefs.get('client_identifier','')
alternate_id = options.id
hostname = os.uname()[1]
@@ -133,8 +133,8 @@ def compareVersions(thisvers, thatvers):
Returns 1 if thisvers is the same as thatvers
Returns 2 if thisvers is newer than thatvers
"""
thisvers = managedinstalls.padVersionString(thisvers,5)
thatvers = managedinstalls.padVersionString(thatvers,5)
thisvers = munkilib.padVersionString(thisvers,5)
thatvers = munkilib.padVersionString(thatvers,5)
if version.LooseVersion(thisvers) < version.LooseVersion(thatvers):
return -1
elif version.LooseVersion(thisvers) == version.LooseVersion(thatvers):
@@ -352,7 +352,7 @@ def compareReceiptVersion(item):
return -2
printandlog("Looking for package %s, version %s" % (pkgid, vers), 2)
installedvers = managedinstalls.getInstalledPackageVersion(pkgid)
installedvers = munkilib.getInstalledPackageVersion(pkgid)
if installedvers:
return compareVersions(installedvers, vers)
else:
@@ -368,7 +368,7 @@ def getInstalledVersion(pl):
if 'receipts' in pl:
if len(pl['receipts']) == 1:
pkgid = pl['receipts'][0]['packageid']
installedvers = managedinstalls.getInstalledPackageVersion(pkgid)
installedvers = munkilib.getInstalledPackageVersion(pkgid)
if installedvers:
return installedvers
@@ -419,7 +419,7 @@ def download_installeritem(pkgurl):
"""
global mytmpdir
managed_install_dir = managedinstalls.managed_install_dir()
managed_install_dir = munkilib.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)
@@ -437,7 +437,7 @@ def download_installeritem(pkgurl):
elif not options.verbose:
dl_message = "Downloading %s" % pkgname
result = managedinstalls.getfilefromhttpurl(pkgurl, tempfilepath, showprogress=not(options.quiet), ifmodifiedsince=itemmodtime, message=dl_message)
result = munkilib.getfilefromhttpurl(pkgurl, tempfilepath, showprogress=not(options.quiet), ifmodifiedsince=itemmodtime, message=dl_message)
if result == 0:
os.rename(tempfilepath, destinationpath)
return True
@@ -511,7 +511,7 @@ def getAllMatchingItems(name,cataloglist):
itemlist = []
# we'll throw away any included version info
(name, includedversion) = nameAndVersion(name)
managedinstalldir = managedinstalls.managed_install_dir()
managedinstalldir = munkilib.managed_install_dir()
catalogsdir = os.path.join(managedinstalldir, 'catalogs')
printandlog("Looking for all items matching: %s..." % name, 1)
for catalogname in cataloglist:
@@ -545,7 +545,7 @@ def getManifestItemDetail(name, cataloglist, version=''):
version = includedversion
else:
version = 'latest'
managedinstalldir = managedinstalls.managed_install_dir()
managedinstalldir = munkilib.managed_install_dir()
catalogsdir = os.path.join(managedinstalldir, 'catalogs')
printandlog("Looking for detail for: %s, version %s..." % (name, version), 1)
for catalogname in cataloglist:
@@ -592,7 +592,7 @@ def enoughDiskSpace(manifestitem_pl):
if 'installed_size' in manifestitem_pl:
installedsize = manifestitem_pl['installed_size']
diskspaceneeded = (installeritemsize + installedsize + fudgefactor)/1024
availablediskspace = managedinstalls.getAvailableDiskSpace()/1024
availablediskspace = munkilib.getAvailableDiskSpace()/1024
if availablediskspace > diskspaceneeded:
return True
else:
@@ -700,7 +700,7 @@ def processInstalls(manifestitem, cataloglist, installinfo):
will stop the installation of a dependent item
"""
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
managed_install_dir = managedinstallprefs['managed_install_dir']
@@ -952,7 +952,7 @@ def processRemovals(manifestitem, cataloglist, installinfo):
# and we're supposed to remove SomePackage--1.0.1.0.0... what do we do?
#
dependentitemsremoved = True
managed_install_dir = managedinstalls.managed_install_dir()
managed_install_dir = munkilib.managed_install_dir()
catalogsdir = os.path.join(managed_install_dir, 'catalogs')
processednamesandaliases = []
@@ -1056,7 +1056,7 @@ def getCatalogs(cataloglist):
"""
Retreives the catalogs from the server
"""
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
catalog_dir = os.path.join(managedinstallprefs['managed_install_dir'], "catalogs")
@@ -1069,7 +1069,7 @@ def getCatalogs(cataloglist):
message = None
elif not options.verbose:
message = "Retreiving catalog '%s'..." % catalog
newcatalog = managedinstalls.getHTTPfileIfNewerAtomically(catalogurl,catalogpath,showprogress=not(options.quiet), message=message)
newcatalog = munkilib.getHTTPfileIfNewerAtomically(catalogurl,catalogpath,showprogress=not(options.quiet), message=message)
if not newcatalog:
logerror("Could not retreive catalog %s from server." % catalog)
@@ -1078,7 +1078,7 @@ def getmanifest(partialurl):
"""
Gets a manifest from the server
"""
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
sw_repo_baseurl = managedinstallprefs['sw_repo_url']
manifest_dir = os.path.join(managedinstallprefs['managed_install_dir'], "manifests")
@@ -1099,7 +1099,7 @@ def getmanifest(partialurl):
elif not options.verbose:
message = "Retreiving list of software for this machine..."
newmanifest = managedinstalls.getHTTPfileIfNewerAtomically(manifesturl,manifestpath,showprogress=not(options.quiet), message=message)
newmanifest = munkilib.getHTTPfileIfNewerAtomically(manifesturl,manifestpath,showprogress=not(options.quiet), message=message)
if not newmanifest:
logerror("Could not retreive manifest %s from the server." % partialurl)
@@ -1109,7 +1109,7 @@ def getPrimaryManifest(alternate_id):
"""
Gets the client manifest from the server
"""
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
manifesturl = managedinstallprefs['manifest_url']
clientidentifier = managedinstallprefs.get('client_identifier','')
@@ -1147,7 +1147,7 @@ def getRemovalCount(installinfo):
def checkServer():
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
manifesturl = managedinstallprefs['manifest_url']
# deconstruct URL so we can check availability
port = 80
@@ -1194,7 +1194,7 @@ def main():
if not options.quiet: print "Managed Software Check\n"
managedinstallprefs = managedinstalls.prefs()
managedinstallprefs = munkilib.prefs()
managed_install_dir = managedinstallprefs['managed_install_dir']
logginglevel = managedinstallprefs.get('logging_level', 1)
manifestsdir = os.path.join(managed_install_dir, "manifests")
-1
View File
@@ -18,7 +18,6 @@
makecatalogs
Created by Greg Neagle on 2009-03-30.
Copyright (c) 2009 Walt Disney Animation Studios. All rights reserved.
Recursively scans a directory, looking for installer item info files. Builds a repo catalog from these files.
+3 -3
View File
@@ -41,7 +41,7 @@ import plistlib
import subprocess
import hashlib
import managedinstalls
import munkilib
def mountdmg(dmgpath):
@@ -115,8 +115,8 @@ def getCatalogInfo(pkgitem):
receipts: an array of packageids that may be installed (some may be optional)
"""
installedsize = 0
installerinfo = managedinstalls.getInstallerPkgInfo(pkgitem)
info = managedinstalls.getPkgInfo(pkgitem)
installerinfo = munkilib.getInstallerPkgInfo(pkgitem)
info = munkilib.getPkgInfo(pkgitem)
highestpkgversion = "0.0"
for infoitem in info:
@@ -14,10 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
ManagedInstaller
managedinstaller
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
(containing pkgs and mpkgs) from a defined folder.
"""
import os
@@ -26,12 +25,9 @@ import sys
import time
import plistlib
import optparse
import managedinstalls
import munkilib
import munkistatus
import cfconsoleuser
pathtoremovepackages = "/Users/Shared/munki/munki/code/client/removepackages"
from removepackages import removepackages
def stopRequested():
if options.munkistatusoutput:
@@ -59,7 +55,6 @@ def createDirsIfNeeded(dirlist):
def log(message):
global logdir
logfile = os.path.join(logdir,'ManagedInstaller.log')
try:
f = open(logfile, mode='a', buffering=1)
@@ -75,7 +70,6 @@ def install(pkgpath):
at pkgpath. Prints status messages to STDOUT.
Returns the installer return code and true if a restart is needed.
"""
global options
restartneeded = False
installeroutput = []
@@ -271,7 +265,6 @@ def getRemovalCount(removalList):
def processRemovals(removalList):
global logdir
restartFlag = False
for item in removalList:
if stopRequested():
@@ -294,51 +287,15 @@ def processRemovals(removalList):
print "Removing %s..." % name
log("Removing %s..." % name)
cmd = [pathtoremovepackages, '-f', '--logfile', os.path.join(logdir,'ManagedInstaller.log')]
if options.munkistatusoutput:
cmd.append('-m')
cmd.append('-d')
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()
retcode = removepackages(item['packages'],
munkistatusoutput=options.munkistatusoutput,
forcedeletebundles=True,
logfile=os.path.join(logdir,'ManagedInstaller.log'))
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 = "-------------------------------------------------"
if retcode == -128:
message = "Uninstall of %s was cancelled." % name
else:
message = "Uninstall of %s failed." % name
print >>sys.stderr, message
log(message)
else:
@@ -437,17 +394,19 @@ def unmountdmg(mountpoint):
# module (global) variables
managedinstallbase = managedinstalls.managed_install_dir()
installdir = os.path.join(managedinstallbase , 'Cache')
logdir = os.path.join(managedinstallbase, 'Logs')
p = optparse.OptionParser()
p.add_option('--munkistatusoutput', '-m', action='store_true')
options, arguments = p.parse_args()
logdir = None
options = None
def main():
global installdir
global logdir, options
managedinstallbase = munkilib.managed_install_dir()
installdir = os.path.join(managedinstallbase , 'Cache')
logdir = os.path.join(managedinstallbase, 'Logs')
p = optparse.OptionParser()
p.add_option('--munkistatusoutput', '-m', action='store_true')
options, arguments = p.parse_args()
needtorestart = False
createDirsIfNeeded([logdir])
@@ -507,20 +466,22 @@ def main():
log("### End managed installer session ###")
if needtorestart:
if cfconsoleuser.getConsoleUser() == None or not options.munkistatusoutput:
if munkilib.getconsoleuser() == None:
time.sleep(5)
cleanup()
retcode = subprocess.call(["/sbin/shutdown", "-r", "now"])
else:
# someone is logged in and we're using MunkiStatus
munkistatus.activate()
munkistatus.osascript('tell application "MunkiStatus" to display alert "Restart Required" message "Software installed requires a restart. You will have a chance to save open documents." as critical default button "Restart"')
cleanup()
munkistatus.osascript('tell application "System Events" to restart')
if options.munkistatusoutput:
# someone is logged in and we're using MunkiStatus
munkistatus.activate()
munkistatus.osascript('tell application "MunkiStatus" to display alert "Restart Required" message "Software installed requires a restart. You will have a chance to save open documents." as critical default button "Restart"')
cleanup()
munkistatus.osascript('tell application "System Events" to restart')
else:
print "Please restart immediately."
else:
cleanup()
if __name__ == '__main__':
main()
@@ -15,11 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
managedinstalls
munkilib
Created by Greg Neagle on 2008-11-18.
Common functions used by the managedinstalls tools.
Common functions used by the munki tools.
"""
import sys
@@ -34,6 +34,13 @@ import tempfile
import shutil
from xml.dom import minidom
from SystemConfiguration import *
def getconsoleuser():
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
#####################################################
# managed installs preferences/metadata
@@ -44,8 +51,8 @@ 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['manifest_url'] = "http:/munki/repo/manifests/"
prefs['sw_repo_url'] = "http:/munki/repo"
prefs['client_identifier'] = ""
prefs['logging_level'] = 1
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
@@ -518,7 +525,7 @@ def getHTTPfileIfNewerAtomically(url,destinationpath,showprogress=False, message
debug = False
def main():
pass
print "This is a library of support tools for the Munki Suite."
if __name__ == '__main__':
@@ -15,12 +15,16 @@
# limitations under the License.
"""
removepackages - a tool to analyze installed packages and remove
removepackages.py
a tool to analyze installed packages and remove
files unique to the packages given at the command line. No attempt
is made to revert to older versions of a file when uninstalling;
only file removals are done.
Callable directly from the command-line and as a python module.
"""
#TO-DO: refactor this so it can be called as a library as well as a command-line script
import optparse
import os
@@ -30,6 +34,7 @@ import plistlib
import sqlite3
import time
import munkistatus
import munkilib
##################################################################
@@ -71,7 +76,7 @@ import munkistatus
# taint VARCHAR NOT NULL);
#################################################################
#################################################################
# our db schema
# our package db schema -- a subset of Apple's
#
# CREATE TABLE paths (path_key INTEGER PRIMARY KEY AUTOINCREMENT,
# path VARCHAR NOT NULL UNIQUE )
@@ -91,7 +96,7 @@ import munkistatus
#################################################################
def stopRequested():
if options.munkistatusoutput:
if munkistatusoutput:
if munkistatus.getStopButtonState() == 1:
return True
@@ -119,15 +124,15 @@ def display_percent_done(current,maximum):
of Apple's tools (like softwareupdate), or tells
MunkiStatus to display percent done via progress bar.
"""
if options.munkistatusoutput:
step = getsteps(101, maximum)
if munkistatusoutput:
step = getsteps(21, maximum)
if current in step:
if current == maximum:
percentdone = 100
else:
percentdone = step.index(current)
percentdone = int(float(current)/float(maximum)*100)
munkistatus.percent(str(percentdone))
elif not options.verbose:
elif not verbose:
step = getsteps(16, maximum)
output = ''
indicator = ['\t0','.','.','20','.','.','40','.','.',
@@ -146,9 +151,9 @@ def display_status(msg):
for verbose/non-verbose and munkistatus-style output.
"""
log(msg)
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(msg)
elif options.verbose:
elif verbose:
print "%s..." % msg
else:
print "%s: " % msg
@@ -160,10 +165,10 @@ def display_info(msg):
Displays minor info messages, formatting as needed
for verbose/non-verbose and munkistatus-style output.
"""
if options.munkistatusoutput:
if munkistatusoutput:
#munkistatus.detail(msg)
pass
elif options.verbose:
elif verbose:
print msg
@@ -178,7 +183,7 @@ def display_error(msg):
def log(msg):
try:
f = open(options.logfile, mode='a', buffering=1)
f = open(logfile, mode='a', buffering=1)
print >>f, time.ctime(), msg
f.close()
except:
@@ -383,15 +388,6 @@ def ImportBom(bompath, c):
c.execute('INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)', t)
pkgkey = c.lastrowid
#pkgdict = {}
#pkgdict['timestamp'] = timestamp
#pkgdict['owner'] = owner
#pkgdict['pkgid'] = pkgid
#pkgdict['vers'] = vers
#pkgdict['ppath'] = ppath
#pkgdict['pkgname'] = pkgname
#pkgdbarray.append(pkgdict)
cmd = ["/usr/bin/lsbom", bompath]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -467,8 +463,8 @@ def initDatabase(packagedb,forcerebuild=False):
conn.close()
#our package db isn't valid, so we should delete it
os.remove(packagedb)
cleanup()
exit()
return False
if item.endswith(".pkg"):
receiptpath = os.path.join(receiptsdir, item)
@@ -485,8 +481,8 @@ def initDatabase(packagedb,forcerebuild=False):
conn.close()
#our package db isn't valid, so we should delete it
os.remove(packagedb)
cleanup()
exit()
return False
if item.endswith(".bom"):
bompath = os.path.join(bomsdir, item)
@@ -503,12 +499,6 @@ def initDatabase(packagedb,forcerebuild=False):
conn.commit()
c.close()
conn.close()
# create a plist with the installed packages
#pl = {}
#pl['packages'] = pkgdbarray
#plistlib.writePlist(pl,'/Users/Shared/InstalledPackages.plist')
return True
@@ -546,7 +536,7 @@ def getpathstoremove(pkgkeylist):
"""
Queries our database for paths to remove.
"""
pkgkeys = tuple(pkgkeyslist)
pkgkeys = tuple(pkgkeylist)
# open connection and cursor to our database
conn = sqlite3.connect(packagedb)
@@ -563,7 +553,7 @@ def getpathstoremove(pkgkeylist):
combined_query = "select path from paths where (path_key in (%s) and path_key not in (%s))" % (in_selected_packages, not_in_other_packages)
display_status('Determining which filesystem items to remove')
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.percent(-1)
c.execute(combined_query)
@@ -577,7 +567,7 @@ def getpathstoremove(pkgkeylist):
return removalpaths
def removeReceipts(pkgkeylist):
def removeReceipts(pkgkeylist, noupdateapplepkgdb):
"""
Removes receipt data from /Library/Receipts,
/Library/Receipts/boms, our internal package database,
@@ -590,11 +580,11 @@ def removeReceipts(pkgkeylist):
c = conn.cursor()
applepkgdb = '/Library/Receipts/db/a.receiptdb'
if not options.noupdateapplepkgdb:
if not noupdateapplepkgdb:
aconn = sqlite3.connect(applepkgdb)
ac = aconn.cursor()
if not options.verbose:
if not verbose:
display_percent_done(1,4)
for pkgkey in pkgkeylist:
@@ -614,18 +604,18 @@ def removeReceipts(pkgkeylist):
retcode = subprocess.call(["/bin/rm", "-rf", receiptpath])
# remove pkg info from our database
if options.verbose:
if verbose:
print "Removing package data from internal database..."
c.execute('DELETE FROM pkgs_paths where pkg_key = ?', t)
c.execute('DELETE FROM pkgs where pkg_key = ?', t)
# then remove pkg info from Apple's database unless option is passed
if not options.noupdateapplepkgdb:
if not noupdateapplepkgdb:
if pkgid:
t = (pkgid, )
row = ac.execute('SELECT pkg_key FROM pkgs where pkgid = ?', t).fetchone()
if row:
if options.verbose:
if verbose:
print "Removing package data from Apple package database..."
apple_pkg_key = row[0]
t = (apple_pkg_key, )
@@ -641,7 +631,7 @@ def removeReceipts(pkgkeylist):
# now remove orphaned paths from paths table
# first, Apple's database if option is passed
if not options.noupdateapplepkgdb:
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()
@@ -701,7 +691,7 @@ def isBundle(pathname):
else:
return False
def removeFilesystemItems(removalpaths):
def removeFilesystemItems(removalpaths, forcedeletebundles):
"""
Attempts to remove all the paths in the array removalpaths
"""
@@ -740,17 +730,13 @@ def removeFilesystemItems(removalpaths):
# if so directed, if it's a bundle (like .app), we should
# remove it anyway - no use having a broken bundle hanging
# around
if (options.forcedeletebundles and isBundle(pathtoremove)):
if (forcedeletebundles and isBundle(pathtoremove)):
retcode = subprocess.call(['/bin/rm', '-rf', pathtoremove])
if retcode:
display_error("ERROR: couldn't remove bundle %s" % pathtoremove)
else:
display_error("WARNING: Did not remove %s because it is not empty." % pathtoremove)
#we'll update the progress bar only on directories because we're too slow
#otherwise
display_percent_done(itemindex, itemcount)
else:
# not a directory, just unlink it
# we're using rm instead of Python because I don't trust
@@ -758,95 +744,103 @@ def removeFilesystemItems(removalpaths):
retcode = subprocess.call(['/bin/rm', pathtoremove])
if retcode:
display_error("ERROR: couldn't remove item %s" % pathtoremove)
# this should be 100%
display_percent_done(itemindex, itemcount)
display_percent_done(itemindex, itemcount)
def cleanup():
if options.munkistatusoutput:
if not options.dontquitmunkistatus:
munkistatus.quit()
######################################################
# Main
######################################################
# location of our internal pkg db
packagedb = "/Users/Shared/b.receiptdb"
pkgdbarray = []
# command-line options
p = optparse.OptionParser()
p.add_option('--forcedeletebundles', '-f', action='store_true',
help='Delete bundles even if they aren\'t empty.')
p.add_option('--listfiles', '-l', action='store_true',
help='List the filesystem objects to be removed, but do not actually remove them.')
p.add_option('--rebuildpkgdb', action='store_true',
help='Force a rebuild of the internal package database.')
p.add_option('--noremovereceipts', action='store_true',
help='Do not remove receipts and boms from /Library/Receipts and update internal package database.')
p.add_option('--noupdateapplepkgdb', action='store_true',
help='Do not update Apple\'s package database. If --noremovereceipts is also given, this is implied')
p.add_option('--munkistatusoutput', '-m', action='store_true',
help='Output is formatted for use with MunkiStatus.')
p.add_option('--dontquitmunkistatus', '-d', action='store_true',
help="Don't quit MunkiStatus on exit. (For example, when called by ManagedInstaller.)")
p.add_option('--verbose', '-v', action='store_true',
help='More verbose output.')
p.add_option('--logfile', default='',
help="Path to a log file.")
# Get our options and our package names
options, pkgnames = p.parse_args()
# check to see if we're root
if os.geteuid() != 0:
display_error("You must run this as root!")
cleanup()
exit(1)
if pkgnames == []:
display_error("You must specify at least one package to remove!")
cleanup()
exit(1)
if not initDatabase(packagedb,options.rebuildpkgdb):
display_error("Could not initialize receipt database.")
cleanup()
exit(-1)
pkgkeyslist = getpkgkeys(pkgnames)
if len(pkgkeyslist) == 0:
cleanup()
exit(1)
def removepackages(pkgnames, forcedeletebundles=False, listfiles=False,
rebuildpkgdb=False, noremovereceipts=False,
noupdateapplepkgdb=False, **kwargs):
if stopRequested():
cleanup()
exit()
removalpaths = getpathstoremove(pkgkeyslist)
if stopRequested():
cleanup()
exit()
# we get the following arguments from the kwargs dictionary
# so we can assign them to the globals without a name conflict
global munkistatusoutput, verbose, verbose
munkistatusoutput = kwargs.get('munkistatusoutput', False)
verbose = kwargs.get('verbose', False)
verbose = kwargs.get('verbose', '')
# check to see if we're root
if os.geteuid() != 0:
display_error("You must run this as root!")
return -1
if pkgnames == []:
display_error("You must specify at least one package to remove!")
return -2
if not initDatabase(packagedb,rebuildpkgdb):
display_error("Could not initialize receipt database.")
return -3
pkgkeyslist = getpkgkeys(pkgnames)
if len(pkgkeyslist) == 0:
return -4
if stopRequested():
return -128
removalpaths = getpathstoremove(pkgkeyslist)
if stopRequested():
return -128
if removalpaths:
if listfiles:
removalpaths.sort()
for item in removalpaths:
print "/" + item.encode("UTF-8")
else:
removeFilesystemItems(removalpaths, forcedeletebundles)
if not noremovereceipts:
removeReceipts(pkgkeyslist, noupdateapplepkgdb)
if munkistatusoutput:
display_status('Package removal complete.')
time.sleep(2)
if removalpaths:
if options.listfiles:
removalpaths.sort()
for item in removalpaths:
print "/" + item.encode("UTF-8")
else:
removeFilesystemItems(removalpaths)
if not options.noremovereceipts:
removeReceipts(pkgkeyslist)
if options.munkistatusoutput:
display_status('Package removal complete.')
time.sleep(2)
display_status('Nothing to remove.')
if munkistatusoutput:
time.sleep(2)
return 0
else:
display_status('Nothing to remove.')
if options.munkistatusoutput:
time.sleep(2)
#cleanup
cleanup()
# some globals
packagedb = os.path.join(munkilib.managed_install_dir(), "b.receiptdb")
munkistatusoutput = False
verbose = False
logfile = ''
def main():
# command-line options
p = optparse.OptionParser()
p.add_option('--forcedeletebundles', '-f', action='store_true',
help='Delete bundles even if they aren\'t empty.')
p.add_option('--listfiles', '-l', action='store_true',
help='List the filesystem objects to be removed, but do not actually remove them.')
p.add_option('--rebuildpkgdb', action='store_true',
help='Force a rebuild of the internal package database.')
p.add_option('--noremovereceipts', action='store_true',
help='Do not remove receipts and boms from /Library/Receipts and update internal package database.')
p.add_option('--noupdateapplepkgdb', action='store_true',
help='Do not update Apple\'s package database. If --noremovereceipts is also given, this is implied')
p.add_option('--munkistatusoutput', '-m', action='store_true',
help='Output is formatted for use with MunkiStatus.')
p.add_option('--verbose', '-v', action='store_true',
help='More verbose output.')
p.add_option('--logfile', default='',
help="Path to a log file.")
# Get our options and our package names
options, pkgnames = p.parse_args()
retcode = removePackages(pkgnames, forcedeletebundles=options.forcedeletebundles, listfiles=options.listfiles,
rebuildpkgdb=options.rebuildpkgdb, noremovereceipts=options.noremovereceipts,
noupdateapplepkgdb=options.noupdateapplepkgdb, munkistatusoutput=options.munkistatusoutput,
verbose=options.verbose, logfile=options.logfile)
if munkistatusoutput:
munkistatus.quit()
exit(retcode)
if __name__ == '__main__':
main()