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

Source Code for Module CedarBackup2.cli

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