mirror of
https://github.com/munki/munki.git
synced 2026-05-04 03:20:19 -05:00
Merge branch 'master' of https://code.google.com/p/munki
This commit is contained in:
@@ -21,9 +21,9 @@
|
||||
</object>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<integer value="29"/>
|
||||
<integer value="639"/>
|
||||
<integer value="511"/>
|
||||
<integer value="29"/>
|
||||
<integer value="516"/>
|
||||
</object>
|
||||
<object class="NSArray" key="IBDocument.PluginDependencies">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -617,7 +617,7 @@
|
||||
<object class="NSTabViewItem" id="705868753">
|
||||
<string key="NSIdentifier">1</string>
|
||||
<object class="NSView" key="NSView" id="78996069">
|
||||
<reference key="NSNextResponder" ref="901524889"/>
|
||||
<nil key="NSNextResponder"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<object class="NSMutableArray" key="NSSubviews">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -1071,13 +1071,29 @@
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>WebKitDefaultFixedFontSize</string>
|
||||
<string>WebKitDefaultFontSize</string>
|
||||
<string>WebKitFixedFont</string>
|
||||
<string>WebKitJavaEnabled</string>
|
||||
<string>WebKitJavaScriptCanOpenWindowsAutomatically</string>
|
||||
<string>WebKitJavaScriptEnabled</string>
|
||||
<string>WebKitMinimumFontSize</string>
|
||||
<string>WebKitPluginsEnabled</string>
|
||||
<string>WebKitSansSerifFont</string>
|
||||
<string>WebKitSerifFont</string>
|
||||
<string>WebKitStandardFont</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<integer value="12"/>
|
||||
<integer value="12"/>
|
||||
<string>Lucida Sans</string>
|
||||
<boolean value="NO"/>
|
||||
<boolean value="NO"/>
|
||||
<boolean value="NO"/>
|
||||
<integer value="1"/>
|
||||
<boolean value="NO"/>
|
||||
<string>Lucida Sans</string>
|
||||
<string>Lucida Sans</string>
|
||||
<string>Lucida Sans</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
@@ -1152,7 +1168,6 @@
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrameSize">{512, 512}</string>
|
||||
<reference key="NSSuperview" ref="901524889"/>
|
||||
<int key="NSViewLayerContentsRedrawPolicy">2</int>
|
||||
</object>
|
||||
<string key="NSLabel">Updates</string>
|
||||
@@ -1162,7 +1177,7 @@
|
||||
<object class="NSTabViewItem" id="311465176">
|
||||
<string key="NSIdentifier">2</string>
|
||||
<object class="NSView" key="NSView" id="574199769">
|
||||
<nil key="NSNextResponder"/>
|
||||
<reference key="NSNextResponder" ref="901524889"/>
|
||||
<int key="NSvFlags">256</int>
|
||||
<object class="NSMutableArray" key="NSSubviews">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
@@ -1416,6 +1431,7 @@
|
||||
<reference key="NSHScroller" ref="563381393"/>
|
||||
<reference key="NSContentView" ref="720875694"/>
|
||||
<reference key="NSHeaderClipView" ref="307161175"/>
|
||||
<reference key="NSCornerView" ref="97640332"/>
|
||||
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
|
||||
</object>
|
||||
<object class="WebView" id="800312662">
|
||||
@@ -1627,20 +1643,21 @@
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrameSize">{512, 512}</string>
|
||||
<reference key="NSSuperview" ref="901524889"/>
|
||||
</object>
|
||||
<string key="NSLabel">Optional software</string>
|
||||
<reference key="NSColor" ref="204534777"/>
|
||||
<reference key="NSTabView" ref="901524889"/>
|
||||
</object>
|
||||
</object>
|
||||
<reference key="NSSelectedTabViewItem" ref="705868753"/>
|
||||
<reference key="NSSelectedTabViewItem" ref="311465176"/>
|
||||
<reference key="NSFont" ref="901400326"/>
|
||||
<int key="NSTvFlags">6</int>
|
||||
<bool key="NSAllowTruncatedLabels">YES</bool>
|
||||
<bool key="NSDrawsBackground">YES</bool>
|
||||
<object class="NSMutableArray" key="NSSubviews">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="78996069"/>
|
||||
<reference ref="574199769"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
@@ -110,7 +110,7 @@ def main():
|
||||
ID = 'com.googlecode.munki.logouthelper'
|
||||
munkicommon.log('%s invoked' % ID)
|
||||
sent_notifications = []
|
||||
logout_time_override = False
|
||||
logout_time_override = None
|
||||
minimum_notifications_logout_time = NSDate.date().addTimeInterval_(
|
||||
60 * MINIMUM_NOTIFICATION_MINS + 30)
|
||||
while True:
|
||||
@@ -121,8 +121,17 @@ def main():
|
||||
munkicommon.log('%s exited' % ID)
|
||||
exit(0)
|
||||
|
||||
if not logout_time_override:
|
||||
logout_time = earliestForceInstallDate()
|
||||
tmp_logout_time = earliestForceInstallDate()
|
||||
if logout_time_override is None:
|
||||
logout_time = tmp_logout_time
|
||||
else:
|
||||
# allow the new (tmp_)logout_time from InstallInfo to be used
|
||||
# if it has changed since when we decided to override it.
|
||||
if logout_time_override != tmp_logout_time:
|
||||
logout_time = tmp_logout_time
|
||||
logout_time_override = None
|
||||
sent_notifications = []
|
||||
|
||||
if not logout_time:
|
||||
# no forced logout needed, so bail
|
||||
munkicommon.log('%s: no forced installs found' % ID)
|
||||
@@ -137,7 +146,7 @@ def main():
|
||||
MINIMUM_NOTIFICATION_MINS))
|
||||
logout_time = minimum_notifications_logout_time
|
||||
munkicommon.log('Reset logout_time to: %s' % logout_time)
|
||||
logout_time_override = True
|
||||
logout_time_override = logout_time
|
||||
|
||||
minutes_until_logout = int(logout_time.timeIntervalSinceNow() / 60)
|
||||
info = {'logout_time': logout_time}
|
||||
|
||||
@@ -107,6 +107,9 @@ def makecatalogs(repopath):
|
||||
|
||||
try:
|
||||
pkginfo = plistlib.readPlist(filepath)
|
||||
# don't copy admin notes to catalogs.
|
||||
if pkginfo.get('notes'):
|
||||
del(pkginfo['notes'])
|
||||
except IOError, inst:
|
||||
errors.append("IO error for %s: %s" % (filepath, inst))
|
||||
continue
|
||||
|
||||
+402
-171
@@ -37,7 +37,6 @@ import os
|
||||
import re
|
||||
import optparse
|
||||
from optparse import OptionValueError
|
||||
import subprocess
|
||||
|
||||
from munkilib import munkicommon
|
||||
from munkilib import FoundationPlist
|
||||
@@ -62,6 +61,10 @@ def getCatalogInfoFromDmg(dmgpath, options):
|
||||
pkgpath = os.path.join(mountpoints[0], options.pkgname)
|
||||
if os.path.exists(pkgpath):
|
||||
cataloginfo = munkicommon.getPackageMetaData(pkgpath)
|
||||
if options.installer_choices_xml:
|
||||
installer_choices_xml = munkicommon.getChoiceChangesXML(pkgpath)
|
||||
if installer_choices_xml:
|
||||
cataloginfo['installer_choices_xml'] = installer_choices_xml
|
||||
if cataloginfo:
|
||||
cataloginfo['package_path'] = options.pkgname
|
||||
elif not options.item:
|
||||
@@ -70,6 +73,12 @@ def getCatalogInfoFromDmg(dmgpath, options):
|
||||
itempath = os.path.join(mountpoints[0], fsitem)
|
||||
if itempath.endswith('.pkg') or itempath.endswith('.mpkg'):
|
||||
cataloginfo = munkicommon.getPackageMetaData(itempath)
|
||||
if options.installer_choices_xml:
|
||||
installer_choices_xml = munkicommon.getChoiceChangesXML(
|
||||
itempath)
|
||||
if installer_choices_xml:
|
||||
cataloginfo['installer_choices_xml'] = \
|
||||
installer_choices_xml
|
||||
# get out of fsitem loop
|
||||
break
|
||||
|
||||
@@ -129,26 +138,21 @@ def getCatalogInfoFromDmg(dmgpath, options):
|
||||
cataloginfo['version'] = \
|
||||
iteminfo.get('CFBundleShortVersionString', "0")
|
||||
cataloginfo['installs'] = [iteminfo]
|
||||
if options.appdmg:
|
||||
cataloginfo['installer_type'] = "appdmg"
|
||||
cataloginfo['uninstallable'] = True
|
||||
cataloginfo['uninstall_method'] = "remove_app"
|
||||
else:
|
||||
cataloginfo['installer_type'] = "copy_from_dmg"
|
||||
item_to_copy = {}
|
||||
item_to_copy['source_item'] = item
|
||||
item_to_copy['destination_path'] = \
|
||||
options.destinationpath or "/Applications"
|
||||
if options.user:
|
||||
item_to_copy['user'] = options.user
|
||||
if options.group:
|
||||
item_to_copy['group'] = options.group
|
||||
if options.mode:
|
||||
item_to_copy['mode'] = options.mode
|
||||
cataloginfo['items_to_copy'] = [item_to_copy]
|
||||
cataloginfo['uninstallable'] = True
|
||||
cataloginfo['uninstall_method'] = "remove_copied_items"
|
||||
|
||||
cataloginfo['installer_type'] = "copy_from_dmg"
|
||||
item_to_copy = {}
|
||||
item_to_copy['source_item'] = item
|
||||
item_to_copy['destination_path'] = \
|
||||
options.destinationpath or "/Applications"
|
||||
if options.user:
|
||||
item_to_copy['user'] = options.user
|
||||
if options.group:
|
||||
item_to_copy['group'] = options.group
|
||||
if options.mode:
|
||||
item_to_copy['mode'] = options.mode
|
||||
cataloginfo['items_to_copy'] = [item_to_copy]
|
||||
cataloginfo['uninstallable'] = True
|
||||
cataloginfo['uninstall_method'] = "remove_copied_items"
|
||||
|
||||
#eject the dmg
|
||||
munkicommon.unmountdmg(mountpoints[0])
|
||||
return cataloginfo
|
||||
@@ -185,6 +189,20 @@ def readfile(path):
|
||||
return ""
|
||||
|
||||
|
||||
def readFileOrString(option_value):
|
||||
"""
|
||||
If option_value is a path to a file,
|
||||
return contents of file.
|
||||
|
||||
Otherwise, return the string.
|
||||
"""
|
||||
if os.path.exists(os.path.expanduser(option_value)):
|
||||
string = readfile(option_value)
|
||||
else:
|
||||
string = option_value
|
||||
|
||||
return string
|
||||
|
||||
def getiteminfo(itempath):
|
||||
"""
|
||||
Gets info for filesystem items passed to makecatalog item, to be used for
|
||||
@@ -256,149 +274,289 @@ def main():
|
||||
usage = """usage: %prog [options] [/path/to/installeritem]
|
||||
%prog --help for more information."""
|
||||
p = optparse.OptionParser(usage=usage)
|
||||
p.add_option('--file', '-f', action="append",
|
||||
help='''Path to a filesystem item installed by this
|
||||
package, typically an application. This generates an
|
||||
"installs" item for the pkginfo, an item munki can
|
||||
use to determine if this software has been installed.
|
||||
Can be specified multiple times.''')
|
||||
p.add_option('--pkgname', '-p',
|
||||
help='''Optional flag.
|
||||
|
||||
-If the installer item is a disk image containing
|
||||
multiple packages, or the package to be installed
|
||||
is not at the root of the mounted disk image, PKGNAME
|
||||
is a relative path from the root of the mounted
|
||||
disk image to the specific package to be installed.
|
||||
|
||||
-If the installer item is a disk image containing
|
||||
an Adobe CS4 Deployment Toolkit installation, PKGNAME
|
||||
is the name of an Adobe CS4 Deployment Toolkit
|
||||
installer package folder at the top level of the
|
||||
mounted dmg.
|
||||
|
||||
If this flag is missing, the AdobeUber* files should
|
||||
be at the top level of the mounted dmg.''')
|
||||
p.add_option('--appdmg', action="store_true",
|
||||
help='''Optional flag.
|
||||
|
||||
Causes makepkginfo to create a pkginfo item describing
|
||||
an appdmg install instead of the newer copy_from_dmg
|
||||
installer type. Meant for use with older munki
|
||||
clients, as copy_from_dmg replaces appdmg in munki
|
||||
0.6.0 and later.''')
|
||||
p.add_option('--itemname', '-i', '--appname', '-a',
|
||||
metavar='ITEM',
|
||||
dest='item',
|
||||
help='''Optional flag.
|
||||
|
||||
-If the installer item is a disk image with a
|
||||
drag-and-drop item, ITEMNAME is the name or
|
||||
relative path of the item to be installed.
|
||||
Useful if there is more than one item at the
|
||||
root of the dmg.''')
|
||||
p.add_option('--displayname',
|
||||
help='''Optional flag.
|
||||
|
||||
String display name of the package.
|
||||
Note: overrides any display_name in the package itself''')
|
||||
p.add_option('--description',
|
||||
help='''Optional flag.
|
||||
|
||||
String description of the package.
|
||||
Note: overrides any description in the package itself''')
|
||||
p.add_option('--destinationpath', '-d',
|
||||
help='''Optional flag.
|
||||
|
||||
If the installer item is a disk image with a
|
||||
drag-and-drop item, this is the path to which
|
||||
the item should be copied. Defaults to
|
||||
"/Applications".''')
|
||||
p.add_option('--uninstallerdmg', '-u',
|
||||
help='''Optional flag.
|
||||
|
||||
If the installer item is a disk image containing an
|
||||
Adobe CS4 Deployment Toolkit installation package or
|
||||
Adobe CS3 deployment package, UNINSTALLERDMG is a path
|
||||
to a disk image containing an AdobeUberUninstaller for
|
||||
this item.''')
|
||||
p.add_option('--postinstall_script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help='''Optional flag.
|
||||
|
||||
Path to an optional postinstall script to be run after
|
||||
installation of the item. The script will be read and
|
||||
embedded into the pkginfo.''')
|
||||
p.add_option('--preinstall_script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help='''Optional flag.
|
||||
|
||||
Path to an optional preinstall script to be run before
|
||||
installation of the item. The script will be read and
|
||||
embedded into the pkginfo.''')
|
||||
p.add_option('--postuninstall_script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help='''Optional flag.
|
||||
p.add_option(
|
||||
'--verify-options-only',
|
||||
action="store_true",
|
||||
help=optparse.SUPPRESS_HELP
|
||||
)
|
||||
p.add_option(
|
||||
'--version', '-V',
|
||||
action='store_true',
|
||||
help='Print the version of the munki tools and exit.'
|
||||
)
|
||||
|
||||
Path to an optional postuninstall script to be run after
|
||||
removal of the item. The script will be read and
|
||||
embedded into the pkginfo.''')
|
||||
p.add_option('--preuninstall_script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help='''Optional flag.
|
||||
|
||||
Path to an optional preuninstall script to be run before
|
||||
removal of the item. The script will be read and
|
||||
embedded into the pkginfo.''')
|
||||
p.add_option('--uninstall_script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help='''Optional flag.
|
||||
|
||||
Path to an uninstall script to be run in order to
|
||||
uninstall this item. The script will be read and
|
||||
embedded into the pkginfo.''')
|
||||
p.add_option('--catalog', '-c', action="append",
|
||||
help='''Optional flag.
|
||||
|
||||
Specifies in which catalog the item should appear. The
|
||||
default is 'testing'. Can be specified multiple times
|
||||
to add the item to multiple catalogs.''')
|
||||
p.add_option('-o', '--owner',
|
||||
metavar='USER',
|
||||
dest='user',
|
||||
help='''Optional flag.
|
||||
|
||||
If the installer item is a disk image used with
|
||||
the copy_from_dmg installer type, this sets the
|
||||
owner of the item specified by the --item flag.
|
||||
The owner may be either a UID or a symbolic name.
|
||||
The owner will be set recursively on the item.''')
|
||||
p.add_option('-g', '--group',
|
||||
metavar='GROUP',
|
||||
dest='group',
|
||||
help='''Optional flag.
|
||||
|
||||
If the installer item is a disk image used with
|
||||
the copy_from_dmg installer type, this sets the
|
||||
group of the item specified by the --item flag.
|
||||
The group may be either a GID or a symbolic name.
|
||||
The group will be set recursively on the item.''')
|
||||
p.add_option('-m', '--mode',
|
||||
metavar='MODE',
|
||||
dest='mode',
|
||||
action='callback',
|
||||
type='string',
|
||||
callback=check_mode,
|
||||
help='''Optional flag.
|
||||
|
||||
If the installer item is a disk used with
|
||||
the copy_from_dmg installer type, this sets the
|
||||
mode of the item specified by the --item flag.
|
||||
The specified mode must be in symbolic form.
|
||||
See the manpage for chmod(1) for more information.
|
||||
The mode is applied recursively.''')
|
||||
p.add_option('--version', '-V', action='store_true',
|
||||
help='Print the version of the munki tools and exit.')
|
||||
# Default override options
|
||||
default_override_options = optparse.OptionGroup(
|
||||
p, 'Default Override Options',
|
||||
('Options specified will override information automatically derived '
|
||||
'from the package.'))
|
||||
default_override_options.add_option(
|
||||
'--name',
|
||||
metavar='NAME',
|
||||
help='Name of the package.'
|
||||
)
|
||||
default_override_options.add_option(
|
||||
'--displayname',
|
||||
metavar='DISPLAY_NAME',
|
||||
help='Display name of the package.'
|
||||
)
|
||||
default_override_options.add_option(
|
||||
'--description',
|
||||
metavar='STRING|PATH',
|
||||
help=('Description of the package. '
|
||||
'Can be a PATH to a file (plain text or html).')
|
||||
)
|
||||
default_override_options.add_option(
|
||||
'--pkgvers',
|
||||
metavar='PACKAGE_VERSION',
|
||||
help='Version of the package.'
|
||||
)
|
||||
default_override_options.add_option(
|
||||
'--RestartAction',
|
||||
metavar='ACTION',
|
||||
help=('Specify a \'RestartAction\' for the package. '
|
||||
'Supported actions: RequireRestart, RequireLogout, or '
|
||||
'RecommendRestart')
|
||||
)
|
||||
default_override_options.add_option(
|
||||
'--uninstall_method', '--uninstall-method',
|
||||
metavar='METHOD|PATH',
|
||||
help=('Specify an \'uninstall_method\' for the package. '
|
||||
'Default method depends on the package type: i.e. '
|
||||
'drag-n-drop, Apple package, or an embedded uninstall script. '
|
||||
'Can be a path to a script on the client computer.')
|
||||
)
|
||||
p.add_option_group(default_override_options)
|
||||
|
||||
# Script options
|
||||
script_options = optparse.OptionGroup(
|
||||
p, 'Script Options',
|
||||
'All scripts are read and embedded into the pkginfo.')
|
||||
script_options.add_option(
|
||||
'--preinstall_script', '--preinstall-script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help=('Path to an optional preinstall script to be '
|
||||
'run before installation of the item.')
|
||||
)
|
||||
script_options.add_option(
|
||||
'--postinstall_script', '--postinstall-script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help=('Path to an optional postinstall script to be '
|
||||
'run after installation of the item.')
|
||||
)
|
||||
script_options.add_option(
|
||||
'--preuninstall_script', '--preuninstall-script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help=('Path to an optional preuninstall script to be run '
|
||||
'before removal of the item.')
|
||||
)
|
||||
script_options.add_option(
|
||||
'--postuninstall_script', '--postuninstall-script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help=('Path to an optional postuninstall script to be run '
|
||||
'after removal of the item.')
|
||||
)
|
||||
script_options.add_option(
|
||||
'--uninstall_script', '--uninstall-script',
|
||||
metavar='SCRIPT_PATH',
|
||||
help=('Path to an uninstall script to be run in order '
|
||||
'to uninstall this item.')
|
||||
)
|
||||
p.add_option_group(script_options)
|
||||
|
||||
# Drag-n-Drop options
|
||||
dragdrop_options = optparse.OptionGroup(
|
||||
p, 'Drag-n-Drop Options',
|
||||
('These options apply to installer items that are "drag-n-drop" '
|
||||
'disk images.')
|
||||
)
|
||||
dragdrop_options.add_option(
|
||||
'--itemname', '-i', '--appname', '-a',
|
||||
metavar='ITEM',
|
||||
dest='item',
|
||||
help=('Name or relative path of the item to be installed. '
|
||||
'Useful if there is more than one item at the root of the dmg.')
|
||||
)
|
||||
dragdrop_options.add_option(
|
||||
'--destinationpath', '-d',
|
||||
metavar='PATH',
|
||||
help=('Path to which the item should be copied. Defaults to '
|
||||
'"/Applications".')
|
||||
)
|
||||
dragdrop_options.add_option(
|
||||
'-o', '--owner',
|
||||
metavar='USER',
|
||||
dest='user',
|
||||
help=('Sets the owner of the item specified by the --item flag. '
|
||||
'The owner may be either a UID or a symbolic name. '
|
||||
'The owner will be set recursively on the item.')
|
||||
)
|
||||
dragdrop_options.add_option(
|
||||
'-g', '--group',
|
||||
metavar='GROUP',
|
||||
dest='group',
|
||||
help=('Sets the group of the item specified by the --item flag. '
|
||||
'The group may be either a GID or a symbolic name. '
|
||||
'The group will be set recursively on the item.')
|
||||
)
|
||||
dragdrop_options.add_option(
|
||||
'-m', '--mode',
|
||||
metavar='MODE',
|
||||
dest='mode',
|
||||
action='callback',
|
||||
type='string',
|
||||
callback=check_mode,
|
||||
help=('Sets the mode of the item specified by the --item flag. '
|
||||
'The specified mode must be in symbolic form. '
|
||||
'See the manpage for chmod(1) for more information. '
|
||||
'The mode is applied recursively.')
|
||||
)
|
||||
p.add_option_group(dragdrop_options)
|
||||
|
||||
# Apple package specific options
|
||||
apple_options = optparse.OptionGroup(p, 'Apple Package Options')
|
||||
apple_options.add_option(
|
||||
'--pkgname', '-p',
|
||||
help=('If the installer item is a disk image containing multiple '
|
||||
'packages, or the package to be installed is not at the root '
|
||||
'of the mounted disk image, PKGNAME is a relative path from '
|
||||
'the root of the mounted disk image to the specific package to '
|
||||
'be installed.'
|
||||
'If the installer item is a disk image containing an Adobe '
|
||||
'CS4 Deployment Toolkit installation, PKGNAME is the name of '
|
||||
'an Adobe CS4 Deployment Toolkit installer package folder at '
|
||||
'the top level of the mounted dmg.'
|
||||
'If this flag is missing, the AdobeUber* files should be at '
|
||||
'the top level of the mounted dmg.')
|
||||
)
|
||||
apple_options.add_option(
|
||||
'--installer_choices_xml', '--installer-choices-xml',
|
||||
action='store_true',
|
||||
help=('Generate installer choices for metapackages. '
|
||||
'Note: Requires Mac OS X 10.6.6 or later.')
|
||||
)
|
||||
apple_options.add_option(
|
||||
'--installer_environment', '--installer-environment', '-E',
|
||||
action="append",
|
||||
metavar='KEY=VALUE',
|
||||
help=('Specifies key/value pairs to set environment variables for use '
|
||||
'by /usr/sbin/installer. A key/value pair of '
|
||||
'USER=CURRENT_CONSOLE_USER indicates that USER be set to the '
|
||||
'GUI user, otherwise root. Can be specified multiple times.')
|
||||
)
|
||||
p.add_option_group(apple_options)
|
||||
|
||||
# Adobe package specific options
|
||||
adobe_options = optparse.OptionGroup(p, 'Adobe-specific Options')
|
||||
adobe_options.add_option(
|
||||
'--uninstallerdmg', '-U',
|
||||
help=('If the installer item is a disk image containing an Adobe CS4 '
|
||||
'Deployment Toolkit installation package or Adobe CS3 deployment '
|
||||
'package, UNINSTALLERDMG is a path to a disk image containing an '
|
||||
'AdobeUberUninstaller for this item.')
|
||||
)
|
||||
p.add_option_group(adobe_options)
|
||||
|
||||
# Forced/Unattended (install) options
|
||||
forced_unattended_options = optparse.OptionGroup(
|
||||
p, 'Forced/Unattended Options')
|
||||
forced_unattended_options.add_option(
|
||||
'--unattended_install', '--unattended-install',
|
||||
action='store_true',
|
||||
help='Item can be installed without notifying the user.')
|
||||
forced_unattended_options.add_option(
|
||||
'--unattended_uninstall', '--unattended-uninstall',
|
||||
action='store_true',
|
||||
help='Item can be uninstalled without notifiying the user.')
|
||||
forced_unattended_options.add_option(
|
||||
'--force_install_after_date', '--force-install-after-date',
|
||||
metavar='DATE',
|
||||
help=('Specify a date, in local time, after which the package will '
|
||||
'be forcefully installed. DATE format: yyyy-mm-ddThh:mm:ssZ '
|
||||
'Example: \'2011-08-11T12:55:00Z\' equates to 11 August 2011 '
|
||||
'at 12:55 PM local time.')
|
||||
)
|
||||
p.add_option_group(forced_unattended_options)
|
||||
|
||||
# 'installs' generation options
|
||||
# (by itself since no installer_item needs to be specified)
|
||||
gen_installs_options = optparse.OptionGroup(
|
||||
p, 'Generating \'installs\' items')
|
||||
gen_installs_options.add_option(
|
||||
'--file', '-f',
|
||||
action="append",
|
||||
metavar='PATH',
|
||||
help=('Path to a filesystem item installed by this package, typically '
|
||||
'an application. This generates an "installs" item for the '
|
||||
'pkginfo, to be used to determine if this software has been '
|
||||
'installed. Can be specified multiple times.')
|
||||
)
|
||||
p.add_option_group(gen_installs_options)
|
||||
|
||||
# Additional options - misc. options that don't fit into other categories,
|
||||
# and don't necessarily warrant the creation of their own option group
|
||||
additional_options = optparse.OptionGroup(p, 'Additional Options')
|
||||
additional_options.add_option(
|
||||
'--autoremove',
|
||||
action='store_true',
|
||||
help=('Indicates this package should be automatically removed if it is '
|
||||
'not listed in any applicable \'managed_installs\'.')
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--minimum_munki_version', '--minimum-munki-version',
|
||||
metavar='VERSION',
|
||||
help=('Minimum version of munki required to perform installation. '
|
||||
'Uses format produced by \'--version\' query from any munki '
|
||||
'utility.')
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--minimum_os_version', '--minimum-os-version', '--min-os-ver',
|
||||
metavar='VERSION',
|
||||
help='Minimum OS version for the installer item.'
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--maximum_os_version', '--maximum-os-version', '--max-os-ver',
|
||||
metavar='VERSION',
|
||||
help='Maximum OS version for the installer item.'
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--update_for', '--update-for', '-u',
|
||||
action="append",
|
||||
metavar='PKG_NAME',
|
||||
help=('Specifies a package for which the current package is an update. '
|
||||
'Can be specified multiple times to build an array of packages.')
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--requires', '-r',
|
||||
action="append",
|
||||
metavar='PKG_NAME',
|
||||
help=('Specifies a package required by the current package. Can be '
|
||||
'specified multiple times to build an array of required '
|
||||
'packages.')
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--blocking_application', '--blocking-application', '-b',
|
||||
action="append",
|
||||
metavar='APP_NAME',
|
||||
help=('Specifies an application that blocks installation. Can be '
|
||||
'specified multiple times to build an array of blocking '
|
||||
'applications.')
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--catalog', '-c',
|
||||
action="append",
|
||||
metavar='CATALOG_NAME',
|
||||
help=('Specifies in which catalog the item should appear. The default '
|
||||
'is \'testing\'. Can be specified multiple times to add the item '
|
||||
'to multiple catalogs.')
|
||||
)
|
||||
additional_options.add_option(
|
||||
'--notes',
|
||||
metavar='STRING|PATH',
|
||||
help=('Specifies administrator provided notes to be embedded into the '
|
||||
'pkginfo. Can be a PATH to a file.')
|
||||
)
|
||||
p.add_option_group(additional_options)
|
||||
|
||||
options, arguments = p.parse_args()
|
||||
|
||||
@@ -406,8 +564,22 @@ def main():
|
||||
print munkicommon.get_version()
|
||||
exit(0)
|
||||
|
||||
if options.verify_options_only:
|
||||
if len(arguments) == 0:
|
||||
print >> sys.stderr, 'makepkginfo options FAILED verification'
|
||||
print >> sys.stderr, 'No installer item was provided.'
|
||||
exit(1)
|
||||
if len(arguments) > 1:
|
||||
print >> sys.stderr, 'makepkginfo options FAILED verification'
|
||||
print >> sys.stderr, \
|
||||
'Can process only one installer item at a time.'
|
||||
exit(3)
|
||||
print >> sys.stdout, arguments[0]
|
||||
exit(0)
|
||||
|
||||
if (len(arguments) == 0
|
||||
and not options.file
|
||||
and not options.file
|
||||
and not options.installer_environment
|
||||
and not options.preinstall_script
|
||||
and not options.postinstall_script
|
||||
and not options.preuninstall_script
|
||||
@@ -421,6 +593,12 @@ def main():
|
||||
print >> sys.stderr, 'Ignoring additional installer items:'
|
||||
print >> sys.stderr, '\t', '\n\t'.join(arguments[1:])
|
||||
|
||||
if options.installer_choices_xml:
|
||||
os_version = munkicommon.getOsVersion(
|
||||
only_major_minor=False, as_tuple=True)
|
||||
if os_version < (10, 6, 6):
|
||||
options.installer_choices_xml = False
|
||||
|
||||
catinfo = {}
|
||||
installs = []
|
||||
if arguments:
|
||||
@@ -452,10 +630,15 @@ def main():
|
||||
|
||||
elif item.endswith('.pkg') or item.endswith('.mpkg'):
|
||||
catinfo = munkicommon.getPackageMetaData(item)
|
||||
if options.installer_choices_xml:
|
||||
installer_choices_xml = munkicommon.getChoiceChangesXML(
|
||||
item)
|
||||
if installer_choices_xml:
|
||||
catinfo['installer_choices_xml'] = installer_choices_xml
|
||||
if not catinfo:
|
||||
print >> sys.stderr, \
|
||||
"%s doesn't appear to be a valid installer item!" % \
|
||||
item
|
||||
print >> sys.stderr, (
|
||||
"%s doesn't appear to be a valid installer item!" %
|
||||
item)
|
||||
exit(-1)
|
||||
if os.path.isdir(item):
|
||||
print >> sys.stderr, (
|
||||
@@ -470,19 +653,23 @@ def main():
|
||||
itemsize += int(os.lstat(filename).st_size)
|
||||
# convert to kbytes
|
||||
itemsize = int(itemsize/1024)
|
||||
|
||||
|
||||
else:
|
||||
print >> sys.stderr, "%s is not an installer package!" % item
|
||||
exit(-1)
|
||||
|
||||
if options.description:
|
||||
catinfo['description'] = options.description
|
||||
catinfo['description'] = readFileOrString(options.description)
|
||||
if options.displayname:
|
||||
catinfo['display_name'] = options.displayname
|
||||
|
||||
if options.name:
|
||||
catinfo['name'] = options.name
|
||||
if options.pkgvers:
|
||||
catinfo['version'] = options.pkgvers
|
||||
|
||||
catinfo['installer_item_size'] = int(itemsize/1024)
|
||||
catinfo['installer_item_hash'] = itemhash
|
||||
|
||||
|
||||
# try to generate the correct item location
|
||||
temppath = item
|
||||
location = ""
|
||||
@@ -600,6 +787,50 @@ def main():
|
||||
if scriptstring:
|
||||
catinfo['uninstall_script'] = scriptstring
|
||||
catinfo['uninstall_method'] = 'uninstall_script'
|
||||
if options.autoremove:
|
||||
catinfo['autoremove'] = True
|
||||
if options.minimum_munki_version:
|
||||
catinfo['minimum_munki_version'] = options.minimum_munki_version
|
||||
if options.unattended_install:
|
||||
catinfo['unattended_install'] = True
|
||||
if options.unattended_uninstall:
|
||||
catinfo['unattended_uninstall'] = True
|
||||
if options.minimum_os_version:
|
||||
catinfo['minimum_os_version'] = options.minimum_os_version
|
||||
if options.maximum_os_version:
|
||||
catinfo['maximum_os_version'] = options.maximum_os_version
|
||||
if options.force_install_after_date:
|
||||
force_install_after_date = (
|
||||
munkicommon.validateDateFormat(options.force_install_after_date))
|
||||
if force_install_after_date:
|
||||
catinfo['force_install_after_date'] = force_install_after_date
|
||||
if options.RestartAction:
|
||||
validActions = ['RequireRestart', 'RequireLogout', 'RecommendRestart']
|
||||
if options.RestartAction in validActions:
|
||||
catinfo['RestartAction'] = options.RestartAction
|
||||
elif 'restart' in options.RestartAction.lower():
|
||||
catinfo['RestartAction'] = 'RequireRestart'
|
||||
elif 'logout' in options.RestartAction.lower():
|
||||
catinfo['RestartAction'] = 'RequireLogout'
|
||||
if options.update_for:
|
||||
catinfo['update_for'] = options.update_for
|
||||
if options.requires:
|
||||
catinfo['requires'] = options.requires
|
||||
if options.blocking_application:
|
||||
catinfo['blocking_application'] = options.blocking_application
|
||||
if options.uninstall_method:
|
||||
catinfo['uninstall_method'] = options.uninstall_method
|
||||
if options.installer_environment:
|
||||
try:
|
||||
installer_environment_dict = dict(
|
||||
(k,v) for k,v in (
|
||||
kv.split('=') for kv in options.installer_environment))
|
||||
except Exception:
|
||||
installer_environment_dict = {}
|
||||
if installer_environment_dict:
|
||||
catinfo['installer_environment'] = installer_environment_dict
|
||||
if options.notes:
|
||||
catinfo['notes'] = readFileOrString(options.notes)
|
||||
|
||||
# and now, what we've all been waiting for...
|
||||
print FoundationPlist.writePlistToString(catinfo)
|
||||
|
||||
+91
-49
@@ -25,14 +25,34 @@ Assists with importing installer items into the munki repo
|
||||
|
||||
import sys
|
||||
import os
|
||||
import optparse
|
||||
import subprocess
|
||||
import time
|
||||
#from distutils import version
|
||||
from optparse import OptionParser, BadOptionError, AmbiguousOptionError
|
||||
|
||||
from munkilib import munkicommon
|
||||
from munkilib import FoundationPlist
|
||||
|
||||
class PassThroughOptionParser(OptionParser):
|
||||
"""
|
||||
An unknown option pass-through implementation of OptionParser.
|
||||
|
||||
When unknown arguments are encountered, bundle with largs and try again,
|
||||
until rargs is depleted.
|
||||
|
||||
sys.exit(status) will still be called if a known argument is passed
|
||||
incorrectly (e.g. missing arguments or bad argument types, etc.)
|
||||
"""
|
||||
def _process_args(self, largs, rargs, values):
|
||||
while rargs:
|
||||
try:
|
||||
OptionParser._process_args(self, largs, rargs, values)
|
||||
except (BadOptionError, AmbiguousOptionError), e:
|
||||
largs.append(e.opt_str)
|
||||
def format_epilog(self, formatter):
|
||||
if not self.epilog:
|
||||
self.epilog = ""
|
||||
return self.epilog
|
||||
|
||||
|
||||
def makeDMG(pkgpath):
|
||||
"""Wraps a non-flat package into a disk image.
|
||||
@@ -59,7 +79,7 @@ def makeDMG(pkgpath):
|
||||
else:
|
||||
print 'Disk image created at: %s' % diskimagepath
|
||||
return diskimagepath
|
||||
|
||||
|
||||
|
||||
def repoAvailable():
|
||||
"""Checks the repo path for proper directory structure.
|
||||
@@ -401,7 +421,7 @@ def findMatchingPkginfo(pkginfo):
|
||||
return {}
|
||||
|
||||
|
||||
def makePkgInfo(item_path):
|
||||
def makePkgInfo(options=None, test_mode=False):
|
||||
"""Calls makepkginfo to generate the pkginfo for item_path."""
|
||||
# first look for a makepkginfo in the same dir as us
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -409,14 +429,36 @@ def makePkgInfo(item_path):
|
||||
if not os.path.exists(makepkginfo_path):
|
||||
# didn't find it; assume the default install path
|
||||
makepkginfo_path = '/usr/local/munki/makepkginfo'
|
||||
proc = subprocess.Popen([makepkginfo_path, item_path],
|
||||
if test_mode:
|
||||
# prepend verification option if in test mode
|
||||
options = ['--verify-options-only'] + options
|
||||
# build makepkginfo command from discovered path and options
|
||||
cmd = [makepkginfo_path] + options
|
||||
proc = subprocess.Popen(cmd,
|
||||
bufsize=-1, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(pliststr, err) = proc.communicate()
|
||||
(stdout, stderr) = proc.communicate()
|
||||
if test_mode:
|
||||
if proc.returncode == 2:
|
||||
# option syntax error or unknown option
|
||||
syntax_error = [error for error in stderr.splitlines()
|
||||
if 'error' in error]
|
||||
print >> sys.stderr, ('Option syntax error: %s' %
|
||||
syntax_error[-1].split(': ',2)[-1])
|
||||
print >> sys.stderr, ('See \'%s --help\' for valid options that '
|
||||
'can be used with munkiimport.') % makepkginfo_path
|
||||
exit(-1)
|
||||
elif proc.returncode:
|
||||
# catch-all for any other error
|
||||
if stderr:
|
||||
print >> sys.stderr, stderr.rstrip('\n')
|
||||
return {}
|
||||
else:
|
||||
return stdout.rstrip('\n')
|
||||
if proc.returncode:
|
||||
print >> sys.stderr, err
|
||||
print >> sys.stderr, stderr.rstrip('\n')
|
||||
return {}
|
||||
return FoundationPlist.readPlistFromString(pliststr)
|
||||
return FoundationPlist.readPlistFromString(stdout)
|
||||
|
||||
|
||||
def makeCatalogs():
|
||||
@@ -449,6 +491,7 @@ def makeCatalogs():
|
||||
|
||||
|
||||
def cleanupAndExit(exitcode):
|
||||
"""Unmounts the repo if we mounted it, then exits"""
|
||||
result = 0
|
||||
if WE_MOUNTED_THE_REPO:
|
||||
if not NOINTERACTIVE:
|
||||
@@ -501,14 +544,27 @@ VERBOSE = False
|
||||
def main():
|
||||
"""Main routine"""
|
||||
global NOINTERACTIVE
|
||||
global VERBOSE
|
||||
|
||||
usage = """usage: %prog [options] [/path/to/installer_item]
|
||||
usage = """usage: %prog [options] /path/to/installer_item
|
||||
Imports an installer item into a munki repo.
|
||||
Installer item can be a pkg, mpkg, dmg, or app.
|
||||
Bundle-style pkgs and apps are wrapped in a dmg
|
||||
file before upload."""
|
||||
Bundle-style pkgs and apps are wrapped in a dmg file before upload.
|
||||
|
||||
Example:
|
||||
munkiimport --subdirectory apps /path/to/installer_item
|
||||
"""
|
||||
|
||||
epilog = """\nExtended Options: (makepkginfo options)
|
||||
In addition to the options described above, options used with
|
||||
'makepkginfo' may also be specified to customize the resulting
|
||||
pkginfo file.
|
||||
|
||||
Example:
|
||||
munkiimport --subdirectory apps -c production --minimum_os_vers 10.6.8 /path/to/installer_item\n"""
|
||||
|
||||
p = PassThroughOptionParser(usage=usage, epilog=epilog)
|
||||
|
||||
p = optparse.OptionParser(usage=usage)
|
||||
p.add_option('--configure', action='store_true',
|
||||
help="""Configure munkiimport with details about your
|
||||
munki repo, preferred editor, and the like. Any other
|
||||
@@ -521,21 +577,13 @@ def main():
|
||||
p.add_option('--nointeractive', '-n', action='store_true',
|
||||
help="""No interactive prompts. May cause a failure
|
||||
if repo path is unavailable.""")
|
||||
p.add_option('--unattended-install', action='store_true',
|
||||
help="""Specify 'unattended_install' for the installer item.""")
|
||||
p.add_option('--catalog', default=[], action='append',
|
||||
help="""Specify target catalogs.""")
|
||||
p.add_option('--min-os-ver', default=None, metavar='VERSION',
|
||||
help="""Minimum OS version for the installer item.""")
|
||||
p.add_option('--max-os-ver', default=None, metavar='VERSION',
|
||||
help="""Maximum OS version for the installer item.""")
|
||||
p.add_option('--version', '-V', action='store_true',
|
||||
help='Print the version of the munki tools and exit.')
|
||||
p.add_option('--verbose', '-v', action='store_true',
|
||||
help='Print more output.')
|
||||
|
||||
options, arguments = p.parse_args()
|
||||
|
||||
|
||||
if options.version:
|
||||
print munkicommon.get_version()
|
||||
exit(0)
|
||||
@@ -550,22 +598,27 @@ def main():
|
||||
if len(arguments) == 0:
|
||||
p.print_usage()
|
||||
exit(0)
|
||||
|
||||
if len(arguments) > 1:
|
||||
print >> sys.stderr, \
|
||||
'This tool supports importing only one item at a time.'
|
||||
print >> sys.stderr, 'Too many items given:'
|
||||
print >> sys.stderr, '\t', '\n\t'.join(arguments)
|
||||
exit(-1)
|
||||
|
||||
installer_item = arguments[0]
|
||||
if not os.path.exists(installer_item):
|
||||
print >> sys.stderr, '%s does not exist!' % installer_item
|
||||
exit(-1)
|
||||
# Verify that arguments, presumed to be for
|
||||
# 'makepkginfo' are valid and return installer_item
|
||||
installer_item = makePkgInfo(arguments, True)
|
||||
if not installer_item:
|
||||
cleanupAndExit(-1)
|
||||
|
||||
# Remove the installer_item from arguments
|
||||
arguments.remove(installer_item)
|
||||
|
||||
# Strip trailing '/' from installer_item
|
||||
installer_item = installer_item.rstrip('/')
|
||||
|
||||
item_ext = os.path.splitext(installer_item)[1]
|
||||
if item_ext not in ['.pkg', '.mpkg', '.dmg', '.app']:
|
||||
print >> sys.stderr, '%s is an unknown type.' % installer_item
|
||||
print >> sys.stderr, (
|
||||
'Unknown installer item type: "%s"' % installer_item)
|
||||
exit(-1)
|
||||
|
||||
if not os.path.exists(installer_item):
|
||||
print >> sys.stderr, '%s does not exist!' % installer_item
|
||||
exit(-1)
|
||||
|
||||
if not pref('repo_path'):
|
||||
@@ -593,12 +646,14 @@ def main():
|
||||
print >> sys.stderr, ('Could not convert %s to a disk image.'
|
||||
% installer_item)
|
||||
cleanupAndExit(-1)
|
||||
|
||||
# generate pkginfo for the item
|
||||
pkginfo = makePkgInfo(installer_item)
|
||||
|
||||
# append the installer_item to arguments which
|
||||
# may have changed if bundle was wrapped into dmg
|
||||
arguments.append(installer_item)
|
||||
pkginfo = makePkgInfo(arguments, False)
|
||||
if not pkginfo:
|
||||
# makepkginfo returned an error
|
||||
print >> sys.stderr, 'Getting package info failed.'
|
||||
cleanupAndExit(-1)
|
||||
|
||||
if not options.nointeractive:
|
||||
@@ -662,9 +717,6 @@ def main():
|
||||
if newvalue:
|
||||
pkginfo[key] = newvalue
|
||||
|
||||
if options.catalog:
|
||||
pkginfo['catalogs'] = options.catalog
|
||||
|
||||
newvalue = raw_input('%15s [%s]: ' % ('Catalogs',
|
||||
', '.join(pkginfo['catalogs'])))
|
||||
if newvalue:
|
||||
@@ -711,16 +763,6 @@ def main():
|
||||
print >> sys.stderr, errmsg
|
||||
cleanupAndExit(-1)
|
||||
|
||||
if options.unattended_install:
|
||||
pkginfo['unattended_install'] = True
|
||||
|
||||
# default OS versions
|
||||
if options.min_os_ver:
|
||||
pkginfo['minimum_os_version'] = options.min_os_ver
|
||||
|
||||
if options.max_os_ver:
|
||||
pkginfo['maximum_os_version'] = options.max_os_ver
|
||||
|
||||
# adjust the installer_item_location to match the actual location and name
|
||||
pkginfo['installer_item_location'] = uploaded_pkgpath
|
||||
|
||||
|
||||
@@ -675,7 +675,7 @@ def runAdobeCS5AAMEEInstall(dmgpath):
|
||||
'--setupBasePath=%s' % tmpdir, '--installDirPath=/',
|
||||
'--mode=install'])
|
||||
|
||||
munkicommon.display_status_minor('Starting Adobe CS5 installer...')
|
||||
munkicommon.display_status_minor('Starting Adobe installer...')
|
||||
retcode = runAdobeInstallTool(cmd, number_of_payloads,
|
||||
killAdobeAIR=True)
|
||||
# now clean up our symlink hackfest
|
||||
|
||||
@@ -53,6 +53,7 @@ DEFAULT_CATALOG_URLS = {
|
||||
'10.5': 'http://swscan.apple.com/content/catalogs/others/index-leopard.merged-1.sucatalog',
|
||||
'10.6': 'http://swscan.apple.com/content/catalogs/others/index-leopard-snowleopard.merged-1.sucatalog',
|
||||
'10.7': 'http://swscan.apple.com/content/catalogs/others/index-lion-snowleopard-leopard.merged-1.sucatalog.gz',
|
||||
'10.8': 'http://swscan.apple.com/content/catalogs/others/index-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz'
|
||||
}
|
||||
|
||||
# Preference domain for Apple Software Update.
|
||||
|
||||
@@ -465,6 +465,16 @@ def format_time(timestamp=None):
|
||||
return str(NSDate.dateWithTimeIntervalSince1970_(timestamp))
|
||||
|
||||
|
||||
def validateDateFormat(datetime_string):
|
||||
formatted_datetime_string = ''
|
||||
try:
|
||||
formatted_datetime_string = time.strftime(
|
||||
'%Y-%m-%dT%H:%M:%SZ', time.strptime(datetime_string,
|
||||
'%Y-%m-%dT%H:%M:%SZ'))
|
||||
except:
|
||||
pass
|
||||
return formatted_datetime_string
|
||||
|
||||
def log(msg, logname=''):
|
||||
"""Generic logging function"""
|
||||
# date/time format string
|
||||
@@ -1510,6 +1520,27 @@ def isInstallerItem(path):
|
||||
return False
|
||||
|
||||
|
||||
def getChoiceChangesXML(pkgitem):
|
||||
"""Queries package for 'ChoiceChangesXML'"""
|
||||
choices = []
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
['/usr/sbin/installer', '-showChoiceChangesXML', '-pkg', pkgitem],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, unused_err) = proc.communicate()
|
||||
if out:
|
||||
plist = FoundationPlist.readPlistFromString(out)
|
||||
|
||||
# list comprehension to populate choices with those items
|
||||
# whose 'choiceAttribute' value is 'selected'
|
||||
choices = [item for item in plist
|
||||
if 'selected' in item['choiceAttribute']]
|
||||
except:
|
||||
# No choices found or something went wrong
|
||||
pass
|
||||
return choices
|
||||
|
||||
|
||||
def getPackageMetaData(pkgitem):
|
||||
"""
|
||||
Queries an installer item (.pkg, .mpkg, .dist)
|
||||
@@ -1847,7 +1878,7 @@ def getSPApplicationData():
|
||||
# system_profiler xml is an array
|
||||
SP_APPCACHE = {}
|
||||
for item in plist[0]['_items']:
|
||||
SP_APPCACHE[item.get('path')] = item
|
||||
SP_APPCACHE[item.get('path')] = item
|
||||
except Exception:
|
||||
pass
|
||||
return SP_APPCACHE
|
||||
@@ -1983,6 +2014,7 @@ def getMachineFacts():
|
||||
MACHINE['machine_model'] = hardware_info.get('machine_model', 'UNKNOWN')
|
||||
MACHINE['munki_version'] = get_version()
|
||||
MACHINE['ipv4_address'] = get_ipv4_addresses()
|
||||
MACHINE['serial_number'] = hardware_info.get('serial_number', 'UNKNOWN')
|
||||
return MACHINE
|
||||
|
||||
|
||||
@@ -1992,11 +2024,13 @@ def getConditions():
|
||||
which can be placed into /usr/local/munki/conditions"""
|
||||
global CONDITIONS
|
||||
if not CONDITIONS:
|
||||
# define path to conditions directory which would contain admin created scripts
|
||||
# define path to conditions directory which would contain
|
||||
# admin created scripts
|
||||
scriptdir = os.path.realpath(os.path.dirname(sys.argv[0]))
|
||||
conditionalscriptdir = os.path.join(scriptdir, "conditions")
|
||||
# define path to ConditionalItems.plist
|
||||
conditionalitemspath = os.path.join(pref('ManagedInstallDir'), 'ConditionalItems.plist')
|
||||
conditionalitemspath = os.path.join(
|
||||
pref('ManagedInstallDir'), 'ConditionalItems.plist')
|
||||
try:
|
||||
# delete CondtionalItems.plist so that we're starting fresh
|
||||
os.unlink(conditionalitemspath)
|
||||
@@ -2008,13 +2042,15 @@ def getConditions():
|
||||
if conditionalscript.startswith('.'):
|
||||
# skip files that start with a period
|
||||
continue
|
||||
conditionalscriptpath = os.path.join(conditionalscriptdir, conditionalscript)
|
||||
conditionalscriptpath = os.path.join(
|
||||
conditionalscriptdir, conditionalscript)
|
||||
if os.path.isdir(conditionalscriptpath):
|
||||
# skip directories in conditions directory
|
||||
continue
|
||||
try:
|
||||
# attempt to execute condition script
|
||||
result, stdout, stderr = utils.runExternalScript(conditionalscriptpath)
|
||||
result, stdout, stderr = utils.runExternalScript(
|
||||
conditionalscriptpath)
|
||||
except utils.ScriptNotFoundError:
|
||||
pass # script is not required, so pass
|
||||
except utils.RunExternalScriptError, e:
|
||||
@@ -2022,12 +2058,14 @@ def getConditions():
|
||||
else:
|
||||
# /usr/local/munki/conditions does not exist
|
||||
pass
|
||||
if os.path.exists(conditionalitemspath) and validPlist(conditionalitemspath):
|
||||
if (os.path.exists(conditionalitemspath) and
|
||||
validPlist(conditionalitemspath)):
|
||||
# import conditions into CONDITIONS dict
|
||||
CONDITIONS = FoundationPlist.readPlist(conditionalitemspath)
|
||||
os.unlink(conditionalitemspath)
|
||||
else:
|
||||
# either ConditionalItems.plist does not exist or does not pass validation
|
||||
# either ConditionalItems.plist does not exist
|
||||
# or does not pass validation
|
||||
CONDITIONS = {}
|
||||
return CONDITIONS
|
||||
|
||||
|
||||
@@ -977,6 +977,27 @@ def getItemDetail(name, cataloglist, vers=''):
|
||||
for index in indexlist:
|
||||
item = CATALOG[catalogname]['items'][index]
|
||||
# we have an item whose name and version matches the request.
|
||||
if item.get('minimum_munki_version'):
|
||||
min_munki_vers = item['minimum_munki_version']
|
||||
munkicommon.display_debug1(
|
||||
'Considering item %s, ' % item['name'] +
|
||||
'version %s ' % item['version'] +
|
||||
'with minimum Munki version required %s'
|
||||
% min_munki_vers)
|
||||
munkicommon.display_debug1('Our Munki version is %s' %
|
||||
MACHINE['munki_version'])
|
||||
if (munkicommon.MunkiLooseVersion(MACHINE['munki_version'])
|
||||
< munkicommon.MunkiLooseVersion(min_munki_vers)):
|
||||
# skip this one, go to the next
|
||||
reason = (('Rejected item %s, version %s '
|
||||
'with minimum Munki version required %s. '
|
||||
"Our Munki version is %s.")
|
||||
% (item['name'], item['version'],
|
||||
item['minimum_munki_version'],
|
||||
MACHINE['munki_version']))
|
||||
rejected_items.append(reason)
|
||||
continue
|
||||
|
||||
# now check to see if it meets os and cpu requirements
|
||||
if item.get('minimum_os_version', ''):
|
||||
min_os_vers = item['minimum_os_version']
|
||||
@@ -1323,7 +1344,8 @@ def lookForUpdates(itemname, cataloglist):
|
||||
num_updates = len(update_list)
|
||||
# format the update list for better on-screen viewing
|
||||
update_list_display = ", ".join(str(x) for x in update_list)
|
||||
munkicommon.display_debug1('Found %s update(s): %s' % (num_updates, update_list_display))
|
||||
munkicommon.display_debug1(
|
||||
'Found %s update(s): %s' % (num_updates, update_list_display))
|
||||
|
||||
return update_list
|
||||
|
||||
@@ -2341,12 +2363,19 @@ def getPrimaryManifest(alternate_id):
|
||||
clientidentifier)
|
||||
manifest = getmanifest(manifesturl + clientidentifier,
|
||||
suppress_errors=True)
|
||||
if not manifest:
|
||||
# last resort - try for the site_default manifest
|
||||
clientidentifier = 'site_default'
|
||||
munkicommon.display_detail('Request failed. ' +
|
||||
'Trying %s...' %
|
||||
if not manifest:
|
||||
# try the machine serial number
|
||||
clientidentifier = MACHINE['serial_number']
|
||||
if clientidentifier != 'UNKNOWN':
|
||||
munkicommon.display_detail('Request failed. Trying %s...' %
|
||||
clientidentifier)
|
||||
manifest = getmanifest(manifesturl + clientidentifier,
|
||||
suppress_errors=True)
|
||||
if not manifest:
|
||||
# last resort - try for the site_default manifest
|
||||
clientidentifier = 'site_default'
|
||||
munkicommon.display_detail('Request failed. Trying %s...' %
|
||||
clientidentifier)
|
||||
|
||||
if not manifest:
|
||||
manifest = getmanifest(
|
||||
@@ -2447,7 +2476,6 @@ def check(client_id='', localmanifestpath=None):
|
||||
global CONDITIONS
|
||||
munkicommon.getConditions()
|
||||
CONDITIONS = munkicommon.getConditions()
|
||||
munkicommon.report['ConditionalItems'] = CONDITIONS
|
||||
|
||||
ManagedInstallDir = munkicommon.pref('ManagedInstallDir')
|
||||
if munkicommon.munkistatusoutput:
|
||||
@@ -2476,6 +2504,7 @@ def check(client_id='', localmanifestpath=None):
|
||||
|
||||
# set up INFO_OBJECT for conditional item comparisons
|
||||
makePredicateInfoObject()
|
||||
munkicommon.report['Conditions'] = INFO_OBJECT
|
||||
|
||||
munkicommon.display_detail('**Checking for installs**')
|
||||
processManifestForKey(mainmanifestpath, 'managed_installs',
|
||||
|
||||
Reference in New Issue
Block a user