Add MCX support, based on code contribution from Dan Roque.

Other minor clean-ups.

git-svn-id: http://munki.googlecode.com/svn/trunk@973 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2010-12-13 20:11:22 +00:00
parent f87284d4d4
commit 681acae624
8 changed files with 109 additions and 164 deletions

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.0.11</string>
<string>3.0.12</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>3.0.11</string>
<string>3.0.12</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@@ -135,8 +135,7 @@ class MSUAppDelegate(NSObject):
if self._listofupdates:
return
# no list of updates; let's check the LastCheckResult for more info
prefs = munki.getManagedInstallsPrefs()
lastCheckResult = prefs.get("LastCheckResult")
lastCheckResult = munki.pref("LastCheckResult")
if lastCheckResult == 0:
self.noUpdatesAlert()
elif lastCheckResult == 1:

View File

@@ -24,6 +24,8 @@ import os
import subprocess
import FoundationPlist
from Foundation import NSFileManager
from Foundation import CFPreferencesCopyAppValue
UPDATECHECKLAUNCHFILE = \
"/private/tmp/.com.googlecode.munki.updatecheck.launchd"
@@ -37,33 +39,29 @@ def call(cmd):
return proc.returncode
def getManagedInstallsPrefs():
'''Define default preference values;
Read preference values from ManagedInstalls.plist if it exists.'''
BUNDLE_ID = 'ManagedInstalls'
prefs = {}
prefs['ManagedInstallDir'] = "/Library/Managed Installs"
prefs['InstallAppleSoftwareUpdates'] = False
prefs['ShowRemovalDetail'] = False
prefs['InstallRequiresLogout'] = False
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
if os.path.exists(prefsfile):
try:
plist = FoundationPlist.readPlist(prefsfile)
except FoundationPlist.NSPropertyListSerializationException:
return prefs
try:
for key in plist.keys():
if type(plist[key]).__name__ == "__NSCFDate":
# convert NSDate/CFDates to strings
prefs[key] = str(plist[key])
else:
prefs[key] = plist[key]
except AttributeError:
pass
return prefs
def pref(pref_name):
"""Return a preference. Since this uses CFPreferencesCopyAppValue,
Preferences can be defined several places. Precedence is:
- MCX
- ~/Library/Preferences/ManagedInstalls.plist
- /Library/Preferences/ManagedInstalls.plist
- default_prefs defined here.
"""
default_prefs = {
'ManagedInstallDir': '/Library/Managed Installs',
'InstallAppleSoftwareUpdates': False,
'ShowRemovalDetail': False,
'InstallRequiresLogout': False
}
pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID)
if pref_value == None:
pref_value = default_prefs.get(pref_name)
if type(pref_value).__name__ == "__NSCFDate":
# convert NSDate/CFDates to strings
pref_value = str(pref_value)
return pref_value
def readSelfServiceManifest():
@@ -72,8 +70,7 @@ def readSelfServiceManifest():
SelfServeManifest = "/Users/Shared/.SelfServeManifest"
if not os.path.exists(SelfServeManifest):
# no working copy, look for system copy
prefs = getManagedInstallsPrefs()
managedinstallbase = prefs['ManagedInstallDir']
managedinstallbase = pref('ManagedInstallDir')
SelfServeManifest = os.path.join(managedinstallbase, "manifests",
"SelfServeManifest")
if os.path.exists(SelfServeManifest):
@@ -97,18 +94,17 @@ def writeSelfServiceManifest(optional_install_choices):
def getRemovalDetailPrefs():
'''Returns preference to control display of removal detail'''
return getManagedInstallsPrefs().get('ShowRemovalDetail', False)
return pref('ShowRemovalDetail')
def installRequiresLogout():
'''Returns preference to force logout for all installs'''
return getManagedInstallsPrefs().get('InstallRequiresLogout', False)
return pref('InstallRequiresLogout')
def getInstallInfo():
'''Returns the dictionary describing the managed installs and removals'''
prefs = getManagedInstallsPrefs()
managedinstallbase = prefs['ManagedInstallDir']
managedinstallbase = pref('ManagedInstallDir')
plist = {}
installinfo = os.path.join(managedinstallbase, 'InstallInfo.plist')
if os.path.exists(installinfo):
@@ -127,12 +123,11 @@ def startUpdateCheck():
def getAppleUpdates():
'''Returns any available Apple updates'''
prefs = getManagedInstallsPrefs()
managedinstallbase = prefs['ManagedInstallDir']
managedinstallbase = pref('ManagedInstallDir')
plist = {}
appleUpdatesFile = os.path.join(managedinstallbase, 'AppleUpdates.plist')
if (os.path.exists(appleUpdatesFile) and
prefs['InstallAppleSoftwareUpdates']):
pref('InstallAppleSoftwareUpdates')):
try:
plist = FoundationPlist.readPlist(appleUpdatesFile)
except FoundationPlist.NSPropertyListSerializationException:

View File

@@ -90,17 +90,7 @@ def networkUp():
def clearLastNotifiedDate():
"""Clear the last date the user was notified of updates."""
try:
plist = FoundationPlist.readPlist(
munkicommon.MANAGED_INSTALLS_PLIST_PATH)
if plist:
if 'LastNotifiedDate' in plist:
cmd = ['/usr/bin/defaults', 'delete',
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
'LastNotifiedDate']
unused_retcode = subprocess.call(cmd)
except FoundationPlist.NSPropertyListSerializationException:
pass
munkicommon.set_pref('LastNotifiedDate', None)
def createDirsIfNeeded(dirlist):
@@ -129,8 +119,7 @@ def initMunkiDirs():
Returns:
Boolean. True if all data dirs existed or were created, False otherwise.
"""
managedinstallprefs = munkicommon.prefs()
ManagedInstallDir = managedinstallprefs['ManagedInstallDir']
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
manifestsdir = os.path.join(ManagedInstallDir, 'manifests')
catalogsdir = os.path.join(ManagedInstallDir, 'catalogs')
cachedir = os.path.join(ManagedInstallDir, 'Cache')
@@ -249,14 +238,8 @@ def munkiUpdatesAvailable():
def recordUpdateCheckResult(result):
"""Record last check date and result"""
now = NSDate.new()
cmd = ['/usr/bin/defaults', 'write',
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
'LastCheckDate', '-date', str(now)]
unused_retcode = subprocess.call(cmd)
cmd = ['/usr/bin/defaults', 'write',
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
'LastCheckResult', '-int', str(result)]
unused_retcode = subprocess.call(cmd)
munkicommon.set_pref('LastCheckDate', now)
munkicommon.set_pref('LastCheckResult', result)
def notifyUserOfUpdates():
@@ -284,11 +267,8 @@ def notifyUserOfUpdates():
nextNotifyDate = lastNotifiedDate.dateByAddingTimeInterval_(interval)
if now.timeIntervalSinceDate_(nextNotifyDate) > 0:
# record current notification date
cmd = ['/usr/bin/defaults', 'write',
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
'LastNotifiedDate', '-date', str(now)]
unused_retcode = subprocess.call(cmd)
munkicommon.set_pref('LastNotifiedDate', now)
# Kill Managed Software Update.app if it's already
# open so it will update its display
# using subprocess.Popen instead of subprocess.call
@@ -486,7 +466,7 @@ def main():
munkicommon.cleanUpTmpDir()
exit(-1)
# Force a prefs refresh, in case preflight modified the prefs file.
munkicommon.prefs(force_refresh=True)
munkicommon.reload_prefs()
# create needed directories if necessary
if not initMunkiDirs():

View File

@@ -99,7 +99,7 @@ def softwareUpdateList():
return CACHEDUPDATELIST
updates = []
munkicommon.display_info(
munkicommon.display_detail(
'Getting list of available Apple Software Updates')
cmd = ['/usr/sbin/softwareupdate', '-l']
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
@@ -109,7 +109,7 @@ def softwareUpdateList():
if proc.returncode == 0:
updates = [str(item)[5:] for item in output.splitlines()
if str(item).startswith(' * ')]
munkicommon.display_info(
munkicommon.display_detail(
'softwareupdate returned %s updates' % len(updates))
CACHEDUPDATELIST = updates
return CACHEDUPDATELIST

View File

@@ -37,17 +37,20 @@ import urllib2
import warnings
from distutils import version
from xml.dom import minidom
from Foundation import NSDate
from Foundation import CFPreferencesCopyAppValue
from Foundation import CFPreferencesSetValue
from Foundation import CFPreferencesAppSynchronize
from Foundation import kCFPreferencesAnyUser
from Foundation import kCFPreferencesCurrentHost
import munkistatus
import FoundationPlist
import LaunchServices
MANAGED_INSTALLS_PLIST_PATH = '/Library/Preferences/ManagedInstalls.plist'
MANAGED_INSTALLS_PLIST_PATH_NO_EXT = '/Library/Preferences/ManagedInstalls'
SECURE_MANAGED_INSTALLS_PLIST_PATH = (
'/private/var/root/Library/Preferences/ManagedInstalls.plist')
BUNDLE_ID = 'ManagedInstalls'
ADDITIONAL_HTTP_HEADERS_KEY = 'AdditionalHttpHeaders'
@@ -288,7 +291,7 @@ def format_time(timestamp=None):
if timestamp is None:
return str(NSDate.new())
else:
return str(NSDate.alloc().initWithTimeIntervalSince1970_(timestamp))
return str(NSDate.dateWithTimeIntervalSince1970_(timestamp))
def log(msg, logname=''):
@@ -613,94 +616,65 @@ def isApplication(pathname):
# managed installs preferences/metadata
#####################################################
def prefs(force_refresh=False):
"""Loads and caches preferences from ManagedInstalls.plist.
Args:
force_refresh: Boolean. If True, wipe prefs and reload from scratch. If
False (default), load from cache if it's already set.
Returns:
Dict of preferences.
"""
global _prefs
if not _prefs or force_refresh:
_prefs = {} # start with a clean state.
_prefs['ManagedInstallDir'] = '/Library/Managed Installs'
# convenience; to be replaced with CatalogURL and PackageURL
_prefs['SoftwareRepoURL'] = 'http://munki/repo'
# effective defaults for the following three; though if they
# are not in the prefs plist, they are calculated relative
# to the SoftwareRepoURL (if it exists)
#prefs['ManifestURL'] = 'http://munki/repo/manifests/'
#prefs['CatalogURL'] = 'http://munki/repo/catalogs/'
#prefs['PackageURL'] = 'http://munki/repo/pkgs/'
_prefs['ClientIdentifier'] = ''
_prefs['LogFile'] = \
'/Library/Managed Installs/Logs/ManagedSoftwareUpdate.log'
_prefs['LoggingLevel'] = 1
_prefs['InstallAppleSoftwareUpdates'] = False
_prefs['SoftwareUpdateServerURL'] = ''
_prefs['DaysBetweenNotifications'] = 1
_prefs['LastNotifiedDate'] = '1970-01-01 00:00:00 -0000'
_prefs['UseClientCertificate'] = False
_prefs['SuppressUserNotification'] = False
_prefs['SuppressAutoInstall'] = False
_prefs['SuppressStopButtonOnInstall'] = False
_prefs['PackageVerificationMode'] = 'hash'
# Load configs from ManagedInstalls.plist file
if not loadPrefsFromFile(_prefs, MANAGED_INSTALLS_PLIST_PATH):
# no prefs file, so we'll write out a 'default' prefs file
del _prefs['LastNotifiedDate']
FoundationPlist.writePlist(_prefs, MANAGED_INSTALLS_PLIST_PATH)
# Load configs from secure ManagedInstalls.plist file.
# Note: this overwrites existing configs.
loadPrefsFromFile(_prefs, SECURE_MANAGED_INSTALLS_PLIST_PATH)
return _prefs
def reload_prefs():
"""Uses CFPreferencesAppSynchronize(BUNDLE_ID)
to make sure we have the latest prefs. Call this
if you have modified /Library/Preferences/ManagedInstalls.plist
or /var/root/Library/Preferences/ManagedInstalls.plist directly"""
CFPreferencesAppSynchronize(BUNDLE_ID)
def loadPrefsFromFile(prefs, filepath):
"""Loads preferences from a file into the passed prefs dictionary.
Args:
prefs: dictionary of configurations to update.
filepath: str path of file to read configurations from.
Returns:
Boolean. True if the file exists and prefs was updated, False otherwise.
Raises:
Error: there was an error reading the specified preferences file.
"""
if not os.path.exists(filepath):
return False
plist = {}
def set_pref(pref_name, pref_value):
"""Sets a preference, writing it to
/Library/Preferences/ManagedInstalls.plist.
This should normally be used only for 'bookkeeping' values;
values that control the behavior of munki may be overridden
elsewhere (by MCX, for example)"""
try:
plist = FoundationPlist.readPlist(filepath)
except FoundationPlist.NSPropertyListSerializationException:
display_error('ERROR: Could not read preferences file %s.' % filepath)
raise PreferencesError(
'Could not read preferences file %s.' % filepath)
try:
for key in plist.keys():
if type(plist[key]).__name__ == '__NSCFDate':
# convert NSDate/CFDates to strings
_prefs[key] = str(plist[key])
else:
_prefs[key] = plist[key]
except AttributeError:
display_error('ERROR: Prefs file %s contains invalid data.' % filepath)
raise PreferencesError('Preferences file %s invalid.' % filepath)
return True
CFPreferencesSetValue(
pref_name, pref_value, BUNDLE_ID,
kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
CFPreferencesAppSynchronize(BUNDLE_ID)
except Exception:
pass
def pref(prefname):
"""Return a prefernce"""
return prefs().get(prefname,'')
def pref(pref_name):
"""Return a preference. Since this uses CFPreferencesCopyAppValue,
Preferences can be defined several places. Precedence is:
- MCX
- /var/root/Library/Preferences/ManagedInstalls.plist
- /Library/Preferences/ManagedInstalls.plist
- default_prefs defined here.
"""
default_prefs = {
'ManagedInstallDir': '/Library/Managed Installs',
'SoftwareRepoURL': 'http://munki/repo',
'ClientIdentifier': '',
'LogFile': '/Library/Managed Installs/Logs/ManagedSoftwareUpdate.log',
'LoggingLevel': 1,
'InstallAppleSoftwareUpdates': False,
'AppleSoftwareUpdatesOnly': False,
'SoftwareUpdateServerURL': '',
'DaysBetweenNotifications': 1,
'LastNotifiedDate': NSDate.dateWithTimeIntervalSince1970_(0),
'UseClientCertificate': False,
'SuppressUserNotification': False,
'SuppressAutoInstall': False,
'SuppressStopButtonOnInstall': False,
'PackageVerificationMode': 'hash'
}
pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID)
if pref_value == None:
pref_value = default_prefs.get(pref_name)
# we're using a default value. We'll write it out to
# /Library/Preferences/<BUNDLE_ID>.plist for admin
# discoverability
set_pref(pref_name, pref_value)
if type(pref_value).__name__ == '__NSCFDate':
# convert NSDate/CFDates to strings
pref_value = str(pref_value)
return pref_value
#####################################################
# Apple package utilities
@@ -1560,7 +1534,8 @@ def getAvailableDiskSpace(volumepath='/'):
try:
st = os.statvfs(volumepath)
except OSError, e:
display_error('Error getting disk space in %s: %s', volumepath, str(e))
display_error(
'Error getting disk space in %s: %s', volumepath, str(e))
return 0
return st.f_frsize * st.f_bavail / 1024 # f_bavail matches df(1) output
@@ -1609,7 +1584,6 @@ def listdir(path):
verbose = 1
munkistatusoutput = False
tmpdir = tempfile.mkdtemp()
_prefs = {} # never access this directly; use prefs() instead.
report = {}
report['Errors'] = []
report['Warnings'] = []

View File

@@ -3,11 +3,8 @@
<plist version="1.0">
<dict>
<key>IFPkgDescriptionTitle</key>
<string>Munki - Managed software installation for OS X</string>
<string>munki tools</string>
<key>IFPkgDescriptionDescription</key>
<string>Munki is a set of tools that, used together with a webserver-based
repository of packages and package metadata, can be used by OS X
administrators to manage software installs (and in many cases removals)
on OS X client machines.</string>
<string>Software installation tools for OS X</string>
</dict>
</plist>

View File

@@ -25,7 +25,7 @@ http://webserver/cgi-bin/getmanifest.py?arbitrarystring
arbitrarystring could be the hostname, a UUID, a username...
This could be extended to do wildcard matching, or to
read another file that mapped hostnames/strings to catalog
read another file that mapped hostnames/strings to manifest
files
"""