--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/ai-webserver/publish_manifest.py Fri Aug 20 11:31:18 2010 -0600
@@ -0,0 +1,1445 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
+
+"""
+
+A/I Publish_Manifest
+
+"""
+
+import os.path
+import sys
+import StringIO
+import gettext
+import lxml.etree
+import hashlib
+from optparse import OptionParser
+
+import osol_install.auto_install.AI_database as AIdb
+import osol_install.auto_install.verifyXML as verifyXML
+import osol_install.libaiscf as smf
+
+INFINITY = str(0xFFFFFFFFFFFFFFFF)
+IMG_AI_MANIFEST_DTD = "auto_install/ai.dtd"
+SYS_AI_MANIFEST_DTD = "/usr/share/auto_install/ai.dtd"
+
+IMG_AI_MANIFEST_SCHEMA = "auto_install/ai_manifest.rng"
+
+def parse_options(cmd_options=None):
+ """
+ Parse and validate options
+ Args: Optional cmd_options, used for unit testing. Otherwise, cmd line
+ options handled by OptionParser
+ Returns: the DataFiles object populated and initialized
+ Raises: The DataFiles initialization of manifest(s) A/I, SC, SMF looks for
+ many error conditions and, when caught, are flagged to the user
+ via raising SystemExit exceptions.
+ """
+
+ usage = _("usage: %prog -n service_name -m AI_manifest"
+ " [-c <criteria=value|range> ... | -C criteria_file]")
+ parser = OptionParser(usage=usage, prog="add-manifest")
+ parser.add_option("-c", dest="criteria_c", action="append",
+ default=[], help=_("Specify criteria: "
+ "<-c criteria=value|range> ..."))
+ parser.add_option("-C", dest="criteria_file",
+ default=None, help=_("Specify name of criteria "
+ "XML file."))
+ parser.add_option("-m", dest="manifest_path",
+ default=None, help=_("Specify name of manifest "
+ "to set criteria for."))
+ parser.add_option("-n", dest="service_name",
+ default=None, help=_("Specify name of install "
+ "service."))
+
+ # Get the parsed options using parse_args(). We know we don't have
+ # args, so we're just grabbing the first item of the tuple returned.
+ options, args = parser.parse_args(cmd_options)
+ if len(args):
+ parser.error(_("Unexpected arguments: %s" % args))
+
+ # options are:
+ # -c criteria=<value/range> ...
+ # -C XML file with criteria specified
+ # -n service name
+ # -m manifest path to work with
+
+ # check that we got the install service's name and
+ # an AI manifest
+ if options.manifest_path is None or options.service_name is None:
+ parser.error(_("Missing one or more required options."))
+
+ # check that we aren't mixing -c and -C
+ if (options.criteria_c and options.criteria_file):
+ parser.error(_("Options used are mutually exclusive."))
+
+ # if we have criteria from cmd line, convert into dictionary
+ criteria_dict = None
+ if options.criteria_c:
+ try:
+ criteria_dict = criteria_to_dict(options.criteria_c)
+ except ValueError as err:
+ parser.error(err)
+
+ if options.criteria_file:
+ if not os.path.exists(options.criteria_file):
+ parser.error(_("Unable to find criteria file: %s") %
+ options.criteria_file)
+
+ # get an AIservice object for requested service
+ try:
+ svc = smf.AIservice(smf.AISCF(FMRI="system/install/server"),
+ options.service_name)
+ except KeyError:
+ parser.error(_("Failed to find service %s") % options.service_name)
+
+ # get the service's data directory path and imagepath
+ try:
+ image_path = svc['image_path']
+ # txt_record is of the form "aiwebserver=example:46503" so split
+ # on ":" and take the trailing portion for the port number
+ port = svc['txt_record'].rsplit(':')[-1]
+ except KeyError, err:
+ parser.error(_("SMF data for service %s is corrupt. Missing "
+ "property: %s\n") % (options.service_name, err))
+ service_dir = os.path.abspath("/var/ai/" + port)
+
+ # check that the service and imagepath directories exist,
+ # and the AI.db, criteria_schema.rng and ai_manifest.rng files
+ # are present otherwise the service is misconfigured
+ if not (os.path.isdir(service_dir) and
+ os.path.exists(os.path.join(service_dir, "AI.db"))):
+ parser.error("Need a valid A/I service directory")
+
+
+ try:
+ files = DataFiles(service_dir=service_dir, image_path=image_path,
+ database_path=os.path.join(service_dir, "AI.db"),
+ manifest_file=options.manifest_path,
+ criteria_dict=criteria_dict,
+ criteria_file=options.criteria_file)
+ except (AssertionError, IOError, ValueError) as err:
+ raise SystemExit(err)
+ except (lxml.etree.LxmlError) as err:
+ raise SystemExit(_("Error:\tmanifest error: %s") % err)
+
+ return(files)
+
+def criteria_to_dict(criteria):
+ """
+ Convert criteria list into dictionary. This function is intended to be
+ called by a main function, or the options parser, so it can potentially
+ raise the SystemExit exception.
+ Args: criteria in list format: [ criteria=value, criteria=value, ... ]
+ where value can be a: single value
+ range (<lower>-<upper>)
+ Returns: dictionary of criteria { criteria: value, criteria: value, ... }
+ with all keys and values in lower case
+ Raises: ValueError on malformed name=value strings in input list.
+ """
+ cri_dict = {}
+ for entry in criteria:
+ entries = entry.lower().partition("=")
+
+ if entries[1]:
+ if not entries[0]:
+ raise ValueError(_("Missing criteria name in "
+ "'%s'\n") % entry)
+ elif entries[0] in cri_dict:
+ raise ValueError(_("Duplicate criteria: '%s'\n") %
+ entries[0])
+ elif not entries[2]:
+ raise ValueError(_("Missing value for criteria "
+ "'%s'\n") % entries[0])
+ cri_dict[entries[0]] = entries[2]
+ else:
+ raise ValueError(_("Criteria must be of the form "
+ "<criteria>=<value>\n"))
+
+ return cri_dict
+
+def find_colliding_criteria(criteria, db, exclude_manifests=None):
+ """
+ Returns: A dictionary of colliding criteria with keys being manifest name
+ and instance tuples and values being the DB column names which
+ collided
+ Args: criteria - Criteria object holding the criteria that is to be
+ added/set for a manifest.
+ db - AI_database object for the install service.
+ exclude_manifests -A list of manifest names from DB to ignore.
+ This arg is passed in when we're calling this
+ function to find criteria collisions for an
+ already published manifest.
+ Raises: SystemExit if: criteria is not found in database
+ value is not valid for type (integer and hexadecimal
+ checks)
+ range is improper
+ """
+ class Fields(object):
+ """
+ Define convenience indexes
+ """
+ # manifest name is row index 0
+ MANNAME = 0
+ # manifest instance is row index 1
+ MANINST = 1
+ # criteria is row index 2 (when a single valued criteria)
+ CRIT = 2
+ # minimum criteria is row index 2 (when a range valued criteria)
+ MINCRIT = 2
+ # maximum criteria is row index 3 (when a range valued criteria)
+ MAXCRIT = 3
+
+ # collisions is a dictionary to hold keys of the form (manifest name,
+ # instance) which will point to a comma-separated string of colliding
+ # criteria
+ collisions = dict()
+
+ # verify each range criteria in the manifest is well formed and collect
+ # collisions with database entries
+ for crit in criteria:
+ # gather this criteria's values from the manifest
+ man_criterion = criteria[crit]
+
+ # check "value" criteria here (check the criteria exists in DB, and
+ # then find collisions)
+ if isinstance(man_criterion, basestring):
+ # only check criteria in use in the DB
+ if crit not in AIdb.getCriteria(db.getQueue(),
+ onlyUsed=False, strip=False):
+ raise SystemExit(_("Error:\tCriteria %s is not a " +
+ "valid criteria!") % crit)
+
+ # get all values in the database for this criteria (and
+ # manifest/instance pairs for each value)
+ db_criteria = AIdb.getSpecificCriteria(
+ db.getQueue(), crit,
+ provideManNameAndInstance=True,
+ excludeManifests=exclude_manifests)
+
+ # will iterate over a list of the form [manName, manInst, crit,
+ # None]
+ for row in db_criteria:
+ # check if the database and manifest values differ
+ if(str(row[Fields.CRIT]).lower() ==
+ str(man_criterion).lower()):
+ # record manifest name, instance and criteria name
+ try:
+ collisions[row[Fields.MANNAME],
+ row[Fields.MANINST]] += crit + ","
+ except KeyError:
+ collisions[row[Fields.MANNAME],
+ row[Fields.MANINST]] = crit + ","
+
+ # This is a range criteria. (Check that ranges are valid, that
+ # "unbounded" gets set to 0/+inf, ensure the criteria exists
+ # in the DB, then look for collisions.)
+ else:
+ # check for a properly ordered range (with unbounded being 0 or
+ # Inf.) but ensure both are not unbounded
+ if(
+ # Check for a range of -inf to inf -- not a valid range
+ (man_criterion[0] == "unbounded" and
+ man_criterion[1] == "unbounded"
+ ) or
+ # Check min > max -- range order reversed
+ (
+ (man_criterion[0] != "unbounded" and
+ man_criterion[1] != "unbounded"
+ ) and
+ (man_criterion[0] > man_criterion[1])
+ )
+ ):
+ raise SystemExit(_("Error:\tCriteria %s "
+ "is not a valid range (MIN > MAX) or "
+ "(MIN and MAX unbounded).") % crit)
+
+ # Clean-up NULL's and changed "unbounded"s to 0 and
+ # really large numbers in case this Python does
+ # not support IEEE754. Note "unbounded"s are already
+ # converted to lower case during manifest processing.
+ if man_criterion[0] == "unbounded":
+ man_criterion[0] = "0"
+ if man_criterion[1] == "unbounded":
+ man_criterion[1] = INFINITY
+ if crit == "mac":
+ # convert hex mac address (w/o colons) to a number
+ try:
+ man_criterion[0] = long(str(man_criterion[0]).upper(), 16)
+ man_criterion[1] = long(str(man_criterion[1]).upper(), 16)
+ except ValueError:
+ raise SystemExit(_("Error:\tCriteria %s "
+ "is not a valid hexadecimal value") %
+ crit)
+
+ else:
+ # this is a decimal value
+ try:
+ man_criterion = [long(str(man_criterion[0]).upper()),
+ long(str(man_criterion[1]).upper())]
+ except ValueError:
+ raise SystemExit(_("Error:\tCriteria %s "
+ "is not a valid integer value") % crit)
+
+ # check to see that this criteria exists in the database columns
+ if ('MIN' + crit not in AIdb.getCriteria(
+ db.getQueue(), onlyUsed=False, strip=False))\
+ and ('MAX' + crit not in AIdb.getCriteria(
+ db.getQueue(), onlyUsed=False, strip=False)):
+ raise SystemExit(_("Error:\tCriteria %s is not a "
+ "valid criteria!") % crit)
+
+ db_criteria = AIdb.getSpecificCriteria(
+ db.getQueue(), 'MIN' + crit, 'MAX' + crit,
+ provideManNameAndInstance=True,
+ excludeManifests=exclude_manifests)
+
+ # will iterate over a list of the form [manName, manInst, mincrit,
+ # maxcrit]
+ for row in db_criteria:
+ # arbitrarily large number in case this Python does
+ # not support IEEE754
+ db_criterion = ["0", INFINITY]
+
+ # now populate in valid database values (i.e. non-NULL values)
+ if row[Fields.MINCRIT]:
+ db_criterion[0] = row[Fields.MINCRIT]
+ if row[Fields.MAXCRIT]:
+ db_criterion[1] = row[Fields.MAXCRIT]
+ if crit == "mac":
+ # use a hexadecimal conversion
+ db_criterion = [long(str(db_criterion[0]), 16),
+ long(str(db_criterion[1]), 16)]
+ else:
+ # these are decimal numbers
+ db_criterion = [long(str(db_criterion[0])),
+ long(str(db_criterion[1]))]
+
+ # these three criteria can determine if there's a range overlap
+ if((man_criterion[1] >= db_criterion[0] and
+ db_criterion[1] >= man_criterion[0]) or
+ man_criterion[0] == db_criterion[1]):
+ # range overlap so record the collision
+ try:
+ collisions[row[Fields.MANNAME],
+ row[Fields.MANINST]] += "MIN" + crit + ","
+ collisions[row[Fields.MANNAME],
+ row[Fields.MANINST]] += "MAX" + crit + ","
+ except KeyError:
+ collisions[row[Fields.MANNAME],
+ row[Fields.MANINST]] = "MIN" + crit + ","
+ collisions[row[Fields.MANNAME],
+ row[Fields.MANINST]] += "MAX" + crit + ","
+ return collisions
+
+def find_colliding_manifests(criteria, db, collisions, append_manifest=None):
+ """
+ For each manifest/instance pair in collisions check that the manifest
+ criteria diverge (i.e. are not exactly the same) and that the ranges do not
+ collide for ranges.
+ Raises if: a range collides, or if the manifest has the same criteria as a
+ manifest already in the database (SystemExit raised)
+ Returns: Nothing
+ Args: criteria - Criteria object holding the criteria that is to be
+ added/set for a manifest.
+ db - AI_database object for the install service.
+ collisions - a dictionary with collisions, as produced by
+ find_colliding_criteria()
+ append_manifest - name of manifest we're appending criteria to.
+ This arg is passed in when we're calling this
+ function to find criteria collisions for an
+ already published manifest that we're appending
+ criteria to.
+ """
+
+ # If we're appending criteria to an already published manifest, get a
+ # dictionary of the criteria that's already published for that manifest.
+ if append_manifest is not None:
+ published_criteria = AIdb.getManifestCriteria(append_manifest, 0,
+ db.getQueue(),
+ humanOutput=True,
+ onlyUsed=False)
+
+ # check every manifest in collisions to see if manifest collides (either
+ # identical criteria, or overlaping ranges)
+ for man_inst in collisions:
+ # get all criteria from this manifest/instance pair
+ db_criteria = AIdb.getManifestCriteria(man_inst[0],
+ man_inst[1],
+ db.getQueue(),
+ humanOutput=True,
+ onlyUsed=False)
+
+ # iterate over every criteria in the database
+ for crit in AIdb.getCriteria(db.getQueue(),
+ onlyUsed=False, strip=False):
+
+ # Get the criteria name (i.e. no MIN or MAX)
+ crit_name = crit.replace('MIN', '', 1).replace('MAX', '', 1)
+ # Set man_criterion to the key of the DB criteria or None
+ man_criterion = criteria[crit_name]
+
+ if man_criterion and crit.startswith('MIN'):
+ man_criterion = man_criterion[0]
+ elif man_criterion and crit.startswith('MAX'):
+ man_criterion = man_criterion[1]
+
+ # If man_criterion is still None, and if we're appending criteria
+ # to an already published manifest, look for criteria in the
+ # published set of criteria for the manifest we're appending to
+ # as well, because existing criteria might cause a collision,
+ # which we need to compare for.
+ if man_criterion is None and append_manifest is not None:
+ man_criterion = published_criteria[str(crit)]
+ # replace database NULL's with Python None
+ if man_criterion == '':
+ man_criterion = None
+
+ # set the database criteria
+ if db_criteria[str(crit)] == '':
+ # replace database NULL's with a Python None
+ db_criterion = None
+ else:
+ db_criterion = db_criteria[str(crit)]
+
+ # Replace unbounded's in the criteria (i.e. 0/+inf)
+ # with a Python None.
+ if isinstance(man_criterion, basestring) and \
+ man_criterion == "unbounded":
+ man_criterion = None
+
+ # check to determine if this is a range collision by using
+ # collisions and if not are the manifests divergent
+
+ if((crit.startswith('MIN') and
+ collisions[man_inst].find(crit + ",") != -1) or
+ (crit.startswith('MAX') and
+ collisions[man_inst].find(crit + ",") != -1)
+ ):
+ if (str(db_criterion).lower() != str(man_criterion).lower()):
+ raise SystemExit(_("Error:\tManifest has a range "
+ "collision with manifest:%s/%i"
+ "\n\tin criteria: %s!") %
+ (man_inst[0], man_inst[1],
+ crit.replace('MIN', '', 1).
+ replace('MAX', '', 1)))
+
+ # the range did not collide or this is a single value (if we
+ # differ we can break out knowing we diverge for this
+ # manifest/instance)
+ elif(str(db_criterion).lower() != str(man_criterion).lower()):
+ # manifests diverge (they don't collide)
+ break
+
+ # end of for loop and we never broke out (diverged)
+ else:
+ raise SystemExit(_("Error:\tManifest has same criteria as " +
+ "manifest: %s/%i!") %
+ (man_inst[0], man_inst[1]))
+
+def insert_SQL(files):
+ """
+ Ensures all data is properly sanitized and formatted, then inserts it into
+ the database
+ Args: None
+ Returns: None
+ """
+ query = "INSERT INTO manifests VALUES("
+
+ # add the manifest name to the query string
+ query += "'" + AIdb.sanitizeSQL(files.manifest_name) + "',"
+ # check to see if manifest name is alreay in database (affects instance
+ # number)
+ if AIdb.sanitizeSQL(files.manifest_name) in \
+ AIdb.getManNames(files.database.getQueue()):
+ # database already has this manifest name get the number of
+ # instances
+ instance = AIdb.numInstances(AIdb.sanitizeSQL(files.manifest_name),
+ files.database.getQueue())
+
+ # this a new manifest
+ else:
+ instance = 0
+
+ # actually add the instance to the query string
+ query += str(instance) + ","
+
+ # we need to fill in the criteria or NULLs for each criteria the database
+ # supports (so iterate over each criteria)
+ for crit in AIdb.getCriteria(files.database.getQueue(),
+ onlyUsed=False, strip=False):
+ # for range values trigger on the MAX criteria (skip the MIN's
+ # arbitrary as we handle rows in one pass)
+ if crit.startswith('MIN'):
+ continue
+
+ # get the values from the manifest
+ values = files.criteria[crit.replace('MAX', '', 1)]
+
+ # If the critera manifest didn't specify this criteria, fill in NULLs
+ if values is None:
+ # use the criteria name to determine if this is a range
+ if crit.startswith('MAX'):
+ query += "NULL,NULL,"
+ # this is a single value
+ else:
+ query += "NULL,"
+
+ # this is a single criteria (not a range)
+ elif isinstance(values, basestring):
+ # translate "unbounded" to a database NULL
+ if values == "unbounded":
+ query += "NULL,"
+ else:
+ # use lower case for text strings
+ query += "'" + AIdb.sanitizeSQL(str(values).lower()) + "',"
+
+ # else values is a range
+ else:
+ for value in values:
+ # translate "unbounded" to a database NULL
+ if value == "unbounded":
+ query += "NULL,"
+ # we need to deal with mac addresses specially being
+ # hexadecimal
+ elif crit.endswith("mac"):
+ # need to insert with hex operand x'<val>'
+ # use an upper case string for hex values
+ query += "x'" + AIdb.sanitizeSQL(str(value).upper())+"',"
+ else:
+ query += AIdb.sanitizeSQL(str(value).upper()) + ","
+
+ # strip trailing comma and close parentheses
+ query = query[:-1] + ")"
+
+ # update the database
+ query = AIdb.DBrequest(query, commit=True)
+ files.database.getQueue().put(query)
+ query.waitAns()
+ # in case there's an error call the response function (which will print the
+ # error)
+ query.getResponse()
+
+def do_default(files):
+ """
+ Removes old default.xml after ensuring proper format of new manifest
+ (does not copy new manifest over -- see place_manifest)
+ Args: None
+ Returns: None
+ Raises if: Manifest has criteria, old manifest can not be removed (exits
+ with SystemExit)
+ """
+ # check to see if any criteria is present -- if so, it can not be a default
+ # manifest (as they do not have criteria)
+ if files.criteria:
+ raise SystemExit(_("Error:\tCan not use AI criteria in a default " +
+ "manifest"))
+ # remove old manifest
+ try:
+ os.remove(os.path.join(files.get_service(), 'AI_data', 'default.xml'))
+ except IOError, ioerr:
+ raise SystemExit(_("Error:\tUnable to remove default.xml:\n\t%s") %
+ ioerr)
+
+def place_manifest(files):
+ """
+ Compares src and dst manifests to ensure they are the same; if manifest
+ does not yet exist, copies new manifest into place and sets correct
+ permissions and ownership
+ Args: files - DataFiles object holding all of the relevant and verified
+ information for the manifest we're publishing.
+ Returns: None
+ Raises if: src and dst manifests differ (in MD5 sum), unable to write dst
+ manifest (raises SystemExit -- no clean up of database performed)
+ """
+
+ manifest_path = os.path.join(files.get_service(), "AI_data",
+ files.manifest_name)
+
+ if files.is_dtd:
+ root = files._AI_root
+ else:
+ root = files._criteria_root
+
+ # if the manifest already exists see if it is different from what was
+ # passed in. If so, warn the user that we're using the existing manifest
+ if os.path.exists(manifest_path):
+ old_manifest = open(manifest_path, "r")
+ existing_MD5 = hashlib.md5("".join(old_manifest.readlines())).digest()
+ old_manifest.close()
+ current_MD5 = hashlib.md5(lxml.etree.tostring(root,
+ pretty_print=True, encoding=unicode)).digest()
+ if existing_MD5 != current_MD5:
+ raise SystemExit(_("Error:\tNot copying manifest, source and "
+ "current versions differ -- criteria in "
+ "place."))
+
+ # the manifest does not yet exist so write it out
+ else:
+
+ # Remove all <ai_criteria> elements if they exist.
+ for tag in root.xpath('/ai_criteria_manifest/ai_criteria'):
+ tag.getparent().remove(tag)
+
+ try:
+ root.write(manifest_path, pretty_print=True)
+ except IOError as err:
+ raise SystemExit(_("Error:\tUnable to write to dest. "
+ "manifest:\n\t%s") % err)
+
+ # change read and write for owner
+ os.chmod(manifest_path, 0600)
+ # change to user/group root (uid/gid 0)
+ os.chown(manifest_path, 0, 0)
+
+def verifyCriteria(schema, criteria_path, db, is_dtd=True):
+ """
+ Used for verifying and loading criteria XML from a Criteria manifest,
+ which can be a combined Criteria manifest (for backwards compatibility
+ in supporting older install services) or a criteria manifest with just
+ criteria.
+ Args: schema - path to schema file for criteria manifest.
+ criteria_path - path to criteria XML manifest file to verify.
+ db - database object for install service
+ is_dtd - criteria file should contain criteria only, no
+ AI or SC manifest info
+ Raises IOError:
+ *if the schema does not open
+ *if the XML file does not open
+ Raises ValueError:
+ *if the XML is invalid according to the schema or has a syntax error
+ Returns: A valid XML DOM of the criteria manifest and all MAC and IPV4
+ values are formatted according to
+ verifyXML.prepValuesAndRanges().
+ """
+ schema = open(schema, 'r')
+
+ # Remove AI and SC elements from within the Criteria manifests if
+ # they exist. We validate those separately later.
+ try:
+ crit = lxml.etree.parse(criteria_path)
+ except lxml.etree.XMLSyntaxError, err:
+ raise ValueError(_("Error: %s") % err.error_log.last_error)
+
+ ai_sc_list = list()
+ ai_sc_paths = (".//ai_manifest_file", ".//ai_embedded_manifest",
+ ".//sc_manifest_file", ".//sc_embedded_manifest")
+ for path in ai_sc_paths:
+ elements = crit.iterfind(path)
+
+ for elem in elements:
+ if is_dtd:
+ raise ValueError(_("Error:\tCriteria file should not contain "
+ "AI or SC manifest tags: %s") %
+ criteria_path)
+ ai_sc_list.append(elem)
+ elem.getparent().remove(elem)
+
+ # Verify the remaing DOM, which should only contain criteria
+ root = (verifyXML.verifyRelaxNGManifest(schema,
+ StringIO.StringIO(lxml.etree.tostring(crit.getroot()))))
+
+ if isinstance(root, lxml.etree._LogEntry):
+ raise ValueError(_("Error:\tFile %s failed validation:\n\t%s") %
+ (criteria_path, root.message))
+ try:
+ verifyXML.prepValuesAndRanges(root, db)
+ except ValueError, err:
+ raise ValueError(_("Error:\tCriteria manifest error: %s") % err)
+
+ # Reinsert AI and SC elements back into the _criteria_root DOM.
+ for ai_sc_element in ai_sc_list:
+ root.getroot().append(ai_sc_element)
+
+ return root
+
+def verifyCriteriaDict(schema, criteria_dict, db):
+ """
+ Used for verifying and loading criteria from a dictionary of criteria.
+ Args: schema - path to schema file for criteria manifest.
+ criteria_dict - dictionary of criteria to verify, in the form
+ of { criteria: value, criteria: value, ... }
+ db - database object for install service
+ Raises IOError:
+ * if the schema does not open
+ ValueError:
+ * if the criteria_dict dictionary is empty
+ * if the XML is invalid according to the schema
+ AssertionError:
+ * if a value in the dictionary is empty
+ Returns: A valid XML DOM of the criteria and all MAC and IPV4 values
+ are formatted according to verifyXML.prepValuesAndRanges().
+ """
+ schema = open(schema, 'r')
+
+ if not criteria_dict:
+ raise ValueError("Error:\tCriteria dictionary empty: %s\n"
+ % criteria_dict)
+
+ root = lxml.etree.Element("ai_criteria_manifest")
+
+ for name, value_or_range in criteria_dict.iteritems():
+
+ if value_or_range is None:
+ raise AssertionError(_("Error: Missing value for criteria "
+ "'%s'") % name)
+
+ crit = lxml.etree.SubElement(root, "ai_criteria")
+ crit.set("name", name)
+
+ # If criteria is a range, split on "-" and add to
+ # XML DOM as a range element.
+ if AIdb.isRangeCriteria(db.getQueue(), name):
+ # Split on "-"
+ range_value = value_or_range.split('-', 1)
+
+ # If there was only a single value, means user specified
+ # this range criteria as a single value, add it as a single
+ # value
+ if len(range_value) == 1:
+ value_elem = lxml.etree.SubElement(crit, "value")
+ value_elem.text = value_or_range
+ else:
+ range_elem = lxml.etree.SubElement(crit, "range")
+ range_elem.text = " ".join(range_value)
+ else:
+ value_elem = lxml.etree.SubElement(crit, "value")
+ value_elem.text = value_or_range
+
+ # Verify the generated criteria DOM
+ root = verifyXML.verifyRelaxNGManifest(schema,
+ StringIO.StringIO(lxml.etree.tostring(root)))
+ if isinstance(root, lxml.etree._LogEntry):
+ raise ValueError(_("Error: Criteria failed validation:\n\t%s") %
+ root.message)
+
+ try:
+ verifyXML.prepValuesAndRanges(root, db)
+ except ValueError, err:
+ raise ValueError(_("Error:\tCriteria error: %s") % err)
+
+ return root
+
+# The criteria class is a list object with an overloaded get_item method
+# to act like a dictionary, looking up values from an underlying XML DOM.
+class Criteria(list):
+ """
+ Wrap list class to provide lookups in the criteria file when requested
+ """
+ def __init__(self, criteria_root):
+ # store the criteria manifest DOM root
+ self._criteria_root = criteria_root
+ # call the _init_() for the list class with a generator provided by
+ # find_criteria() to populate this _criteria() instance.
+ super(Criteria, self).__init__(self.find_criteria())
+
+ def find_criteria(self):
+ """
+ Find criteria from the criteria DOM.
+ Returns: A generator providing all criteria name attributes from
+ <ai_criteria> tags
+ """
+
+ if self._criteria_root is None:
+ return
+
+ root = self._criteria_root.findall(".//ai_criteria")
+
+ # actually find criteria
+ for tag in root:
+ for child in tag.getchildren():
+ if (child.tag == "range" or child.tag == "value") and \
+ child.text is not None:
+ # criteria names are lower case
+ yield tag.attrib['name'].lower()
+ else:
+ # should not happen according to schema
+ raise AssertionError(_("Criteria contains no values"))
+
+ def get_criterion(self, criterion):
+ """
+ Return criterion out of the criteria DOM
+ Returns: A list for range criterion with a min and max entry
+ A string for value criterion
+ """
+
+ if self._criteria_root is None:
+ return None
+
+ source = self._criteria_root
+ for tag in source.getiterator('ai_criteria'):
+ crit = tag.get('name')
+ # compare criteria name case-insensitive
+ if crit.lower() == criterion.lower():
+ for child in tag.getchildren():
+ if child.tag == "range":
+ # this is a range response (split on white space)
+ return child.text.split()
+ elif child.tag == "value":
+ # this is a value response (strip white space)
+ return child.text.strip()
+ # should not happen according to schema
+ elif child.text is None:
+ raise AssertionError(_("Criteria contains no values"))
+ return None
+
+ """
+ Look up a requested criteria (akin to dictionary access) but for an
+ uninitialized key will not raise an exception but return None)
+ """
+ __getitem__ = get_criterion
+
+ # disable trying to update criteria
+ __setitem__ = None
+ __delitem__ = None
+
+
+class DataFiles(object):
+ """
+ Class to contain and work with data files necessary for program
+ """
+ # schema for validating an AI criteria manifest
+ criteriaSchema = "/usr/share/auto_install/criteria_schema.rng"
+ # DTD for validating an SMF SC manifest
+ smfDtd = "/usr/share/lib/xml/dtd/service_bundle.dtd.1"
+
+ def __init__(self, service_dir=None, image_path=None,
+ database_path=None, manifest_file=None,
+ criteria_dict=None, criteria_file=None):
+
+ """
+ Initialize DataFiles instance. All parameters optional, however, proper
+ setup order asurred, if all data provided upon instantiation.
+ """
+
+ #
+ # State variables
+ #################
+ #
+
+ # Variable to cache criteria class for criteria property
+ self._criteria_cache = None
+
+ # Holds path to AI manifest being published (may not be set if an
+ # embedded manifest)
+ self._manifest = None
+
+ self.criteria_dict = criteria_dict
+ self.criteria_file = criteria_file
+
+ # Flag to indicate we're operating with the newer AI DTD,
+ # or with the older AI rng schema
+ self.is_dtd = True
+
+ #
+ # File system path variables
+ ############################
+ #
+
+ # Check AI Criteria Schema exists
+ if not os.path.exists(self.criteriaSchema):
+ raise IOError(_("Error:\tUnable to find criteria_schema: " +
+ "%s") % self.criteriaSchema)
+
+ # Check SC manifest SMF DTD exists
+ if not os.path.exists(self.smfDtd):
+ raise IOError(_("Error:\tUnable to find SMF system " +
+ "configuration DTD: %s") % self.smfDtd)
+
+ # A/I Manifest Schema
+ self._AIschema = None
+
+ # Holds path to service directory (i.e. /var/ai/46501)
+ self._service = None
+ if service_dir:
+ self.service = service_dir
+
+ # Holds path to AI image
+ self._imagepath = None
+ if image_path:
+ self.image_path = image_path
+ # set the AI schema once image_path is set
+ self.set_AI_schema()
+
+ # Holds database object for criteria database
+ self._db = None
+ if database_path:
+ # Set Database Path and Open SQLite3 Object
+ self.database = database_path
+ # verify the database's table/column structure (or exit if errors)
+ self.database.verifyDBStructure()
+
+ # Holds DOM for criteria manifest
+ self._criteria_root = None
+
+ # Determine if we're operating with the newer AI DTD,
+ # or with the older AI rng schema
+ try:
+ lxml.etree.DTD(self.AI_schema)
+ self.is_dtd = True
+ except lxml.etree.DTDParseError:
+ try:
+ lxml.etree.RelaxNG(file=self.AI_schema)
+ self.is_dtd = False
+ except lxml.etree.RelaxNGParseError:
+ raise ValueError(_("Error: Unable to determine AI manifest "
+ "validation type.\n"))
+
+ # Verify the AI manifest to make sure its valid
+ if self.is_dtd:
+ self._manifest = manifest_file
+ self.verify_AI_manifest()
+
+ # Holds DOMs for SC manifests
+ self._smfDict = dict()
+
+ # Look for a SC manifests specified within the manifest file
+ # sets _smfDict DOMs
+ self.find_SC_from_manifest(self._AI_root, self.manifest_path)
+ else:
+ # Holds path for manifest file (if reference to AI manifest URI
+ # found inside the combined Criteria manifest)
+ self._manifest = None
+
+ self.criteria_path = manifest_file
+ self._criteria_root = verifyCriteria(self.criteriaSchema,
+ self.criteria_path,
+ self.database,
+ is_dtd=self.is_dtd)
+
+ # Holds DOM for AI manifest
+ self._AI_root = None
+
+ # Since we were provided a combined criteria manifest, look for
+ # an A/I manifest specified by the criteria manifest
+ if self._criteria_root:
+
+ # This will set _manifest to be the AI manifest path (if a
+ # file), or set _AI_root to the correct location in the
+ # criteria DOM (if embedded), or exit (if unable to find an
+ # AI manifest)
+ self.find_AI_from_criteria()
+
+ # This will parse _manifest (if it was set from above), load
+ # it into an XML DOM and set _AI_root to it. The _AI_root DOM
+ # will then be verified. The function will exit on error.
+ self.verify_AI_manifest()
+
+ # Holds DOMs for SC manifests
+ self._smfDict = dict()
+
+ # Look for a SC manifests specified within the manifest file
+ # sets _smfDict DOMs
+ self.find_SC_from_manifest(self._criteria_root,
+ self.criteria_path)
+
+ # Process criteria from -c, or -C. This will setup _criteria_root
+ # as a DOM and will overwrite the DOM from criteria found in a
+ # combined Criteria manifest.
+ if self.criteria_file:
+ self.criteria_path = self.criteria_file
+ root = verifyCriteria(self.criteriaSchema, self.criteria_path,
+ self.database, is_dtd=self.is_dtd)
+ # Set this criteria into _criteria_root
+ self.set_criteria_root(root)
+ elif self.criteria_dict:
+ root = verifyCriteriaDict(self.criteriaSchema, self.criteria_dict,
+ self.database)
+ # Set this criteria into _criteria_root
+ self.set_criteria_root(root)
+
+ @property
+ def criteria(self):
+ """
+ Function to provide access to criteria class (and provide caching of
+ class created)
+ Returns: A criteria instance
+ """
+ # if we don't have a cached _criteria class, create one and update the
+ # cache
+ if not self._criteria_cache:
+ self._criteria_cache = Criteria(self._criteria_root)
+ # now return cached _criteria class
+ return self._criteria_cache
+
+ def open_database(self, db_file):
+ """
+ Sets self._db (opens database object) and errors if already set or file
+ does not yet exist
+ Args: A file path to an SQLite3 database
+ Raises: SystemExit if path does not exist,
+ AssertionError if self._db is already set
+ Returns: Nothing
+ """
+ if not os.path.exists(db_file):
+ raise SystemExit(_("Error:\tFile %s is not a valid database "
+ "file") % db_file)
+ elif self._db is None:
+ self._db = AIdb.DB(db_file, commit=True)
+ else:
+ raise AssertionError('Opening database when already open!')
+
+ def get_database(self):
+ """
+ Returns self._db (database object) and errors if not set
+ Raises: AssertionError if self._db is not yet set
+ Returns: SQLite3 database object
+ """
+ if isinstance(self._db, AIdb.DB):
+ return(self._db)
+ else:
+ raise AssertionError('Database not yet open!')
+
+ database = property(get_database, open_database, None,
+ "Holds database object for criteria database")
+
+ def get_service(self):
+ """
+ Returns self._service and errors if not yet set
+ Raises: AssertionError if self._service is not yet set
+ Returns: String object
+ """
+ if self._service is not None:
+ return(self._service)
+ else:
+ raise AssertionError('Service not yet set!')
+
+ def set_service(self, serv=None):
+ """
+ Sets self._service and errors if already set
+ Args: A string path to an AI service directory
+ Raises: SystemExit if path does not exist,
+ AssertionError if self._service is already set
+ Returns: Nothing
+ """
+ if not os.path.isdir(serv):
+ raise SystemExit(_("Error:\tDirectory %s is not a valid AI "
+ "directory") % serv)
+ elif self._service is None:
+ self._service = os.path.abspath(serv)
+ else:
+ raise AssertionError('Setting service when already set!')
+
+ service = property(get_service, set_service, None,
+ "Holds path to service directory (i.e. /var/ai/46501)")
+
+ def find_SC_from_manifest(self, manifest_root, manifest_path):
+ """
+ Find SC manifests as referenced in the manifest file. We search for
+ embedded SC manifests first, then do a subsequent search for SC file
+ references and expand them in-place in the manifest_root DOM, to be
+ embedded SC manifests.
+ Preconditions: None
+ Postconditions: self._smfDict will be a dictionary containing all
+ SC manifest DOMs
+ Raises: AssertionError: - if manifest_root or manifest_path are not set
+ - if two SC manifests are named the same
+ Args: manifest_root - a valid XML DOM of the manifest from which
+ will find SC manifests.
+ manifest_path - a path to the file from which manifest_root
+ was created.
+ Returns: None
+ """
+ root = manifest_root.iterfind(".//sc_embedded_manifest")
+
+ # For each SC manifest embedded: verify it, adding it to the
+ # dictionary of SMF SC manifests
+ for SC_man in root:
+ # strip the comments off the SC manifest
+ xml_data = lxml.etree.tostring(SC_man.getchildren()[0])
+ xml_data = xml_data.replace("<!-- ", "").replace("-->", "")
+ xml_data = StringIO.StringIO(xml_data)
+ # parse and read in the SC manifest
+ self._smfDict[SC_man.attrib['name']] = \
+ self.verify_SC_manifest(xml_data, name=SC_man.attrib['name'])
+
+ root = manifest_root.iterfind(".//sc_manifest_file")
+
+ # For each SC manifest file: get the URI and verify it, adding it to
+ # the dictionary of SMF SC manifests (this means we can support a
+ # manifest file with multiple SC manifests embedded or referenced)
+ for SC_man in root:
+ if SC_man.attrib['name'] in self._smfDict:
+ raise AssertionError(_("Error:\tTwo SC manifests with name %s")
+ % SC_man.attrib['name'])
+ # if this is an absolute path just hand it off
+ if os.path.isabs(str(SC_man.attrib['URI'])):
+ self._smfDict[SC_man.attrib['name']] = \
+ self.verify_SC_manifest(SC_man.attrib['URI'])
+ # this is not an absolute path - make it one
+ else:
+ self._smfDict[SC_man.attrib['name']] = \
+ self.verify_SC_manifest(os.path.join(os.path.dirname(
+ manifest_path),
+ SC_man.attrib['URI']))
+
+ # Replace each SC manifest file element in the manifest root DOM
+ # with an embedded manifest, using the content from its referenced
+ # file, which has just been loaded into the _smfDict of SC DOMs.
+ old_sc = self._smfDict[SC_man.attrib['name']]
+ new_sc = lxml.etree.Element("sc_embedded_manifest")
+ new_sc.set("name", SC_man.get("name"))
+ new_sc.text = "\n\t"
+ new_sc.tail = "\n"
+ embedded_sc = lxml.etree.Comment(" <?xml version='%s'?>\n\t"%
+ old_sc.docinfo.xml_version +
+ lxml.etree.tostring(old_sc, pretty_print=True,
+ encoding=unicode, xml_declaration=False))
+ embedded_sc.tail = "\n"
+
+ new_sc.append(embedded_sc)
+ SC_man.getparent().replace(SC_man, new_sc)
+
+
+ def find_AI_from_criteria(self):
+ """
+ Find AI manifest as referenced or embedded in a criteria manifest.
+ Preconditions: self._criteria_root is a valid XML DOM
+ Postconditions: self.manifest_path will be set if using a free-standing
+ AI manifest otherwise self._AI_root will be set to a
+ valid XML DOM for the AI manifest
+ Raises: ValueError for XML processing errors
+ for no ai_manifest_file specification
+ AssertionError if _criteria_root not set
+ """
+ if self._criteria_root is None:
+ raise AssertionError(_("Error:\t_criteria_root not set!"))
+
+ # Try to find an embedded AI manifest.
+ root = self._criteria_root.find(".//ai_embedded_manifest")
+ if root is not None:
+ self._AI_root = root.find(".//ai_manifest")
+ if self._AI_root is not None:
+ return
+ else:
+ raise ValueError(_("Error: <ai_embedded_manifest> missing "
+ "<ai_manifest>"))
+
+ # Try to find an AI manifest file reference.
+ root = self._criteria_root.find(".//ai_manifest_file")
+
+ if root is not None:
+ try:
+ if os.path.isabs(root.attrib['URI']):
+ self.manifest_path = root.attrib['URI']
+ else:
+ # if we do not have an absolute path try using the criteria
+ # manifest's location for a base
+ self.manifest_path = \
+ os.path.join(os.path.dirname(self.criteria_path),
+ root.attrib['URI'])
+ return
+ except KeyError:
+ raise ValueError(_("Error: <ai_manifest_file> missing URI"))
+
+ raise ValueError(_("Error: No <ai_manifest_file> or "
+ "<ai_embedded_manifest> element in "
+ "criteria manifest."))
+
+ @property
+ def AI_schema(self):
+ """
+ Returns self._AIschema and errors if not yet set
+ Args: None
+ Raises: AssertionError if self._AIschema is not yet set
+ Returns: String object
+ """
+ if self._AIschema is not None:
+ return (self._AIschema)
+ else:
+ raise AssertionError('AIschema not set')
+
+ def set_AI_schema(self):
+ """
+ Sets self._AIschema and errors if imagepath not yet set.
+ Args: None
+ Raises: SystemExit if unable to find a valid AI schema
+ Returns: None
+ """
+ if os.path.exists(os.path.join(self.image_path,
+ IMG_AI_MANIFEST_DTD)):
+ self._AIschema = os.path.join(self.image_path,
+ IMG_AI_MANIFEST_DTD)
+ elif os.path.exists(os.path.join(self.image_path,
+ IMG_AI_MANIFEST_SCHEMA)):
+ self._AIschema = os.path.join(self.image_path,
+ IMG_AI_MANIFEST_SCHEMA)
+ else:
+ if os.path.exists(SYS_AI_MANIFEST_DTD):
+ self._AIschema = SYS_AI_MANIFEST_DTD
+ print (_("Warning: Using AI manifest dtd <%s>\n") %
+ self._AIschema)
+ else:
+ raise SystemExit(_("Error:\tUnable to find an AI dtd!"))
+
+ def get_image_path(self):
+ """
+ Returns self._imagepath and errors if not set
+ Raises: AssertionError if self._imagepath is not yet set
+ Returns: String object
+ """
+ if self._imagepath is not None:
+ return (self._imagepath)
+ else:
+ raise AssertionError('Imagepath not set')
+
+ def set_image_path(self, imagepath):
+ """
+ Sets self._imagepath but exits if already set or not a directory
+ Args: image path to a valid AI image
+ Raises: SystemExit if image path provided is not a directory
+ AssertionError if image path is already set
+ Returns: None
+ """
+ if not os.path.isdir(imagepath):
+ raise SystemExit(_("Error:\tInvalid imagepath " +
+ "directory: %s") % imagepath)
+ if self._imagepath is None:
+ self._imagepath = os.path.abspath(imagepath)
+ else:
+ raise AssertionError('imagepath already set')
+
+ image_path = property(get_image_path, set_image_path, None,
+ "Holds path to service's AI image")
+
+ def get_manifest_path(self):
+ """
+ Returns self._manifest and errors if not set
+ Raises: AssertionError if self._manifest is not yet set
+ Returns: String object path to AI manifest
+ """
+ if self._manifest is not None:
+ return(self._manifest)
+ else:
+ raise AssertionError('Manifest path not yet set!')
+
+ def set_manifest_path(self, mani=None):
+ """
+ Sets self._manifest and errors if already set
+ Args: path to an AI manifest
+ Raises: AssertionError if manifest is already set
+ Returns: None
+ """
+ if self._manifest is None:
+ self._manifest = os.path.abspath(mani)
+ else:
+ raise AssertionError('Setting manifest when already set!')
+
+ manifest_path = property(get_manifest_path, set_manifest_path, None,
+ "Holds path to AI manifest being published")
+ @property
+ def manifest_name(self):
+ """
+ Returns: manifest name as defined in the A/I manifest (ensuring .xml is
+ applied to the string)
+ Raises: SystemExit if <ai_manifest> tag can not be found
+ """
+ if self._AI_root.getroot().tag == "ai_manifest":
+ name = self._AI_root.getroot().attrib['name']
+ elif self._AI_root.getroot().tag == "auto_install":
+ try:
+ ai_instance = self._AI_root.find(".//ai_instance")
+ except lxml.etree.LxmlError, err:
+ raise SystemExit(_("Error:\tAI manifest error: %s") %err)
+
+ name = ai_instance.attrib['name']
+ else:
+ raise SystemExit(_("Error:\tCan not find <ai_manifest> tag!"))
+ # everywhere we expect manifest names to be file names so ensure
+ # the name matches
+ if not name.endswith('.xml'):
+ name += ".xml"
+ return name
+
+ def verify_AI_manifest(self):
+ """
+ Used for verifying and loading AI manifest as defined by
+ DataFiles._AIschema.
+ Args: None.
+ Preconditions: Expects its is_dtd variable to be set to determine
+ how to validate the AI manifest.
+ Postconditions: Sets _AI_root on success to a XML DOM of the AI
+ manifest.
+ Raises: IOError on file open error.
+ ValueError on validation error.
+ """
+ schema = file(self.AI_schema, 'r')
+
+ try:
+ xml_data = file(self.manifest_path, 'r')
+ except AssertionError:
+ # manifest path will be unset if we're not using a separate file
+ # for A/I manifest so we must emulate a file
+ xml_data = StringIO.StringIO(lxml.etree.tostring(self._AI_root))
+
+ if self.is_dtd:
+ self._AI_root = verifyXML.verifyDTDManifest(xml_data,
+ self.AI_schema)
+
+ if isinstance(self._AI_root, list):
+ err = '\n'.join(self._AI_root)
+ raise ValueError(_("Error: AI manifest failed validation:\n%s")
+ % err)
+
+
+ else:
+ self._AI_root = verifyXML.verifyRelaxNGManifest(schema, xml_data)
+
+ if isinstance(self._AI_root, lxml.etree._LogEntry):
+ # catch if we are not using a manifest we can name with
+ # manifest_path
+ try:
+ # manifest_path is a property that may raise an
+ # AssertionError
+ man_path = self.manifest_path
+ raise ValueError(_("Error:\tFile %s failed validation:"
+ "\n\t%s") %
+ (os.path.basename(man_path),
+ self._AI_root.message))
+ # manifest_path will throw an AssertionError if it does not
+ # have a path use a different error message
+ except AssertionError:
+ raise ValueError(_("Error: AI manifest failed validation:"
+ "\n\t%s") % self._AI_root.message)
+
+ # Replace the <ai_manifest_file> element (if one exists) with an
+ # <ai_embedded_manifest> element, using content from its referenced
+ # file which was just loaded into the _AI_root XML DOM
+ ai_manifest_file = self._criteria_root.find(".//ai_manifest_file")
+
+ if ai_manifest_file is not None:
+ new_ai = lxml.etree.Element("ai_embedded_manifest")
+ # add newlines to separate ai_embedded_manifest
+ # from children
+ new_ai.text = "\n\t"
+ new_ai.tail = "\n"
+ self._AI_root.getroot().tail = "\n"
+ new_ai.append(self._AI_root.getroot())
+
+ ai_manifest_file.getparent().replace(ai_manifest_file, new_ai)
+
+
+ def verify_SC_manifest(self, data, name=None):
+ """
+ Used for verifying and loading SC manifest
+ Args: data - file path, or StringIO object.
+ name - Optionally, takes a name to provide error output,
+ as a StringIO object will not have a file path to
+ provide.
+ Returns: Provide an XML DOM for the SC manifest
+ Raises: SystemExit on validation or file open error.
+ """
+ if not isinstance(data, StringIO.StringIO):
+ try:
+ data = file(data, 'r')
+ except IOError:
+ if name is None:
+ raise SystemExit(_("Error:\tCan not open: %s") % data)
+ else:
+ raise SystemExit(_("Error:\tCan not open: %s") % name)
+ xml_root = verifyXML.verifyDTDManifest(data, self.smfDtd)
+ if isinstance(xml_root, list):
+ if not isinstance(data, StringIO.StringIO):
+ print >> sys.stderr, (_("Error:\tFile %s failed validation:") %
+ data.name)
+ else:
+ print >> sys.stderr, (_("Error:\tSC Manifest %s failed "
+ "validation:") % name)
+ for err in xml_root:
+ print >> sys.stderr, err
+ raise SystemExit()
+ return(xml_root)
+
+ def set_criteria_root(self, root=None):
+ """
+ Used to set _criteria_root DOM with the criteria from the passed
+ in root DOM. If _criteria_root already exists, overwrite its
+ <ai_criteria> elements with the criteria found in root. If
+ _criteria_root doesn't already exist, simply set the root DOM
+ passed in as _criteria_root.
+
+ Args: root - A DOM for a criteria manifest
+ Postconditions: _criteria_root will be set with a criteria manifest
+ DOM, or have its <ai_criteria> elements replaced with
+ the criteria from the root DOM passed in.
+ """
+
+ # If the _criteria_root is not yet set, set it to the root
+ # DOM passed in.
+ if self._criteria_root is None:
+ self._criteria_root = root
+
+ # Else _criteria_root already exists (because this is being
+ # called with an older install service where the manifest input
+ # is a combined Criteria manifest), use the criteria specified
+ # in 'root' to overwrite any criteria in _criteria_root.
+ else:
+ # Remove all <ai_criteria> elements if they exist.
+ removed_criteria = False
+ path = '/ai_criteria_manifest/ai_criteria'
+ for tag in self._criteria_root.xpath(path):
+ removed_criteria = True
+ tag.getparent().remove(tag)
+
+ # If we removed a criteria from _criteria_root, this means
+ # criteria was also specified in a combined Criteria manifest.
+ # Warn user that those will be ignored, and criteria specified
+ # on the command line via -c or -C override those.
+ if removed_criteria:
+ print("Warning: criteria specified in multiple places.\n"
+ " Ignoring criteria from combined Criteria manifest "
+ "file.\n")
+
+ # Append all criteria from the new criteria root.
+ ai_criteria = root.iterfind(".//ai_criteria")
+
+ self._criteria_root.getroot().extend(ai_criteria)
+
+if __name__ == '__main__':
+ gettext.install("ai", "/usr/lib/locale")
+
+ # check that we are root
+ if os.geteuid() != 0:
+ raise SystemExit(_("Error:\tNeed root privileges to execute"))
+
+ # load in all the options and file data
+ data = parse_options()
+
+ # if we have a default manifest do default manifest handling
+ if data.manifest_name == "default.xml":
+ do_default(data)
+
+ # if we have a non-default manifest first ensure it is a unique criteria
+ # set and then, if unique, add the manifest to the criteria database
+ else:
+ # if we have a None criteria from the criteria list then the manifest
+ # has no criteria which is illegal for a non-default manifest
+ if not data.criteria:
+ raise SystemExit(_("Error:\tAt least one criterion must be " +
+ "provided with a non-default manifest."))
+ find_colliding_manifests(data.criteria, data.database,
+ find_colliding_criteria(data.criteria, data.database))
+ insert_SQL(data)
+
+ # move the manifest into place
+ place_manifest(data)