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