Initial commit for snf-image-creator-dialog
[snf-image-creator] / image_creator / dialog_main.py
1 #!/usr/bin/env python
2
3 # Copyright 2012 GRNET S.A. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8 #
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12 #
13 #   2. Redistributions in binary form must reproduce the above
14 #      copyright notice, this list of conditions and the following
15 #      disclaimer in the documentation and/or other materials
16 #      provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30 #
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
35
36 import dialog
37 import sys
38 import os
39 import textwrap
40 import signal
41
42 from image_creator import __version__ as version
43 from image_creator.util import FatalError, MD5
44 from image_creator.output.dialog import InitializationOutput
45 from image_creator.disk import Disk
46 from image_creator.os_type import os_cls
47
48 MSGBOX_WIDTH = 60
49 YESNO_WIDTH = 50
50 MENU_WIDTH = 70
51
52
53 class Reset(Exception):
54     pass
55
56
57 def confirm_exit(d, msg=''):
58     return not d.yesno("%s Do you want to exit?" % msg, width=YESNO_WIDTH)
59
60
61 def confirm_reset(d):
62     return not d.yesno(
63         "Are you sure you want to reset everything?",
64         width=YESNO_WIDTH)
65
66
67 def upload_image(session):
68     d = session["dialog"]
69
70     if "account" not in session:
71         d.msgbox("You need to provide your ~okeanos login username before you "
72                  "can upload images to pithos+", width=MSGBOX_WIDTH)
73         return False
74
75     if "token" not in session:
76         d.msgbox("You need to provide your ~okeanos account authentication "
77                  "token before you can upload images to pithos+",
78                  width=MSGBOX_WIDTH)
79         return False
80
81     while 1:
82         (code, answer) = d.inputbox("Please provide a filename:",
83                         init=session["upload"] if "upload" in session else '')
84         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
85             return False
86
87         answer = answer.strip()
88         if len(answer) == 0:
89             d.msgbox("Filename cannot be empty", width=MSGBOX_WIDTH)
90             continue
91
92         session["upload"] = answer
93         return True
94
95
96 def register_image(session):
97     d = session["dialog"]
98
99     if "account" not in session:
100         d.msgbox("You need to provide your ~okeanos login username before you "
101                  "can register an images to cyclades",
102                  width=MSGBOX_WIDTH)
103         return False
104
105     if "token" not in session:
106         d.msgbox("You need to provide your ~okeanos account authentication "
107                  "token before you can register an images to cyclades",
108                  width=MSGBOX_WIDTH)
109         return False
110
111     if "upload" not in session:
112         d.msgbox("You need to have an image uploaded to pithos+ before you "
113                  "can register it to cyclades",
114                  width=MSGBOX_WIDTH)
115         return False
116
117     return True
118
119
120 def kamaki_menu(session):
121     d = session['dialog']
122     default_item = "Account"
123     while 1:
124         account = session["account"] if "account" in session else "<none>"
125         token = session["token"] if "token" in session else "<none>"
126         upload = session["upload"] if "upload" in session else "<none>"
127         (code, choice) = d.menu(
128             "Choose one of the following or press <Back> to go back.",
129             width=MENU_WIDTH,
130             choices=[("Account", "Change your ~okeanos username: %s" %
131                       account),
132                      ("Token", "Change your ~okeanos token: %s" %
133                       token),
134                      ("Upload", "Upload image to pithos+"),
135                      ("Register", "Register image to cyclades: %s" % upload)],
136             cancel="Back",
137             default_item=default_item,
138             title="Image Registration Menu")
139
140         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
141             return False
142
143         if choice == "Account":
144             default_item = "Account"
145             (code, answer) = d.inputbox(
146                 "Please provide your ~okeanos account e-mail address:",
147                 init=session["account"] if "account" in session else '',
148                 width=70)
149             if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
150                 continue
151             if len(answer) == 0 and "account" in session:
152                     del session["account"]
153             else:
154                 session["account"] = answer.strip()
155                 default_item = "Token"
156         elif choice == "Token":
157             default_item = "Token"
158             (code, answer) = d.inputbox(
159                 "Please provide your ~okeanos account authetication token:",
160                 init=session["token"] if "token" in session else '',
161                 width=70)
162             if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
163                 continue
164             if len(answer) == 0 and "token" in session:
165                 del session["token"]
166             else:
167                 session["token"] = answer.strip()
168                 default_item = "Upload"
169         elif choice == "Upload":
170             if upload_image(session):
171                 default_item = "Register"
172             else:
173                 default_item = "Upload"
174         elif choice == "Register":
175             if register_image(session):
176                 return True
177             else:
178                 default_item = "Register"
179
180
181 def main_menu(session):
182     d = session['dialog']
183     dev = session['device']
184     d.setBackgroundTitle("OS: %s, Distro: %s" % (dev.ostype, dev.distro))
185     actions = {"Register": kamaki_menu}
186     default_item = "Customize"
187
188     while 1:
189         (code, choice) = d.menu(
190             "Choose one of the following or press <Exit> to exit.",
191             width=MENU_WIDTH,
192             choices=[("Customize", "Run various image customization tasks"),
193                      ("Deploy", "Configure ~okeanos image deployment options"),
194                      ("Register", "Register image to ~okeanos"),
195                      ("Extract", "Dump image to local file system"),
196                      ("Reset", "Reset everything and start over again"),
197                      ("Help", "Get help for using snf-image-creator")],
198             cancel="Exit",
199             default_item=default_item,
200             title="Image Creator for ~okeanos (snf-image-creator version %s)" %
201                   version)
202
203         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
204             if confirm_exit(d):
205                 break
206             else:
207                 continue
208
209         if choice == "Reset":
210             if confirm_reset(d):
211                 d.infobox("Resetting snf-image-creator. Please wait...")
212                 raise Reset
213             else:
214                 continue
215         elif choice in actions:
216             actions[choice](session)
217
218
219 def select_file(d):
220     root = os.sep
221     while 1:
222         (code, path) = d.fselect(root, 10, 50,
223                                  title="Please select input media")
224         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
225             if confirm_exit(d, "You canceled the media selection dialog box."):
226                 sys.exit(0)
227             else:
228                 continue
229
230         if not os.path.exists(path):
231             d.msgbox("The file you choose does not exist", width=MSGBOX_WIDTH)
232             continue
233         else:
234             break
235
236     return path
237
238
239 def collect_metadata(dev, out):
240
241     dev.mount(readonly=True)
242     out.output("Collecting image metadata...")
243     cls = os_cls(dev.distro, dev.ostype)
244     image_os = cls(dev.root, dev.g, out)
245     out.success("done")
246     dev.umount()
247
248     return image_os.meta
249
250
251 def image_creator(d):
252     basename = os.path.basename(sys.argv[0])
253     usage = "Usage: %s [input_media]" % basename
254     if len(sys.argv) > 2:
255         sys.stderr.write("%s\n" % usage)
256         return 1
257
258     if os.geteuid() != 0:
259         raise FatalError("You must run %s as root" % basename)
260
261     media = sys.argv[1] if len(sys.argv) == 2 else select_file(d)
262
263     out = InitializationOutput(d)
264     disk = Disk(media, out)
265
266     def signal_handler(signum, fram):
267         out.cleanup()
268         disk.cleanup()
269
270     signal.signal(signal.SIGINT, signal_handler)
271     try:
272         snapshot = disk.snapshot()
273         dev = disk.get_device(snapshot)
274
275         metadata = collect_metadata(dev, out)
276         out.cleanup()
277
278         # Make sure the signal handler does not call out.cleanup again
279         def dummy(self):
280             pass
281         instancemethod = type(InitializationOutput.cleanup)
282         out.cleanup = instancemethod(dummy, out, InitializationOutput)
283
284         session = {"dialog": d,
285                    "disk": disk,
286                    "device": dev,
287                    "metadata": metadata}
288
289         main_menu(session)
290         d.infobox("Thank you for using snf-image-creator. Bye", width=53)
291     finally:
292         disk.cleanup()
293
294     return 0
295
296
297 def main():
298
299     d = dialog.Dialog(dialog="dialog")
300
301     while 1:
302         try:
303             try:
304                 ret = image_creator(d)
305                 sys.exit(ret)
306             except FatalError as e:
307                 msg = textwrap.fill(str(e), width=70)
308                 d.infobox(msg, width=70, title="Fatal Error")
309                 sys.exit(1)
310         except Reset:
311             continue
312
313 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :