Statistics
| Branch: | Tag: | Revision:

root / image_creator / dialog_main.py @ e482b7f9

History | View | Annotate | Download (10.3 kB)

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
#
4
# Copyright 2012 GRNET S.A. All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or
7
# without modification, are permitted provided that the following
8
# conditions are met:
9
#
10
#   1. Redistributions of source code must retain the above
11
#      copyright notice, this list of conditions and the following
12
#      disclaimer.
13
#
14
#   2. Redistributions in binary form must reproduce the above
15
#      copyright notice, this list of conditions and the following
16
#      disclaimer in the documentation and/or other materials
17
#      provided with the distribution.
18
#
19
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
# POSSIBILITY OF SUCH DAMAGE.
31
#
32
# The views and conclusions contained in the software and
33
# documentation are those of the authors and should not be
34
# interpreted as representing official policies, either expressed
35
# or implied, of GRNET S.A.
36

    
37
"""This module is the entrance point for the dialog-based version of the
38
snf-image-creator program. The main function will create a dialog where the
39
user is asked if he wants to use the program in expert or wizard mode.
40
"""
41

    
42
import dialog
43
import sys
44
import os
45
import stat
46
import textwrap
47
import signal
48
import optparse
49
import types
50

    
51
from image_creator import __version__ as version
52
from image_creator.util import FatalError
53
from image_creator.output import Output
54
from image_creator.output.cli import SimpleOutput
55
from image_creator.output.dialog import GaugeOutput
56
from image_creator.output.composite import CompositeOutput
57
from image_creator.disk import Disk
58
from image_creator.dialog_wizard import start_wizard
59
from image_creator.dialog_menu import main_menu
60
from image_creator.dialog_util import SMALL_WIDTH, WIDTH, confirm_exit, \
61
    Reset, update_background_title
62

    
63

    
64
def create_image(d, media, out, tmp):
65
    """Create an image out of `media'"""
66
    d.setBackgroundTitle('snf-image-creator')
67

    
68
    gauge = GaugeOutput(d, "Initialization", "Initializing...")
69
    out.add(gauge)
70
    disk = Disk(media, out, tmp)
71

    
72
    def signal_handler(signum, frame):
73
        gauge.cleanup()
74
        disk.cleanup()
75

    
76
    signal.signal(signal.SIGINT, signal_handler)
77
    signal.signal(signal.SIGTERM, signal_handler)
78
    try:
79
        snapshot = disk.snapshot()
80
        image = disk.get_image(snapshot)
81

    
82
        out.output("Collecting image metadata ...")
83
        metadata = {}
84
        for (key, value) in image.meta.items():
85
            metadata[str(key)] = str(value)
86

    
87
        for (key, value) in image.os.meta.items():
88
            metadata[str(key)] = str(value)
89

    
90
        out.success("done")
91
        gauge.cleanup()
92
        out.remove(gauge)
93

    
94
        # Make sure the signal handler does not call gauge.cleanup again
95
        def dummy(self):
96
            pass
97
        gauge.cleanup = type(GaugeOutput.cleanup)(dummy, gauge, GaugeOutput)
98

    
99
        session = {"dialog": d,
100
                   "disk": disk,
101
                   "image": image,
102
                   "metadata": metadata}
103

    
104
        if hasattr(image, "unsupported"):
105

    
106
            session['excluded_tasks'] = [-1]
107
            session['task_metadata'] = ["EXCLUDE_ALL_TASKS"]
108

    
109
            msg = "The system on the input media is not supported." \
110
                "\n\nReason: %s\n\n" \
111
                "We highly recommend not to create an image out of this, " \
112
                "since the image won't be cleaned up and you will not be " \
113
                "able to configure it during the deployment. Press <YES> if " \
114
                "you still want to continue with the image creation process." \
115
                % image.unsupported
116

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

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

    
123
        msg = "snf-image-creator detected a %s system on the input media. " \
124
              "Would you like to run a wizard to assist you through the " \
125
              "image creation process?\n\nChoose <Wizard> to run the wizard," \
126
              " <Expert> to run the snf-image-creator in expert mode or " \
127
              "press ESC to quit the program." \
128
              % (image.ostype if image.ostype == image.distro or
129
                 image.distro == "unknown" else "%s (%s)" %
130
                 (image.ostype, image.distro))
131

    
132
        update_background_title(session)
133

    
134
        while True:
135
            code = d.yesno(msg, width=WIDTH, height=12, yes_label="Wizard",
136
                           no_label="Expert")
137
            if code == d.DIALOG_OK:
138
                if start_wizard(session):
139
                    break
140
            elif code == d.DIALOG_CANCEL:
141
                main_menu(session)
142
                break
143

    
144
            if confirm_exit(d):
145
                break
146

    
147
        d.infobox("Thank you for using snf-image-creator. Bye", width=53)
148
    finally:
149
        disk.cleanup()
150

    
151
    return 0
152

    
153

    
154
def select_file(d, media):
155
    """Select a media file"""
156
    if media == '/':
157
        return '/'
158

    
159
    default = os.getcwd() + os.sep
160
    while 1:
161
        if media is not None:
162
            if not os.path.exists(media):
163
                d.msgbox("The file `%s' you choose does not exist." % media,
164
                         width=SMALL_WIDTH)
165
            else:
166
                mode = os.stat(media).st_mode
167
                if not stat.S_ISDIR(mode):
168
                    break
169
                default = media
170

    
171
        (code, media) = d.fselect(default, 10, 60, extra_button=1,
172
                                  title="Please select an input media.",
173
                                  extra_label="Bundle Host")
174
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
175
            if confirm_exit(d, "You canceled the media selection dialog box."):
176
                sys.exit(0)
177
            else:
178
                media = None
179
                continue
180
        elif code == d.DIALOG_EXTRA:
181
            return '/'
182

    
183
    return media
184

    
185

    
186
def _dialog_form(self, text, height=20, width=60, form_height=15, fields=[],
187
                 **kwargs):
188
    """Display a form box.
189

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

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

    
195
    label_len = 0
196
    for field in fields:
197
        if len(field[0]) > label_len:
198
            label_len = len(field[0])
199

    
200
    input_len = width - label_len - 1
201

    
202
    line = 1
203
    for field in fields:
204
        label = field[0]
205
        item = field[1]
206
        item_len = field[2]
207
        cmd.extend((label, str(line), str(1), item, str(line),
208
                   str(label_len + 1), str(input_len), str(item_len)))
209
        line += 1
210

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

    
213
    if not output:
214
        return (code, [])
215

    
216
    return (code, output.splitlines())
217

    
218

    
219
def main():
220

    
221
    # In OpenSUSE dialog is buggy under xterm
222
    if os.environ['TERM'] == 'xterm':
223
        os.environ['TERM'] = 'linux'
224

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

    
227
    # Add extra button in dialog library
228
    dialog._common_args_syntax["extra_button"] = \
229
        lambda enable: dialog._simple_option("--extra-button", enable)
230

    
231
    dialog._common_args_syntax["extra_label"] = \
232
        lambda string: ("--extra-label", string)
233

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

    
238
    dialog._common_args_syntax["no_label"] = \
239
        lambda string: ("--no-label", string)
240

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

    
245
    usage = "Usage: %prog [options] [<input_media>]"
246
    parser = optparse.OptionParser(version=version, usage=usage)
247
    parser.add_option("-l", "--logfile", type="string", dest="logfile",
248
                      default=None, help="log all messages to FILE",
249
                      metavar="FILE")
250
    parser.add_option("--tmpdir", type="string", dest="tmp", default=None,
251
                      help="create large temporary image files under DIR",
252
                      metavar="DIR")
253

    
254
    options, args = parser.parse_args(sys.argv[1:])
255

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

    
259
    d.setBackgroundTitle('snf-image-creator')
260

    
261
    try:
262
        if os.geteuid() != 0:
263
            raise FatalError("You must run %s as root" %
264
                             parser.get_prog_name())
265

    
266
        if options.tmp is not None and not os.path.isdir(options.tmp):
267
            raise FatalError("The directory `%s' specified with --tmpdir is "
268
                             "not valid" % options.tmp)
269

    
270
        logfile = None
271
        if options.logfile is not None:
272
            try:
273
                logfile = open(options.logfile, 'w')
274
            except IOError as e:
275
                raise FatalError(
276
                    "Unable to open logfile `%s' for writing. Reason: %s" %
277
                    (options.logfile, e.strerror))
278

    
279
        media = select_file(d, args[0] if len(args) == 1 else None)
280

    
281
        try:
282
            log = SimpleOutput(False, logfile) if logfile is not None \
283
                else Output()
284
            while 1:
285
                try:
286
                    out = CompositeOutput([log])
287
                    out.output("Starting %s v%s ..." %
288
                               (parser.get_prog_name(), version))
289
                    ret = create_image(d, media, out, options.tmp)
290
                    sys.exit(ret)
291
                except Reset:
292
                    log.output("Resetting everything ...")
293
                    continue
294
        finally:
295
            if logfile is not None:
296
                logfile.close()
297
    except FatalError as e:
298
        msg = textwrap.fill(str(e), width=WIDTH)
299
        d.infobox(msg, width=WIDTH, title="Fatal Error")
300
        sys.exit(1)
301

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