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

Source Code for Module CedarBackup2.cli

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