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