Statistics
| Branch: | Tag: | Revision:

root / image_creator / dialog_main.py @ c71f38be

History | View | Annotate | Download (9.5 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
        msg = "snf-image-creator detected a %s system on the input media. " \
105
              "Would you like to run a wizard to assist you through the " \
106
              "image creation process?\n\nChoose <Wizard> to run the wizard," \
107
              " <Expert> to run the snf-image-creator in expert mode or " \
108
              "press ESC to quit the program." \
109
              % (image.ostype if image.ostype == image.distro or
110
                 image.distro == "unknown" else "%s (%s)" %
111
                 (image.ostype, image.distro))
112

    
113
        update_background_title(session)
114

    
115
        while True:
116
            code = d.yesno(msg, width=WIDTH, height=12, yes_label="Wizard",
117
                           no_label="Expert")
118
            if code == d.DIALOG_OK:
119
                if start_wizard(session):
120
                    break
121
            elif code == d.DIALOG_CANCEL:
122
                main_menu(session)
123
                break
124

    
125
            if confirm_exit(d):
126
                break
127

    
128
        d.infobox("Thank you for using snf-image-creator. Bye", width=53)
129
    finally:
130
        disk.cleanup()
131

    
132
    return 0
133

    
134

    
135
def select_file(d, media):
136
    """Select a media file"""
137
    if media == '/':
138
        return '/'
139

    
140
    default = os.getcwd() + os.sep
141
    while 1:
142
        if media is not None:
143
            if not os.path.exists(media):
144
                d.msgbox("The file `%s' you choose does not exist." % media,
145
                         width=SMALL_WIDTH)
146
            else:
147
                mode = os.stat(media).st_mode
148
                if not stat.S_ISDIR(mode):
149
                    break
150
                default = media
151

    
152
        (code, media) = d.fselect(default, 10, 60, extra_button=1,
153
                                  title="Please select an input media.",
154
                                  extra_label="Bundle Host")
155
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
156
            if confirm_exit(d, "You canceled the media selection dialog box."):
157
                sys.exit(0)
158
            else:
159
                media = None
160
                continue
161
        elif code == d.DIALOG_EXTRA:
162
            return '/'
163

    
164
    return media
165

    
166

    
167
def _dialog_form(self, text, height=20, width=60, form_height=15, fields=[],
168
                 **kwargs):
169
    """Display a form box.
170

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

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

    
176
    label_len = 0
177
    for field in fields:
178
        if len(field[0]) > label_len:
179
            label_len = len(field[0])
180

    
181
    input_len = width - label_len - 1
182

    
183
    line = 1
184
    for field in fields:
185
        label = field[0]
186
        item = field[1]
187
        item_len = field[2]
188
        cmd.extend((label, str(line), str(1), item, str(line),
189
                   str(label_len + 1), str(input_len), str(item_len)))
190
        line += 1
191

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

    
194
    if not output:
195
        return (code, [])
196

    
197
    return (code, output.splitlines())
198

    
199

    
200
def main():
201

    
202
    # In OpenSUSE dialog is buggy under xterm
203
    if os.environ['TERM'] == 'xterm':
204
        os.environ['TERM'] = 'linux'
205

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

    
208
    # Add extra button in dialog library
209
    dialog._common_args_syntax["extra_button"] = \
210
        lambda enable: dialog._simple_option("--extra-button", enable)
211

    
212
    dialog._common_args_syntax["extra_label"] = \
213
        lambda string: ("--extra-label", string)
214

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

    
219
    dialog._common_args_syntax["no_label"] = \
220
        lambda string: ("--no-label", string)
221

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

    
226
    usage = "Usage: %prog [options] [<input_media>]"
227
    parser = optparse.OptionParser(version=version, usage=usage)
228
    parser.add_option("-l", "--logfile", type="string", dest="logfile",
229
                      default=None, help="log all messages to FILE",
230
                      metavar="FILE")
231
    parser.add_option("--tmpdir", type="string", dest="tmp", default=None,
232
                      help="create large temporary image files under DIR",
233
                      metavar="DIR")
234

    
235
    options, args = parser.parse_args(sys.argv[1:])
236

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

    
240
    d.setBackgroundTitle('snf-image-creator')
241

    
242
    try:
243
        if os.geteuid() != 0:
244
            raise FatalError("You must run %s as root" %
245
                             parser.get_prog_name())
246

    
247
        if options.tmp is not None and not os.path.isdir(options.tmp):
248
            raise FatalError("The directory `%s' specified with --tmpdir is "
249
                             "not valid" % options.tmp)
250

    
251
        logfile = None
252
        if options.logfile is not None:
253
            try:
254
                logfile = open(options.logfile, 'w')
255
            except IOError as e:
256
                raise FatalError(
257
                    "Unable to open logfile `%s' for writing. Reason: %s" %
258
                    (options.logfile, e.strerror))
259

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

    
262
        try:
263
            log = SimpleOutput(False, logfile) if logfile is not None \
264
                else Output()
265
            while 1:
266
                try:
267
                    out = CompositeOutput([log])
268
                    out.output("Starting %s v%s ..." %
269
                               (parser.get_prog_name(), version))
270
                    ret = create_image(d, media, out, options.tmp)
271
                    sys.exit(ret)
272
                except Reset:
273
                    log.output("Resetting everything ...")
274
                    continue
275
        finally:
276
            if logfile is not None:
277
                logfile.close()
278
    except FatalError as e:
279
        msg = textwrap.fill(str(e), width=WIDTH)
280
        d.infobox(msg, width=WIDTH, title="Fatal Error")
281
        sys.exit(1)
282

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