7075427 Make aimanifest friendly for script checkout
authorJack Schwartz <Jack.Schwartz@Oracle.COM>
Fri, 05 Aug 2011 12:08:36 -0700
changeset 1360 8559e4b4c3f4
parent 1359 b4e753bfa2b4
child 1361 8fa0488f54d3
7075427 Make aimanifest friendly for script checkout
usr/src/cmd/aimanifest/aimanifest.py
usr/src/lib/install_manifest_input/__init__.py
usr/src/lib/install_manifest_input/mim.py
--- a/usr/src/cmd/aimanifest/aimanifest.py	Fri Aug 05 08:42:53 2011 -0600
+++ b/usr/src/cmd/aimanifest/aimanifest.py	Fri Aug 05 12:08:36 2011 -0700
@@ -46,7 +46,6 @@
                         fallback=True).gettext
 
 AIM_LOGGER = None
-SCHEMA_FILE = "/usr/share/install/ai.dtd"
 
 VALIDATE = True
 NO_VALIDATE = False
@@ -89,7 +88,14 @@
                   "the returned element\n" +
         "    in terms of node IDs.  This path may be used in " +
                   "subsequent calls to\n" +
-        "    %s to specify the affected element more directly.\n") % (
+        "    %s to specify the affected element more directly.\n" +
+        "\n    The following environment variables are read:\n" +
+        "      AIM_MANIFEST: Pathname of the evolving manifest.            " +
+                  "(Must be set)\n" +
+        "      AIM_DTD: Overrides the DTD given in the evolving manifest.  " +
+                  "(Optional)\n" +
+        "      AIM_LOGFILE: Logfile for additional information.            " +
+                  "(Optional)\n") % (
         name, name)
     return usage_str
 
@@ -224,7 +230,8 @@
 
     # Pass AIM_MANIFEST as the output file.
     try:
-        mim = ManifestInput(os.environ.get("AIM_MANIFEST"), SCHEMA_FILE)
+        mim = ManifestInput(os.environ.get("AIM_MANIFEST"),
+                            os.environ.get("AIM_DTD"))
     except (milib.MimEtreeParseError, milib.MimDTDInvalid) as err:
         for error in err.errors:
             # These messages come already localized.
@@ -281,7 +288,7 @@
         try:
             mim.load(path, options.is_incremental)
             mim.commit(NO_VALIDATE)
-        except milib.MimEtreeParseError as err:
+        except (milib.MimEtreeParseError, milib.MimDTDInvalid) as err:
             for error in err.errors:
                 # These messages come already localized.
                 print >> sys.stderr, error
@@ -297,7 +304,7 @@
         try:
             mim.validate()
             AIM_LOGGER.info(_("Validation successful"))
-        except milib.MimDTDInvalid as err:
+        except (milib.MimEtreeParseError, milib.MimDTDInvalid) as err:
             errorlist = err.errors
             for error in errorlist:
                 # These messages come already localized.
--- a/usr/src/lib/install_manifest_input/__init__.py	Fri Aug 05 08:42:53 2011 -0600
+++ b/usr/src/lib/install_manifest_input/__init__.py	Fri Aug 05 12:08:36 2011 -0700
@@ -146,9 +146,9 @@
 ERR_AMBIG_PATH = _("Ambiguity error:  Path matches more than one element")
 ERR_AMBIG_PARENT_PATH = _("Ambiguity error: "
                           "Parent path matches more than one element")
-ERR_NO_PARENT_PATH = _("Error: no matching parent path exists")
-ERR_NO_ELEM_MATCH = _("Error:  Path matches no elements")
-ERR_NO_ATTR_MATCH = _("Error: Path matches no attributes")
+ERR_NO_PARENT_PATH = _("No matching parent path exists")
+ERR_NO_ELEM_MATCH = _("Path matches no elements")
+ERR_NO_ATTR_MATCH = _("Path matches no attributes")
 ERR_FINAL_BRANCH_VAL_INVALID = _("Final path branch has a value or is invalid")
 ERR_FINAL_BRANCH_INVALID = _("Final path branch is invalid")
 ERR_ETREE_PARSE_XPATH = _("Etree error parsing path %(mxpath)s: %(merr)s")
--- a/usr/src/lib/install_manifest_input/mim.py	Fri Aug 05 08:42:53 2011 -0600
+++ b/usr/src/lib/install_manifest_input/mim.py	Fri Aug 05 12:08:36 2011 -0700
@@ -54,7 +54,6 @@
 import re
 
 from lxml import etree
-from urllib2 import urlopen
 
 import solaris_install.manifest_input as milib
 import solaris_install.manifest_input.process_dtd as pdtd
@@ -133,10 +132,8 @@
           evolving_file: Pathname to the file providing initial data, and
               storing the result.
 
-          schema_file: Pathname to the DTD file used.  Used only if the
-              manifest does not name a DTD.  Optional, but if not specified
-              here and manifest does not specify either, then an exception
-              is raised.
+          schema_file: Pathname to the DTD file to use.  Overrides any DTD
+              specified in the manifest, if specified.  Optional.
 
         Raises:
           IOError - Could not access DTD file
@@ -154,6 +151,11 @@
 
         self.evfile_name = evolving_file
         self.tree = None
+        self.schema = None
+        self.schema_data = None
+        self.schema_file_from_mfest = None
+        self.schema_file_from_init = None
+        self.schema_file_from_overlay = None
 
         # Set up parser to remove blank text and processing instructions.
         # Have parser leave in comments, so they can be written out later.
@@ -174,15 +176,35 @@
         if mfest_size:
             self.tree = self.parse_xml_file(self.evfile_name, self.parser)
 
+        # Save explicit requested schema.  Could be None.
+        self.schema_file_from_init = schema_file
+
+        if self.tree and self.tree.docinfo:
+            self.schema_file_from_mfest = self.tree.docinfo.system_url
+            if schema_file is None:
+                schema_file = self.tree.docinfo.system_url
+
+        # Load schema.  Forgo setting the schema now if there is no place to
+        # get it from.  Try to get it from the loaded manifest later.
+        if schema_file is not None:
+            self.load_schema(schema_file)
+
+    def load_schema(self, schema_file):
+        '''
         # Open schema for validator, and build table of children order.
 
-        # Use the schema refered to in the manifest itself, if it is listed in
-        # the manifest and it exists.  Else use the schema passed in as an arg.
-        if (self.tree and self.tree.docinfo and
-            self.tree.docinfo.system_url and
-            ManifestInput._is_accessible(self.tree.docinfo.system_url)):
-            schema_file = self.tree.docinfo.system_url
+        Args:
+            schema_file: DTD
+
+        Returns:
+            initializes self.schema and self.schema_data
 
+        Raises:
+          IOError - Could not access DTD file.
+          IOError - Could not digest DTD file.
+          MimDTDInvalid - Error parsing DTD
+          MimDTDError - SchemaData error processing DTD data from file.
+        '''
         if schema_file is None:
             raise milib.MimInvalidError(milib.ERR_NO_SCHEMA)
 
@@ -198,7 +220,8 @@
                  err.error_log.filter_from_level(GET_ALL)])
 
         try:
-            self.schema_data = pdtd.SchemaData(schema_file)  # For order table
+            # For order table
+            self.schema_data = pdtd.SchemaData(schema_file)
         except IOError as err:
             raise IOError(err.args[0], milib.IOERR_DTD_DIGEST %
                           {"mserr": err.strerror, "mfile": schema_file})
@@ -207,33 +230,6 @@
                                 {"mfile": schema_file, "merr": str(err)})
 
     @staticmethod
-    def _is_accessible(url):
-        '''
-        Return true if url is accessible.
-
-        Checks for local files as well as proper URLs.
-
-        Args:
-          url: A string that is either a URL or a local filename.
-
-        Returns:
-          True: The url is accessible.
-          False: The url is not accessible.
-        '''
-        if os.access(url, os.R_OK):
-            return True
-
-        try:
-            # Send no additional data to the server.
-            # Allow up to 5 seconds for connection to be made.
-            fd = urlopen(url, data=None, timeout=5)
-            fd.close()
-        except IOError:
-            return False
-
-        return True
-
-    @staticmethod
     def parse_xml_file(manifest_name, parser):
         '''
         Call etree.parse with proper exception handling.
@@ -252,7 +248,7 @@
         '''
         try:
             tree = etree.parse(manifest_name, parser)
-        except etree.XMLSyntaxError as err:
+        except etree.XMLSyntaxError:
             raise milib.MimEtreeParseError(
                 [msg.__repr__() for msg in
                  parser.error_log.filter_from_level(GET_ALL)])
@@ -273,6 +269,7 @@
           IOError - Error reading overlay_filename
           MimInvalidError - Argument is missing or invalid
           MimEtreeParseError - IO errors or parser errors while parsing.
+          MimDTDInvalid - Error reading DTD
         '''
         if overlay_filename is None:
             raise milib.MimInvalidError(milib.ERR_ARG_INVALID)
@@ -280,9 +277,27 @@
         if (not incremental) or not self.tree:
             # Load a fresh tree.  Discard old data.
             self.tree = self.parse_xml_file(overlay_filename, self.parser)
+
+            # Take the schema from the manifest being loaded.
+            # Record that tree stores a schema.
+            if self.tree and self.tree.docinfo:
+                self.schema_file_from_mfest = self.tree.docinfo.system_url
+
+                # Allow for no schemas from anywhere for fresh trees since
+                # no merges are needed.
+                if (self.tree.docinfo.system_url is not None and
+                    not self.schema):
+                    self.load_schema(self.tree.docinfo.system_url)
         else:
             # Read tree of overlay data, then overlay it.
             overlay_tree = self.parse_xml_file(overlay_filename, self.parser)
+
+            # No schema loaded.  Take the schema from the overlay tree.
+            # Overlay tree schema doesn't get stored in tree.
+            if overlay_tree and overlay_tree.docinfo and not self.schema:
+                self.load_schema(overlay_tree.docinfo.system_url)
+                self.schema_file_from_overlay = overlay_tree.docinfo.system_url
+
             self._overlay_recurse(self.tree.getroot(), overlay_tree.getroot(),
                                   None)
 
@@ -319,6 +334,8 @@
           MimEmptyTreeError - No XML data present
           Various lxml.etree (StandardError subclass) exceptions
         '''
+        if not self.schema:
+            raise milib.MimDTDInvalid([milib.ERR_NO_SCHEMA])
         if not self.tree:
             raise milib.MimEmptyTreeError(milib.ERR_EMPTY_TREE)
         try:
@@ -349,8 +366,31 @@
             raise milib.MimEmptyTreeError(milib.ERR_EMPTY_TREE)
         if validate:
             self.validate()
+        xml_data = etree.tostring(self.tree, pretty_print=True).splitlines(
+                                                                        True)
+        # DOCTYPE string will have one of the following, in order:
+        # 1) User specified DTD (self.schema_file_from_init)
+        # 2) DTD from loaded tree (self.schema_file_from_mfest)
+        # 3) DTD from overlaid tree (self.schema_file_from_overlay)
+
+        if self.schema_file_from_init:
+            # Store as doctype the explicit schema requested by the user.
+            if self.schema_file_from_mfest:
+                # Replace the DOCTYPE string, always the first line.
+                xml_data[0] = '<!DOCTYPE %s SYSTEM "%s">\n' % (
+                    self.tree.getroot().tag, self.schema_file_from_init)
+            else:
+                # No doctype is stored in the tree.  Just add requested one.
+                xml_data.insert(0, '<!DOCTYPE %s SYSTEM "%s">\n' % (
+                    self.tree.getroot().tag, self.schema_file_from_init))
+        elif self.schema_file_from_overlay and not self.schema_file_from_mfest:
+            # No doctype is stored in the tree.  Just add requested one.
+            xml_data.insert(0, '<!DOCTYPE %s SYSTEM "%s">\n' % (
+                self.tree.getroot().tag, self.schema_file_from_overlay))
+
         try:
-            self.tree.write(self.evfile_name, pretty_print=True)
+            with open(self.evfile_name, "w") as outfile:
+                outfile.writelines(xml_data)
         except IOError as err:
             raise IOError(milib.IOERR_DTD_DEST %
                           {"mserr": err.strerror, "mdest": self.evfile_name})
@@ -508,10 +548,10 @@
           does not exist), a new node is created.  Otherwise, an existing one
           is followed.
 
-        * A simple branch is simply an idenfier. /a/b are two simple branches.
-        A non-simple branch is any other kind of branch, such as /a=5 or
-        /b[c/d@e=123].  Non-simple branches always specify a value and may
-        specify a subpath.
+        * A simple branch is simply an identifier. /a/b are two simple
+        branches.  A non-simple branch is any other kind of branch, such as
+        /a=5 or /b[c/d@e=123].  Non-simple branches always specify a value and
+        may specify a subpath.
 
         The goal here is to honor subpaths which may be specified to narrow
         down where to add new items, but to still create a second node of a
@@ -617,8 +657,14 @@
         for branch in right_set:
             # insert_before holds a list of names which can follow
             # an element named "branch" as children of curr_elem.
-            (insert_before, mults_ok) =  \
-                self.schema_data.find_element_info(curr_elem.tag, branch)
+            #
+            if self.schema_data:
+                (insert_before, mults_ok) =  \
+                    self.schema_data.find_element_info(curr_elem.tag, branch)
+            else:
+                # No schema to go on here.  Just allow the insert.
+                insert_before = []
+                mults_ok = True
 
             # Check for no list (as oppoosed to empty list)
             if insert_before == None:
@@ -812,6 +858,8 @@
           - If the overlay_element is a leaf node, then replace the node in
             the main tree with the overlay_element.
 
+        Assumes self.schema_data is initialized.
+
         Args:
           main_parent: Parent of where new element would be added.