CedarBackup2.cli

Package CedarBackup2 :: Module cli |
[hide private]
[frames] | no frames]

# Source Code for Module CedarBackup2.cli

       1  # -*- coding: iso-8859-1 -*-
       2  # vim: set ft=python ts=3 sw=3 expandtab:
       3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
       4  #
       5  #              C E D A R
       6  #          S O L U T I O N S       "Software done right."
       7  #           S O F T W A R E
       8  #
       9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      10  #
      11  # Copyright (c) 2004-2007,2010,2015 Kenneth J. Pronovici.
      12  # All rights reserved.
      13  #
      14  # This program is free software; you can redistribute it and/or
      15  # modify it under the terms of the GNU General Public License,
      16  # Version 2, as published by the Free Software Foundation.
      17  #
      18  # This program is distributed in the hope that it will be useful,
      19  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      21  #
      22  # Copies of the GNU General Public License are available from
      23  # the Free Software Foundation website, http://www.gnu.org/.
      24  #
      25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      26  #
      27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org>
      28  # Language : Python 2 (>= 2.7)
      29  # Project  : Cedar Backup, release 2
      30  # Purpose  : Provides command-line interface implementation.
      31  #
      32  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      33
      34  ########################################################################
      35  # Module documentation
      36  ########################################################################
      37
      38  """
      39  Provides command-line interface implementation for the cback script.
      40
      41  Summary
      42  =======
      43
      44     The functionality in this module encapsulates the command-line interface for
      45     the cback script.  The cback script itself is very short, basically just an
      46     invokation of one function implemented here.  That, in turn, makes it
      47     simpler to validate the command line interface (for instance, it's easier to
      48     run pychecker against a module, and unit tests are easier, too).
      49
      50     The objects and functions implemented in this module are probably not useful
      51     to any code external to Cedar Backup.   Anyone else implementing their own
      52     command-line interface would have to reimplement (or at least enhance) all
      53     of this anyway.
      54
      55  Backwards Compatibility
      56  =======================
      57
      58     The command line interface has changed between Cedar Backup 1.x and Cedar
      59     Backup 2.x.  Some new switches have been added, and the actions have become
      60     simple arguments rather than switches (which is a much more standard command
      61     line format).  Old 1.x command lines are generally no longer valid.
      62
      63  @var DEFAULT_CONFIG: The default configuration file.
      64  @var DEFAULT_LOGFILE: The default log file path.
      65  @var DEFAULT_OWNERSHIP: Default ownership for the logfile.
      66  @var DEFAULT_MODE: Default file permissions mode on the logfile.
      67  @var VALID_ACTIONS: List of valid actions.
      68  @var COMBINE_ACTIONS: List of actions which can be combined with other actions.
      69  @var NONCOMBINE_ACTIONS: List of actions which cannot be combined with other actions.
      70
      71  @sort: cli, Options, DEFAULT_CONFIG, DEFAULT_LOGFILE, DEFAULT_OWNERSHIP,
      72         DEFAULT_MODE, VALID_ACTIONS, COMBINE_ACTIONS, NONCOMBINE_ACTIONS
      73
      74  @author: Kenneth J. Pronovici <pronovic@ieee.org>
      75  """
      76
      77  ########################################################################
      78  # Imported modules
      79  ########################################################################
      80
      81  # System modules
      82  import sys
      83  import os
      84  import logging
      85  import getopt
      86
      87  # Cedar Backup modules
      88  from CedarBackup2.release import AUTHOR, EMAIL, VERSION, DATE, COPYRIGHT
      89  from CedarBackup2.customize import customizeOverrides
      90  from CedarBackup2.util import DirectedGraph, PathResolverSingleton
      91  from CedarBackup2.util import sortDict, splitCommandLine, executeCommand, getFunctionReference
      92  from CedarBackup2.util import getUidGid, encodePath, Diagnostics
      93  from CedarBackup2.config import Config
      94  from CedarBackup2.peer import RemotePeer
      95  from CedarBackup2.actions.collect import executeCollect
      96  from CedarBackup2.actions.stage import executeStage
      97  from CedarBackup2.actions.store import executeStore
      98  from CedarBackup2.actions.purge import executePurge
      99  from CedarBackup2.actions.rebuild import executeRebuild
     100  from CedarBackup2.actions.validate import executeValidate
     101  from CedarBackup2.actions.initialize import executeInitialize
     102
     103
     104  ########################################################################
     105  # Module-wide constants and variables
     106  ########################################################################
     107
     108  logger = logging.getLogger("CedarBackup2.log.cli")
     109
     110  DISK_LOG_FORMAT    = "%(asctime)s --> [%(levelname)-7s] %(message)s"
     111  DISK_OUTPUT_FORMAT = "%(message)s"
     112  SCREEN_LOG_FORMAT  = "%(message)s"
     113  SCREEN_LOG_STREAM  = sys.stdout
     114  DATE_FORMAT        = "%Y-%m-%dT%H:%M:%S %Z"
     115
     116  DEFAULT_CONFIG     = "/etc/cback.conf"
     117  DEFAULT_LOGFILE    = "/var/log/cback.log"
     118  DEFAULT_OWNERSHIP  = [ "root", "adm", ]
     119  DEFAULT_MODE       = 0640
     120
     121  REBUILD_INDEX      = 0        # can't run with anything else, anyway
     122  VALIDATE_INDEX     = 0        # can't run with anything else, anyway
     123  INITIALIZE_INDEX   = 0        # can't run with anything else, anyway
     124  COLLECT_INDEX      = 100
     125  STAGE_INDEX        = 200
     126  STORE_INDEX        = 300
     127  PURGE_INDEX        = 400
     128
     129  VALID_ACTIONS      = [ "collect", "stage", "store", "purge", "rebuild", "validate", "initialize", "all", ]
     130  COMBINE_ACTIONS    = [ "collect", "stage", "store", "purge", ]
     131  NONCOMBINE_ACTIONS = [ "rebuild", "validate", "initialize", "all", ]
     132
     133  SHORT_SWITCHES     = "hVbqc:fMNl:o:m:OdsD"
     134  LONG_SWITCHES      = [ 'help', 'version', 'verbose', 'quiet',
     135                         'config=', 'full', 'managed', 'managed-only',
     136                         'logfile=', 'owner=', 'mode=',
     137                         'output', 'debug', 'stack', 'diagnostics', ]
     138
     139
     140  #######################################################################
     141  # Public functions
     142  #######################################################################
     143
     144  #################
     145  # cli() function
     146  #################
     147
     148 -def cli():
     149     """
     150     Implements the command-line interface for the C{cback} script.
     151
     152     Essentially, this is the "main routine" for the cback script.  It does all
     153     of the argument processing for the script, and then sets about executing the
     154     indicated actions.
     155
     156     As a general rule, only the actions indicated on the command line will be
     157     executed.   We will accept any of the built-in actions and any of the
     158     configured extended actions (which makes action list verification a two-
     159     step process).
     160
     161     The C{'all'} action has a special meaning: it means that the built-in set of
     162     actions (collect, stage, store, purge) will all be executed, in that order.
     163     Extended actions will be ignored as part of the C{'all'} action.
     164
     165     Raised exceptions always result in an immediate return.  Otherwise, we
     166     generally return when all specified actions have been completed.  Actions
     167     are ignored if the help, version or validate flags are set.
     168
     169     A different error code is returned for each type of failure:
     170
     171        - C{1}: The Python interpreter version is < 2.7
     172        - C{2}: Error processing command-line arguments
     173        - C{3}: Error configuring logging
     174        - C{4}: Error parsing indicated configuration file
     175        - C{5}: Backup was interrupted with a CTRL-C or similar
     176        - C{6}: Error executing specified backup actions
     177
     178     @note: This function contains a good amount of logging at the INFO level,
     179     because this is the right place to document high-level flow of control (i.e.
     180     what the command-line options were, what config file was being used, etc.)
     181
     182     @note: We assume that anything that I{must} be seen on the screen is logged
     183     at the ERROR level.  Errors that occur before logging can be configured are
     184     written to C{sys.stderr}.
     185
     186     @return: Error code as described above.
     187     """
     188     try:
     189        if map(int, [sys.version_info[0], sys.version_info[1]]) < [2, 7]:
     190           sys.stderr.write("Python 2 version 2.7 or greater required.\n")
     191           return 1
     192     except:
     193        # sys.version_info isn't available before 2.0
     194        sys.stderr.write("Python 2 version 2.7 or greater required.\n")
     195        return 1
     196
     197     try:
     198        options = Options(argumentList=sys.argv[1:])
     199        logger.info("Specified command-line actions: %s", options.actions)
     200     except Exception, e:
     201        _usage()
     202        sys.stderr.write(" *** Error: %s\n" % e)
     203        return 2
     204
     205     if options.help:
     206        _usage()
     207        return 0
     208     if options.version:
     209        _version()
     210        return 0
     211     if options.diagnostics:
     212        _diagnostics()
     213        return 0
     214
     215     if options.stacktrace:
     216        logfile = setupLogging(options)
     217     else:
     218        try:
     219           logfile = setupLogging(options)
     220        except Exception as e:
     221           sys.stderr.write("Error setting up logging: %s\n" % e)
     222           return 3
     223
     224     logger.info("Cedar Backup run started.")
     225     logger.info("Options were [%s]", options)
     226     logger.info("Logfile is [%s]", logfile)
     227     Diagnostics().logDiagnostics(method=logger.info)
     228
     229     if options.config is None:
     230        logger.debug("Using default configuration file.")
     231        configPath = DEFAULT_CONFIG
     232     else:
     233        logger.debug("Using user-supplied configuration file.")
     234        configPath = options.config
     235
     236     executeLocal = True
     237     executeManaged = False
     238     if options.managedOnly:
     239        executeLocal = False
     240        executeManaged = True
     241     if options.managed:
     242        executeManaged = True
     243     logger.debug("Execute local actions: %s", executeLocal)
     244     logger.debug("Execute managed actions: %s", executeManaged)
     245
     246     try:
     247        logger.info("Configuration path is [%s]", configPath)
     248        config = Config(xmlPath=configPath)
     249        customizeOverrides(config)
     250        setupPathResolver(config)
     251        actionSet = _ActionSet(options.actions, config.extensions, config.options,
     252                               config.peers, executeManaged, executeLocal)
     253     except Exception, e:
     254        logger.error("Error reading or handling configuration: %s", e)
     255        logger.info("Cedar Backup run completed with status 4.")
     256        return 4
     257
     258     if options.stacktrace:
     259        actionSet.executeActions(configPath, options, config)
     260     else:
     261        try:
     262           actionSet.executeActions(configPath, options, config)
     263        except KeyboardInterrupt:
     264           logger.error("Backup interrupted.")
     265           logger.info("Cedar Backup run completed with status 5.")
     266           return 5
     267        except Exception, e:
     268           logger.error("Error executing backup: %s", e)
     269           logger.info("Cedar Backup run completed with status 6.")
     270           return 6
     271
     272     logger.info("Cedar Backup run completed with status 0.")
     273     return 0
     274
     275
     276  ########################################################################
     277  # Action-related class definition
     278  ########################################################################
     279
     280  ####################
     281  # _ActionItem class
     282  ####################
     283
     284 -class _ActionItem(object):
     285
     286     """
     287     Class representing a single action to be executed.
     288
     289     This class represents a single named action to be executed, and understands
     290     how to execute that action.
     291
     292     The built-in actions will use only the options and config values.  We also
     293     pass in the config path so that extension modules can re-parse configuration
     294     if they want to, to add in extra information.
     295
     296     This class is also where pre-action and post-action hooks are executed.  An
     297     action item is instantiated in terms of optional pre- and post-action hook
     298     objects (config.ActionHook), which are then executed at the appropriate time
     299     (if set).
     300
     301     @note: The comparison operators for this class have been implemented to only
     302     compare based on the index and SORT_ORDER value, and ignore all other
     303     values.  This is so that the action set list can be easily sorted first by
     304     type (_ActionItem before _ManagedActionItem) and then by index within type.
     305
     306     @cvar SORT_ORDER: Defines a sort order to order properly between types.
     307     """
     308
     309     SORT_ORDER = 0
     310
     311 -   def __init__(self, index, name, preHooks, postHooks, function):
     312        """
     313        Default constructor.
     314
     315        It's OK to pass C{None} for C{index}, C{preHooks} or C{postHooks}, but not
     316        for C{name}.
     317
     318        @param index: Index of the item (or C{None}).
     319        @param name: Name of the action that is being executed.
     320        @param preHooks: List of pre-action hooks in terms of an C{ActionHook} object, or C{None}.
     321        @param postHooks: List of post-action hooks in terms of an C{ActionHook} object, or C{None}.
     322        @param function: Reference to function associated with item.
     323        """
     324        self.index = index
     325        self.name = name
     326        self.preHooks = preHooks
     327        self.postHooks = postHooks
     328        self.function = function
     329
     330 -   def __cmp__(self, other):
     331        """
     332        Definition of equals operator for this class.
     333        The only thing we compare is the item's index.
     334        @param other: Other object to compare to.
     335        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     336        """
     337        if other is None:
     338           return 1
     339        if self.index != other.index:
     340           if self.index < other.index:
     341              return -1
     342           else:
     343              return 1
     344        else:
     345           if self.SORT_ORDER != other.SORT_ORDER:
     346              if self.SORT_ORDER < other.SORT_ORDER:
     347                 return -1
     348              else:
     349                 return 1
     350        return 0
     351
     352 -   def executeAction(self, configPath, options, config):
     353        """
     354        Executes the action associated with an item, including hooks.
     355
     356        See class notes for more details on how the action is executed.
     357
     358        @param configPath: Path to configuration file on disk.
     359        @param options: Command-line options to be passed to action.
     360        @param config: Parsed configuration to be passed to action.
     361
     362        @raise Exception: If there is a problem executing the action.
     363        """
     364        logger.debug("Executing [%s] action.", self.name)
     365        if self.preHooks is not None:
     366           for hook in self.preHooks:
     367              self._executeHook("pre-action", hook)
     368        self._executeAction(configPath, options, config)
     369        if self.postHooks is not None:
     370           for hook in self.postHooks:
     371              self._executeHook("post-action", hook)
     372
     373 -   def _executeAction(self, configPath, options, config):
     374        """
     375        Executes the action, specifically the function associated with the action.
     376        @param configPath: Path to configuration file on disk.
     377        @param options: Command-line options to be passed to action.
     378        @param config: Parsed configuration to be passed to action.
     379        """
     380        name = "%s.%s" % (self.function.__module__, self.function.__name__)
     381        logger.debug("Calling action function [%s], execution index [%d]", name, self.index)
     382        self.function(configPath, options, config)
     383
     384 -   def _executeHook(self, type, hook):  # pylint: disable=W0622,R0201
     385        """
     386        Executes a hook command via L{util.executeCommand()}.
     387        @param type: String describing the type of hook, for logging.
     388        @param hook: Hook, in terms of a C{ActionHook} object.
     389        """
     390        fields = splitCommandLine(hook.command)
     391        logger.debug("Executing %s hook for action [%s]: %s", type, hook.action, fields[0:1])
     392        result = executeCommand(command=fields[0:1], args=fields[1:])[0]
     393        if result != 0:
     394           raise IOError("Error (%d) executing %s hook for action [%s]: %s" % (result, type, hook.action, fields[0:1]))
     395
     396
     397  ###########################
     398  # _ManagedActionItem class
     399  ###########################
     400
     401 -class _ManagedActionItem(object):
     402
     403     """
     404     Class representing a single action to be executed on a managed peer.
     405
     406     This class represents a single named action to be executed, and understands
     407     how to execute that action.
     408
     409     Actions to be executed on a managed peer rely on peer configuration and
     410     on the full-backup flag.  All other configuration takes place on the remote
     411     peer itself.
     412
     413     @note: The comparison operators for this class have been implemented to only
     414     compare based on the index and SORT_ORDER value, and ignore all other
     415     values.  This is so that the action set list can be easily sorted first by
     416     type (_ActionItem before _ManagedActionItem) and then by index within type.
     417
     418     @cvar SORT_ORDER: Defines a sort order to order properly between types.
     419     """
     420
     421     SORT_ORDER = 1
     422
     423 -   def __init__(self, index, name, remotePeers):
     424        """
     425        Default constructor.
     426
     427        @param index: Index of the item (or C{None}).
     428        @param name: Name of the action that is being executed.
     429        @param remotePeers: List of remote peers on which to execute the action.
     430        """
     431        self.index = index
     432        self.name = name
     433        self.remotePeers = remotePeers
     434
     435 -   def __cmp__(self, other):
     436        """
     437        Definition of equals operator for this class.
     438        The only thing we compare is the item's index.
     439        @param other: Other object to compare to.
     440        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     441        """
     442        if other is None:
     443           return 1
     444        if self.index != other.index:
     445           if self.index < other.index:
     446              return -1
     447           else:
     448              return 1
     449        else:
     450           if self.SORT_ORDER != other.SORT_ORDER:
     451              if self.SORT_ORDER < other.SORT_ORDER:
     452                 return -1
     453              else:
     454                 return 1
     455        return 0
     456
     457 -   def executeAction(self, configPath, options, config):
     458        """
     459        Executes the managed action associated with an item.
     460
     461        @note: Only options.full is actually used.  The rest of the arguments
     462        exist to satisfy the ActionItem iterface.
     463
     464        @note: Errors here result in a message logged to ERROR, but no thrown
     465        exception.  The analogy is the stage action where a problem with one host
     466        should not kill the entire backup.  Since we're logging an error, the
     467        administrator will get an email.
     468
     469        @param configPath: Path to configuration file on disk.
     470        @param options: Command-line options to be passed to action.
     471        @param config: Parsed configuration to be passed to action.
     472
     473        @raise Exception: If there is a problem executing the action.
     474        """
     475        for peer in self.remotePeers:
     476           logger.debug("Executing managed action [%s] on peer [%s].", self.name, peer.name)
     477           try:
     478              peer.executeManagedAction(self.name, options.full)
     479           except IOError, e:
     480              logger.error(e)   # log the message and go on, so we don't kill the backup
     481
     482
     483  ###################
     484  # _ActionSet class
     485  ###################
     486
     487 -class _ActionSet(object):
     488
     489     """
     490     Class representing a set of local actions to be executed.
     491
     492     This class does four different things.  First, it ensures that the actions
     493     specified on the command-line are sensible.  The command-line can only list
     494     either built-in actions or extended actions specified in configuration.
     495     Also, certain actions (in L{NONCOMBINE_ACTIONS}) cannot be combined with
     496     other actions.
     497
     498     Second, the class enforces an execution order on the specified actions.  Any
     499     time actions are combined on the command line (either built-in actions or
     500     extended actions), we must make sure they get executed in a sensible order.
     501
     502     Third, the class ensures that any pre-action or post-action hooks are
     503     scheduled and executed appropriately.  Hooks are configured by building a
     504     dictionary mapping between hook action name and command.  Pre-action hooks
     505     are executed immediately before their associated action, and post-action
     506     hooks are executed immediately after their associated action.
     507
     508     Finally, the class properly interleaves local and managed actions so that
     509     the same action gets executed first locally and then on managed peers.
     510
     511     @sort: __init__, executeActions
     512     """
     513
     514 -   def __init__(self, actions, extensions, options, peers, managed, local):
     515        """
     516        Constructor for the C{_ActionSet} class.
     517
     518        This is kind of ugly, because the constructor has to set up a lot of data
     519        before being able to do anything useful.  The following data structures
     520        are initialized based on the input:
     521
     522           - C{extensionNames}: List of extensions available in configuration
     523           - C{preHookMap}: Mapping from action name to list of C{PreActionHook}
     524           - C{postHookMap}: Mapping from action name to list of C{PostActionHook}
     525           - C{functionMap}: Mapping from action name to Python function
     526           - C{indexMap}: Mapping from action name to execution index
     527           - C{peerMap}: Mapping from action name to set of C{RemotePeer}
     528           - C{actionMap}: Mapping from action name to C{_ActionItem}
     529
     530        Once these data structures are set up, the command line is validated to
     531        make sure only valid actions have been requested, and in a sensible
     532        combination.  Then, all of the data is used to build C{self.actionSet},
     533        the set action items to be executed by C{executeActions()}.  This list
     534        might contain either C{_ActionItem} or C{_ManagedActionItem}.
     535
     536        @param actions: Names of actions specified on the command-line.
     537        @param extensions: Extended action configuration (i.e. config.extensions)
     538        @param options: Options configuration (i.e. config.options)
     539        @param peers: Peers configuration (i.e. config.peers)
     540        @param managed: Whether to include managed actions in the set
     541        @param local: Whether to include local actions in the set
     542
     543        @raise ValueError: If one of the specified actions is invalid.
     544        """
     545        extensionNames = _ActionSet._deriveExtensionNames(extensions)
     546        (preHookMap, postHookMap) = _ActionSet._buildHookMaps(options.hooks)
     547        functionMap = _ActionSet._buildFunctionMap(extensions)
     548        indexMap = _ActionSet._buildIndexMap(extensions)
     549        peerMap = _ActionSet._buildPeerMap(options, peers)
     550        actionMap = _ActionSet._buildActionMap(managed, local, extensionNames, functionMap,
     551                                               indexMap, preHookMap, postHookMap, peerMap)
     552        _ActionSet._validateActions(actions, extensionNames)
     553        self.actionSet = _ActionSet._buildActionSet(actions, actionMap)
     554
     555     @staticmethod
     556 -   def _deriveExtensionNames(extensions):
     557        """
     558        Builds a list of extended actions that are available in configuration.
     559        @param extensions: Extended action configuration (i.e. config.extensions)
     560        @return: List of extended action names.
     561        """
     562        extensionNames = []
     563        if extensions is not None and extensions.actions is not None:
     564           for action in extensions.actions:
     565              extensionNames.append(action.name)
     566        return extensionNames
     567
     568     @staticmethod
     569 -   def _buildHookMaps(hooks):
     570        """
     571        Build two mappings from action name to configured C{ActionHook}.
     572        @param hooks: List of pre- and post-action hooks (i.e. config.options.hooks)
     573        @return: Tuple of (pre hook dictionary, post hook dictionary).
     574        """
     575        preHookMap = {}
     576        postHookMap = {}
     577        if hooks is not None:
     578           for hook in hooks:
     579              if hook.before:
     580                 if not hook.action in preHookMap:
     581                    preHookMap[hook.action] = []
     582                 preHookMap[hook.action].append(hook)
     583              elif hook.after:
     584                 if not hook.action in postHookMap:
     585                    postHookMap[hook.action] = []
     586                 postHookMap[hook.action].append(hook)
     587        return (preHookMap, postHookMap)
     588
     589     @staticmethod
     590 -   def _buildFunctionMap(extensions):
     591        """
     592        Builds a mapping from named action to action function.
     593        @param extensions: Extended action configuration (i.e. config.extensions)
     594        @return: Dictionary mapping action to function.
     595        """
     596        functionMap = {}
     597        functionMap['rebuild'] = executeRebuild
     598        functionMap['validate'] = executeValidate
     599        functionMap['initialize'] = executeInitialize
     600        functionMap['collect'] = executeCollect
     601        functionMap['stage'] = executeStage
     602        functionMap['store'] = executeStore
     603        functionMap['purge'] = executePurge
     604        if extensions is not None and extensions.actions is not None:
     605           for action in extensions.actions:
     606              functionMap[action.name] = getFunctionReference(action.module, action.function)
     607        return functionMap
     608
     609     @staticmethod
     610 -   def _buildIndexMap(extensions):
     611        """
     612        Builds a mapping from action name to proper execution index.
     613
     614        If extensions configuration is C{None}, or there are no configured
     615        extended actions, the ordering dictionary will only include the built-in
     616        actions and their standard indices.
     617
     618        Otherwise, if the extensions order mode is C{None} or C{"index"}, actions
     619        will scheduled by explicit index; and if the extensions order mode is
     620        C{"dependency"}, actions will be scheduled using a dependency graph.
     621
     622        @param extensions: Extended action configuration (i.e. config.extensions)
     623
     624        @return: Dictionary mapping action name to integer execution index.
     625        """
     626        indexMap = {}
     627        if extensions is None or extensions.actions is None or extensions.actions == []:
     628           logger.info("Action ordering will use 'index' order mode.")
     629           indexMap['rebuild'] = REBUILD_INDEX
     630           indexMap['validate'] = VALIDATE_INDEX
     631           indexMap['initialize'] = INITIALIZE_INDEX
     632           indexMap['collect'] = COLLECT_INDEX
     633           indexMap['stage'] = STAGE_INDEX
     634           indexMap['store'] = STORE_INDEX
     635           indexMap['purge'] = PURGE_INDEX
     636           logger.debug("Completed filling in action indices for built-in actions.")
     637           logger.info("Action order will be: %s", sortDict(indexMap))
     638        else:
     639           if extensions.orderMode is None or extensions.orderMode == "index":
     640              logger.info("Action ordering will use 'index' order mode.")
     641              indexMap['rebuild'] = REBUILD_INDEX
     642              indexMap['validate'] = VALIDATE_INDEX
     643              indexMap['initialize'] = INITIALIZE_INDEX
     644              indexMap['collect'] = COLLECT_INDEX
     645              indexMap['stage'] = STAGE_INDEX
     646              indexMap['store'] = STORE_INDEX
     647              indexMap['purge'] = PURGE_INDEX
     648              logger.debug("Completed filling in action indices for built-in actions.")
     649              for action in extensions.actions:
     650                 indexMap[action.name] = action.index
     651              logger.debug("Completed filling in action indices for extended actions.")
     652              logger.info("Action order will be: %s", sortDict(indexMap))
     653           else:
     654              logger.info("Action ordering will use 'dependency' order mode.")
     655              graph = DirectedGraph("dependencies")
     656              graph.createVertex("rebuild")
     657              graph.createVertex("validate")
     658              graph.createVertex("initialize")
     659              graph.createVertex("collect")
     660              graph.createVertex("stage")
     661              graph.createVertex("store")
     662              graph.createVertex("purge")
     663              for action in extensions.actions:
     664                 graph.createVertex(action.name)
     665              graph.createEdge("collect", "stage")   # Collect must run before stage, store or purge
     666              graph.createEdge("collect", "store")
     667              graph.createEdge("collect", "purge")
     668              graph.createEdge("stage", "store")     # Stage must run before store or purge
     669              graph.createEdge("stage", "purge")
     670              graph.createEdge("store", "purge")     # Store must run before purge
     671              for action in extensions.actions:
     672                 if action.dependencies.beforeList is not None:
     673                    for vertex in action.dependencies.beforeList:
     674                       try:
     675                          graph.createEdge(action.name, vertex)   # actions that this action must be run before
     676                       except ValueError:
     677                          logger.error("Dependency [%s] on extension [%s] is unknown.", vertex, action.name)
     678                          raise ValueError("Unable to determine proper action order due to invalid dependency.")
     679                 if action.dependencies.afterList is not None:
     680                    for vertex in action.dependencies.afterList:
     681                       try:
     682                          graph.createEdge(vertex, action.name)   # actions that this action must be run after
     683                       except ValueError:
     684                          logger.error("Dependency [%s] on extension [%s] is unknown.", vertex, action.name)
     685                          raise ValueError("Unable to determine proper action order due to invalid dependency.")
     686              try:
     687                 ordering = graph.topologicalSort()
     688                 indexMap = dict([(ordering[i], i+1) for i in range(0, len(ordering))])
     689                 logger.info("Action order will be: %s", ordering)
     690              except ValueError:
     691                 logger.error("Unable to determine proper action order due to dependency recursion.")
     692                 logger.error("Extensions configuration is invalid (check for loops).")
     693                 raise ValueError("Unable to determine proper action order due to dependency recursion.")
     694        return indexMap
     695
     696     @staticmethod
     697 -   def _buildActionMap(managed, local, extensionNames, functionMap, indexMap, preHookMap, postHookMap, peerMap):
     698        """
     699        Builds a mapping from action name to list of action items.
     700
     701        We build either C{_ActionItem} or C{_ManagedActionItem} objects here.
     702
     703        In most cases, the mapping from action name to C{_ActionItem} is 1:1.
     704        The exception is the "all" action, which is a special case.  However, a
     705        list is returned in all cases, just for consistency later.  Each
     706        C{_ActionItem} will be created with a proper function reference and index
     707        value for execution ordering.
     708
     709        The mapping from action name to C{_ManagedActionItem} is always 1:1.
     710        Each managed action item contains a list of peers which the action should
     711        be executed.
     712
     713        @param managed: Whether to include managed actions in the set
     714        @param local: Whether to include local actions in the set
     715        @param extensionNames: List of valid extended action names
     716        @param functionMap: Dictionary mapping action name to Python function
     717        @param indexMap: Dictionary mapping action name to integer execution index
     718        @param preHookMap: Dictionary mapping action name to pre hooks (if any) for the action
     719        @param postHookMap: Dictionary mapping action name to post hooks (if any) for the action
     720        @param peerMap: Dictionary mapping action name to list of remote peers on which to execute the action
     721
     722        @return: Dictionary mapping action name to list of C{_ActionItem} objects.
     723        """
     724        actionMap = {}
     725        for name in extensionNames + VALID_ACTIONS:
     726           if name != 'all': # do this one later
     727              function = functionMap[name]
     728              index = indexMap[name]
     729              actionMap[name] = []
     730              if local:
     731                 (preHooks, postHooks) = _ActionSet._deriveHooks(name, preHookMap, postHookMap)
     732                 actionMap[name].append(_ActionItem(index, name, preHooks, postHooks, function))
     733              if managed:
     734                 if name in peerMap:
     735                    actionMap[name].append(_ManagedActionItem(index, name, peerMap[name]))
     736        actionMap['all'] = actionMap['collect'] + actionMap['stage'] + actionMap['store'] + actionMap['purge']
     737        return actionMap
     738
     739     @staticmethod
     740 -   def _buildPeerMap(options, peers):
     741        """
     742        Build a mapping from action name to list of remote peers.
     743
     744        There will be one entry in the mapping for each managed action.  If there
     745        are no managed peers, the mapping will be empty.  Only managed actions
     746        will be listed in the mapping.
     747
     748        @param options: Option configuration (i.e. config.options)
     749        @param peers: Peers configuration (i.e. config.peers)
     750        """
     751        peerMap = {}
     752        if peers is not None:
     753           if peers.remotePeers is not None:
     754              for peer in peers.remotePeers:
     755                 if peer.managed:
     756                    remoteUser = _ActionSet._getRemoteUser(options, peer)
     757                    rshCommand = _ActionSet._getRshCommand(options, peer)
     758                    cbackCommand = _ActionSet._getCbackCommand(options, peer)
     759                    managedActions = _ActionSet._getManagedActions(options, peer)
     760                    remotePeer = RemotePeer(peer.name, None, options.workingDir, remoteUser, None,
     761                                            options.backupUser, rshCommand, cbackCommand)
     762                    if managedActions is not None:
     763                       for managedAction in managedActions:
     764                          if managedAction in peerMap:
     765                             if remotePeer not in peerMap[managedAction]:
     766                                peerMap[managedAction].append(remotePeer)
     767                          else:
     768                             peerMap[managedAction] = [ remotePeer, ]
     769        return peerMap
     770
     771     @staticmethod
     772 -   def _deriveHooks(action, preHookDict, postHookDict):
     773        """
     774        Derive pre- and post-action hooks, if any, associated with named action.
     775        @param action: Name of action to look up
     776        @param preHookDict: Dictionary mapping pre-action hooks to action name
     777        @param postHookDict: Dictionary mapping post-action hooks to action name
     778        @return Tuple (preHooks, postHooks) per mapping, with None values if there is no hook.
     779        """
     780        preHooks = None
     781        postHooks = None
     782        if preHookDict.has_key(action):
     783           preHooks = preHookDict[action]
     784        if postHookDict.has_key(action):
     785           postHooks = postHookDict[action]
     786        return (preHooks, postHooks)
     787
     788     @staticmethod
     789 -   def _validateActions(actions, extensionNames):
     790        """
     791        Validate that the set of specified actions is sensible.
     792
     793        Any specified action must either be a built-in action or must be among
     794        the extended actions defined in configuration.  The actions from within
     795        L{NONCOMBINE_ACTIONS} may not be combined with other actions.
     796
     797        @param actions: Names of actions specified on the command-line.
     798        @param extensionNames: Names of extensions specified in configuration.
     799
     800        @raise ValueError: If one or more configured actions are not valid.
     801        """
     802        if actions is None or actions == []:
     803           raise ValueError("No actions specified.")
     804        for action in actions:
     805           if action not in VALID_ACTIONS and action not in extensionNames:
     806              raise ValueError("Action [%s] is not a valid action or extended action." % action)
     807        for action in NONCOMBINE_ACTIONS:
     808           if action in actions and actions != [ action, ]:
     809              raise ValueError("Action [%s] may not be combined with other actions." % action)
     810
     811     @staticmethod
     812 -   def _buildActionSet(actions, actionMap):
     813        """
     814        Build set of actions to be executed.
     815
     816        The set of actions is built in the proper order, so C{executeActions} can
     817        spin through the set without thinking about it.  Since we've already validated
     818        that the set of actions is sensible, we don't take any precautions here to
     819        make sure things are combined properly.  If the action is listed, it will
     820        be "scheduled" for execution.
     821
     822        @param actions: Names of actions specified on the command-line.
     823        @param actionMap: Dictionary mapping action name to C{_ActionItem} object.
     824
     825        @return: Set of action items in proper order.
     826        """
     827        actionSet = []
     828        for action in actions:
     829           actionSet.extend(actionMap[action])
     830        actionSet.sort()  # sort the actions in order by index
     831        return actionSet
     832
     833 -   def executeActions(self, configPath, options, config):
     834        """
     835        Executes all actions and extended actions, in the proper order.
     836
     837        Each action (whether built-in or extension) is executed in an identical
     838        manner.  The built-in actions will use only the options and config
     839        values.  We also pass in the config path so that extension modules can
     840        re-parse configuration if they want to, to add in extra information.
     841
     842        @param configPath: Path to configuration file on disk.
     843        @param options: Command-line options to be passed to action functions.
     844        @param config: Parsed configuration to be passed to action functions.
     845
     846        @raise Exception: If there is a problem executing the actions.
     847        """
     848        logger.debug("Executing local actions.")
     849        for actionItem in self.actionSet:
     850           actionItem.executeAction(configPath, options, config)
     851
     852     @staticmethod
     853 -   def _getRemoteUser(options, remotePeer):
     854        """
     855        Gets the remote user associated with a remote peer.
     856        Use peer's if possible, otherwise take from options section.
     857        @param options: OptionsConfig object, as from config.options
     858        @param remotePeer: Configuration-style remote peer object.
     859        @return: Name of remote user associated with remote peer.
     860        """
     861        if remotePeer.remoteUser is None:
     862           return options.backupUser
     863        return remotePeer.remoteUser
     864
     865     @staticmethod
     866 -   def _getRshCommand(options, remotePeer):
     867        """
     868        Gets the RSH command associated with a remote peer.
     869        Use peer's if possible, otherwise take from options section.
     870        @param options: OptionsConfig object, as from config.options
     871        @param remotePeer: Configuration-style remote peer object.
     872        @return: RSH command associated with remote peer.
     873        """
     874        if remotePeer.rshCommand is None:
     875           return options.rshCommand
     876        return remotePeer.rshCommand
     877
     878     @staticmethod
     879 -   def _getCbackCommand(options, remotePeer):
     880        """
     881        Gets the cback command associated with a remote peer.
     882        Use peer's if possible, otherwise take from options section.
     883        @param options: OptionsConfig object, as from config.options
     884        @param remotePeer: Configuration-style remote peer object.
     885        @return: cback command associated with remote peer.
     886        """
     887        if remotePeer.cbackCommand is None:
     888           return options.cbackCommand
     889        return remotePeer.cbackCommand
     890
     891     @staticmethod
     892 -   def _getManagedActions(options, remotePeer):
     893        """
     894        Gets the managed actions list associated with a remote peer.
     895        Use peer's if possible, otherwise take from options section.
     896        @param options: OptionsConfig object, as from config.options
     897        @param remotePeer: Configuration-style remote peer object.
     898        @return: Set of managed actions associated with remote peer.
     899        """
     900        if remotePeer.managedActions is None:
     901           return options.managedActions
     902        return remotePeer.managedActions
     903
     904
     905  #######################################################################
     906  # Utility functions
     907  #######################################################################
     908
     909  ####################
     910  # _usage() function
     911  ####################
     912
     913 -def _usage(fd=sys.stderr):
     914     """
     915     Prints usage information for the cback script.
     916     @param fd: File descriptor used to print information.
     917     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
     918     """
     919     fd.write("\n")
     920     fd.write(" Usage: cback [switches] action(s)\n")
     921     fd.write("\n")
     922     fd.write(" The following switches are accepted:\n")
     923     fd.write("\n")
     924     fd.write("   -h, --help         Display this usage/help listing\n")
     925     fd.write("   -V, --version      Display version information\n")
     926     fd.write("   -b, --verbose      Print verbose output as well as logging to disk\n")
     927     fd.write("   -q, --quiet        Run quietly (display no output to the screen)\n")
     928     fd.write("   -c, --config       Path to config file (default: %s)\n" % DEFAULT_CONFIG)
     929     fd.write("   -f, --full         Perform a full backup, regardless of configuration\n")
     930     fd.write("   -M, --managed      Include managed clients when executing actions\n")
     931     fd.write("   -N, --managed-only Include ONLY managed clients when executing actions\n")
     932     fd.write("   -l, --logfile      Path to logfile (default: %s)\n" % DEFAULT_LOGFILE)
     933     fd.write("   -o, --owner        Logfile ownership, user:group (default: %s:%s)\n" % (DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1]))
     934     fd.write("   -m, --mode         Octal logfile permissions mode (default: %o)\n" % DEFAULT_MODE)
     935     fd.write("   -O, --output       Record some sub-command (i.e. cdrecord) output to the log\n")
     936     fd.write("   -d, --debug        Write debugging information to the log (implies --output)\n")
     937     fd.write("   -s, --stack        Dump a Python stack trace instead of swallowing exceptions\n") # exactly 80 characters in width!
     938     fd.write("   -D, --diagnostics  Print runtime diagnostics to the screen and exit\n")
     939     fd.write("\n")
     940     fd.write(" The following actions may be specified:\n")
     941     fd.write("\n")
     942     fd.write("   all                Take all normal actions (collect, stage, store, purge)\n")
     943     fd.write("   collect            Take the collect action\n")
     944     fd.write("   stage              Take the stage action\n")
     945     fd.write("   store              Take the store action\n")
     946     fd.write("   purge              Take the purge action\n")
     947     fd.write("   rebuild            Rebuild \"this week's\" disc if possible\n")
     948     fd.write("   validate           Validate configuration only\n")
     949     fd.write("   initialize         Initialize media for use with Cedar Backup\n")
     950     fd.write("\n")
     951     fd.write(" You may also specify extended actions that have been defined in\n")
     952     fd.write(" configuration.\n")
     953     fd.write("\n")
     954     fd.write(" You must specify at least one action to take.  More than one of\n")
     955     fd.write(" the \"collect\", \"stage\", \"store\" or \"purge\" actions and/or\n")
     956     fd.write(" extended actions may be specified in any arbitrary order; they\n")
     957     fd.write(" will be executed in a sensible order.  The \"all\", \"rebuild\",\n")
     958     fd.write(" \"validate\", and \"initialize\" actions may not be combined with\n")
     959     fd.write(" other actions.\n")
     960     fd.write("\n")
     961
     962
     963  ######################
     964  # _version() function
     965  ######################
     966
     967 -def _version(fd=sys.stdout):
     968     """
     969     Prints version information for the cback script.
     970     @param fd: File descriptor used to print information.
     971     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
     972     """
     973     fd.write("\n")
     974     fd.write(" Cedar Backup version %s, released %s.\n" % (VERSION, DATE))
     975     fd.write("\n")
     976     fd.write(" Copyright (c) %s %s <%s>.\n" % (COPYRIGHT, AUTHOR, EMAIL))
     977     fd.write(" See CREDITS for a list of included code and other contributors.\n")
     978     fd.write(" This is free software; there is NO warranty.  See the\n")
     979     fd.write(" GNU General Public License version 2 for copying conditions.\n")
     980     fd.write("\n")
     981     fd.write(" Use the --help option for usage information.\n")
     982     fd.write("\n")
     983
     984
     985  ##########################
     986  # _diagnostics() function
     987  ##########################
     988
     989 -def _diagnostics(fd=sys.stdout):
     990     """
     991     Prints runtime diagnostics information.
     992     @param fd: File descriptor used to print information.
     993     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
     994     """
     995     fd.write("\n")
     996     fd.write("Diagnostics:\n")
     997     fd.write("\n")
     998     Diagnostics().printDiagnostics(fd=fd, prefix="   ")
     999     fd.write("\n")
    1000
    1001
    1002  ##########################
    1003  # setupLogging() function
    1004  ##########################
    1005
    1006 -def setupLogging(options):
    1007     """
    1008     Set up logging based on command-line options.
    1009
    1010     There are two kinds of logging: flow logging and output logging.  Output
    1011     logging contains information about system commands executed by Cedar Backup,
    1012     for instance the calls to C{mkisofs} or C{mount}, etc.  Flow logging
    1013     contains error and informational messages used to understand program flow.
    1014     Flow log messages and output log messages are written to two different
    1015     loggers target (C{CedarBackup2.log} and C{CedarBackup2.output}).  Flow log
    1016     messages are written at the ERROR, INFO and DEBUG log levels, while output
    1017     log messages are generally only written at the INFO log level.
    1018
    1019     By default, output logging is disabled.  When the C{options.output} or
    1020     C{options.debug} flags are set, output logging will be written to the
    1021     configured logfile.  Output logging is never written to the screen.
    1022
    1023     By default, flow logging is enabled at the ERROR level to the screen and at
    1024     the INFO level to the configured logfile.  If the C{options.quiet} flag is
    1025     set, flow logging is enabled at the INFO level to the configured logfile
    1026     only (i.e. no output will be sent to the screen).  If the C{options.verbose}
    1027     flag is set, flow logging is enabled at the INFO level to both the screen
    1028     and the configured logfile.  If the C{options.debug} flag is set, flow
    1029     logging is enabled at the DEBUG level to both the screen and the configured
    1030     logfile.
    1031
    1032     @param options: Command-line options.
    1033     @type options: L{Options} object
    1034
    1035     @return: Path to logfile on disk.
    1036     """
    1037     logfile = _setupLogfile(options)
    1038     _setupFlowLogging(logfile, options)
    1039     _setupOutputLogging(logfile, options)
    1040     return logfile
    1041
    1042 -def _setupLogfile(options):
    1043     """
    1044     Sets up and creates logfile as needed.
    1045
    1046     If the logfile already exists on disk, it will be left as-is, under the
    1047     assumption that it was created with appropriate ownership and permissions.
    1048     If the logfile does not exist on disk, it will be created as an empty file.
    1049     Ownership and permissions will remain at their defaults unless user/group
    1050     and/or mode are set in the options.  We ignore errors setting the indicated
    1051     user and group.
    1052
    1053     @note: This function is vulnerable to a race condition.  If the log file
    1054     does not exist when the function is run, it will attempt to create the file
    1055     as safely as possible (using C{O_CREAT}).  If two processes attempt to
    1056     create the file at the same time, then one of them will fail.  In practice,
    1057     this shouldn't really be a problem, but it might happen occassionally if two
    1058     instances of cback run concurrently or if cback collides with logrotate or
    1059     something.
    1060
    1061     @param options: Command-line options.
    1062
    1063     @return: Path to logfile on disk.
    1064     """
    1065     if options.logfile is None:
    1066        logfile = DEFAULT_LOGFILE
    1067     else:
    1068        logfile = options.logfile
    1069     if not os.path.exists(logfile):
    1070        mode = DEFAULT_MODE if options.mode is None else options.mode
    1071        orig = os.umask(0) # Per os.open(), "When computing mode, the current umask value is first masked out"
    1072        try:
    1073           fd = os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, mode)
    1074           with os.fdopen(fd, "a+") as f:
    1075              f.write("")
    1076        finally:
    1077           os.umask(orig)
    1078        try:
    1079           if options.owner is None or len(options.owner) < 2:
    1080              (uid, gid) = getUidGid(DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1])
    1081           else:
    1082              (uid, gid) = getUidGid(options.owner[0], options.owner[1])
    1083           os.chown(logfile, uid, gid)
    1084        except: pass
    1085     return logfile
    1086
    1087 -def _setupFlowLogging(logfile, options):
    1088     """
    1089     Sets up flow logging.
    1090     @param logfile: Path to logfile on disk.
    1091     @param options: Command-line options.
    1092     """
    1093     flowLogger = logging.getLogger("CedarBackup2.log")
    1094     flowLogger.setLevel(logging.DEBUG)    # let the logger see all messages
    1095     _setupDiskFlowLogging(flowLogger, logfile, options)
    1096     _setupScreenFlowLogging(flowLogger, options)
    1097
    1098 -def _setupOutputLogging(logfile, options):
    1099     """
    1100     Sets up command output logging.
    1101     @param logfile: Path to logfile on disk.
    1102     @param options: Command-line options.
    1103     """
    1104     outputLogger = logging.getLogger("CedarBackup2.output")
    1105     outputLogger.setLevel(logging.DEBUG)      # let the logger see all messages
    1106     _setupDiskOutputLogging(outputLogger, logfile, options)
    1107
    1108 -def _setupDiskFlowLogging(flowLogger, logfile, options):
    1109     """
    1110     Sets up on-disk flow logging.
    1111     @param flowLogger: Python flow logger object.
    1112     @param logfile: Path to logfile on disk.
    1113     @param options: Command-line options.
    1114     """
    1115     formatter = logging.Formatter(fmt=DISK_LOG_FORMAT, datefmt=DATE_FORMAT)
    1116     handler = logging.FileHandler(logfile, mode="a")
    1117     handler.setFormatter(formatter)
    1118     if options.debug:
    1119        handler.setLevel(logging.DEBUG)
    1120     else:
    1121        handler.setLevel(logging.INFO)
    1122     flowLogger.addHandler(handler)
    1123
    1124 -def _setupScreenFlowLogging(flowLogger, options):
    1125     """
    1126     Sets up on-screen flow logging.
    1127     @param flowLogger: Python flow logger object.
    1128     @param options: Command-line options.
    1129     """
    1130     formatter = logging.Formatter(fmt=SCREEN_LOG_FORMAT)
    1131     handler = logging.StreamHandler(SCREEN_LOG_STREAM)
    1132     handler.setFormatter(formatter)
    1133     if options.quiet:
    1134        handler.setLevel(logging.CRITICAL)  # effectively turn it off
    1135     elif options.verbose:
    1136        if options.debug:
    1137           handler.setLevel(logging.DEBUG)
    1138        else:
    1139           handler.setLevel(logging.INFO)
    1140     else:
    1141        handler.setLevel(logging.ERROR)
    1142     flowLogger.addHandler(handler)
    1143
    1144 -def _setupDiskOutputLogging(outputLogger, logfile, options):
    1145     """
    1146     Sets up on-disk command output logging.
    1147     @param outputLogger: Python command output logger object.
    1148     @param logfile: Path to logfile on disk.
    1149     @param options: Command-line options.
    1150     """
    1151     formatter = logging.Formatter(fmt=DISK_OUTPUT_FORMAT, datefmt=DATE_FORMAT)
    1152     handler = logging.FileHandler(logfile, mode="a")
    1153     handler.setFormatter(formatter)
    1154     if options.debug or options.output:
    1155        handler.setLevel(logging.DEBUG)
    1156     else:
    1157        handler.setLevel(logging.CRITICAL)  # effectively turn it off
    1158     outputLogger.addHandler(handler)
    1159
    1160
    1161  ###############################
    1162  # setupPathResolver() function
    1163  ###############################
    1164
    1165 -def setupPathResolver(config):
    1166     """
    1167     Set up the path resolver singleton based on configuration.
    1168
    1169     Cedar Backup's path resolver is implemented in terms of a singleton, the
    1170     L{PathResolverSingleton} class.  This function takes options configuration,
    1171     converts it into the dictionary form needed by the singleton, and then
    1172     initializes the singleton.  After that, any function that needs to resolve
    1173     the path of a command can use the singleton.
    1174
    1175     @param config: Configuration
    1176     @type config: L{Config} object
    1177     """
    1178     mapping = {}
    1179     if config.options.overrides is not None:
    1180        for override in config.options.overrides:
    1181           mapping[override.command] = override.absolutePath
    1182     singleton = PathResolverSingleton()
    1183     singleton.fill(mapping)
    1184
    1185
    1186  #########################################################################
    1187  # Options class definition
    1188  ########################################################################
    1189
    1190 -class Options(object):
    1191
    1192     ######################
    1193     # Class documentation
    1194     ######################
    1195
    1196     """
    1197     Class representing command-line options for the cback script.
    1198
    1199     The C{Options} class is a Python object representation of the command-line
    1200     options of the cback script.
    1201
    1202     The object representation is two-way: a command line string or a list of
    1203     command line arguments can be used to create an C{Options} object, and then
    1204     changes to the object can be propogated back to a list of command-line
    1205     arguments or to a command-line string.  An C{Options} object can even be
    1206     created from scratch programmatically (if you have a need for that).
    1207
    1208     There are two main levels of validation in the C{Options} class.  The first
    1209     is field-level validation.  Field-level validation comes into play when a
    1210     given field in an object is assigned to or updated.  We use Python's
    1211     C{property} functionality to enforce specific validations on field values,
    1212     and in some places we even use customized list classes to enforce
    1213     validations on list members.  You should expect to catch a C{ValueError}
    1214     exception when making assignments to fields if you are programmatically
    1215     filling an object.
    1216
    1217     The second level of validation is post-completion validation.  Certain
    1218     validations don't make sense until an object representation of options is
    1219     fully "complete".  We don't want these validations to apply all of the time,
    1220     because it would make building up a valid object from scratch a real pain.
    1221     For instance, we might have to do things in the right order to keep from
    1222     throwing exceptions, etc.
    1223
    1224     All of these post-completion validations are encapsulated in the
    1225     L{Options.validate} method.  This method can be called at any time by a
    1226     client, and will always be called immediately after creating a C{Options}
    1227     object from a command line and before exporting a C{Options} object back to
    1228     a command line.  This way, we get acceptable ease-of-use but we also don't
    1229     accept or emit invalid command lines.
    1230
    1231     @note: Lists within this class are "unordered" for equality comparisons.
    1232
    1233     @sort: __init__, __repr__, __str__, __cmp__
    1234     """
    1235
    1236     ##############
    1237     # Constructor
    1238     ##############
    1239
    1240 -   def __init__(self, argumentList=None, argumentString=None, validate=True):
    1241        """
    1242        Initializes an options object.
    1243
    1244        If you initialize the object without passing either C{argumentList} or
    1245        C{argumentString}, the object will be empty and will be invalid until it
    1246        is filled in properly.
    1247
    1248        No reference to the original arguments is saved off by this class.  Once
    1249        the data has been parsed (successfully or not) this original information
    1250        is discarded.
    1251
    1252        The argument list is assumed to be a list of arguments, not including the
    1253        name of the command, something like C{sys.argv[1:]}.  If you pass
    1254        C{sys.argv} instead, things are not going to work.
    1255
    1256        The argument string will be parsed into an argument list by the
    1257        L{util.splitCommandLine} function (see the documentation for that
    1258        function for some important notes about its limitations).  There is an
    1259        assumption that the resulting list will be equivalent to C{sys.argv[1:]},
    1260        just like C{argumentList}.
    1261
    1262        Unless the C{validate} argument is C{False}, the L{Options.validate}
    1263        method will be called (with its default arguments) after successfully
    1264        parsing any passed-in command line.  This validation ensures that
    1265        appropriate actions, etc. have been specified.  Keep in mind that even if
    1266        C{validate} is C{False}, it might not be possible to parse the passed-in
    1267        command line, so an exception might still be raised.
    1268
    1269        @note: The command line format is specified by the L{_usage} function.
    1270        Call L{_usage} to see a usage statement for the cback script.
    1271
    1272        @note: It is strongly suggested that the C{validate} option always be set
    1273        to C{True} (the default) unless there is a specific need to read in
    1274        invalid command line arguments.
    1275
    1276        @param argumentList: Command line for a program.
    1277        @type argumentList: List of arguments, i.e. C{sys.argv}
    1278
    1279        @param argumentString: Command line for a program.
    1280        @type argumentString: String, i.e. "cback --verbose stage store"
    1281
    1282        @param validate: Validate the command line after parsing it.
    1283        @type validate: Boolean true/false.
    1284
    1285        @raise getopt.GetoptError: If the command-line arguments could not be parsed.
    1286        @raise ValueError: If the command-line arguments are invalid.
    1287        """
    1288        self._help = False
    1289        self._version = False
    1290        self._verbose = False
    1291        self._quiet = False
    1292        self._config = None
    1293        self._full = False
    1294        self._managed = False
    1295        self._managedOnly = False
    1296        self._logfile = None
    1297        self._owner = None
    1298        self._mode = None
    1299        self._output = False
    1300        self._debug = False
    1301        self._stacktrace = False
    1302        self._diagnostics = False
    1303        self._actions = None
    1304        self.actions = []    # initialize to an empty list; remainder are OK
    1305        if argumentList is not None and argumentString is not None:
    1306           raise ValueError("Use either argumentList or argumentString, but not both.")
    1307        if argumentString is not None:
    1308           argumentList = splitCommandLine(argumentString)
    1309        if argumentList is not None:
    1310           self._parseArgumentList(argumentList)
    1311           if validate:
    1312              self.validate()
    1313
    1314
    1315     #########################
    1316     # String representations
    1317     #########################
    1318
    1319 -   def __repr__(self):
    1320        """
    1321        Official string representation for class instance.
    1322        """
    1323        return self.buildArgumentString(validate=False)
    1324
    1325 -   def __str__(self):
    1326        """
    1327        Informal string representation for class instance.
    1328        """
    1329        return self.__repr__()
    1330
    1331
    1332     #############################
    1333     # Standard comparison method
    1334     #############################
    1335
    1336 -   def __cmp__(self, other):
    1337        """
    1338        Definition of equals operator for this class.
    1339        Lists within this class are "unordered" for equality comparisons.
    1340        @param other: Other object to compare to.
    1341        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1342        """
    1343        if other is None:
    1344           return 1
    1345        if self.help != other.help:
    1346           if self.help < other.help:
    1347              return -1
    1348           else:
    1349              return 1
    1350        if self.version != other.version:
    1351           if self.version < other.version:
    1352              return -1
    1353           else:
    1354              return 1
    1355        if self.verbose != other.verbose:
    1356           if self.verbose < other.verbose:
    1357              return -1
    1358           else:
    1359              return 1
    1360        if self.quiet != other.quiet:
    1361           if self.quiet < other.quiet:
    1362              return -1
    1363           else:
    1364              return 1
    1365        if self.config != other.config:
    1366           if self.config < other.config:
    1367              return -1
    1368           else:
    1369              return 1
    1370        if self.full != other.full:
    1371           if self.full < other.full:
    1372              return -1
    1373           else:
    1374              return 1
    1375        if self.managed != other.managed:
    1376           if self.managed < other.managed:
    1377              return -1
    1378           else:
    1379              return 1
    1380        if self.managedOnly != other.managedOnly:
    1381           if self.managedOnly < other.managedOnly:
    1382              return -1
    1383           else:
    1384              return 1
    1385        if self.logfile != other.logfile:
    1386           if self.logfile < other.logfile:
    1387              return -1
    1388           else:
    1389              return 1
    1390        if self.owner != other.owner:
    1391           if self.owner < other.owner:
    1392              return -1
    1393           else:
    1394              return 1
    1395        if self.mode != other.mode:
    1396           if self.mode < other.mode:
    1397              return -1
    1398           else:
    1399              return 1
    1400        if self.output != other.output:
    1401           if self.output < other.output:
    1402              return -1
    1403           else:
    1404              return 1
    1405        if self.debug != other.debug:
    1406           if self.debug < other.debug:
    1407              return -1
    1408           else:
    1409              return 1
    1410        if self.stacktrace != other.stacktrace:
    1411           if self.stacktrace < other.stacktrace:
    1412              return -1
    1413           else:
    1414              return 1
    1415        if self.diagnostics != other.diagnostics:
    1416           if self.diagnostics < other.diagnostics:
    1417              return -1
    1418           else:
    1419              return 1
    1420        if self.actions != other.actions:
    1421           if self.actions < other.actions:
    1422              return -1
    1423           else:
    1424              return 1
    1425        return 0
    1426
    1427
    1428     #############
    1429     # Properties
    1430     #############
    1431
    1432 -   def _setHelp(self, value):
    1433        """
    1434        Property target used to set the help flag.
    1435        No validations, but we normalize the value to C{True} or C{False}.
    1436        """
    1437        if value:
    1438           self._help = True
    1439        else:
    1440           self._help = False
    1441
    1442 -   def _getHelp(self):
    1443        """
    1444        Property target used to get the help flag.
    1445        """
    1446        return self._help
    1447
    1448 -   def _setVersion(self, value):
    1449        """
    1450        Property target used to set the version flag.
    1451        No validations, but we normalize the value to C{True} or C{False}.
    1452        """
    1453        if value:
    1454           self._version = True
    1455        else:
    1456           self._version = False
    1457
    1458 -   def _getVersion(self):
    1459        """
    1460        Property target used to get the version flag.
    1461        """
    1462        return self._version
    1463
    1464 -   def _setVerbose(self, value):
    1465        """
    1466        Property target used to set the verbose flag.
    1467        No validations, but we normalize the value to C{True} or C{False}.
    1468        """
    1469        if value:
    1470           self._verbose = True
    1471        else:
    1472           self._verbose = False
    1473
    1474 -   def _getVerbose(self):
    1475        """
    1476        Property target used to get the verbose flag.
    1477        """
    1478        return self._verbose
    1479
    1480 -   def _setQuiet(self, value):
    1481        """
    1482        Property target used to set the quiet flag.
    1483        No validations, but we normalize the value to C{True} or C{False}.
    1484        """
    1485        if value:
    1486           self._quiet = True
    1487        else:
    1488           self._quiet = False
    1489
    1490 -   def _getQuiet(self):
    1491        """
    1492        Property target used to get the quiet flag.
    1493        """
    1494        return self._quiet
    1495
    1496 -   def _setConfig(self, value):
    1497        """
    1498        Property target used to set the config parameter.
    1499        """
    1500        if value is not None:
    1501           if len(value) < 1:
    1502              raise ValueError("The config parameter must be a non-empty string.")
    1503        self._config = value
    1504
    1505 -   def _getConfig(self):
    1506        """
    1507        Property target used to get the config parameter.
    1508        """
    1509        return self._config
    1510
    1511 -   def _setFull(self, value):
    1512        """
    1513        Property target used to set the full flag.
    1514        No validations, but we normalize the value to C{True} or C{False}.
    1515        """
    1516        if value:
    1517           self._full = True
    1518        else:
    1519           self._full = False
    1520
    1521 -   def _getFull(self):
    1522        """
    1523        Property target used to get the full flag.
    1524        """
    1525        return self._full
    1526
    1527 -   def _setManaged(self, value):
    1528        """
    1529        Property target used to set the managed flag.
    1530        No validations, but we normalize the value to C{True} or C{False}.
    1531        """
    1532        if value:
    1533           self._managed = True
    1534        else:
    1535           self._managed = False
    1536
    1537 -   def _getManaged(self):
    1538        """
    1539        Property target used to get the managed flag.
    1540        """
    1541        return self._managed
    1542
    1543 -   def _setManagedOnly(self, value):
    1544        """
    1545        Property target used to set the managedOnly flag.
    1546        No validations, but we normalize the value to C{True} or C{False}.
    1547        """
    1548        if value:
    1549           self._managedOnly = True
    1550        else:
    1551           self._managedOnly = False
    1552
    1553 -   def _getManagedOnly(self):
    1554        """
    1555        Property target used to get the managedOnly flag.
    1556        """
    1557        return self._managedOnly
    1558
    1559 -   def _setLogfile(self, value):
    1560        """
    1561        Property target used to set the logfile parameter.
    1562        @raise ValueError: If the value cannot be encoded properly.
    1563        """
    1564        if value is not None:
    1565           if len(value) < 1:
    1566              raise ValueError("The logfile parameter must be a non-empty string.")
    1567        self._logfile = encodePath(value)
    1568
    1569 -   def _getLogfile(self):
    1570        """
    1571        Property target used to get the logfile parameter.
    1572        """
    1573        return self._logfile
    1574
    1575 -   def _setOwner(self, value):
    1576        """
    1577        Property target used to set the owner parameter.
    1578        If not C{None}, the owner must be a C{(user,group)} tuple or list.
    1579        Strings (and inherited children of strings) are explicitly disallowed.
    1580        The value will be normalized to a tuple.
    1581        @raise ValueError: If the value is not valid.
    1582        """
    1583        if value is None:
    1584           self._owner = None
    1585        else:
    1586           if isinstance(value, str):
    1587              raise ValueError("Must specify user and group tuple for owner parameter.")
    1588           if len(value) != 2:
    1589              raise ValueError("Must specify user and group tuple for owner parameter.")
    1590           if len(value[0]) < 1 or len(value[1]) < 1:
    1591              raise ValueError("User and group tuple values must be non-empty strings.")
    1592           self._owner = (value[0], value[1])
    1593
    1594 -   def _getOwner(self):
    1595        """
    1596        Property target used to get the owner parameter.
    1597        The parameter is a tuple of C{(user, group)}.
    1598        """
    1599        return self._owner
    1600
    1601 -   def _setMode(self, value):
    1602        """
    1603        Property target used to set the mode parameter.
    1604        """
    1605        if value is None:
    1606           self._mode = None
    1607        else:
    1608           try:
    1609              if isinstance(value, str):
    1610                 value = int(value, 8)
    1611              else:
    1612                 value = int(value)
    1613           except TypeError:
    1614              raise ValueError("Mode must be an octal integer >= 0, i.e. 644.")
    1615           if value < 0:
    1616              raise ValueError("Mode must be an octal integer >= 0. i.e. 644.")
    1617           self._mode = value
    1618
    1619 -   def _getMode(self):
    1620        """
    1621        Property target used to get the mode parameter.
    1622        """
    1623        return self._mode
    1624
    1625 -   def _setOutput(self, value):
    1626        """
    1627        Property target used to set the output flag.
    1628        No validations, but we normalize the value to C{True} or C{False}.
    1629        """
    1630        if value:
    1631           self._output = True
    1632        else:
    1633           self._output = False
    1634
    1635 -   def _getOutput(self):
    1636        """
    1637        Property target used to get the output flag.
    1638        """
    1639        return self._output
    1640
    1641 -   def _setDebug(self, value):
    1642        """
    1643        Property target used to set the debug flag.
    1644        No validations, but we normalize the value to C{True} or C{False}.
    1645        """
    1646        if value:
    1647           self._debug = True
    1648        else:
    1649           self._debug = False
    1650
    1651 -   def _getDebug(self):
    1652        """
    1653        Property target used to get the debug flag.
    1654        """
    1655        return self._debug
    1656
    1657 -   def _setStacktrace(self, value):
    1658        """
    1659        Property target used to set the stacktrace flag.
    1660        No validations, but we normalize the value to C{True} or C{False}.
    1661        """
    1662        if value:
    1663           self._stacktrace = True
    1664        else:
    1665           self._stacktrace = False
    1666
    1667 -   def _getStacktrace(self):
    1668        """
    1669        Property target used to get the stacktrace flag.
    1670        """
    1671        return self._stacktrace
    1672
    1673 -   def _setDiagnostics(self, value):
    1674        """
    1675        Property target used to set the diagnostics flag.
    1676        No validations, but we normalize the value to C{True} or C{False}.
    1677        """
    1678        if value:
    1679           self._diagnostics = True
    1680        else:
    1681           self._diagnostics = False
    1682
    1683 -   def _getDiagnostics(self):
    1684        """
    1685        Property target used to get the diagnostics flag.
    1686        """
    1687        return self._diagnostics
    1688
    1689 -   def _setActions(self, value):
    1690        """
    1691        Property target used to set the actions list.
    1692        We don't restrict the contents of actions.  They're validated somewhere else.
    1693        @raise ValueError: If the value is not valid.
    1694        """
    1695        if value is None:
    1696           self._actions = None
    1697        else:
    1698           try:
    1699              saved = self._actions
    1700              self._actions = []
    1701              self._actions.extend(value)
    1702           except Exception, e:
    1703              self._actions = saved
    1704              raise e
    1705
    1706 -   def _getActions(self):
    1707        """
    1708        Property target used to get the actions list.
    1709        """
    1710        return self._actions
    1711
    1712     help = property(_getHelp, _setHelp, None, "Command-line help (C{-h,--help}) flag.")
    1713     version = property(_getVersion, _setVersion, None, "Command-line version (C{-V,--version}) flag.")
    1714     verbose = property(_getVerbose, _setVerbose, None, "Command-line verbose (C{-b,--verbose}) flag.")
    1715     quiet = property(_getQuiet, _setQuiet, None, "Command-line quiet (C{-q,--quiet}) flag.")
    1716     config = property(_getConfig, _setConfig, None, "Command-line configuration file (C{-c,--config}) parameter.")
    1717     full = property(_getFull, _setFull, None, "Command-line full-backup (C{-f,--full}) flag.")
    1718     managed = property(_getManaged, _setManaged, None, "Command-line managed (C{-M,--managed}) flag.")
    1719     managedOnly = property(_getManagedOnly, _setManagedOnly, None, "Command-line managed-only (C{-N,--managed-only}) flag.")
    1720     logfile = property(_getLogfile, _setLogfile, None, "Command-line logfile (C{-l,--logfile}) parameter.")
    1721     owner = property(_getOwner, _setOwner, None, "Command-line owner (C{-o,--owner}) parameter, as tuple C{(user,group)}.")
    1722     mode = property(_getMode, _setMode, None, "Command-line mode (C{-m,--mode}) parameter.")
    1723     output = property(_getOutput, _setOutput, None, "Command-line output (C{-O,--output}) flag.")
    1724     debug = property(_getDebug, _setDebug, None, "Command-line debug (C{-d,--debug}) flag.")
    1725     stacktrace = property(_getStacktrace, _setStacktrace, None, "Command-line stacktrace (C{-s,--stack}) flag.")
    1726     diagnostics = property(_getDiagnostics, _setDiagnostics, None, "Command-line diagnostics (C{-D,--diagnostics}) flag.")
    1727     actions = property(_getActions, _setActions, None, "Command-line actions list.")
    1728
    1729
    1730     ##################
    1731     # Utility methods
    1732     ##################
    1733
    1734 -   def validate(self):
    1735        """
    1736        Validates command-line options represented by the object.
    1737
    1738        Unless C{--help} or C{--version} are supplied, at least one action must
    1739        be specified.  Other validations (as for allowed values for particular
    1740        options) will be taken care of at assignment time by the properties
    1741        functionality.
    1742
    1743        @note: The command line format is specified by the L{_usage} function.
    1744        Call L{_usage} to see a usage statement for the cback script.
    1745
    1746        @raise ValueError: If one of the validations fails.
    1747        """
    1748        if not self.help and not self.version and not self.diagnostics:
    1749           if self.actions is None or len(self.actions) == 0:
    1750              raise ValueError("At least one action must be specified.")
    1751        if self.managed and self.managedOnly:
    1752           raise ValueError("The --managed and --managed-only options may not be combined.")
    1753
    1754 -   def buildArgumentList(self, validate=True):
    1755        """
    1756        Extracts options into a list of command line arguments.
    1757
    1758        The original order of the various arguments (if, indeed, the object was
    1759        initialized with a command-line) is not preserved in this generated
    1760        argument list.   Besides that, the argument list is normalized to use the
    1761        long option names (i.e. --version rather than -V).  The resulting list
    1762        will be suitable for passing back to the constructor in the
    1763        C{argumentList} parameter.  Unlike L{buildArgumentString}, string
    1764        arguments are not quoted here, because there is no need for it.
    1765
    1766        Unless the C{validate} parameter is C{False}, the L{Options.validate}
    1767        method will be called (with its default arguments) against the
    1768        options before extracting the command line.  If the options are not valid,
    1769        then an argument list will not be extracted.
    1770
    1771        @note: It is strongly suggested that the C{validate} option always be set
    1772        to C{True} (the default) unless there is a specific need to extract an
    1773        invalid command line.
    1774
    1775        @param validate: Validate the options before extracting the command line.
    1776        @type validate: Boolean true/false.
    1777
    1778        @return: List representation of command-line arguments.
    1779        @raise ValueError: If options within the object are invalid.
    1780        """
    1781        if validate:
    1782           self.validate()
    1783        argumentList = []
    1784        if self._help:
    1785           argumentList.append("--help")
    1786        if self.version:
    1787           argumentList.append("--version")
    1788        if self.verbose:
    1789           argumentList.append("--verbose")
    1790        if self.quiet:
    1791           argumentList.append("--quiet")
    1792        if self.config is not None:
    1793           argumentList.append("--config")
    1794           argumentList.append(self.config)
    1795        if self.full:
    1796           argumentList.append("--full")
    1797        if self.managed:
    1798           argumentList.append("--managed")
    1799        if self.managedOnly:
    1800           argumentList.append("--managed-only")
    1801        if self.logfile is not None:
    1802           argumentList.append("--logfile")
    1803           argumentList.append(self.logfile)
    1804        if self.owner is not None:
    1805           argumentList.append("--owner")
    1806           argumentList.append("%s:%s" % (self.owner[0], self.owner[1]))
    1807        if self.mode is not None:
    1808           argumentList.append("--mode")
    1809           argumentList.append("%o" % self.mode)
    1810        if self.output:
    1811           argumentList.append("--output")
    1812        if self.debug:
    1813           argumentList.append("--debug")
    1814        if self.stacktrace:
    1815           argumentList.append("--stack")
    1816        if self.diagnostics:
    1817           argumentList.append("--diagnostics")
    1818        if self.actions is not None:
    1819           for action in self.actions:
    1820              argumentList.append(action)
    1821        return argumentList
    1822
    1823 -   def buildArgumentString(self, validate=True):
    1824        """
    1825        Extracts options into a string of command-line arguments.
    1826
    1827        The original order of the various arguments (if, indeed, the object was
    1828        initialized with a command-line) is not preserved in this generated
    1829        argument string.   Besides that, the argument string is normalized to use
    1830        the long option names (i.e. --version rather than -V) and to quote all
    1831        string arguments with double quotes (C{"}).  The resulting string will be
    1832        suitable for passing back to the constructor in the C{argumentString}
    1833        parameter.
    1834
    1835        Unless the C{validate} parameter is C{False}, the L{Options.validate}
    1836        method will be called (with its default arguments) against the options
    1837        before extracting the command line.  If the options are not valid, then
    1838        an argument string will not be extracted.
    1839
    1840        @note: It is strongly suggested that the C{validate} option always be set
    1841        to C{True} (the default) unless there is a specific need to extract an
    1842        invalid command line.
    1843
    1844        @param validate: Validate the options before extracting the command line.
    1845        @type validate: Boolean true/false.
    1846
    1847        @return: String representation of command-line arguments.
    1848        @raise ValueError: If options within the object are invalid.
    1849        """
    1850        if validate:
    1851           self.validate()
    1852        argumentString = ""
    1853        if self._help:
    1854           argumentString += "--help "
    1855        if self.version:
    1856           argumentString += "--version "
    1857        if self.verbose:
    1858           argumentString += "--verbose "
    1859        if self.quiet:
    1860           argumentString += "--quiet "
    1861        if self.config is not None:
    1862           argumentString += "--config \"%s\" " % self.config
    1863        if self.full:
    1864           argumentString += "--full "
    1865        if self.managed:
    1866           argumentString += "--managed "
    1867        if self.managedOnly:
    1868           argumentString += "--managed-only "
    1869        if self.logfile is not None:
    1870           argumentString += "--logfile \"%s\" " % self.logfile
    1871        if self.owner is not None:
    1872           argumentString += "--owner \"%s:%s\" " % (self.owner[0], self.owner[1])
    1873        if self.mode is not None:
    1874           argumentString += "--mode %o " % self.mode
    1875        if self.output:
    1876           argumentString += "--output "
    1877        if self.debug:
    1878           argumentString += "--debug "
    1879        if self.stacktrace:
    1880           argumentString += "--stack "
    1881        if self.diagnostics:
    1882           argumentString += "--diagnostics "
    1883        if self.actions is not None:
    1884           for action in self.actions:
    1885              argumentString +=  "\"%s\" " % action
    1886        return argumentString
    1887
    1888 -   def _parseArgumentList(self, argumentList):
    1889        """
    1890        Internal method to parse a list of command-line arguments.
    1891
    1892        Most of the validation we do here has to do with whether the arguments
    1893        can be parsed and whether any values which exist are valid.  We don't do
    1894        any validation as to whether required elements exist or whether elements
    1895        exist in the proper combination (instead, that's the job of the
    1896        L{validate} method).
    1897
    1898        For any of the options which supply parameters, if the option is
    1899        duplicated with long and short switches (i.e. C{-l} and a C{--logfile})
    1900        then the long switch is used.  If the same option is duplicated with the
    1901        same switch (long or short), then the last entry on the command line is
    1902        used.
    1903
    1904        @param argumentList: List of arguments to a command.
    1905        @type argumentList: List of arguments to a command, i.e. C{sys.argv[1:]}
    1906
    1907        @raise ValueError: If the argument list cannot be successfully parsed.
    1908        """
    1909        switches = { }
    1910        opts, self.actions = getopt.getopt(argumentList, SHORT_SWITCHES, LONG_SWITCHES)
    1911        for o, a in opts:  # push the switches into a hash
    1912           switches[o] = a
    1913        if switches.has_key("-h") or switches.has_key("--help"):
    1914           self.help = True
    1915        if switches.has_key("-V") or switches.has_key("--version"):
    1916           self.version = True
    1917        if switches.has_key("-b") or switches.has_key("--verbose"):
    1918           self.verbose = True
    1919        if switches.has_key("-q") or switches.has_key("--quiet"):
    1920           self.quiet = True
    1921        if switches.has_key("-c"):
    1922           self.config = switches["-c"]
    1923        if switches.has_key("--config"):
    1924           self.config = switches["--config"]
    1925        if switches.has_key("-f") or switches.has_key("--full"):
    1926           self.full = True
    1927        if switches.has_key("-M") or switches.has_key("--managed"):
    1928           self.managed = True
    1929        if switches.has_key("-N") or switches.has_key("--managed-only"):
    1930           self.managedOnly = True
    1931        if switches.has_key("-l"):
    1932           self.logfile = switches["-l"]
    1933        if switches.has_key("--logfile"):
    1934           self.logfile = switches["--logfile"]
    1935        if switches.has_key("-o"):
    1936           self.owner = switches["-o"].split(":", 1)
    1937        if switches.has_key("--owner"):
    1938           self.owner = switches["--owner"].split(":", 1)
    1939        if switches.has_key("-m"):
    1940           self.mode = switches["-m"]
    1941        if switches.has_key("--mode"):
    1942           self.mode = switches["--mode"]
    1943        if switches.has_key("-O") or switches.has_key("--output"):
    1944           self.output = True
    1945        if switches.has_key("-d") or switches.has_key("--debug"):
    1946           self.debug = True
    1947        if switches.has_key("-s") or switches.has_key("--stack"):
    1948           self.stacktrace = True
    1949        if switches.has_key("-D") or switches.has_key("--diagnostics"):
    1950           self.diagnostics = True
    1951
    1952
    1953  #########################################################################
    1954  # Main routine
    1955  ########################################################################
    1956
    1957  if __name__ == "__main__":
    1958     result = cli()
    1959     sys.exit(result)
    1960