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