Current File : //bin/aimanifest
#!/usr/bin/python2.7 -E

#
# Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
#

"""
aimanifest.py: aimanifest commandline wrapper around Manifest Input Module
"""

import errno
import gettext
import logging
import os
import sys

from optparse import OptionParser
from traceback import print_exc

import solaris_install.manifest_input as milib

from solaris_install.logger import InstallLogger
from solaris_install.manifest_input.mim import ManifestInput

_ = gettext.translation('solaris_install_aimanifest', '/usr/share/locale',
                        fallback=True).gettext

AIM_LOGGER = None

VALIDATE = True
NO_VALIDATE = False


class AimOptionParser(OptionParser):
    '''
    OptionParser which provides error_w_errno() to return a more correct errno
    '''

    def error_w_errno(self, errnum, message):
        '''
        Log and display a message, and exit with errnum status
        '''
        AIM_LOGGER.error(message)
        print >> sys.stderr, _("Usage:\n") + self.usage + "\n"
        print >> sys.stderr, os.path.basename(sys.argv[0]) + \
            ": error: " + message
        sys.exit(errnum)


def usage(argv):
    '''
    Assemble command usage string.
    '''
    name = os.path.basename(argv[0])
    usage_str = _(
        "    %(name)s subcommand cmd_options\n"
        "    subcommands:\n"
        "    %(name)s add [-r] <path> <value>  Add new element\n"
        "    %(name)s set [-r] <path> <value>  "
        "Change values or add/change attributes\n"
        "    %(name)s delete <path>            Remove elements\n"
        "    %(name)s get [-r] <path>          "
        "Retrieve element values or attributes\n"
        "    %(name)s load [-i] <filename>     "
        "Load / incrementally overlay XML file\n"
        "    %(name)s validate                 Validate XML data\n"
        "\n    The -r option to set/add/get displays the path of "
        "the returned element\n"
        "    in terms of node IDs.  This path may be used in "
        "subsequent calls to\n"
        "    %(name)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


def _handle_error(error):
    '''
    Display the error message, log the whole traceback and set errno

    Args:
      error: exception containing message and, if IOError, an errno.
              Assume messages are localized.

    Returns:
      errno: EINVAL unless overridden by an IOError exception's errno.
    '''
    if isinstance(error, milib.MimMultMsgError):
        for msg in error.errors:
            # These messages come already localized.
            print >> sys.stderr, msg
            AIM_LOGGER.error(msg)
    else:
        print >> sys.stderr, str(error)
        AIM_LOGGER.error(str(error))
    rval = getattr(error, "errno", errno.EINVAL)

    # Handle the cases where the errno field exists in the exception
    # but is None or 0
    if not rval:
        rval = errno.EIO if isinstance(error, IOError) else errno.EINVAL
    return rval


def _log_final_status(mim, title, paths=None):
    '''
    Log status.  Usually called after running a subcommand.  Checks to see if
    the manifest validates, when this information would be logged.  Does
    nothing if logging is disabled.

    Args:
      mim: Reference to the Manifest Input Module, needed for validation.

      title: Title that preceeds the "paths" argument in output, already
          localized.

      paths: List of nodepaths, used for printing only.
    '''
    if AIM_LOGGER.isEnabledFor(logging.INFO):
        try:
            mim.validate()
            validated = _("Pass")
        except (milib.MimError, IOError):
            validated = _("Fail")
        out = _("cmd:success, validation:") + validated
        AIM_LOGGER.info(out)
        if paths:
            for path in paths:
                out = "  %s: %s" % (title, path)
                AIM_LOGGER.info(out)


def _setup_logging():
    '''
    Set up logging.

    If AIM_LOGFILE is set in the environment, enable logging to the file
    specified by AIM_LOGFILE.  Enable logging at the level specified by
    the environment variable AIM_LOGLEVEL if set, else set to the INFO level
    by default.  (These two variables (and logging here in general) is to
    support the Derived Manifest Module (DMM).
    '''
    # pylint: disable-msg=W0603
    global AIM_LOGGER

    logging.setLoggerClass(InstallLogger)
    AIM_LOGGER = logging.getLogger("aimanifest")
    logfile_name = os.environ.get("AIM_LOGFILE")
    if logfile_name is not None:
        try:
            logging.basicConfig(format="%(asctime)s: %(name)s: "
                                "%(levelname)s: %(message)s",
                                datefmt="%H:%M:%S",
                                filename=logfile_name,
                                filemode="a")
        except IOError as (errnum, strerror):
            raise SystemExit(_("AIM_LOGFILE I/O Error(%(errnum)s) :"
                               "%(strerror)s : %(fname)s"
                               % ({"errnum": errnum, "strerror": strerror,
                               "fname": logfile_name})))

        logging_level = os.environ.get("AIM_LOGLEVEL")
        if logging_level and logging_level.isdigit():
            AIM_LOGGER.setLevel(int(logging_level))
        else:
            AIM_LOGGER.setLevel(logging.INFO)
    else:
        logging.disable(logging.CRITICAL)


def _shutdown_logging():
    '''
    Shut down logging.
    '''
    logging.shutdown()


def _do_aimanifest(argv):
    '''
    Main.  See usage for argv details.
    '''
    usage_str = usage(argv)

    if len(argv) <= 1:
        AIM_LOGGER.error(_("Error: Missing subcommand"))
        print >> sys.stderr, _("Usage:\n") + usage_str
        return errno.EINVAL

    parser = AimOptionParser(usage=usage_str)
    parser.add_option("-i", "--incremental", dest="is_incremental",
                      default=False, action="store_true",
                      help=_("Do not clear data before adding new data"))
    parser.add_option("-r", "--return-path", dest="show_path", default=False,
                      action="store_true",
                      help=_("Return unique path to affected node"))

    (options, args) = parser.parse_args(argv[1:])
    len_args = len(args)
    command = args[0]
    path = args[1] if (len_args > 1) else None
    value = args[2] if (len_args > 2) else None

    cmds_i_option = ["load"]
    cmds_r_option = ["add", "set", "get"]

    two_arg_cmds = ["add", "set"]
    one_arg_cmds = ["delete", "get", "load"]
    no_arg_cmds = ["validate"]

    if options.is_incremental and command not in cmds_i_option:
        parser.error_w_errno(errno.EINVAL,
                             _("-i|--incremental is not applicable "
                               "for given subcommand"))

    if options.show_path and command not in cmds_r_option:
        parser.error_w_errno(errno.EINVAL,
                             _("-r|--return_path is not applicable "
                               "for given subcommand"))

    if ((command in two_arg_cmds and
         (len_args < 3)) or
        (command in one_arg_cmds and
         (len_args < 2))):
        parser.error_w_errno(errno.EINVAL, _("missing argument"))

    if ((command in two_arg_cmds and
         (len_args > 3)) or
        (command in one_arg_cmds and
         (len_args > 2)) or
        (command in no_arg_cmds and
         (len_args > 1))):
        parser.error_w_errno(errno.EINVAL, _("extra arguments given"))

    aim_manifest = os.environ.get("AIM_MANIFEST")
    if aim_manifest is None:
        parser.error_w_errno(errno.EINVAL,
                             _("AIM_MANIFEST environment variable is not set"))

    # Pass AIM_MANIFEST as the output file.
    try:
        mim = ManifestInput(aim_manifest, os.environ.get("AIM_DTD"))
    except (milib.MimError, IOError) as err:
        return (_handle_error(err))

    if command == "add":
        AIM_LOGGER.info(_("command:%(mcommand)s, path:%(mpath)s, "
                          "value:%(mvalue)s") %
                        {"mcommand": command, "mpath": path, "mvalue": value})
        try:
            path = mim.add(path, value)
            mim.commit(NO_VALIDATE)
        except (milib.MimError, IOError) as err:
            return (_handle_error(err))

        _log_final_status(mim, _("Node"), [path])
        if options.show_path:
            # Localization not needed here.
            print path

    elif command == "set":
        AIM_LOGGER.info(_("command:%(mcommand)s, path:%(mpath)s, "
                          "value:%(mvalue)s") %
                        {"mcommand": command, "mpath": path, "mvalue": value})
        try:
            paths, is_attr = mim.set(path, value)
            mim.commit(NO_VALIDATE)
        except (milib.MimError, IOError) as err:
            return (_handle_error(err))

        _log_final_status(mim, _("Node"), paths)
        if options.show_path:
            # Localization not needed here.
            for path in paths:
                print path
        elif is_attr:
            print _("%d attribute(s) set") % len(paths)
        else:
            print _("%d element(s) set") % len(paths)

    elif command == "get":
        AIM_LOGGER.info(_("command:%(mcommand)s, path:%(mpath)s") %
                        {"mcommand": command, "mpath": path})
        try:
            get_tuples = mim.get(path)
        except (milib.MimError, IOError) as err:
            return (_handle_error(err))

        for (value, path) in get_tuples:
            if value is None or not len(value):
                value = '""'

            AIM_LOGGER.info(_("successful: returns value:%(mvalue)s, "
                              "path:%(mpath)s") %
                            {"mvalue": value, "mpath": path})

            # Localization not needed here.
            if not options.show_path:
                print "%s" % value
            else:
                print "%s %s" % (value, path)

    elif command == "delete":
        AIM_LOGGER.info(_("command:%(mcommand)s, path:%(mpath)s") %
                        {"mcommand": command, "mpath": path})
        try:
            paths, is_attr = mim.delete(path)
            mim.commit(NO_VALIDATE)
        except (milib.MimError, IOError) as err:
            return (_handle_error(err))

        _log_final_status(mim, _("Node"), paths)
        if is_attr:
            print _("%d attribute(s) deleted") % len(paths)
        else:
            print _("%d element(s)/subtree(s) deleted") % len(paths)

    elif command == "load":
        # path arg holds the filename
        AIM_LOGGER.info(_("command:%(mcommand)s, "
                        "incremental:%(mincr)s, file:%(mfile)s") %
                        {"mcommand": command,
                         "mincr": str(options.is_incremental),
                         "mfile": path})
        try:
            mim.load(path, options.is_incremental)
            mim.commit(NO_VALIDATE)
        except (milib.MimError, IOError) as err:
            return (_handle_error(err))

        _log_final_status(mim, _("File"), [path])

    elif (command == "validate"):
        AIM_LOGGER.info(_("Command:%s") % command)
        try:
            mim.validate()
            AIM_LOGGER.info(_("Validation successful"))
        except (milib.MimError, IOError) as err:
            return (_handle_error(err))

    else:
        AIM_LOGGER.error(_("Invalid subcommand \"%s\"") % command)
        print >> sys.stderr, _("Usage:\n") + usage_str
        return errno.EINVAL

    return 0  # No errors


def main(argv):
    '''
    Main program.
    '''
    _setup_logging()
    rval = 1
    try:
        rval = _do_aimanifest(argv)
    except StandardError as err:
        # Catch unexpected exceptions, logging and displaying a traceback.
        print >> sys.stderr, str(err)
        AIM_LOGGER.exception(str(err))
        print_exc()
    _shutdown_logging()
    return rval


if __name__ == "__main__":
    sys.exit(main(sys.argv))