d8e86b1ba1d7991d37997e4d6b7cc19f3646ef12
[snf-image-creator] / image_creator / dialog_main.py
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
50 from image_creator import __version__ as version
51 from image_creator.util import FatalError
52 from image_creator.output import Output
53 from image_creator.output.cli import SimpleOutput
54 from image_creator.output.dialog import GaugeOutput
55 from image_creator.output.composite import CompositeOutput
56 from image_creator.disk import Disk
57 from image_creator.dialog_wizard import start_wizard
58 from image_creator.dialog_menu import main_menu
59 from image_creator.dialog_util import SMALL_WIDTH, WIDTH, confirm_exit, \
60     Reset, update_background_title
61
62
63 def create_image(d, media, out, tmp):
64     """Create an image out of `media'"""
65     d.setBackgroundTitle('snf-image-creator')
66
67     gauge = GaugeOutput(d, "Initialization", "Initializing...")
68     out.add(gauge)
69     disk = Disk(media, out, tmp)
70
71     def signal_handler(signum, frame):
72         gauge.cleanup()
73         disk.cleanup()
74
75     signal.signal(signal.SIGINT, signal_handler)
76     signal.signal(signal.SIGTERM, signal_handler)
77     try:
78         snapshot = disk.snapshot()
79         image = disk.get_image(snapshot)
80
81         out.output("Collecting image metadata ...")
82         metadata = {}
83         for (key, value) in image.meta.items():
84             metadata[str(key)] = str(value)
85
86         for (key, value) in image.os.meta.items():
87             metadata[str(key)] = str(value)
88
89         out.success("done")
90         gauge.cleanup()
91         out.remove(gauge)
92
93         # Make sure the signal handler does not call gauge.cleanup again
94         def dummy(self):
95             pass
96         gauge.cleanup = type(GaugeOutput.cleanup)(dummy, gauge, GaugeOutput)
97
98         session = {"dialog": d,
99                    "disk": disk,
100                    "image": image,
101                    "metadata": metadata}
102
103         msg = "snf-image-creator detected a %s system on the input media. " \
104               "Would you like to run a wizard to assist you through the " \
105               "image creation process?\n\nChoose <Wizard> to run the wizard," \
106               " <Expert> to run the snf-image-creator in expert mode or " \
107               "press ESC to quit the program." \
108               % (image.ostype if image.ostype == image.distro or
109                  image.distro == "unknown" else "%s (%s)" %
110                  (image.ostype, image.distro))
111
112         update_background_title(session)
113
114         while True:
115             code = d.yesno(msg, width=WIDTH, height=12, yes_label="Wizard",
116                            no_label="Expert")
117             if code == d.DIALOG_OK:
118                 if start_wizard(session):
119                     break
120             elif code == d.DIALOG_CANCEL:
121                 main_menu(session)
122                 break
123
124             if confirm_exit(d):
125                 break
126
127         d.infobox("Thank you for using snf-image-creator. Bye", width=53)
128     finally:
129         disk.cleanup()
130
131     return 0
132
133
134 def select_file(d, media):
135     """Select a media file"""
136     if media == '/':
137         return '/'
138
139     default = os.getcwd() + os.sep
140     while 1:
141         if media is not None:
142             if not os.path.exists(media):
143                 d.msgbox("The file `%s' you choose does not exist." % media,
144                          width=SMALL_WIDTH)
145             else:
146                 mode = os.stat(media).st_mode
147                 if not stat.S_ISDIR(mode):
148                     break
149                 default = media
150
151         (code, media) = d.fselect(default, 10, 60, extra_button=1,
152                                   title="Please select an input media.",
153                                   extra_label="Bundle Host")
154         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
155             if confirm_exit(d, "You canceled the media selection dialog box."):
156                 sys.exit(0)
157             else:
158                 media = None
159                 continue
160         elif code == d.DIALOG_EXTRA:
161             return '/'
162
163     return media
164
165
166 def main():
167
168     d = dialog.Dialog(dialog="dialog")
169
170     # Add extra button in dialog library
171     dialog._common_args_syntax["extra_button"] = \
172         lambda enable: dialog._simple_option("--extra-button", enable)
173
174     dialog._common_args_syntax["extra_label"] = \
175         lambda string: ("--extra-label", string)
176
177     # Allow yes-no label overwriting
178     dialog._common_args_syntax["yes_label"] = \
179         lambda string: ("--yes-label", string)
180
181     dialog._common_args_syntax["no_label"] = \
182         lambda string: ("--no-label", string)
183
184     usage = "Usage: %prog [options] [<input_media>]"
185     parser = optparse.OptionParser(version=version, usage=usage)
186     parser.add_option("-l", "--logfile", type="string", dest="logfile",
187                       default=None, help="log all messages to FILE",
188                       metavar="FILE")
189     parser.add_option("--tmpdir", type="string", dest="tmp", default=None,
190                       help="create large temporary image files under DIR",
191                       metavar="DIR")
192
193     options, args = parser.parse_args(sys.argv[1:])
194
195     if len(args) > 1:
196         parser.error("Wrong number of arguments")
197
198     d.setBackgroundTitle('snf-image-creator')
199
200     try:
201         if os.geteuid() != 0:
202             raise FatalError("You must run %s as root" %
203                              parser.get_prog_name())
204
205         if options.tmp is not None and not os.path.isdir(options.tmp):
206             raise FatalError("The directory `%s' specified with --tmpdir is "
207                              "not valid" % options.tmp)
208
209         logfile = None
210         if options.logfile is not None:
211             try:
212                 logfile = open(options.logfile, 'w')
213             except IOError as e:
214                 raise FatalError(
215                     "Unable to open logfile `%s' for writing. Reason: %s" %
216                     (options.logfile, e.strerror))
217
218         media = select_file(d, args[0] if len(args) == 1 else None)
219
220         try:
221             log = SimpleOutput(False, logfile) if logfile is not None \
222                 else Output()
223             while 1:
224                 try:
225                     out = CompositeOutput([log])
226                     out.output("Starting %s v%s ..." %
227                                (parser.get_prog_name(), version))
228                     ret = create_image(d, media, out, options.tmp)
229                     sys.exit(ret)
230                 except Reset:
231                     log.output("Resetting everything ...")
232                     continue
233         finally:
234             if logfile is not None:
235                 logfile.close()
236     except FatalError as e:
237         msg = textwrap.fill(str(e), width=WIDTH)
238         d.infobox(msg, width=WIDTH, title="Fatal Error")
239         sys.exit(1)
240
241 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :