dialog_main.py 9.69 KB
Newer Older
1
#!/usr/bin/env python
Nikos Skalkotos's avatar
Nikos Skalkotos committed
2 3
# -*- coding: utf-8 -*-
#
Nikos Skalkotos's avatar
Nikos Skalkotos committed
4
# Copyright (C) 2011-2014 GRNET S.A.
5
#
Nikos Skalkotos's avatar
Nikos Skalkotos committed
6 7 8 9
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
10
#
Nikos Skalkotos's avatar
Nikos Skalkotos committed
11 12 13 14
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
15
#
Nikos Skalkotos's avatar
Nikos Skalkotos committed
16 17
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18

Nikos Skalkotos's avatar
Nikos Skalkotos committed
19 20 21 22 23
"""This module is the entrance point for the dialog-based version of the
snf-image-creator program. The main function will create a dialog where the
user is asked if he wants to use the program in expert or wizard mode.
"""

24 25 26 27 28
import dialog
import sys
import os
import textwrap
import signal
29
import optparse
30
import types
31
import termios
32
import traceback
33 34

from image_creator import __version__ as version
Nikos Skalkotos's avatar
Nikos Skalkotos committed
35
from image_creator.util import FatalError
36
from image_creator.output import Output
37
from image_creator.output.cli import SimpleOutput
Nikos Skalkotos's avatar
Nikos Skalkotos committed
38
from image_creator.output.dialog import GaugeOutput
39
from image_creator.output.composite import CompositeOutput
40
from image_creator.disk import Disk
Nikos Skalkotos's avatar
Nikos Skalkotos committed
41
from image_creator.dialog_wizard import start_wizard
Nikos Skalkotos's avatar
Nikos Skalkotos committed
42
from image_creator.dialog_menu import main_menu
43 44
from image_creator.dialog_util import WIDTH, confirm_exit, Reset, \
    update_background_title, select_file
45

46 47
PROGNAME = os.path.basename(sys.argv[0])

48

Nikos Skalkotos's avatar
Nikos Skalkotos committed
49
def create_image(d, media, out, tmp, snapshot):
Nikos Skalkotos's avatar
Nikos Skalkotos committed
50
    """Create an image out of `media'"""
Nikos Skalkotos's avatar
Nikos Skalkotos committed
51 52
    d.setBackgroundTitle('snf-image-creator')

53 54
    gauge = GaugeOutput(d, "Initialization", "Initializing...")
    out.add(gauge)
55
    disk = Disk(media, out, tmp)
56

Nikos Skalkotos's avatar
Nikos Skalkotos committed
57
    def signal_handler(signum, frame):
58
        gauge.cleanup()
59 60 61
        disk.cleanup()

    signal.signal(signal.SIGINT, signal_handler)
62
    signal.signal(signal.SIGTERM, signal_handler)
63
    try:
Nikos Skalkotos's avatar
Nikos Skalkotos committed
64 65

        device = disk.file if not snapshot else disk.snapshot()
66 67

        image = disk.get_image(device)
68

69
        out.output("Collecting image metadata ...")
Nikos Skalkotos's avatar
Nikos Skalkotos committed
70
        metadata = {}
71
        for (key, value) in image.meta.items():
Nikos Skalkotos's avatar
Nikos Skalkotos committed
72 73
            metadata[str(key)] = str(value)

74
        for (key, value) in image.os.meta.items():
Nikos Skalkotos's avatar
Nikos Skalkotos committed
75 76
            metadata[str(key)] = str(value)

77
        out.success("done")
78 79
        gauge.cleanup()
        out.remove(gauge)
80

81
        # Make sure the signal handler does not call gauge.cleanup again
82 83
        def dummy(self):
            pass
84
        gauge.cleanup = type(GaugeOutput.cleanup)(dummy, gauge, GaugeOutput)
85 86 87

        session = {"dialog": d,
                   "disk": disk,
88
                   "image": image,
89 90
                   "metadata": metadata}

91
        if image.is_unsupported():
92 93 94 95 96 97 98 99 100 101

            session['excluded_tasks'] = [-1]
            session['task_metadata'] = ["EXCLUDE_ALL_TASKS"]

            msg = "The system on the input media is not supported." \
                "\n\nReason: %s\n\n" \
                "We highly recommend not to create an image out of this, " \
                "since the image won't be cleaned up and you will not be " \
                "able to configure it during the deployment. Press <YES> if " \
                "you still want to continue with the image creation process." \
102
                % image._unsupported
103 104 105 106 107 108 109

            if not d.yesno(msg, width=WIDTH, defaultno=1, height=12):
                main_menu(session)

            d.infobox("Thank you for using snf-image-creator. Bye", width=53)
            return 0

110
        msg = "snf-image-creator detected a %s system on the input media. " \
111 112
              "Would you like to run a wizard to assist you through the " \
              "image creation process?\n\nChoose <Wizard> to run the wizard," \
Nikos Skalkotos's avatar
Nikos Skalkotos committed
113 114 115
              " <Expert> to run snf-image-creator in expert mode or press " \
              "ESC to quit the program." \
              % (image.ostype.capitalize() if image.ostype == image.distro or
116
                 image.distro == "unknown" else "%s (%s)" %
Nikos Skalkotos's avatar
Nikos Skalkotos committed
117
                 (image.ostype.capitalize(), image.distro.capitalize()))
118

Nikos Skalkotos's avatar
Nikos Skalkotos committed
119 120
        update_background_title(session)

121
        while True:
Nikos Skalkotos's avatar
Nikos Skalkotos committed
122 123
            code = d.yesno(msg, width=WIDTH, height=12, yes_label="Wizard",
                           no_label="Expert")
124
            if code == d.DIALOG_OK:
Nikos Skalkotos's avatar
Nikos Skalkotos committed
125
                if start_wizard(session):
126 127 128 129
                    break
            elif code == d.DIALOG_CANCEL:
                main_menu(session)
                break
130

131
            if confirm_exit(d):
132
                break
133

134 135 136 137 138 139 140
        d.infobox("Thank you for using snf-image-creator. Bye", width=53)
    finally:
        disk.cleanup()

    return 0


141 142 143 144 145 146 147 148 149 150 151 152 153 154
def _dialog_form(self, text, height=20, width=60, form_height=15, fields=[],
                 **kwargs):
    """Display a form box.

    fields is in the form: [(label1, item1, item_length1), ...]
    """

    cmd = ["--form", text, str(height), str(width), str(form_height)]

    label_len = 0
    for field in fields:
        if len(field[0]) > label_len:
            label_len = len(field[0])

Nikos Skalkotos's avatar
Nikos Skalkotos committed
155
    input_len = width - label_len - 1
156 157 158 159 160 161 162

    line = 1
    for field in fields:
        label = field[0]
        item = field[1]
        item_len = field[2]
        cmd.extend((label, str(line), str(1), item, str(line),
Nikos Skalkotos's avatar
Nikos Skalkotos committed
163
                    str(label_len + 1), str(input_len), str(item_len)))
164 165 166 167 168 169 170 171 172 173
        line += 1

    code, output = self._perform(*(cmd,), **kwargs)

    if not output:
        return (code, [])

    return (code, output.splitlines())


Nikos Skalkotos's avatar
Nikos Skalkotos committed
174
def dialog_main(media, logfile, tmpdir, snapshot):
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197

    # In openSUSE dialog is buggy under xterm
    if os.environ['TERM'] == 'xterm':
        os.environ['TERM'] = 'linux'

    d = dialog.Dialog(dialog="dialog")

    # Add extra button in dialog library
    dialog._common_args_syntax["extra_button"] = \
        lambda enable: dialog._simple_option("--extra-button", enable)
    dialog._common_args_syntax["extra_label"] = \
        lambda string: ("--extra-label", string)

    # Allow yes-no label overwriting
    dialog._common_args_syntax["yes_label"] = \
        lambda string: ("--yes-label", string)
    dialog._common_args_syntax["no_label"] = \
        lambda string: ("--no-label", string)

    # Monkey-patch pythondialog to include support for form dialog boxes
    if not hasattr(dialog, 'form'):
        d.form = types.MethodType(_dialog_form, d)

198 199
    d.setBackgroundTitle('snf-image-creator')

200
    try:
201 202 203 204 205 206 207 208 209 210
        while True:
            media = select_file(d, init=media, ftype="br", bundle_host=True,
                                title="Please select an input media.")
            if media is None:
                if confirm_exit(
                        d, "You canceled the media selection dialog box."):
                    return 0
                continue
            break

211 212 213 214 215
        log = SimpleOutput(False, logfile) if logfile is not None else Output()
        while 1:
            try:
                out = CompositeOutput([log])
                out.output("Starting %s v%s ..." % (PROGNAME, version))
Nikos Skalkotos's avatar
Nikos Skalkotos committed
216
                return create_image(d, media, out, tmpdir, snapshot)
217 218 219
            except Reset:
                log.output("Resetting everything ...")
                continue
Nikos Skalkotos's avatar
Nikos Skalkotos committed
220 221
    except FatalError as error:
        msg = textwrap.fill(str(error), width=WIDTH-4)
222 223 224 225 226
        d.infobox(msg, width=WIDTH, title="Fatal Error")
        return 1


def main():
Nikos Skalkotos's avatar
Nikos Skalkotos committed
227
    """Entrance Point"""
228 229 230 231 232 233 234 235 236
    if os.geteuid() != 0:
        sys.stderr.write("Error: You must run %s as root\n" % PROGNAME)
        sys.exit(2)

    usage = "Usage: %prog [options] [<input_media>]"
    parser = optparse.OptionParser(version=version, usage=usage)
    parser.add_option("-l", "--logfile", type="string", dest="logfile",
                      default=None, help="log all messages to FILE",
                      metavar="FILE")
Nikos Skalkotos's avatar
Nikos Skalkotos committed
237 238 239 240
    parser.add_option("--no-snapshot", dest="snapshot", default=True,
                      help="don't snapshot the input media. (THIS IS "
                      "DANGEROUS AS IT WILL ALTER THE ORIGINAL MEDIA!!!)",
                      action="store_false")
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    parser.add_option("--tmpdir", type="string", dest="tmp", default=None,
                      help="create large temporary image files under DIR",
                      metavar="DIR")

    opts, args = parser.parse_args(sys.argv[1:])

    if len(args) > 1:
        parser.error("Wrong number of arguments")

    media = args[0] if len(args) == 1 else None

    if opts.tmp is not None and not os.path.isdir(opts.tmp):
        parser.error("Directory: `%s' specified with --tmpdir is not valid"
                     % opts.tmp)

    try:
        logfile = open(opts.logfile, 'w') if opts.logfile is not None else None
Nikos Skalkotos's avatar
Nikos Skalkotos committed
258
    except IOError as error:
259
        parser.error("Unable to open logfile `%s' for writing. Reason: %s" %
Nikos Skalkotos's avatar
Nikos Skalkotos committed
260
                     (opts.logfile, error.strerror))
261 262 263 264 265

    try:
        # Save the terminal attributes
        attr = termios.tcgetattr(sys.stdin.fileno())
        try:
Nikos Skalkotos's avatar
Nikos Skalkotos committed
266
            ret = dialog_main(media, logfile, opts.tmp, opts.snapshot)
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
        finally:
            # Restore the terminal attributes. If an error occurs make sure
            # that the terminal turns back to normal.
            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attr)
    except:
        # Clear the screen
        sys.stdout.write('\033[2J')  # Erase Screen
        sys.stdout.write('\033[H')  # Cursor Home
        sys.stdout.flush()

        exception = traceback.format_exc()
        sys.stderr.write(exception)
        if logfile is not None:
            logfile.write(exception)

        sys.exit(3)
283
    finally:
284 285
        if logfile is not None:
            logfile.close()
286 287

    sys.exit(ret)
288 289

# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :