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