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