mirror of
https://github.com/munki/munki.git
synced 2026-01-07 23:20:00 -06:00
git-svn-id: http://munki.googlecode.com/svn/trunk@601 a4e17f2e-e282-11dd-95e1-755cbddbdd66
272 lines
9.7 KiB
Python
Executable File
272 lines
9.7 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2010 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.
|
|
|
|
"""
|
|
makeChoices.py
|
|
|
|
Created by Greg Neagle on 2008-11-10.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import plistlib
|
|
import subprocess
|
|
import tempfile
|
|
import optparse
|
|
|
|
def getchoicestatus(p,choiceID):
|
|
(enabled, selected) = (False, -2)
|
|
for plitem in p:
|
|
if choiceID == plitem['choiceIdentifier']:
|
|
return (plitem['choiceIsEnabled'], plitem['choiceIsSelected'])
|
|
else:
|
|
(enabled, selected) = getchoicestatus(plitem['childItems'],
|
|
choiceID)
|
|
if selected <> -2:
|
|
return (enabled, selected)
|
|
return (enabled, selected)
|
|
|
|
|
|
def choiceItemHasChildren(p, choice):
|
|
for item in p:
|
|
if item['choiceIdentifier'] == choice:
|
|
if len(item['childItems']):
|
|
return 1
|
|
else:
|
|
return 0
|
|
childreturn = choiceItemHasChildren(item['childItems'], choice)
|
|
if childreturn != -1:
|
|
return childreturn
|
|
return -1
|
|
|
|
|
|
def getChoicesXML(pkgpath, target):
|
|
cmd = ['/usr/sbin/installer', '-showChoicesXML', '-pkg', pkgpath, "-target", target]
|
|
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(plist, err) = p.communicate()
|
|
if p.returncode == 0:
|
|
if plist:
|
|
return plistlib.readPlistFromString(plist)
|
|
|
|
return ''
|
|
|
|
|
|
def OLDmakeChoiceForID(choicesxml, choiceID, desiredState, choiceArray):
|
|
if choicesxml:
|
|
(choiceEnabled, choiceSelected) = getchoicestatus(choicesxml,
|
|
choiceID)
|
|
#print choiceID, choiceEnabled, choiceSelected
|
|
if choiceSelected == -2:
|
|
print >>sys.stderr, "%s is not a valid choice." % choiceID
|
|
exit(1)
|
|
# if choice is not enabled, we can't change it
|
|
if choiceEnabled:
|
|
if choiceSelected == desiredState:
|
|
# no clicks needed
|
|
return
|
|
if not choiceSelected == -1:
|
|
# current state is not mixed, so one click
|
|
# to invert
|
|
choiceArray.append(choiceID)
|
|
return
|
|
# current state is mixed
|
|
if desiredState == 1:
|
|
# one click to go from mixed to on
|
|
choiceArray.append(choiceID)
|
|
return
|
|
else:
|
|
# two clicks to go from mixed to off
|
|
choiceArray.append(choiceID)
|
|
choiceArray.append(choiceID)
|
|
return
|
|
|
|
else:
|
|
if choiceSelected != desiredState:
|
|
print >>sys.stderr, "Warning: Choice '%s' is not enabled; you cannot modify its selection state. Ignoring." % choiceID
|
|
|
|
|
|
def makeChoiceForID(choicesxml, choiceID, desiredState, choiceArray):
|
|
choice = {}
|
|
choice['attributeSetting'] = (desiredState!=0)
|
|
choice['choiceAttribute'] = "selected"
|
|
choice['choiceIdentifier'] = choiceID
|
|
choiceArray.append(choice)
|
|
return
|
|
|
|
|
|
def selectionInfo(enabled, selected, identifier, description):
|
|
selChar = ' '
|
|
required = ' '
|
|
requiredText = ''
|
|
if selected == 0:
|
|
selChar = "O"
|
|
if selected == 1:
|
|
selChar = "X"
|
|
if selected == -1:
|
|
selChar = "-"
|
|
if selected and not enabled:
|
|
required = '!'
|
|
requiredText = "REQUIRED"
|
|
if not selected and not enabled:
|
|
required = '!'
|
|
requiredText = "NOT APPLICABLE"
|
|
return "%s%s %s\t%s\t%s" % (required, selChar, identifier, requiredText, description)
|
|
|
|
|
|
#def getPkgInfo(path):
|
|
# if path.startswith("file://localhost") and path.endswith('.pkg'):
|
|
# p = path[16:]
|
|
# if os.path.exists(p):
|
|
# if os.path.isfile(p): # new flat package
|
|
# info = getPackageInfo.getFlatPackageInfo(p)
|
|
# if os.path.isdir(p): # bundle-style package
|
|
# info = getPackageInfo.getBundlePackageInfo(p)
|
|
# if len(info):
|
|
# return info[0]
|
|
#
|
|
# return {}
|
|
|
|
|
|
def printchoices(p,indent,hideunselected=False,verbose=False):
|
|
indentspace = " "
|
|
for plitem in p:
|
|
choice = {}
|
|
choice['Identifier'] = plitem['choiceIdentifier']
|
|
choice['IsEnabled'] = plitem.get('choiceIsEnabled', False)
|
|
choice['IsSelected'] = plitem.get('choiceIsSelected', False)
|
|
choice['IsVisible'] = plitem.get('choiceIsVisible', False)
|
|
choice['pkgPaths'] = plitem.get('pathsOfActivePackagesInChoice')
|
|
if 'choiceDescription' in plitem:
|
|
choice['Description'] = plitem['choiceDescription']
|
|
else:
|
|
choice['Description'] = ''
|
|
|
|
if (not choice['IsSelected'] and hideunselected) or not choice['IsVisible']:
|
|
pass
|
|
else:
|
|
print indentspace*indent, selectionInfo(choice['IsEnabled'],
|
|
choice['IsSelected'], choice['Identifier'].encode('UTF-8'), choice['Description'].encode('UTF-8'))
|
|
if verbose:
|
|
for item in choice['pkgPaths']:
|
|
print indentspace*(indent+1), item.encode('UTF-8')
|
|
#pkginfo = getPkgInfo(item)
|
|
#if pkginfo:
|
|
#print indentspace*(indent+1), "pkgid: %s\tversion: %s" % (pkginfo['id'], pkginfo['version'])
|
|
|
|
if 'childItems' in plitem:
|
|
printchoices(plitem['childItems'],indent+1,hideunselected)
|
|
|
|
|
|
def getTopLevelEnabledSelectedChoices(p):
|
|
choicelist = []
|
|
for plitem in p:
|
|
if plitem['choiceIsEnabled'] and plitem['choiceIsSelected'] and plitem['choiceIsVisible'] and plitem['choiceIdentifier'] != '__ROOT_CHOICE_IDENT__':
|
|
choicelist.append(plitem['choiceIdentifier'])
|
|
if plitem['choiceIdentifier'] == '__ROOT_CHOICE_IDENT__':
|
|
for item in plitem['childItems']:
|
|
if item['choiceIsEnabled'] and item['choiceIsSelected'] and item['choiceIsVisible']:
|
|
choicelist.append(item['choiceIdentifier'])
|
|
|
|
return choicelist
|
|
|
|
|
|
def main():
|
|
p = optparse.OptionParser()
|
|
p.set_usage("""Usage: %prog [options]
|
|
Examples:
|
|
%prog --pkg /path/to/some.pkg --listchoices
|
|
%prog --pkg /path/to/some.pkg --doinstall CHOICEID
|
|
%prog --pkg /path/to/some.pkg --doinstall CHOICEID1 --dontinstall CHOICEID2""")
|
|
|
|
p.add_option("--pkg", help="Path to package.")
|
|
p.add_option('--target', default='/',
|
|
help="Installer target. Defaults to /")
|
|
p.add_option("--doinstall", action="append",
|
|
help="Optional choices to install.")
|
|
p.add_option("--dontinstall", action="append",
|
|
help="Optional choices not to install.")
|
|
p.add_option('--onlyinstall',
|
|
help="Deselects all available choices except this one, selects this choice.")
|
|
p.add_option("--listchoices", action='store_true',
|
|
help="List the available choices.")
|
|
p.add_option("--listselectedchoices", action='store_true',
|
|
help="List only the selected choices.")
|
|
p.add_option('--verbose', action='store_true', help='More output.')
|
|
|
|
# Get our options and our package path
|
|
options, arguments = p.parse_args()
|
|
if not options.pkg:
|
|
print >>sys.stderr, "Must specify a package!"
|
|
exit(1)
|
|
|
|
choicesxml = getChoicesXML(options.pkg, options.target)
|
|
if choicesxml == '':
|
|
print >>sys.stderr, "Error getting choicesxml."
|
|
exit(1)
|
|
|
|
if options.listchoices:
|
|
printchoices(choicesxml,0,False,options.verbose)
|
|
exit(0)
|
|
|
|
if options.listselectedchoices:
|
|
printchoices(choicesxml,0,True,options.verbose)
|
|
exit(0)
|
|
|
|
choiceArray = []
|
|
|
|
if options.onlyinstall:
|
|
selectedChoices = getTopLevelEnabledSelectedChoices(choicesxml)
|
|
for item in selectedChoices:
|
|
if not item == options.onlyinstall:
|
|
makeChoiceForID(choicesxml, item, 0, choiceArray)
|
|
|
|
makeChoiceForID(choicesxml, options.onlyinstall, 1, choiceArray)
|
|
|
|
else:
|
|
if options.doinstall:
|
|
for item in options.doinstall:
|
|
makeChoiceForID(choicesxml, item, 1, choiceArray)
|
|
if options.dontinstall:
|
|
for item in options.dontinstall:
|
|
makeChoiceForID(choicesxml, item, 0, choiceArray)
|
|
|
|
mytmpdir = tempfile.mkdtemp()
|
|
choicesxmlfile = os.path.join(mytmpdir, "choices.xml")
|
|
plistlib.writePlist(choiceArray, choicesxmlfile)
|
|
|
|
print "Current choices:"
|
|
cmd = ['/usr/sbin/installer', '-showChoicesAfterApplyingChangesXML',
|
|
choicesxmlfile, '-pkg', options.pkg, "-target", options.target]
|
|
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(plist, err) = p.communicate()
|
|
if p.returncode == 0:
|
|
if plist:
|
|
pl = plistlib.readPlistFromString(plist)
|
|
printchoices(pl,0,False,options.verbose)
|
|
else:
|
|
print >>sys.stderr, err
|
|
|
|
print "choicesxmlfile at:", choicesxmlfile
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|