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