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