File: main.py

package info (click to toggle)
bootstrap-vz 0.9.11%2B20180121git-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 2,244 kB
  • sloc: python: 8,800; sh: 813; makefile: 16
file content (144 lines) | stat: -rw-r--r-- 5,511 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""Main module containing all the setup necessary for running the bootstrapping process
"""


def main():
    """Main function for invoking the bootstrap process

    :raises Exception: When the invoking user is not root and --dry-run isn't specified
    """
    # Get the commandline arguments
    opts = get_opts()

    # Require root privileges, except when doing a dry-run where they aren't needed
    import os
    if os.geteuid() != 0 and not opts['--dry-run']:
        raise Exception('This program requires root privileges.')

    # Set up logging
    setup_loggers(opts)

    # Load the manifest
    from manifest import Manifest
    manifest = Manifest(path=opts['MANIFEST'])

    # Everything has been set up, begin the bootstrapping process
    run(manifest,
        debug=opts['--debug'],
        pause_on_error=opts['--pause-on-error'],
        dry_run=opts['--dry-run'])


def get_opts():
    """Creates an argument parser and returns the arguments it has parsed
    """
    import docopt
    usage = """bootstrap-vz

Usage: bootstrap-vz [options] MANIFEST

Options:
  --log <path>       Log to given directory [default: /var/log/bootstrap-vz]
                     If <path> is `-' file logging will be disabled.
  --pause-on-error   Pause on error, before rollback
  --dry-run          Don't actually run the tasks
  --color=auto|always|never
                     Colorize the console output [default: auto]
  --debug            Print debugging information
  -h, --help         show this help
    """
    opts = docopt.docopt(usage)
    if opts['--color'] not in ('auto', 'always', 'never'):
        raise docopt.DocoptExit('Value of --color must be one of auto, always or never.')
    return opts


def setup_loggers(opts):
    """Sets up the file and console loggers

    :params dict opts: Dictionary of options from the commandline
    """
    import logging
    root = logging.getLogger()
    root.setLevel(logging.NOTSET)

    import log
    # Log to file unless --log is a single dash
    if opts['--log'] != '-':
        import os.path
        log_filename = log.get_log_filename(opts['MANIFEST'])
        logpath = os.path.join(opts['--log'], log_filename)
        file_handler = log.get_file_handler(path=logpath, debug=True)
        root.addHandler(file_handler)

    if opts['--color'] == 'never':
        colorize = False
    elif opts['--color'] == 'always':
        colorize = True
    else:
        # If --color=auto (default), decide whether to colorize by whether stderr is a tty.
        import os
        colorize = os.isatty(2)
    console_handler = log.get_console_handler(debug=opts['--debug'], colorize=colorize)
    root.addHandler(console_handler)


def run(manifest, debug=False, pause_on_error=False, dry_run=False):
    """Runs the bootstrapping process

    :params Manifest manifest: The manifest to run the bootstrapping process for
    :params bool debug: Whether to turn debugging mode on
    :params bool pause_on_error: Whether to pause on error, before rollback
    :params bool dry_run: Don't actually run the tasks
    """
    import logging

    log = logging.getLogger(__name__)
    # Get the tasklist
    from tasklist import load_tasks
    from tasklist import TaskList
    log.info('Generating tasklist')
    tasks = load_tasks('resolve_tasks', manifest)
    tasklist = TaskList(tasks)
    # 'resolve_tasks' is the name of the function to call on the provider and plugins

    # Create the bootstrap information object that'll be used throughout the bootstrapping process
    from bootstrapinfo import BootstrapInformation
    bootstrap_info = BootstrapInformation(manifest=manifest, debug=debug)

    try:
        # Run all the tasks the tasklist has gathered
        tasklist.run(info=bootstrap_info, dry_run=dry_run)
        # We're done! :-)
        log.info('Successfully completed bootstrapping')
    except (Exception, KeyboardInterrupt) as e:
        # When an error occurs, log it and begin rollback
        log.exception(e)
        if pause_on_error:
            # The --pause-on-error is useful when the user wants to inspect the volume before rollback
            raw_input('Press Enter to commence rollback')
        log.error('Rolling back')

        # Create a useful little function for the provider and plugins to use,
        # when figuring out what tasks should be added to the rollback list.
        def counter_task(taskset, task, counter):
            """counter_task() adds the third argument to the rollback tasklist
            if the second argument is present in the list of completed tasks

            :param set taskset: The taskset to add the rollback task to
            :param Task task: The task to look for in the completed tasks list
            :param Task counter: The task to add to the rollback tasklist
            """
            if task in tasklist.tasks_completed and counter not in tasklist.tasks_completed:
                taskset.add(counter)

        # Ask the provider and plugins for tasks they'd like to add to the rollback tasklist
        # Any additional arguments beyond the first two are passed directly to the provider and plugins
        rollback_tasks = load_tasks('resolve_rollback_tasks', manifest, tasklist.tasks_completed, counter_task)
        rollback_tasklist = TaskList(rollback_tasks)

        # Run the rollback tasklist
        rollback_tasklist.run(info=bootstrap_info, dry_run=dry_run)
        log.info('Successfully completed rollback')
        raise
    return bootstrap_info