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