Files
munki/code/tools/makeChoices.py
2010-07-23 16:50:22 +00:00

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()