Moved all support libraries into munkilib package.

Combined installcheck and managedinstaller into managedsoftwareupdate to avoid race conditions between the two tools.
Removed support for Apple Software Updates (because it didn't work correctly!)


git-svn-id: http://munki.googlecode.com/svn/trunk@112 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2009-07-14 18:04:34 +00:00
parent a8a44ed50a
commit 07cfc5235c
16 changed files with 454 additions and 461 deletions
-15
View File
@@ -1,15 +0,0 @@
The actual tools are:
installcheck - gets the manifest for the client and processes it to determine what, if anything, needs to be installed.
managedinstaller - meant to be run as part of a logout hook or as a launchd job. Does the actual installs and removals.
ManagedSoftwareUpdate.app - user notification tool.
MunkiStatus.app - 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.
Supporting libraries:
munkilib.py - shared functions
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.
+5 -5
View File
@@ -41,7 +41,7 @@ import plistlib
import subprocess
import hashlib
import munkilib
import munkilib.munkicommon
def getCatalogInfoFromDmg(dmgpath):
@@ -53,12 +53,12 @@ def getCatalogInfoFromDmg(dmgpath):
To-do: handle multiple installer items on a disk image
"""
cataloginfo = None
mountpoints = munkilib.mountdmg(dmgpath)
mountpoints = munkilib.munkicommon.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 = munkilib.getPackageMetaData(itempath)
cataloginfo = munkilib.munkicommon.getPackageMetaData(itempath)
# get out of fsitem loop
break
if cataloginfo:
@@ -67,7 +67,7 @@ def getCatalogInfoFromDmg(dmgpath):
#unmount all the mountpoints from the dmg
for mountpoint in mountpoints:
munkilib.unmountdmg(mountpoint)
munkilib.munkicommon.unmountdmg(mountpoint)
return cataloginfo
@@ -187,7 +187,7 @@ def main():
if item.endswith('.dmg'):
catinfo = getCatalogInfoFromDmg(item)
elif item.endswith('.pkg') or item.endswith('.mpkg'):
catinfo = munkilib.getPackageMetaData(item)
catinfo = munkilib.munkicommon.getPackageMetaData(item)
else:
print >>sys.stderr, "%s is not an installer package!" % item
exit(-1)
+145
View File
@@ -0,0 +1,145 @@
#!/usr/bin/env python
#
# Copyright 2009 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
managedsoftwareupdate
"""
import sys
import os
import optparse
import datetime
import dateutil.parser
import subprocess
import plistlib
import munkilib.munkicommon
import munkilib.updatecheck
import munkilib.installer
from munkilib.munkistatus import osascript
def getIdleSeconds():
# stolen from Karl Kuehn -- thanks, Karl!
# I'd like to Python-ize it a bit better; calling awk seems unPythonic, but it works.
commandString = "/usr/sbin/ioreg -c IOHIDSystem -d 4 | /usr/bin/awk '/Idle/ { print $4 }'"
ioregProcess = subprocess.Popen([commandString], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if ioregProcess.wait() != 0:
return 0
return int(int(ioregProcess.stdout.read()) / 1000000000) # convert from Nanoseconds
def main():
# check to see if we're root
if os.geteuid() != 0:
print >>sys.stderr, "You must run this as root!"
exit(-1)
# check to see if another instance of this script is running
myname = os.path.basename(sys.argv[0])
if munkilib.munkicommon.pythonScriptRunning(myname):
# another instance of this script is running, so we should quit
print >>sys.stderr, "Another instance of %s is running. Exiting." % myname
exit(0)
p = optparse.OptionParser()
p.add_option('--auto', '-a', action='store_true',
help='No user feedback or intervention. All other options ignored.')
p.add_option('--munkistatusoutput', '-m', action='store_true',
help='Uses MunkiStatus.app for progress feedback when installing.')
p.add_option('--id', default='',
help='Alternate identifier for catalog retreival')
p.add_option('--quiet', '-q', action='store_true',
help='Quiet mode. Logs messages, but nothing to stdout.')
p.add_option('--verbose', '-v', action='count', default=0,
help='More verbose output. May be specified multiple times.')
p.add_option('--checkonly', action='store_true',
help="Check for updates, but don't install them.")
p.add_option('--installonly', action='store_true',
help='Skip checking and install any pending updates.')
options, arguments = p.parse_args()
if options.auto:
options.munkistatusoutput = True
options.quiet = True
options.verbose = 0
options.checkonly = False
options.installonly = False
if options.checkonly and options.installonly:
print >>sys.stderr, "--checkonly and --installonly options are mutually exclusive!"
exit(-1)
updatesavailable = False
if not options.installonly:
result = munkilib.updatecheck.check(id=options.id, verbosity=options.verbose, quiet=options.quiet)
if result > 0:
updatesavailable = True
if result == -1:
# there were errors checking for updates.
# let's check to see if there's a InstallInfo.plist with waiting updates from
# an earlier successful run
installinfo = os.path.join(munkilib.munkicommon.ManagedInstallDir(), 'InstallInfo.plist')
if os.path.exists(installinfo):
try:
pl = plistlib.readPlist(installinfo)
removalcount = munkilib.installer.getRemovalCount(pl.get('removals',[]))
installcount = munkilib.installer.getInstallCount(pl.get('managed_installs',[]))
if removalcount or installcount:
updatesavailable = True
except:
print >>sys.stderr, "Invalid %s" % installinfo
if updatesavailable or options.installonly:
if options.auto:
if munkilib.munkicommon.getconsoleuser() == None:
if getIdleSeconds() > 10:
# no-one is logged in, and
# no keyboard or mouse movement for the past 10 seconds,
# therefore no-one is in the middle of logging in (we hope!)
# so just install
munkilib.installer.run(options.munkistatusoutput)
else:
# someone is logged in, and we have updates.
# if we haven't notified in a (admin-configurable) while, notify:
lastNotifiedString = munkilib.munkicommon.pref('LastNotifiedDate')
daysBetweenNotifications = munkilib.munkicommon.pref('DaysBetweenNotifications')
nowString = munkilib.munkicommon.NSDateNowString()
now = dateutil.parser.parse(nowString)
if lastNotifiedString:
lastNotifiedDate = dateutil.parser.parse(lastNotifiedString)
interval = datetime.timedelta(days=daysBetweenNotifications)
nextNotifyDate = lastNotifiedDate + interval
if now >= nextNotifyDate:
# record current notification date
cmd = ['/usr/bin/defaults', 'write', '/Library/Preferences/ManagedInstalls',
'LastNotifiedDate', '-date', now.ctime()]
retcode = subprocess.call(cmd)
# notify user of available updates
result = osascript('tell application "Managed Software Update" to activate')
else:
if options.installonly:
# install!
munkilib.installer.run(options.munkistatusoutput)
else:
print "Run %s --installonly to install the downloaded updates." % myname
if __name__ == '__main__':
main()
+1
View File
@@ -0,0 +1 @@
# this is needed to make Python recognize the directory as a module package.
Binary file not shown.
+52 -87
View File
@@ -14,8 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
managedinstaller
Tool to automatically install pkgs, mpkgs, and dmgs
installer.py
munki module to automatically install pkgs, mpkgs, and dmgs
(containing pkgs and mpkgs) from a defined folder.
"""
@@ -24,13 +24,14 @@ import subprocess
import sys
import time
import plistlib
import optparse
import munkilib
import munkicommon
import munkistatus
from removepackages import removepackages
global munkistatusoutput
def stopRequested():
if options.munkistatusoutput:
if munkistatusoutput:
if munkistatus.getStopButtonState() == 1:
log("### User stopped session ###")
return True
@@ -38,7 +39,7 @@ def stopRequested():
def cleanup():
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.quit()
@@ -55,7 +56,7 @@ def createDirsIfNeeded(dirlist):
def log(message):
logfile = os.path.join(logdir,'ManagedInstaller.log')
logfile = os.path.join(logdir,'ManagedSoftwareUpdate.log')
try:
f = open(logfile, mode='a', buffering=1)
print >>f, time.ctime(), message
@@ -80,7 +81,7 @@ def install(pkgpath):
(output, err) = p.communicate()
packagename = output.splitlines()[0]
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.message("Installing %s..." % packagename)
# clear indeterminate progress bar
munkistatus.percent(0)
@@ -93,7 +94,7 @@ def install(pkgpath):
restartaction = output.rstrip("\n")
if restartaction == "RequireRestart":
message = "%s requires a restart after installation." % packagename
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(message)
else:
print message
@@ -117,7 +118,7 @@ def install(pkgpath):
if msg.startswith("PHASE:"):
phase = msg[6:]
if phase:
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(phase)
else:
print phase
@@ -125,24 +126,24 @@ def install(pkgpath):
elif msg.startswith("STATUS:"):
status = msg[7:]
if status:
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(status)
else:
print status
sys.stdout.flush()
elif msg.startswith("%"):
if options.munkistatusoutput:
if munkistatusoutput:
percent = float(msg[1:])
percent = int(percent * 100)
munkistatus.percent(percent)
elif msg.startswith(" Error"):
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(msg)
else:
print >>sys.stderr, msg
log(msg)
elif msg.startswith(" Cannot install"):
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(msg)
else:
print >>sys.stderr, msg
@@ -153,7 +154,7 @@ def install(pkgpath):
retcode = p.poll()
if retcode:
message = "Install of %s failed." % packagename
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.detail(message)
print >>sys.stderr, message
log(message)
@@ -169,7 +170,7 @@ def install(pkgpath):
restartneeded = False
else:
log("Install of %s was successful." % packagename)
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.percent(100)
return (retcode, restartneeded)
@@ -188,18 +189,18 @@ def installall(dirpath):
return restartflag
itempath = os.path.join(dirpath, item)
if item.endswith(".dmg"):
if not options.munkistatusoutput:
if not munkistatusoutput:
print "Mounting disk image %s" % item
log("Mounting disk image %s" % item)
mountpoints = munkilib.mountdmg(itempath)
mountpoints = munkicommon.mountdmg(itempath)
if mountpoints == []:
if not options.munkistatusoutput:
if not munkistatusoutput:
print >>sys.stderr, "ERROR: No filesystems mounted from %s" % item
log("ERROR: No filesystems mounted from %s" % item)
return restartflag
if stopRequested():
for mountpoint in mountpoints:
munkilib.unmountdmg(mountpoint)
munkicommon.unmountdmg(mountpoint)
return restartflag
for mountpoint in mountpoints:
# install all the pkgs and mpkgs at the root
@@ -207,7 +208,7 @@ def installall(dirpath):
needtorestart = installall(mountpoint)
if needtorestart:
restartflag = True
munkilib.unmountdmg(mountpoint)
munkicommon.unmountdmg(mountpoint)
if (item.endswith(".pkg") or item.endswith(".mpkg")):
(retcode, needsrestart) = install(itempath)
@@ -244,18 +245,18 @@ def installWithInfo(dirpath, installlist):
return restartflag
if itempath.endswith(".dmg"):
if not options.munkistatusoutput:
if not munkistatusoutput:
print "Mounting disk image %s" % item["installer_item"]
log("Mounting disk image %s" % item["installer_item"])
mountpoints = munkilib.mountdmg(itempath)
mountpoints = munkicommon.mountdmg(itempath)
if mountpoints == []:
if not options.munkistatusoutput:
if not munkistatusoutput:
print >>sys.stderr, "ERROR: No filesystems mounted from %s" % item["installer_item"]
log("ERROR: No filesystems mounted from %s" % item["installer_item"])
return restartflag
if stopRequested():
for mountpoint in mountpoints:
munkilib.unmountdmg(mountpoint)
munkicommon.unmountdmg(mountpoint)
return restartflag
for mountpoint in mountpoints:
# install all the pkgs and mpkgs at the root
@@ -263,9 +264,9 @@ def installWithInfo(dirpath, installlist):
needtorestart = installall(mountpoint)
if needtorestart:
restartflag = True
munkilib.unmountdmg(mountpoint)
munkicommon.unmountdmg(mountpoint)
else:
itempath = munkilib.findInstallerItem(itempath)
itempath = munkicommon.findInstallerItem(itempath)
if (itempath.endswith(".pkg") or itempath.endswith(".mpkg") or itempath.endswith(".dist")):
(retcode, needsrestart) = install(itempath)
if needsrestart:
@@ -304,7 +305,7 @@ def processRemovals(removalList):
if 'packages' in item:
if item.get('RestartAction') == "RequireRestart":
restartFlag = True
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.message("Removing %s..." % name)
# clear indeterminate progress bar
munkistatus.percent(0)
@@ -313,9 +314,9 @@ def processRemovals(removalList):
log("Removing %s..." % name)
retcode = removepackages(item['packages'],
munkistatusoutput=options.munkistatusoutput,
munkistatusoutput=munkistatusoutput,
forcedeletebundles=True,
logfile=os.path.join(logdir,'ManagedInstaller.log'))
logfile=os.path.join(logdir,'ManagedSoftwareUpdate.log'))
if retcode:
if retcode == -128:
message = "Uninstall of %s was cancelled." % name
@@ -328,7 +329,7 @@ def processRemovals(removalList):
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:
if munkistatusoutput:
munkistatus.message("Running uninstall script for %s..." % name)
# set indeterminate progress bar
munkistatus.percent(-1)
@@ -347,7 +348,7 @@ def processRemovals(removalList):
# an error so we can dump it to the log
uninstalleroutput.append(msg)
msg = msg.rstrip("\n")
if options.munkistatusoutput:
if munkistatusoutput:
# do nothing with the output
pass
else:
@@ -370,7 +371,7 @@ def processRemovals(removalList):
else:
log("Uninstall of %s was successful." % name)
if options.munkistatusoutput:
if munkistatusoutput:
# clear indeterminate progress bar
munkistatus.percent(0)
@@ -383,56 +384,31 @@ def processRemovals(removalList):
# module (global) variables
logdir = None
options = None
munkistatusoutput = False
def main():
global logdir, options
def run(use_munkistatus):
global logdir
global munkistatusoutput
# check to see if we're root
if os.geteuid() != 0:
print >>sys.stderr, "You must run this as root!"
exit(-1)
if use_munkistatus:
munkistatusoutput = True
managedinstallbase = munkilib.ManagedInstallDir()
managedinstallbase = munkicommon.ManagedInstallDir()
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 = removals_need_restart = installs_need_restart = appleupdates_need_restart = False
needtorestart = removals_need_restart = installs_need_restart = False
createDirsIfNeeded([logdir])
log("### Beginning managed installer session ###")
if munkilib.pythonScriptRunning("installcheck"):
# sleep one second and check again in case we
# were launched by installcheck
time.sleep(1)
if munkilib.pythonScriptRunning("installcheck"):
# installcheck is running, so we should quit
print "installcheck is running. Exiting."
log("installcheck is running. Exiting.")
log("### End managed installer session ###")
exit(0)
# check to see if another instance of this script is running
myname = os.path.basename(sys.argv[0])
if munkilib.pythonScriptRunning(myname):
# another instance of this script is running, so we should quit
print "Another instance of %s is running. Exiting." % myname
log("Another instance of %s is running. Exiting." % myname)
log("### End managed installer session ###")
exit(0)
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)
return -1
# remove the install info file
# it's no longer valid once we start running
@@ -441,7 +417,7 @@ def main():
if "removals" in pl:
removalcount = getRemovalCount(pl['removals'])
if removalcount:
if options.munkistatusoutput:
if munkistatusoutput:
if removalcount == 1:
munkistatus.message("Removing 1 item...")
else:
@@ -453,10 +429,8 @@ def main():
if "managed_installs" in pl:
if not stopRequested():
installcount = getInstallCount(pl['managed_installs'])
if "apple_updates" in pl:
installcount = installcount + len(pl['apple_updates'])
if installcount:
if options.munkistatusoutput:
if munkistatusoutput:
if installcount == 1:
munkistatus.message("Installing 1 item...")
else:
@@ -465,19 +439,14 @@ def main():
munkistatus.percent(-1)
log("Processing installs")
installs_need_restart = installWithInfo(installdir, pl['managed_installs'])
if "apple_updates" in pl:
if not stopRequested():
log("Installing Apple updates")
swupdldir = '/var/root/Downloads'
appleupdates_need_restart = installWithInfo(swupdldir, pl['apple_updates']) or needtorestart
else:
log("No %s found." % installinfo)
needtorestart = removals_need_restart or installs_need_restart or appleupdates_need_restart
needtorestart = removals_need_restart or installs_need_restart
if needtorestart:
log("Software installed or removed requires a restart.")
if options.munkistatusoutput:
if munkistatusoutput:
munkistatus.hideStopButton()
munkistatus.message("Software installed or removed requires a restart.")
munkistatus.percent(-1)
@@ -488,22 +457,18 @@ def main():
log("### End managed installer session ###")
if needtorestart:
if munkilib.getconsoleuser() == None:
if munkicommon.getconsoleuser() == None:
time.sleep(5)
cleanup()
retcode = subprocess.call(["/sbin/shutdown", "-r", "now"])
else:
if options.munkistatusoutput:
# someone is logged in and we're using MunkiStatus
if 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"')
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()
cleanup()
Binary file not shown.
@@ -24,6 +24,7 @@ Common functions used by the munki tools.
import sys
import os
import re
import plistlib
import urllib2
import urlparse
@@ -56,17 +57,23 @@ def readPlist(plistfile):
def getconsoleuser():
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]
# workaround no longer needed, but leaving this here for now...
#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]
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
def pythonScriptRunning(scriptname):
@@ -195,6 +202,7 @@ def prefs():
#####################################################
def getInstallerPkgInfo(filename):
"""Uses Apple's installer tool to get basic info about an installer item."""
installerinfo = {}
p = subprocess.Popen(["/usr/sbin/installer", "-pkginfo", "-verbose", "-plist", "-pkg", filename], bufsize=1,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -211,6 +219,12 @@ def getInstallerPkgInfo(filename):
installerinfo['RestartAction'] = "RequireRestart"
if "Title" in pl:
installerinfo['display_name'] = pl['Title']
p = subprocess.Popen(["/usr/sbin/installer", "-query", "RestartAction", "-pkg", filename], bufsize=1,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
if out:
installerinfo['RestartAction'] = out.rstrip('\n')
return installerinfo
@@ -254,9 +268,12 @@ def getExtendedVersion(bundlepath):
return padVersionString(pl["CFBundleShortVersionString"],5)
else:
return "0.0.0.0.0"
def parsePkgRefs(filename):
"""Parses a .dist file looking for pkg-ref or pkg-info tags to build a list of
included sub-packages"""
info = []
dom = minidom.parse(filename)
pkgrefs = dom.getElementsByTagName("pkg-ref")
@@ -269,7 +286,7 @@ def parsePkgRefs(filename):
print key, "=>", ref.attributes[key].value.encode('UTF-8')
pkginfo = {}
pkginfo['id'] = ref.attributes['id'].value.encode('UTF-8')
pkginfo['packageid'] = ref.attributes['id'].value.encode('UTF-8')
pkginfo['version'] = padVersionString(ref.attributes['version'].value.encode('UTF-8'),5)
if 'installKBytes' in keys:
pkginfo['installed_size'] = int(ref.attributes['installKBytes'].value.encode('UTF-8'))
@@ -287,7 +304,7 @@ def parsePkgRefs(filename):
print key, "=>", ref.attributes[key].value.encode('UTF-8')
pkginfo = {}
pkginfo['id'] = ref.attributes['identifier'].value.encode('UTF-8')
pkginfo['packageid'] = ref.attributes['identifier'].value.encode('UTF-8')
pkginfo['version'] = padVersionString(ref.attributes['version'].value.encode('UTF-8'),5)
if not pkginfo in info:
info.append(pkginfo)
@@ -296,7 +313,7 @@ def parsePkgRefs(filename):
def getFlatPackageInfo(pkgpath):
"""
returns array of dictionaries with info on packages
returns array of dictionaries with info on subpackages
contained in the flat package
"""
@@ -310,17 +327,16 @@ def getFlatPackageInfo(pkgpath):
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 getOnePackageInfo(pkgpath):
"""Gets receipt info for a single bundle-style package"""
pkginfo = {}
plistpath = os.path.join(pkgpath, "Contents", "Info.plist")
if os.path.exists(plistpath):
@@ -328,9 +344,9 @@ def getOnePackageInfo(pkgpath):
pl = plistlib.readPlist(plistpath)
if "CFBundleIdentifier" in pl:
pkginfo['id'] = pl["CFBundleIdentifier"]
pkginfo['packageid'] = pl["CFBundleIdentifier"]
else:
pkginfo['id'] = os.path.basename(pkgpath)
pkginfo['packageid'] = os.path.basename(pkgpath)
if "CFBundleName" in pl:
pkginfo['name'] = pl["CFBundleName"]
@@ -382,7 +398,7 @@ def getBundlePackageInfo(pkgpath):
return infoarray
def getPkgInfo(p):
def getReceiptInfo(p):
info = []
if p.endswith(".pkg") or p.endswith(".mpkg"):
if debug:
@@ -392,38 +408,13 @@ def getPkgInfo(p):
if os.path.isdir(p): # bundle-style package?
info = getBundlePackageInfo(p)
elif p.endswith('.dist'):
info = parsePkgRefs(p)
info = parsePkgRefs(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
@@ -442,7 +433,7 @@ def getInstalledPackageVersion(pkgid):
info = getBundlePackageInfo(os.path.join(receiptsdir, item))
if len(info):
infoitem = info[0]
foundbundleid = infoitem['id']
foundbundleid = infoitem['packageid']
foundvers = infoitem['version']
if pkgid == foundbundleid:
if version.LooseVersion(foundvers) > version.LooseVersion(highestversion):
@@ -452,7 +443,7 @@ def getInstalledPackageVersion(pkgid):
return highestversion
# If we got to this point, we haven't found the pkgid yet.
# Now check new (Leopard) package database
# Now check new (Leopard and later) package database
p = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid], bufsize=1,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
@@ -529,9 +520,11 @@ def getPackageMetaData(pkgitem):
pkgitem = findInstallerItem(pkgitem)
if pkgitem == None:
return {}
# first get the data /usr/sbin/installer will give us
installerinfo = getInstallerPkgInfo(pkgitem)
info = getPkgInfo(pkgitem)
# now look for receipt/subpkg info
receiptinfo = getReceiptInfo(pkgitem)
name = os.path.split(pkgitem)[1]
shortname = os.path.splitext(name)[0]
@@ -540,7 +533,7 @@ def getPackageMetaData(pkgitem):
metaversion = nameAndVersion(shortname)[1]
highestpkgversion = "0.0"
for infoitem in info:
for infoitem in receiptinfo:
if version.LooseVersion(infoitem['version']) > version.LooseVersion(highestpkgversion):
highestpkgversion = infoitem['version']
if "installed_size" in infoitem:
@@ -549,35 +542,26 @@ def getPackageMetaData(pkgitem):
if metaversion == "0.0.0.0.0":
metaversion = highestpkgversion
elif len(info) == 1:
elif len(receiptinfo) == 1:
# there is only one package in this item
metaversion = highestpkgversion
elif highestpkgversion.startswith(metaversion):
# for example, highestpkgversion is 2.0.3124.0, version in filename is 2.0
metaversion = highestpkgversion
if 'installed_size' in installerinfo:
if installerinfo['installed_size'] > 0:
installedsize = installerinfo['installed_size']
cataloginfo = {}
cataloginfo['name'] = nameAndVersion(shortname)[0]
cataloginfo['version'] = metaversion
for key in ('display_name', 'RestartAction', 'description'):
if key in installerinfo:
cataloginfo[key] = installerinfo[key]
if installedsize > 0:
cataloginfo['installed_size'] = installedsize
cataloginfo['receipts'] = []
for infoitem in info:
pkginfo = {}
if 'filename' in infoitem:
pkginfo['filename'] = infoitem['filename']
pkginfo['packageid'] = infoitem['id']
pkginfo['version'] = infoitem['version']
cataloginfo['receipts'].append(pkginfo)
if 'installed_size' in installerinfo:
if installerinfo['installed_size'] > 0:
cataloginfo['installed_size'] = installerinfo['installed_size']
cataloginfo['receipts'] = receiptinfo
return cataloginfo
@@ -731,6 +715,7 @@ def getHTTPfileIfNewerAtomically(url,destinationpath,showprogress=False, message
Gets file from HTTP URL, only if newer on web server.
Replaces pre-existing file only on success. (thus 'Atomically')
"""
err = None
mytmpdir = tempfile.mkdtemp()
mytemppath = os.path.join(mytmpdir,"TempDownload")
if os.path.exists(destinationpath):
@@ -741,21 +726,21 @@ def getHTTPfileIfNewerAtomically(url,destinationpath,showprogress=False, message
if result == 0:
try:
os.rename(mytemppath, destinationpath)
return destinationpath
return destinationpath, err
except:
print >>sys.stderr, "Could not write to %s" % destinationpath
err = "Could not write to %s" % destinationpath
destinationpath = None
elif result == 304:
# not modified, return existing file
return destinationpath
return destinationpath, err
else:
print >>sys.stderr, "Error code: %s retreiving %s" % (result, url)
err = "Error code: %s retreiving %s" % (result, url)
destinationpath = None
if os.path.exists(mytemppath):
os.remove(mytemppath)
os.rmdir(mytmpdir)
return destinationpath
return destinationpath, err
debug = False
Binary file not shown.
Binary file not shown.
@@ -34,7 +34,7 @@ import plistlib
import sqlite3
import time
import munkistatus
import munkilib
import munkicommon
##################################################################
@@ -326,32 +326,35 @@ def ImportPackage(packagepath, c):
if not line and (p.poll() != None):
break
item = line.rstrip("\n").split("\t")
path = item[0]
perms = item[1]
uidgid = item[2].split("/")
uid = uidgid[0]
gid = uidgid[1]
if path != ".":
# special case for MS Office 2008 installers
if ppath == "./tmp/com.microsoft.updater/office_location/":
ppath = "./Applications/"
try:
item = line.rstrip("\n").split("\t")
path = item[0]
perms = item[1]
uidgid = item[2].split("/")
uid = uidgid[0]
gid = uidgid[1]
if path != ".":
# special case for MS Office 2008 installers
if ppath == "./tmp/com.microsoft.updater/office_location/":
ppath = "./Applications/"
# prepend the ppath so the paths match the actual install locations
path = path.lstrip("./")
path = ppath + path
path = path.lstrip("./")
# 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 = (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)
t = (pkgkey, pathkey, uid, gid, perms)
c.execute('INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) values (?, ?, ?, ?, ?)', t)
except:
pass
def ImportBom(bompath, c):
@@ -703,15 +706,16 @@ 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 receiptpath and os.path.exists(receiptpath):
display_info("Removing %s..." % receiptpath)
log("Removing %s..." % receiptpath)
retcode = subprocess.call(["/bin/rm", "-rf", receiptpath])
if osvers < 10:
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 receiptpath and os.path.exists(receiptpath):
display_info("Removing %s..." % receiptpath)
log("Removing %s..." % receiptpath)
retcode = subprocess.call(["/bin/rm", "-rf", receiptpath])
# remove pkg info from our database
if verbose:
@@ -882,7 +886,7 @@ def removepackages(pkgnames, forcedeletebundles=False, listfiles=False,
display_error("You must specify at least one package to remove!")
return -2
if not initDatabase(packagedb,rebuildpkgdb):
if not initDatabase(packagedb,forcerebuild=rebuildpkgdb):
display_error("Could not initialize receipt database.")
return -3
@@ -918,7 +922,7 @@ def removepackages(pkgnames, forcedeletebundles=False, listfiles=False,
# some globals
packagedb = os.path.join(munkilib.ManagedInstallDir(), "b.receiptdb")
packagedb = os.path.join(munkicommon.ManagedInstallDir(), "b.receiptdb")
munkistatusoutput = False
verbose = False
logfile = ''
Binary file not shown.
+123 -245
View File
@@ -15,15 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
installcheck
updatecheck
Created by Greg Neagle on 2008-11-13.
2009-04-27: renamed to 'installcheck'
terminology change!
replacing "catalog" with "manifest"
a "catalog" is now a list of what's available on the server;
a "manifest" is a list of what should be installed or
uninstalled on this client
"""
#standard libs
@@ -34,22 +29,21 @@ import tempfile
import subprocess
from distutils import version
import urlparse
import optparse
import hashlib
import datetime
import dateutil.parser
#import dateutil.parser
import time
import random
#import random
import socket
#our lib
import munkilib
import munkicommon
from munkistatus import osascript
def log(message):
global logdir
logfile = os.path.join(logdir,'ManagedInstallerCheck.log')
logfile = os.path.join(logdir,'ManagedSoftwareUpdate.log')
f = open(logfile, mode='a', buffering=1)
if f:
print >>f, datetime.datetime.now().ctime(), message
@@ -66,7 +60,7 @@ def logerror(message):
def printandlog(message, verbositylevel=0):
if (options.verbose >= verbositylevel) and not options.quiet:
if (option_verbose >= verbositylevel) and not option_quiet:
print message
if logginglevel >= verbositylevel:
log(message)
@@ -76,10 +70,10 @@ def reporterrors():
# just a placeholder right now;
# this needs to be expanded to support error reporting
# via email and HTTP CGI.
global options
managedinstallprefs = munkilib.prefs()
managedinstallprefs = munkicommon.prefs()
clientidentifier = managedinstallprefs.get('ClientIdentifier','')
alternate_id = options.id
alternate_id = option_id
hostname = os.uname()[1]
print "installcheck errors %s:" % datetime.datetime.now().ctime()
@@ -137,8 +131,8 @@ def compareVersions(thisvers, thatvers):
Returns 1 if thisvers is the same as thatvers
Returns 2 if thisvers is newer than thatvers
"""
thisvers = munkilib.padVersionString(thisvers,5)
thatvers = munkilib.padVersionString(thatvers,5)
thisvers = munkicommon.padVersionString(thisvers,5)
thatvers = munkicommon.padVersionString(thatvers,5)
if version.LooseVersion(thisvers) < version.LooseVersion(thatvers):
return -1
elif version.LooseVersion(thisvers) == version.LooseVersion(thatvers):
@@ -356,7 +350,7 @@ def compareReceiptVersion(item):
return -2
printandlog("Looking for package %s, version %s" % (pkgid, vers), 2)
installedvers = munkilib.getInstalledPackageVersion(pkgid)
installedvers = munkicommon.getInstalledPackageVersion(pkgid)
if installedvers:
return compareVersions(installedvers, vers)
else:
@@ -373,7 +367,7 @@ def getInstalledVersion(pl):
if 'receipts' in pl:
maxversion = "0.0.0.0.0"
for receipt in pl['receipts']:
pkgvers = munkilib.getInstalledPackageVersion(receipt['packageid'])
pkgvers = munkicommon.getInstalledPackageVersion(receipt['packageid'])
if compareVersions(pkgvers, maxversion) == 2:
# version is higher
maxversion = pkgvers
@@ -426,7 +420,7 @@ def download_installeritem(pkgurl):
"""
global mytmpdir
ManagedInstallDir = munkilib.ManagedInstallDir()
ManagedInstallDir = munkicommon.ManagedInstallDir()
mycachedir = os.path.join(ManagedInstallDir, "Cache")
pkgname = os.path.basename(urlparse.urlsplit(pkgurl)[2])
destinationpath = os.path.join(mycachedir, pkgname)
@@ -439,12 +433,12 @@ def download_installeritem(pkgurl):
dl_message = "Downloading %s from %s" % (pkgname, pkgurl)
log(dl_message)
if options.quiet:
if option_quiet:
dl_message = None
elif not options.verbose:
elif not option_verbose:
dl_message = "Downloading %s" % pkgname
result = munkilib.getfilefromhttpurl(pkgurl, tempfilepath, showprogress=not(options.quiet), ifmodifiedsince=itemmodtime, message=dl_message)
result = munkicommon.getfilefromhttpurl(pkgurl, tempfilepath, showprogress=not(option_quiet), ifmodifiedsince=itemmodtime, message=dl_message)
if result == 0:
os.rename(tempfilepath, destinationpath)
return True
@@ -518,7 +512,7 @@ def getAllMatchingItems(name,cataloglist):
itemlist = []
# we'll throw away any included version info
(name, includedversion) = nameAndVersion(name)
managedinstalldir = munkilib.ManagedInstallDir()
managedinstalldir = munkicommon.ManagedInstallDir()
catalogsdir = os.path.join(managedinstalldir, 'catalogs')
printandlog("Looking for all items matching: %s..." % name, 1)
for catalogname in cataloglist:
@@ -552,7 +546,7 @@ def getManifestItemDetail(name, cataloglist, version=''):
version = includedversion
else:
version = 'latest'
managedinstalldir = munkilib.ManagedInstallDir()
managedinstalldir = munkicommon.ManagedInstallDir()
catalogsdir = os.path.join(managedinstalldir, 'catalogs')
printandlog("Looking for detail for: %s, version %s..." % (name, version), 1)
for catalogname in cataloglist:
@@ -599,7 +593,7 @@ def enoughDiskSpace(manifestitem_pl):
if 'installed_size' in manifestitem_pl:
installedsize = manifestitem_pl['installed_size']
diskspaceneeded = (installeritemsize + installedsize + fudgefactor)/1024
availablediskspace = munkilib.getAvailableDiskSpace()/1024
availablediskspace = munkicommon.getAvailableDiskSpace()/1024
if availablediskspace > diskspaceneeded:
return True
else:
@@ -707,7 +701,7 @@ def processInstalls(manifestitem, cataloglist, installinfo):
will stop the installation of a dependent item
"""
managedinstallprefs = munkilib.prefs()
managedinstallprefs = munkicommon.prefs()
sw_repo_baseurl = managedinstallprefs['SoftwareRepoURL']
ManagedInstallDir = managedinstallprefs['ManagedInstallDir']
@@ -959,7 +953,7 @@ def processRemovals(manifestitem, cataloglist, installinfo):
# and we're supposed to remove SomePackage--1.0.1.0.0... what do we do?
#
dependentitemsremoved = True
ManagedInstallDir = munkilib.ManagedInstallDir()
ManagedInstallDir = munkicommon.ManagedInstallDir()
catalogsdir = os.path.join(ManagedInstallDir, 'catalogs')
# make a list of the name and aliases of the current uninstall_item
@@ -1068,7 +1062,7 @@ def getCatalogs(cataloglist):
"""
Retreives the catalogs from the server
"""
managedinstallprefs = munkilib.prefs()
managedinstallprefs = munkicommon.prefs()
sw_repo_baseurl = managedinstallprefs['SoftwareRepoURL']
catalog_dir = os.path.join(managedinstallprefs['ManagedInstallDir'], "catalogs")
@@ -1077,20 +1071,21 @@ def getCatalogs(cataloglist):
catalogpath = os.path.join(catalog_dir, catalog)
message = "Getting catalog %s from %s..." % (catalog, catalogurl)
log(message)
if options.quiet:
if option_quiet:
message = None
elif not options.verbose:
elif not option_verbose:
message = "Retreiving catalog '%s'..." % catalog
newcatalog = munkilib.getHTTPfileIfNewerAtomically(catalogurl,catalogpath,showprogress=not(options.quiet), message=message)
(newcatalog, err) = munkicommon.getHTTPfileIfNewerAtomically(catalogurl,catalogpath,showprogress=not(option_quiet), message=message)
if not newcatalog:
logerror("Could not retreive catalog %s from server." % catalog)
logerror(err)
def getmanifest(partialurl):
def getmanifest(partialurl, suppress_errors=False):
"""
Gets a manifest from the server
"""
managedinstallprefs = munkilib.prefs()
managedinstallprefs = munkicommon.prefs()
sw_repo_baseurl = managedinstallprefs['SoftwareRepoURL']
manifest_dir = os.path.join(managedinstallprefs['ManagedInstallDir'], "manifests")
@@ -1106,14 +1101,15 @@ def getmanifest(partialurl):
manifestpath = os.path.join(manifest_dir, manifestname)
message = "Getting manifest %s from %s..." % (manifestname, manifesturl)
log(message)
if options.quiet:
if option_quiet:
message = None
elif not options.verbose:
elif not option_verbose:
message = "Retreiving list of software for this machine..."
newmanifest = munkilib.getHTTPfileIfNewerAtomically(manifesturl,manifestpath,showprogress=not(options.quiet), message=message)
if not newmanifest:
(newmanifest, err) = munkicommon.getHTTPfileIfNewerAtomically(manifesturl,manifestpath,showprogress=not(option_quiet), message=message)
if not newmanifest and not suppress_errors:
logerror("Could not retreive manifest %s from the server." % partialurl)
logerror(err)
return newmanifest
@@ -1122,7 +1118,7 @@ def getPrimaryManifest(alternate_id):
Gets the client manifest from the server
"""
global errors
managedinstallprefs = munkilib.prefs()
managedinstallprefs = munkicommon.prefs()
manifesturl = managedinstallprefs['ManifestURL']
clientidentifier = managedinstallprefs.get('ClientIdentifier','')
@@ -1137,10 +1133,10 @@ def getPrimaryManifest(alternate_id):
else:
# no client identifier specified, so use the hostname
hostname = os.uname()[1]
manifest = getmanifest(manifesturl + hostname)
manifest = getmanifest(manifesturl + hostname,suppress_errors=True)
if not manifest:
# try the short hostname
manifest = getmanifest(manifesturl + hostname.split('.')[0])
manifest = getmanifest(manifesturl + hostname.split('.')[0], suppress_errors=True)
if not manifest:
# last resort - try for the site_default manifest
manifesturl = manifesturl + "site_default"
@@ -1148,7 +1144,7 @@ def getPrimaryManifest(alternate_id):
if not manifest:
manifest = getmanifest(manifesturl)
if manifest:
# clear out any errors we got while try to find
# clear out any errors we got while trying to find
# the primary manifest
errors = ""
@@ -1183,86 +1179,9 @@ def isSUinstallItem(itempath):
return False
def doSoftwareUpdate(installinfo):
installinfo['apple_updates'] = []
if munkilib.pref('InstallAppleSoftwareUpdates'):
# switch to our preferred Software Update Server if supplied
if munkilib.pref('SoftwareUpdateServerURL'):
oldsuserver = ''
cmd = ['/usr/bin/defaults', 'read', '/Library/Preferences/com.apple.SoftwareUpdate', 'CatalogURL']
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:
oldsusserver = out.rstrip('\n')
cmd = ['/usr/bin/defaults', 'write', '/Library/Preferences/com.apple.SoftwareUpdate',
'CatalogURL', munkilib.pref('SoftwareUpdateServerURL')]
retcode = subprocess.call(cmd)
swupdldir = '/var/root/Downloads'
runcheck = True
# check downloads dir and skip checking if there's
# anything in it
for item in os.listdir(swupdldir):
itempath = os.path.join(swupdldir,item)
if isSUinstallItem(itempath):
runcheck = False
break
if runcheck:
cmd = ['/usr/sbin/softwareupdate', '-d', '-a']
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
swupdout = p.stdout.readline()
if not swupdout and (p.poll() != None):
#process completed and we've deal with all the output
break
printandlog(swupdout.rstrip("\n"))
sys.stdout.flush()
retcode = p.poll()
if retcode:
# some problem occurred.
# right now, we'll just stumble through in case some downloads
# succeeded
pass
# now check for downloads and process
for item in os.listdir(swupdldir):
itempath = os.path.join(swupdldir,item)
if isSUinstallItem(itempath):
pl = munkilib.getPackageMetaData(itempath)
if pl:
# check to see if there is enough free space to install
if enoughDiskSpace(pl):
iteminfo = {}
iteminfo["installer_item"] = item
iteminfo["name"] = pl.get('name', '')
iteminfo["description"] = pl.get('description', '')
if iteminfo["description"] == '':
iteminfo["description"] = "Updated Apple software."
iteminfo["version_to_install"] = pl.get('version',"UNKNOWN")
iteminfo['display_name'] = pl.get('display_name','')
if 'RestartAction' in pl:
iteminfo['RestartAction'] = pl['RestartAction']
installinfo['apple_updates'].append(iteminfo)
# switch back to original Software Update server
if munkilib.pref('SoftwareUpdateServerURL'):
if oldsuserver:
cmd = ['/usr/bin/defaults', 'write', '/Library/Preferences/com.apple.SoftwareUpdate',
'CatalogURL', oldsuserver]
else:
cmd = ['/usr/bin/defaults', 'delete', '/Library/Preferences/com.apple.SoftwareUpdate']
retcode = subprocess.call(cmd)
return installinfo
def checkServer():
managedinstallprefs = munkilib.prefs()
managedinstallprefs = munkicommon.prefs()
manifesturl = managedinstallprefs['ManifestURL']
# deconstruct URL so we can check availability
port = 80
@@ -1279,7 +1198,7 @@ def checkServer():
s = socket.socket()
#try:
s.connect((host, port))
s.close
s.close()
return True
#except:
#return False
@@ -1291,27 +1210,26 @@ logdir = ''
logginglevel = 1
mytmpdir = tempfile.mkdtemp()
errors = ''
p = optparse.OptionParser()
p.add_option('--id', '-i', default='',
help='Alternate identifier for catalog retreival')
p.add_option('--quiet', '-q', action='store_true',
help='Quiet mode. Logs messages, but nothing to stdout.')
p.add_option('--verbose', '-v', action='count', default=0,
help='More verbose output. May be specified multiple times.')
options, arguments = p.parse_args()
option_id=''
option_verbose=0
option_quiet=False
def main():
global mytmpdir, options, logdir, errors
def check(id='', verbosity=0, quiet=False):
'''Checks for available new or updated managed software, downloading installer items
if needed. Returns 1 if there are available updates, 0 if there are no available updates,
and -1 if there were errors.'''
# check to see if we're root
if os.geteuid() != 0:
print >>sys.stderr, "You must run this as root!"
exit(-1)
global mytmpdir, logdir, errors
global option_id, option_verbose, option_quiet
if not options.quiet: print "Managed Software Check\n"
option_id =id
option_verbose = verbosity
option_quiet = quiet
managedinstallprefs = munkilib.prefs()
if not option_quiet: print "Managed Software Check\n"
managedinstallprefs = munkicommon.prefs()
ManagedInstallDir = managedinstallprefs['ManagedInstallDir']
logginglevel = managedinstallprefs.get('LoggingLevel', 1)
manifestsdir = os.path.join(ManagedInstallDir, "manifests")
@@ -1328,71 +1246,58 @@ def main():
exit(-1)
log("### Beginning managed software check ###")
if munkilib.pythonScriptRunning("managedinstaller"):
# managedinstaller is running, so we should quit
printandlog("managedinstaller is running. Exiting.")
log("### End managed software check ###")
exit(0)
mainmanifestpath = getPrimaryManifest(option_id)
if mainmanifestpath:
# initialize our installinfo record
installinfo = {}
installinfo['managed_installs'] = []
installinfo['removals'] = []
# check to see if another instance of this script is running
myname = os.path.basename(sys.argv[0])
if munkilib.pythonScriptRunning(myname):
# another instance of this script is running, so we should quit
printandlog("Another instance of %s is running. Exiting." % myname)
log("### End managed software check ###")
exit(0)
printandlog("**Checking for installs**", 1)
installinfo = processManifestForInstalls(mainmanifestpath, installinfo)
# 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 installinfo['managed_installs']:
if "installer_item" in item:
installer_item_list.append(item["installer_item"])
mainmanifestpath = getPrimaryManifest(options.id)
if not mainmanifestpath:
logerror("Could not retreive managed install primary manifest.")
# we can't continue.
# we need a way to notify the admin of this problem --
# logging to a local file isn't really sufficient.
reporterrors()
exit(-1)
# initialize our installinfo record
installinfo = {}
installinfo['managed_installs'] = []
installinfo['removals'] = []
printandlog("**Checking for installs**", 1)
installinfo = processManifestForInstalls(mainmanifestpath, installinfo)
# 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 installinfo['managed_installs']:
if "installer_item" in item:
installer_item_list.append(item["installer_item"])
for item in os.listdir(cachedir):
if item not in installer_item_list:
if options.verbose > 1:
print "Removing %s from cache" % item
os.unlink(os.path.join(cachedir, item))
for item in os.listdir(cachedir):
if item not in installer_item_list:
if option_verbose > 1:
print "Removing %s from cache" % item
os.unlink(os.path.join(cachedir, item))
# now generate a list of items to be uninstalled
printandlog("**Checking for removals**", 1)
installinfo = processManifestForRemovals(mainmanifestpath, installinfo)
printandlog("**Checking for Apple Software Updates**", 1)
installinfo = doSoftwareUpdate(installinfo)
# need to write out install list so the autoinstaller
# can use it to install things in the right order
installinfochanged = True
installinfopath = os.path.join(ManagedInstallDir, "InstallInfo.plist")
if os.path.exists(installinfopath):
oldinstallinfo = plistlib.readPlist(installinfopath)
if oldinstallinfo == installinfo:
installinfochanged = False
printandlog("No change in InstallInfo.", 1)
if installinfochanged:
plistlib.writePlist(installinfo, os.path.join(ManagedInstallDir, "InstallInfo.plist"))
# now generate a list of items to be uninstalled
printandlog("**Checking for removals**", 1)
installinfo = processManifestForRemovals(mainmanifestpath, installinfo)
# need to write out install list so the autoinstaller
# can use it to install things in the right order
installinfochanged = True
installinfopath = os.path.join(ManagedInstallDir, "InstallInfo.plist")
if os.path.exists(installinfopath):
oldinstallinfo = plistlib.readPlist(installinfopath)
if oldinstallinfo == installinfo:
installinfochanged = False
printandlog("No change in InstallInfo.", 1)
if installinfochanged:
plistlib.writePlist(installinfo, os.path.join(ManagedInstallDir, "InstallInfo.plist"))
else:
# couldn't get a primary manifest. Check to see if we have a valid InstallList from
# an earlier run.
logerror("Could not retreive managed install primary manifest.")
installinfopath = os.path.join(ManagedInstallDir, "InstallInfo.plist")
if os.path.exists(installinfopath):
try:
installinfo = plistlib.readPlist(installinfopath)
except:
pass
try:
# clean up our tmp dir
@@ -1400,11 +1305,11 @@ def main():
except:
# not fatal if it fails
pass
installcount = getInstallCount(installinfo)
removalcount = getRemovalCount(installinfo)
appleupdatecount = len(installinfo.get('apple_updates',[]))
if installcount:
printandlog("The following items will be installed or upgraded:")
for item in installinfo['managed_installs']:
@@ -1421,54 +1326,27 @@ def main():
printandlog(" - %s" % item.get('name'))
if item.get('RestartAction') == 'RequireRestart':
printandlog(" *Restart required")
if appleupdatecount:
printandlog("The following Apple updates will be installed:")
for item in installinfo['apple_updates']:
printandlog (" + %s-%s" % (item.get('name'), item.get('version_to_install')))
if item.get('description') != "Updated Apple software.":
printandlog(" %s" % item['description'])
if item.get('RestartAction') == 'RequireRestart':
printandlog(" *Restart required")
if installcount == 0 and removalcount == 0:
printandlog("No changes to managed software scheduled.")
log("### End managed software check ###")
if errors:
reporterrors()
if installcount == 0 and removalcount == 0 and appleupdatecount == 0:
printandlog("No changes to managed software scheduled.")
if installcount or removalcount:
return 1
else:
if munkilib.getconsoleuser() == None:
# eventually trigger managedinstaller here
# we could:
# - call managedinstaller directly, but we'd have to either
# hard-code its path or search a few paths for it
# - call `launchctl start com.googlecode.munki-managedinstaller`, but that
# relies on that launchd job being installed
# - indirectly trigger `launchctl start com.googlecode.munki-managedinstaller`
# by touching its watch file, just like Managed Software Update.app does
#
pass
else:
# some one is logged in, and we have updates.
# if we haven't notified in a (admin-configurable) while, notify:
lastNotifiedString = munkilib.pref('LastNotifiedDate')
daysBetweenNotifications = munkilib.pref('DaysBetweenNotifications')
nowString = munkilib.NSDateNowString()
now = dateutil.parser.parse(nowString)
if lastNotifiedString:
lastNotifiedDate = dateutil.parser.parse(lastNotifiedString)
interval = datetime.timedelta(days=daysBetweenNotifications)
nextNotifyDate = lastNotifiedDate + interval
if now >= nextNotifyDate:
# record current notification date
cmd = ['/usr/bin/defaults', 'write', '/Library/Preferences/ManagedInstalls',
'LastNotifiedDate', '-date', now.ctime()]
retcode = subprocess.call(cmd)
# notify user of available updates
result = osascript('tell application "Managed Software Update" to activate')
log("### End managed software check ###")
return 0
def main():
pass
if __name__ == '__main__':
main()
main()
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
#!/bin/sh
# this script simply sleeps a random number of seconds
# up to MAX_DELAY_MINUTES, then runs managedsoftwareupdate --auto.
# It's meant to spread the load on the munki server
# so all clients don't hit it simultaneously.
# If launchd had a way to randomize the StartInterval,
# we wouldn't need to do this.
# 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.
if [ "$1" != "" ]; then
MAX_DELAY_MINUTES="$1"
else
MAX_DELAY_MINUTES=60
fi
MAX_DELAY_SECONDS=$((MAX_DELAY_MINUTES*60))
seconds=$(( (RANDOM%MAX_DELAY_SECONDS+1) ))
sleep $seconds
/usr/local/munki/managedsoftwareupdate --auto