mirror of
https://github.com/munki/munki.git
synced 2026-04-22 12:38:23 -05:00
Added MunkiStatus.xcodeproject to build MunkiStatus.app.
Added munkistatus.py to communicate with MunkiStstus.app Modified ManagedInstaller and removepackages to use MunkStatus.app for user feedback instead of iHook. Removed iHook example files (100.mangagedinstalllogout.hook anbd MI_logout.hook) since we no longer use iHook for user feedback. git-svn-id: http://munki.googlecode.com/svn/trunk@44 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
+242
-159
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
installomatic
|
||||
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
|
||||
@@ -27,13 +27,29 @@ import time
|
||||
import plistlib
|
||||
import optparse
|
||||
import managedinstalls
|
||||
import munkistatus
|
||||
|
||||
pathtoremovepackages = "/Users/Shared/munki/munki/code/client/removepackages"
|
||||
|
||||
|
||||
def stopRequested():
|
||||
if options.munkistatusoutput:
|
||||
if munkistatus.getStopButtonState() == 1:
|
||||
log("### User stopped session ###")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def cleanup():
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.quit()
|
||||
|
||||
|
||||
def createDirsIfNeeded(dirlist):
|
||||
for dir in dirlist:
|
||||
if not os.path.exists(dir):
|
||||
try:
|
||||
os.mkdir(dir)
|
||||
os.makedirs(dir, mode=0755)
|
||||
except:
|
||||
print >>sys.stderr, "Could not create %s" % dir
|
||||
return False
|
||||
@@ -53,8 +69,7 @@ def log(message):
|
||||
|
||||
def countinstallcandidates(dirpath):
|
||||
"""
|
||||
Counts the number of pkgs, mpkgs, and dmgs
|
||||
in dirpath
|
||||
Get the count of items to be installed
|
||||
"""
|
||||
candidatecount = 0
|
||||
items = os.listdir(dirpath)
|
||||
@@ -70,11 +85,8 @@ def install(pkgpath):
|
||||
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 = []
|
||||
|
||||
@@ -84,9 +96,11 @@ def install(pkgpath):
|
||||
(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)
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.message("Installing %s..." % packagename)
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
|
||||
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,
|
||||
@@ -95,8 +109,11 @@ def install(pkgpath):
|
||||
restartaction = output.rstrip("\n")
|
||||
if restartaction == "RequireRestart":
|
||||
message = "%s requires a restart after installation." % packagename
|
||||
print message
|
||||
sys.stdout.flush()
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(message)
|
||||
else:
|
||||
print message
|
||||
sys.stdout.flush()
|
||||
log(message)
|
||||
restartneeded = True
|
||||
|
||||
@@ -114,31 +131,35 @@ def install(pkgpath):
|
||||
if msg.startswith("PHASE:"):
|
||||
phase = msg[6:]
|
||||
if phase:
|
||||
print phase
|
||||
sys.stdout.flush()
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(phase)
|
||||
else:
|
||||
print phase
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("STATUS:"):
|
||||
status = msg[7:]
|
||||
if status:
|
||||
print status
|
||||
sys.stdout.flush()
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(status)
|
||||
else:
|
||||
print status
|
||||
sys.stdout.flush()
|
||||
elif msg.startswith("%"):
|
||||
if options.ihookoutput:
|
||||
if options.munkistatusoutput:
|
||||
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()
|
||||
munkistatus.percent(percent)
|
||||
elif msg.startswith(" Error"):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
print >>sys.stderr, msg
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(msg)
|
||||
else:
|
||||
print >>sys.stderr, msg
|
||||
log(msg)
|
||||
elif msg.startswith(" Cannot install"):
|
||||
print msg
|
||||
sys.stdout.flush()
|
||||
print >>sys.stderr, msg
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(msg)
|
||||
else:
|
||||
print >>sys.stderr, msg
|
||||
log(msg)
|
||||
else:
|
||||
print >>sys.stderr, msg
|
||||
@@ -146,6 +167,8 @@ def install(pkgpath):
|
||||
retcode = p.poll()
|
||||
if retcode:
|
||||
message = "Install of %s failed." % packagename
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(message)
|
||||
print >>sys.stderr, message
|
||||
log(message)
|
||||
message = "-------------------------------------------------"
|
||||
@@ -160,7 +183,9 @@ def install(pkgpath):
|
||||
restartneeded = False
|
||||
else:
|
||||
log("Install of %s was successful." % packagename)
|
||||
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.percent(100)
|
||||
|
||||
return (retcode, restartneeded)
|
||||
|
||||
|
||||
@@ -173,6 +198,8 @@ def installall(dirpath):
|
||||
restartflag = False
|
||||
installitems = os.listdir(dirpath)
|
||||
for item in installitems:
|
||||
if stopRequested():
|
||||
return restartflag
|
||||
itempath = os.path.join(dirpath, item)
|
||||
if (item.endswith(".pkg") or item.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath)
|
||||
@@ -180,6 +207,10 @@ def installall(dirpath):
|
||||
restartflag = True
|
||||
if item.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
if stopRequested():
|
||||
for mountpoint in mountpoints:
|
||||
unmountdmg(mountpoint)
|
||||
return restartflag
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
# of the mountpoint -- call us recursively!
|
||||
@@ -190,6 +221,15 @@ def installall(dirpath):
|
||||
|
||||
return restartflag
|
||||
|
||||
|
||||
def getInstallCount(installList):
|
||||
count = 0
|
||||
for item in installList:
|
||||
if 'installed' in item:
|
||||
if not item['installed']:
|
||||
count +=1
|
||||
return count
|
||||
|
||||
|
||||
def installWithInfo(dirpath, installlist):
|
||||
"""
|
||||
@@ -198,17 +238,23 @@ def installWithInfo(dirpath, installlist):
|
||||
"""
|
||||
restartflag = False
|
||||
for item in installlist:
|
||||
if stopRequested():
|
||||
return restartflag
|
||||
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
|
||||
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)
|
||||
if stopRequested():
|
||||
for mountpoint in mountpoints:
|
||||
unmountdmg(mountpoint)
|
||||
return restartflag
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
# of the mountpoint -- call us recursively!
|
||||
@@ -224,115 +270,133 @@ def installWithInfo(dirpath, installlist):
|
||||
return restartflag
|
||||
|
||||
|
||||
def getRemovalCount(removalList):
|
||||
count = 0
|
||||
for item in removalList:
|
||||
if 'installed' in item:
|
||||
if item['installed']:
|
||||
count +=1
|
||||
return count
|
||||
|
||||
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 %s.." % name
|
||||
#print "%%%s Item %s of %s" % (0, currentinstallable, installablecount)
|
||||
else:
|
||||
print "Removing %s..." % name
|
||||
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)
|
||||
if stopRequested():
|
||||
return restartFlag
|
||||
if 'installed' in item:
|
||||
if item['installed']:
|
||||
name = ""
|
||||
if 'name' in item:
|
||||
name = item['name']
|
||||
elif 'catalogitem' in item:
|
||||
name = item['catalogitem']
|
||||
if 'uninstall_method' in item:
|
||||
uninstallmethod = item['uninstall_method'].split(' ')
|
||||
if uninstallmethod[0] == "removepackages":
|
||||
if 'packages' in item:
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.message("Removing %s..." % name)
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
else:
|
||||
print "Removing %s..." % name
|
||||
cmd = [pathtoremovepackages, '-f']
|
||||
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
|
||||
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.munkistatusoutput:
|
||||
munkistatus.message("Running uninstall script for %s..." % name)
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
|
||||
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.munkistatusoutput:
|
||||
# do nothing with the output
|
||||
pass
|
||||
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:
|
||||
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)
|
||||
log("Uninstall of %s was successful." % name)
|
||||
|
||||
if options.munkstatusoutput:
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
|
||||
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)
|
||||
log("Uninstall of %s failed because there was no valid uninstall method." % name)
|
||||
|
||||
return restartFlag
|
||||
|
||||
@@ -377,30 +441,17 @@ def unmountdmg(mountpoint):
|
||||
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')
|
||||
p.add_option('--munkistatusoutput', '-m', 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')
|
||||
@@ -410,30 +461,62 @@ def main():
|
||||
except:
|
||||
print >>sys.stderr, "Invalid %s" % installinfo
|
||||
exit(-1)
|
||||
|
||||
# remove the install info file
|
||||
# it's no longer valid once we start running
|
||||
os.unlink(installinfo)
|
||||
|
||||
if "removals" in pl:
|
||||
log("Processing removals")
|
||||
needtorestart = processRemovals(pl['removals'])
|
||||
removalcount = getRemovalCount(pl['removals'])
|
||||
if removalcount:
|
||||
if options.munkistatusoutput:
|
||||
if removalcount == 1:
|
||||
munkistatus.message("Removing 1 item...")
|
||||
else:
|
||||
munkistatus.message("Removing %i items..." % removalcount)
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
log("Processing removals")
|
||||
needtorestart = processRemovals(pl['removals'])
|
||||
if "managed_installs" in pl:
|
||||
log("Processing installs")
|
||||
needtorestart = installWithInfo(installdir, pl['managed_installs'])
|
||||
# remove the install info
|
||||
os.unlink(installinfo)
|
||||
if not stopRequested():
|
||||
installcount = getInstallCount(pl['managed_installs'])
|
||||
if installcount:
|
||||
if options.munkistatusoutput:
|
||||
if installcount == 1:
|
||||
munkistatus.message("Installing 1 item...")
|
||||
else:
|
||||
munkistatus.message("Installing %i items..." % installcount)
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
log("Processing installs")
|
||||
needtorestart = installWithInfo(installdir, pl['managed_installs'])
|
||||
|
||||
else:
|
||||
log("No %s found." % installinfo)
|
||||
log("Installing everything in the cache.")
|
||||
#log("Installing everything in the cache.")
|
||||
# install all pkgs and mpkgs
|
||||
needtorestart = installall(installdir)
|
||||
#needtorestart = installall(installdir)
|
||||
|
||||
if needtorestart:
|
||||
print "Software installed requires a restart."
|
||||
log("Software installed requires a restart.")
|
||||
sys.stdout.flush()
|
||||
log("Software installed or removed requires a restart.")
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.message("Software installed or removed requires a restart.")
|
||||
munkistatus.detail("")
|
||||
munkistatus.percent(-1)
|
||||
else:
|
||||
print "Software installed or removed requires a restart."
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
log("### End automated install session ###")
|
||||
if needtorestart:
|
||||
time.sleep(5)
|
||||
cleanup()
|
||||
# uncomment this when testing is done so it will restart.
|
||||
#retcode = subprocess.call(["/sbin/shutdown", "-r", "now"])
|
||||
#retcode = subprocess.call(["/sbin/shutdown", "-r", "now"])
|
||||
else:
|
||||
cleanup()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -5,6 +5,7 @@ ManagedInstaller - meant to be run as part of a logout hook. Does the actual in
|
||||
makecatalogitem.py: Helper tool to help create catalogitem plists for each installer item.
|
||||
removepackages: used by ManagedInstaller to do package removals.
|
||||
|
||||
Supporting library:
|
||||
managedinstalls.py
|
||||
Supporting libraries:
|
||||
managedinstalls.py - shared functions
|
||||
munkistatus.py - functions to display status using MunkiStatus.app
|
||||
|
||||
|
||||
@@ -914,8 +914,7 @@ def main():
|
||||
|
||||
# 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 = {}
|
||||
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009 Greg Neagle.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
munkistatus.py
|
||||
|
||||
Created by Greg Neagle on 2009-04-17.
|
||||
|
||||
Utility functions for using MunkiStatus.app.
|
||||
Can be called as a standalone script with options,
|
||||
or as a Python library
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import optparse
|
||||
import subprocess
|
||||
|
||||
|
||||
def osascript(osastring):
|
||||
cmd = ['osascript', '-e', osastring]
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print >>sys.stderr, "Error: ", err
|
||||
if out:
|
||||
return out.rstrip("\n")
|
||||
|
||||
|
||||
def quit():
|
||||
# see if MunkiStatus is running first
|
||||
cmd = ['/usr/bin/killall', '-s', 'MunkiStatus']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
if p.returncode == 0:
|
||||
# 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)
|
||||
|
||||
|
||||
def message(messageText):
|
||||
result = osascript('tell application "MunkiStatus" to set contents of text field "mainTextFld" of window "mainWindow" to "%s"' % messageText)
|
||||
|
||||
|
||||
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,
|
||||
# you may find it slows the loop down unacceptibly, as
|
||||
# Your loop spends more time waiting for this operation
|
||||
# than actually doing its work. You might
|
||||
# instead try calling it once at the beginning of the loop,
|
||||
# once every X iterations, then once at the end to speed things up.
|
||||
# X might equal (number of iterations / 10 or even 20)
|
||||
percent = int(float(percentage))
|
||||
if percent > 100:
|
||||
percent = 100
|
||||
if percent < 0:
|
||||
# set an indeterminate progress bar
|
||||
result = osascript('tell application "MunkiStatus" to set indeterminate of progress indicator "progressBar" of window "mainWindow" to true')
|
||||
result = osascript('tell application "MunkiStatus" to tell window "mainWindow" to tell progress indicator "progressBar" to start')
|
||||
elif percent == 0:
|
||||
# we only clear the indeterminate status when we set the percentage to 0;
|
||||
# we tried always doing it, but it really slows down response times such
|
||||
# that a script spends way more time updating MunkiStatus than it does
|
||||
# actually performing its task
|
||||
result = osascript('tell application "MunkiStatus" to set indeterminate of progress indicator "progressBar" of window "mainWindow" to false')
|
||||
result = osascript('tell application "MunkiStatus" to set contents of progress indicator "progressBar" of window "mainWindow" to 0')
|
||||
else:
|
||||
percentStr = str(percent)
|
||||
result = osascript('tell application "MunkiStatus" to set contents of progress indicator "progressBar" of window "mainWindow" to %s' % percentStr)
|
||||
|
||||
|
||||
def getStopButtonState():
|
||||
# see if MunkiStatus is running first
|
||||
cmd = ['/usr/bin/killall', '-s', 'MunkiStatus']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
if p.returncode == 0:
|
||||
# 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
|
||||
|
||||
|
||||
def hideStopButton():
|
||||
result = osascript('tell application "MunkiStatus" to set visible of button "stopBtn" of window "mainWindow" to false')
|
||||
|
||||
|
||||
def showStopButton():
|
||||
result = osascript('tell application "MunkiStatus" to set visible of button "stopBtn" of window "mainWindow" to true')
|
||||
|
||||
|
||||
def disableStopButton():
|
||||
result = osascript('tell application "MunkiStatus" to set enabled of button "stopBtn" of window "mainWindow" to false')
|
||||
|
||||
|
||||
def enableStopButton():
|
||||
result = osascript('tell application "MunkiStatus" to set enabled of button "stopBtn" of window "mainWindow" to true')
|
||||
|
||||
|
||||
def main():
|
||||
p = optparse.OptionParser()
|
||||
p.add_option('--activate', '-a', action='store_true',
|
||||
help='Bring MunkiStatus to the front.')
|
||||
p.add_option('--title', '-t', default='',
|
||||
help='Window title.')
|
||||
p.add_option('--message', '-m', default='',
|
||||
help='Main message text.')
|
||||
p.add_option('--detail', '-d',
|
||||
help='Minor message text.')
|
||||
p.add_option('--percent', '-p', default='',
|
||||
help='Update progress bar to show percent done. Negative values show an indeterminate progress bar.')
|
||||
p.add_option('--quit', '-q', action='store_true',
|
||||
help='Tell MunkiStatus to quit.')
|
||||
p.add_option('--getStopButtonState', '-g', action='store_true',
|
||||
help='Returns 1 if stop button has been clicked.')
|
||||
p.add_option('--hideStopButton', action='store_true',
|
||||
help='Hide the stop button.')
|
||||
p.add_option('--showStopButton', action='store_true',
|
||||
help='Show the stop button.')
|
||||
p.add_option('--disableStopButton', action='store_true',
|
||||
help='Disable the stop button.')
|
||||
p.add_option('--enableStopButton', action='store_true',
|
||||
help='Enable the stop button.')
|
||||
|
||||
|
||||
options, arguments = p.parse_args()
|
||||
|
||||
if options.quit:
|
||||
quit()
|
||||
exit()
|
||||
if options.activate:
|
||||
activate()
|
||||
if options.title:
|
||||
title(options.title)
|
||||
if options.message:
|
||||
message(options.message)
|
||||
if options.detail:
|
||||
detail(options.detail)
|
||||
if options.percent:
|
||||
percent(options.percent)
|
||||
if options.getStopButtonState:
|
||||
print getStopButtonState()
|
||||
if options.hideStopButton:
|
||||
hideStopButton()
|
||||
if options.showStopButton:
|
||||
showStopButton()
|
||||
if options.disableStopButton:
|
||||
disableStopButton()
|
||||
if options.enableStopButton:
|
||||
enableStopButton()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
+72
-22
@@ -20,6 +20,7 @@ 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.
|
||||
"""
|
||||
#TO-DO: refactor this so it can be called as a library as well as a command-line script
|
||||
|
||||
import optparse
|
||||
import os
|
||||
@@ -28,6 +29,7 @@ import sys
|
||||
import plistlib
|
||||
import sqlite3
|
||||
import time
|
||||
import munkistatus
|
||||
#import applereceiptutils
|
||||
|
||||
##################################################################
|
||||
@@ -88,6 +90,14 @@ import time
|
||||
# perms INTEGER )
|
||||
#################################################################
|
||||
|
||||
def stopRequested():
|
||||
if options.munkistatusoutput:
|
||||
if munkistatus.getStopButtonState() == 1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def getsteps(num_of_steps, limit):
|
||||
"""
|
||||
Helper function for display_percent_done
|
||||
@@ -106,18 +116,17 @@ def getsteps(num_of_steps, limit):
|
||||
def display_percent_done(current,maximum):
|
||||
"""
|
||||
Mimics the command-line progress meter seen in some
|
||||
of Apple's tools (like softwareupdate), or prints
|
||||
percent-done output in iHook directive format.
|
||||
of Apple's tools (like softwareupdate), or tells
|
||||
MunkiStatus to display percent done via progress bar.
|
||||
"""
|
||||
if options.ihookoutput:
|
||||
if options.munkistatusoutput:
|
||||
step = getsteps(101, maximum)
|
||||
if current in step:
|
||||
if current == maximum:
|
||||
percentdone = 100
|
||||
else:
|
||||
percentdone = step.index(current)
|
||||
print "%%%s %s of %s" % (str(percentdone), current, maximum)
|
||||
sys.stdout.flush()
|
||||
munkistatus.percent(str(percentdone))
|
||||
elif not options.verbose:
|
||||
step = getsteps(16, maximum)
|
||||
output = ''
|
||||
@@ -136,8 +145,8 @@ def display_status(msg):
|
||||
Displays major status messages, formatting as needed
|
||||
for verbose/non-verbose and iHook-style output.
|
||||
"""
|
||||
if options.ihookoutput:
|
||||
print "STATUS: %s" % msg
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.detail(msg)
|
||||
elif options.verbose:
|
||||
print "%s..." % msg
|
||||
else:
|
||||
@@ -150,9 +159,9 @@ def display_info(msg):
|
||||
Displays minor info messages, formatting as needed
|
||||
for verbose/non-verbose and iHook-style output.
|
||||
"""
|
||||
if options.ihookoutput:
|
||||
#iHook puts stderr in the drawer
|
||||
print >>sys.stderr, "INFO: %s" % msg
|
||||
if options.munkistatusoutput:
|
||||
#munkistatus.detail(msg)
|
||||
pass
|
||||
elif options.verbose:
|
||||
print msg
|
||||
|
||||
@@ -162,8 +171,7 @@ def display_error(msg):
|
||||
Prints msg to stderr and eventually to the log
|
||||
"""
|
||||
print >>sys.stderr, "ERROR: %s" % msg
|
||||
if options.ihookoutput:
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
|
||||
def shouldRebuildDB(pkgdbpath):
|
||||
@@ -443,6 +451,14 @@ def initDatabase(packagedb,forcerebuild=False):
|
||||
if os.path.exists(receiptsdir):
|
||||
receiptlist = os.listdir(receiptsdir)
|
||||
for item in receiptlist:
|
||||
if stopRequested():
|
||||
c.close()
|
||||
conn.close()
|
||||
#our package db isn't valid, so we should delete it
|
||||
os.remove(packagedb)
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
if item.endswith(".pkg"):
|
||||
receiptpath = os.path.join(receiptsdir, item)
|
||||
display_info("Importing %s..." % receiptpath)
|
||||
@@ -453,6 +469,14 @@ def initDatabase(packagedb,forcerebuild=False):
|
||||
if os.path.exists(bomsdir):
|
||||
bomslist = os.listdir(bomsdir)
|
||||
for item in bomslist:
|
||||
if stopRequested():
|
||||
c.close()
|
||||
conn.close()
|
||||
#our package db isn't valid, so we should delete it
|
||||
os.remove(packagedb)
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
if item.endswith(".bom"):
|
||||
bompath = os.path.join(bomsdir, item)
|
||||
display_info("Importing %s..." % bompath)
|
||||
@@ -528,9 +552,8 @@ 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.ihookoutput:
|
||||
print '%BEGINPOLE'
|
||||
sys.stdout.flush()
|
||||
if options.munkistatusoutput:
|
||||
munkistatus.percent(-1)
|
||||
|
||||
c.execute(combined_query)
|
||||
results = c.fetchall()
|
||||
@@ -710,6 +733,11 @@ def removeFilesystemItems(removalpaths):
|
||||
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
|
||||
@@ -717,9 +745,14 @@ def removeFilesystemItems(removalpaths):
|
||||
retcode = subprocess.call(['/bin/rm', pathtoremove])
|
||||
if retcode:
|
||||
display_error("ERROR: couldn't remove item %s" % pathtoremove)
|
||||
|
||||
display_percent_done(itemindex, itemcount)
|
||||
|
||||
# this should be 100%
|
||||
display_percent_done(itemindex, itemcount)
|
||||
|
||||
def cleanup():
|
||||
if options.munkistatusoutput:
|
||||
if not options.dontquitmunkistatus:
|
||||
munkistatus.quit()
|
||||
|
||||
######################################################
|
||||
# Main
|
||||
@@ -741,8 +774,10 @@ 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('--ihookoutput', '-i', action='store_true',
|
||||
help='Output is formatted for use with iHook.')
|
||||
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.')
|
||||
# Get our options and our package names
|
||||
@@ -751,21 +786,33 @@ 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)
|
||||
|
||||
|
||||
if stopRequested():
|
||||
cleanup()
|
||||
exit()
|
||||
removalpaths = getpathstoremove(pkgkeyslist)
|
||||
if stopRequested():
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
|
||||
if removalpaths:
|
||||
if options.listfiles:
|
||||
removalpaths.sort()
|
||||
@@ -775,14 +822,17 @@ if removalpaths:
|
||||
removeFilesystemItems(removalpaths)
|
||||
if not options.noremovereceipts:
|
||||
removeReceipts(pkgkeyslist)
|
||||
if options.ihookoutput:
|
||||
if options.munkistatusoutput:
|
||||
display_status('Package removal complete.')
|
||||
time.sleep(2)
|
||||
|
||||
else:
|
||||
display_status('Nothing to remove.')
|
||||
if options.ihookoutput:
|
||||
if options.munkistatusoutput:
|
||||
time.sleep(2)
|
||||
|
||||
#cleanup
|
||||
cleanup()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user