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

Source Code for Module CedarBackup2.cli

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