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