Statistics
| Branch: | Tag: | Revision:

root / image_creator / dialog_main.py @ a42a42b3

History | View | Annotate | Download (27.6 kB)

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
import StringIO
42

    
43
from image_creator import __version__ as version
44
from image_creator.util import FatalError, MD5
45
from image_creator.output.dialog import GaugeOutput, InfoBoxOutput
46
from image_creator.disk import Disk
47
from image_creator.os_type import os_cls
48
from image_creator.kamaki_wrapper import Kamaki, ClientError
49
from image_creator.help import get_help_file
50

    
51
MSGBOX_WIDTH = 60
52
YESNO_WIDTH = 50
53
MENU_WIDTH = 70
54
INPUTBOX_WIDTH = 70
55
CHECKBOX_WIDTH = 70
56
HELP_WIDTH = 70
57
INFOBOX_WIDTH = 70
58

    
59
CONFIGURATION_TASKS = [
60
 ("Partition table manipulation", ["FixPartitionTable"],
61
  ["linux", "windows"]),
62
 ("File system resize",
63
  ["FilesystemResizeUnmounted", "FilesystemResizeMounted"],
64
  ["linux", "windows"]),
65
 ("Swap partition configuration", ["AddSwap"], ["linux"]),
66
 ("SSH keys removal", ["DeleteSSHKeys"], ["linux"]),
67
 ("Temporal RDP disabling", ["DisableRemoteDesktopConnections"], ["windows"]),
68
 ("SELinux relabeling at next boot", ["SELinuxAutorelabel"],
69
  ["linux"]),
70
 ("Hostname/Computer Name assignment", ["AssignHostname"],
71
  ["windows", "linux"]),
72
 ("Password change", ["ChangePassword"], ["windows", "linux"]),
73
 ("File injection", ["EnforcePersonality"], ["windows", "linux"])
74
]
75

    
76

    
77
class Reset(Exception):
78
    pass
79

    
80

    
81
def confirm_exit(d, msg=''):
82
    return not d.yesno("%s Do you want to exit?" % msg, width=YESNO_WIDTH)
83

    
84

    
85
def confirm_reset(d):
86
    return not d.yesno("Are you sure you want to reset everything?",
87
                       width=YESNO_WIDTH)
88

    
89

    
90
def update_background_title(session):
91
    d = session['dialog']
92
    dev = session['device']
93

    
94
    MB = 2 ** 20
95

    
96
    size = (dev.meta['SIZE'] + MB - 1) // MB
97
    shrinked = 'shrinked' in session and session['shrinked'] == True
98
    postfix = " (shrinked)" if shrinked else ''
99

    
100
    title = "OS: %s, Distro: %s, Size: %dMB%s" % \
101
            (dev.ostype, dev.distro, size, postfix)
102

    
103
    d.setBackgroundTitle(title)
104

    
105

    
106
def extract_image(session):
107
    d = session['dialog']
108
    dir = os.getcwd()
109
    while 1:
110
        if dir and dir[-1] != os.sep:
111
            dir = dir + os.sep
112

    
113
        (code, path) = d.fselect(dir, 10, 50, title="Save image as...")
114
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
115
            return False
116

    
117
        if os.path.isdir(path):
118
            dir = path
119
            continue
120

    
121
        if os.path.isdir("%s.meta" % path):
122
            d.msgbox("Can't overwrite directory `%s.meta'" % path,
123
                     width=MSGBOX_WIDTH)
124
            continue
125

    
126
        if os.path.isdir("%s.md5sum" % path):
127
            d.msgbox("Can't overwrite directory `%s.md5sum'" % path,
128
                     width=MSGBOX_WIDTH)
129
            continue
130

    
131
        basedir = os.path.dirname(path)
132
        name = os.path.basename(path)
133
        if not os.path.exists(basedir):
134
            d.msgbox("Directory `%s' does not exist" % basedir,
135
                     width=MSGBOX_WIDTH)
136
            continue
137

    
138
        dir = basedir
139
        if len(name) == 0:
140
            continue
141

    
142
        files = ["%s%s" % (path, ext) for ext in ('', '.meta', '.md5sum')]
143
        overwrite = filter(os.path.exists, files)
144

    
145
        if len(overwrite) > 0:
146
            if d.yesno("The following file(s) exist:\n"
147
                        "%s\nDo you want to overwrite them?" %
148
                        "\n".join(overwrite), width=YESNO_WIDTH):
149
                continue
150

    
151
        out = GaugeOutput(d, "Image Extraction", "Extracting image...")
152
        try:
153
            dev = session['device']
154
            if "checksum" not in session:
155
                size = dev.meta['SIZE']
156
                md5 = MD5(out)
157
                session['checksum'] = md5.compute(session['snapshot'], size)
158

    
159
            # Extract image file
160
            dev.out = out
161
            dev.dump(path)
162

    
163
            # Extract metadata file
164
            out.output("Extracting metadata file...")
165
            metastring = '\n'.join(
166
                ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()])
167
            metastring += '\n'
168
            with open('%s.meta' % path, 'w') as f:
169
                f.write(metastring)
170
            out.success('done')
171

    
172
            # Extract md5sum file
173
            out.output("Extracting md5sum file...")
174
            md5str = "%s %s\n" % (session['checksum'], name)
175
            with open('%s.md5sum' % path, 'w') as f:
176
                f.write(md5str)
177
            out.success("done")
178

    
179
        finally:
180
            out.cleanup()
181
        d.msgbox("Image file `%s' was successfully extracted!" % path,
182
                 width=MSGBOX_WIDTH)
183
        break
184

    
185
    return True
186

    
187

    
188
def upload_image(session):
189
    d = session["dialog"]
190
    size = session['device'].meta['SIZE']
191

    
192
    if "account" not in session:
193
        d.msgbox("You need to provide your ~okeanos login username before you "
194
                 "can upload images to pithos+", width=MSGBOX_WIDTH)
195
        return False
196

    
197
    if "token" not in session:
198
        d.msgbox("You need to provide your ~okeanos account authentication "
199
                 "token before you can upload images to pithos+",
200
                 width=MSGBOX_WIDTH)
201
        return False
202

    
203
    while 1:
204
        init = session["upload"] if "upload" in session else ''
205
        (code, answer) = d.inputbox("Please provide a filename:", init=init,
206
                                    width=INPUTBOX_WIDTH)
207

    
208
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
209
            return False
210

    
211
        filename = answer.strip()
212
        if len(filename) == 0:
213
            d.msgbox("Filename cannot be empty", width=MSGBOX_WIDTH)
214
            continue
215

    
216
        break
217

    
218
    out = GaugeOutput(d, "Image Upload", "Uploading...")
219
    if 'checksum' not in session:
220
        md5 = MD5(out)
221
        session['checksum'] = md5.compute(session['snapshot'], size)
222
    try:
223
        kamaki = Kamaki(session['account'], session['token'], out)
224
        try:
225
            # Upload image file
226
            with open(session['snapshot'], 'rb') as f:
227
                session["upload"] = kamaki.upload(f, size, filename,
228
                                                  "Calculating block hashes",
229
                                                  "Uploading missing blocks")
230
            # Upload metadata file
231
            out.output("Uploading metadata file...")
232
            metastring = '\n'.join(
233
                ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()])
234
            metastring += '\n'
235
            kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
236
                          remote_path="%s.meta" % filename)
237
            out.success("done")
238

    
239
            # Upload md5sum file
240
            out.output("Uploading md5sum file...")
241
            md5str = "%s %s\n" % (session['checksum'], filename)
242
            kamaki.upload(StringIO.StringIO(md5str), size=len(md5str),
243
                          remote_path="%s.md5sum" % filename)
244
            out.success("done")
245

    
246
        except ClientError as e:
247
            d.msgbox("Error in pithos+ client: %s" % e.message,
248
                     title="Pithos+ Client Error", width=MSGBOX_WIDTH)
249
            if 'upload' in session:
250
                del session['upload']
251
            return False
252
    finally:
253
        out.cleanup()
254

    
255
    d.msgbox("Image file `%s' was successfully uploaded to pithos+" % filename,
256
             width=MSGBOX_WIDTH)
257

    
258
    return True
259

    
260

    
261
def register_image(session):
262
    d = session["dialog"]
263

    
264
    if "account" not in session:
265
        d.msgbox("You need to provide your ~okeanos login username before you "
266
                 "can register an images to cyclades",
267
                 width=MSGBOX_WIDTH)
268
        return False
269

    
270
    if "token" not in session:
271
        d.msgbox("You need to provide your ~okeanos account authentication "
272
                 "token before you can register an images to cyclades",
273
                 width=MSGBOX_WIDTH)
274
        return False
275

    
276
    if "upload" not in session:
277
        d.msgbox("You need to have an image uploaded to pithos+ before you "
278
                 "can register it to cyclades",
279
                 width=MSGBOX_WIDTH)
280
        return False
281

    
282
    while 1:
283
        (code, answer) = d.inputbox("Please provide a registration name:"
284
                                " be registered:", width=INPUTBOX_WIDTH)
285
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
286
            return False
287

    
288
        name = answer.strip()
289
        if len(name) == 0:
290
            d.msgbox("Registration name cannot be empty", width=MSGBOX_WIDTH)
291
            continue
292
        break
293

    
294
    out = GaugeOutput(d, "Image Registration", "Registrating image...")
295
    try:
296
        out.output("Registring image to cyclades...")
297
        try:
298
            kamaki = Kamaki(session['account'], session['token'], out)
299
            kamaki.register(name, session['upload'], session['metadata'])
300
            out.success('done')
301
        except ClientError as e:
302
            d.msgbox("Error in pithos+ client: %s" % e.message)
303
            return False
304
    finally:
305
        out.cleanup()
306

    
307
    d.msgbox("Image `%s' was successfully registered to cyclades as `%s'" %
308
             (session['upload'], name), width=MSGBOX_WIDTH)
309
    return True
310

    
311

    
312
def kamaki_menu(session):
313
    d = session['dialog']
314
    default_item = "Account"
315
    while 1:
316
        account = session["account"] if "account" in session else "<none>"
317
        token = session["token"] if "token" in session else "<none>"
318
        upload = session["upload"] if "upload" in session else "<none>"
319

    
320
        choices = [("Account", "Change your ~okeanos username: %s" % account),
321
                   ("Token", "Change your ~okeanos token: %s" % token),
322
                   ("Upload", "Upload image to pithos+"),
323
                   ("Register", "Register image to cyclades: %s" % upload)]
324

    
325
        (code, choice) = d.menu(
326
            text="Choose one of the following or press <Back> to go back.",
327
            width=MENU_WIDTH, choices=choices, cancel="Back", help_button=1,
328
            default_item=default_item, title="Image Registration Menu")
329

    
330
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
331
            return False
332

    
333
        if choice == "Account":
334
            default_item = "Account"
335
            (code, answer) = d.inputbox(
336
                "Please provide your ~okeanos account e-mail address:",
337
                init=session["account"] if "account" in session else '',
338
                width=70)
339
            if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
340
                continue
341
            if len(answer) == 0 and "account" in session:
342
                    del session["account"]
343
            else:
344
                session["account"] = answer.strip()
345
                default_item = "Token"
346
        elif choice == "Token":
347
            default_item = "Token"
348
            (code, answer) = d.inputbox(
349
                "Please provide your ~okeanos account authetication token:",
350
                init=session["token"] if "token" in session else '',
351
                width=70)
352
            if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
353
                continue
354
            if len(answer) == 0 and "token" in session:
355
                del session["token"]
356
            else:
357
                session["token"] = answer.strip()
358
                default_item = "Upload"
359
        elif choice == "Upload":
360
            if upload_image(session):
361
                default_item = "Register"
362
            else:
363
                default_item = "Upload"
364
        elif choice == "Register":
365
            if register_image(session):
366
                return True
367
            else:
368
                default_item = "Register"
369

    
370

    
371
def add_property(session):
372
    d = session['dialog']
373

    
374
    while 1:
375
        (code, answer) = d.inputbox("Please provide a name for a new image"
376
                                    " property:", width=INPUTBOX_WIDTH)
377
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
378
            return False
379

    
380
        name = answer.strip()
381
        if len(name) == 0:
382
            d.msgbox("A property name cannot be empty", width=MSGBOX_WIDTH)
383
            continue
384

    
385
        break
386

    
387
    while 1:
388
        (code, answer) = d.inputbox("Please provide a value for image "
389
                                   "property %s" % name, width=INPUTBOX_WIDTH)
390
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
391
            return False
392

    
393
        value = answer.strip()
394
        if len(value) == 0:
395
            d.msgbox("Value cannot be empty", width=MSGBOX_WIDTH)
396
            continue
397

    
398
        break
399

    
400
    session['metadata'][name] = value
401

    
402
    return True
403

    
404

    
405
def modify_properties(session):
406
    d = session['dialog']
407

    
408
    while 1:
409
        choices = []
410
        for (key, val) in session['metadata'].items():
411
            choices.append((str(key), str(val)))
412

    
413
        (code, choice) = d.menu(
414
            "In this menu you can edit existing image properties or add new "
415
            "ones. Be careful! Most properties have special meaning and "
416
            "alter the image deployment behaviour. Press <HELP> to see more "
417
            "information about image properties. Press <BACK> when done.",
418
            height=18, width=MENU_WIDTH, choices=choices, menu_height=10,
419
            ok_label="Edit", extra_button=1, extra_label="Add", cancel="Back",
420
            help_button=1, title="Image Metadata")
421

    
422
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
423
            break
424
        # Edit button
425
        elif code == d.DIALOG_OK:
426
            (code, answer) = d.inputbox("Please provide a new value for "
427
                    "the image property with name `%s':" % choice,
428
                    init=session['metadata'][choice], width=INPUTBOX_WIDTH)
429
            if code not in (d.DIALOG_CANCEL, d.DIALOG_ESC):
430
                value = answer.strip()
431
                if len(value) == 0:
432
                    d.msgbox("Value cannot be empty!")
433
                    continue
434
                else:
435
                    session['metadata'][choice] = value
436
        # ADD button
437
        elif code == d.DIALOG_EXTRA:
438
            add_property(session)
439

    
440

    
441
def delete_properties(session):
442
    d = session['dialog']
443

    
444
    choices = []
445
    for (key, val) in session['metadata'].items():
446
        choices.append((key, "%s" % val, 0))
447

    
448
    (code, to_delete) = d.checklist("Choose which properties to delete:",
449
                                    choices=choices, width=CHECKBOX_WIDTH)
450

    
451
    # If the user exits with ESC or CANCEL, the returned tag list is empty.
452
    for i in to_delete:
453
        del session['metadata'][i]
454

    
455
    cnt = len(to_delete)
456
    if cnt > 0:
457
        d.msgbox("%d image properties were deleted." % cnt, width=MSGBOX_WIDTH)
458

    
459

    
460
def exclude_tasks(session):
461
    d = session['dialog']
462

    
463
    index = 0
464
    displayed_index = 1
465
    choices = []
466
    mapping = {}
467
    if 'excluded_tasks' not in session:
468
        session['excluded_tasks'] = []
469

    
470
    if -1 in session['excluded_tasks']:
471
        if not d.yesno("Image deployment configuration is disabled. "
472
                       "Do you wish to enable it?", width=YESNO_WIDTH):
473
            session['excluded_tasks'].remove(-1)
474
        else:
475
            return
476

    
477
    for (msg, task, osfamily) in CONFIGURATION_TASKS:
478
        if session['metadata']['OSFAMILY'] in osfamily:
479
            checked = 1 if index in session['excluded_tasks'] else 0
480
            choices.append((str(displayed_index), msg, checked))
481
            mapping[displayed_index] = index
482
            displayed_index += 1
483
        index += 1
484

    
485
    while 1:
486
        (code, tags) = d.checklist(
487
            text="Please choose which configuration tasks you would like to "
488
                 "prevent from running during image deployment. "
489
                 "Press <No Config> to supress any configuration. "
490
                 "Press <Help> for more help on the image deployment "
491
                 "configuration tasks.",
492
            choices=choices, height=19, list_height=8, width=CHECKBOX_WIDTH,
493
            help_button=1, extra_button=1, extra_label="No Config",
494
            title="Exclude Configuration Tasks")
495

    
496
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
497
            break
498
        elif code == d.DIALOG_HELP:
499
            help_file = get_help_file("configuration_tasks")
500
            assert os.path.exists(help_file)
501
            d.textbox(help_file, title="Configuration Tasks",
502
                      width=70, height=40)
503
        # No Config button
504
        elif code == d.DIALOG_EXTRA:
505
            session['excluded_tasks'] = [-1]
506
            session['task_metadata'] = ["EXCLUDE_ALL_TASKS"]
507
            break
508
        elif code == d.DIALOG_OK:
509
            session['excluded_tasks'] = []
510
            for tag in tags:
511
                session['excluded_tasks'].append(mapping[int(tag)])
512

    
513
            exclude_metadata = []
514
            for task in session['excluded_tasks']:
515
                exclude_metadata.extend(CONFIGURATION_TASKS[task][1])
516

    
517
            session['task_metadata'] = \
518
                        map(lambda x: "EXCLUDE_TASK_%s" % x, exclude_metadata)
519
            break
520

    
521

    
522
def sysprep(session):
523
    d = session['dialog']
524
    image_os = session['image_os']
525

    
526
    # Is the image already shrinked?
527
    if 'shrinked' in session and session['shrinked'] == True:
528
        msg = "It seems you have shrinked the image. Running system " \
529
              "preparation tasks on a shrinked image is dangerous."
530

    
531
        if d.yesno("%s\n\nDo you really want to continue?" % msg,
532
                   width=YESNO_WIDTH, defaultno=1):
533
            return
534

    
535
    wrapper = textwrap.TextWrapper(width=65)
536

    
537
    help_title = "System Preperation Tasks"
538
    sysprep_help = "%s\n%s\n\n" % (help_title, '=' * len(help_title))
539

    
540
    if 'exec_syspreps' not in session:
541
        session['exec_syspreps'] = []
542

    
543
    all_syspreps = image_os.list_syspreps()
544
    # Only give the user the choice between syspreps that have not ran yet
545
    syspreps = [s for s in all_syspreps if s not in session['exec_syspreps']]
546

    
547
    if len(syspreps) == 0:
548
        d.msgbox("No system preparation task left to run!", width=MSGBOX_WIDTH)
549
        return
550

    
551
    while 1:
552
        choices = []
553
        index = 0
554
        for sysprep in syspreps:
555
            name, descr = image_os.sysprep_info(sysprep)
556
            display_name = name.replace('-', ' ').capitalize()
557
            sysprep_help += "%s\n" % display_name
558
            sysprep_help += "%s\n" % ('-' * len(display_name))
559
            sysprep_help += "%s\n\n" % wrapper.fill(" ".join(descr.split()))
560
            enabled = 1 if sysprep.enabled else 0
561
            choices.append((str(index + 1), display_name, enabled))
562
            index += 1
563

    
564
        (code, tags) = d.checklist(
565
            "Please choose which system preperation tasks you would like to "
566
            "run on the image. Press <Help> to see details about the system "
567
            "preperation tasks.", title="Run system preperation tasks",
568
            choices=choices, width=70, ok_label="Run", help_button=1)
569

    
570
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
571
            break
572
        elif code == d.DIALOG_HELP:
573
            d.scrollbox(sysprep_help, width=HELP_WIDTH)
574
        elif code == d.DIALOG_OK:
575
            # Enable selected syspreps and disable the rest
576
            for i in range(len(syspreps)):
577
                if str(i + 1) in tags:
578
                    image_os.enable_sysprep(syspreps[i])
579
                    session['exec_syspreps'].append(syspreps[i])
580
                else:
581
                    image_os.disable_sysprep(syspreps[i])
582

    
583
            out = InfoBoxOutput(d, "Image Configuration")
584
            try:
585
                dev = session['device']
586
                dev.out = out
587
                dev.mount(readonly=False)
588
                try:
589
                    # The checksum is invalid. We have mounted the image rw
590
                    if 'checksum' in session:
591
                        del session['checksum']
592

    
593
                    image_os.out = out
594
                    image_os.do_sysprep()
595

    
596
                    for (k, v) in image_os.meta.items():
597
                        session['metadata'][str(k)] = str(v)
598

    
599
                    # Disable syspreps that have ran
600
                    for sysprep in session['exec_syspreps']:
601
                        image_os.disable_sysprep(sysprep)
602

    
603
                    image_os.out.finalize()
604
                finally:
605
                    dev.umount()
606
            finally:
607
                out.cleanup()
608
            break
609

    
610

    
611
def shrink(session):
612
    d = session['dialog']
613
    dev = session['device']
614

    
615
    shrinked = 'shrinked' in session and session['shrinked'] == True
616

    
617
    if shrinked:
618
        d.msgbox("You have already shrinked your image!")
619
        return
620

    
621
    msg = "This operation will shrink the last partition of the image to " \
622
          "reduce the total image size. If the last partition is a swap " \
623
          "partition, then this partition is removed and the partition " \
624
          "before that is shrinked. The removed swap partition will be " \
625
          "recreated during image deployment."
626

    
627
    if not d.yesno("%s\n\nDo you want to continue?" % msg, width=70,
628
                   height=12, title="Image Shrinking"):
629
        dev.out = InfoBoxOutput(d, "Image Shrinking", height=3)
630
        session['metadata']['SIZE'] = str(dev.shrink())
631
        session['shrinked'] = True
632
        update_background_title(session)
633
        dev.out.finalize()
634

    
635

    
636
def customization_menu(session):
637
    d = session['dialog']
638

    
639
    choices = [("Sysprep", "Run various image preperation tasks"),
640
               ("Shrink", "Shrink image"),
641
               ("View/Modify", "View/Modify image properties"),
642
               ("Delete", "Delete image properties"),
643
               ("Exclude", "Exclude various deployment tasks from running")]
644

    
645
    default_item = "Sysprep"
646

    
647
    actions = {"Sysprep": sysprep,
648
               "Shrink": shrink,
649
               "View/Modify": modify_properties,
650
               "Delete": delete_properties,
651
               "Exclude": exclude_tasks}
652
    while 1:
653
        (code, choice) = d.menu(
654
            text="Choose one of the following or press <Back> to exit.",
655
            width=MENU_WIDTH, choices=choices, cancel="Back", height=13,
656
            menu_height=len(choices), default_item=default_item,
657
            title="Image Customization Menu")
658

    
659
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
660
            break
661
        elif choice in actions:
662
            default_item = choice
663
            actions[choice](session)
664

    
665

    
666
def main_menu(session):
667
    d = session['dialog']
668
    dev = session['device']
669

    
670
    update_background_title(session)
671

    
672
    choices = [("Customize", "Customize image & ~okeanos deployment options"),
673
               ("Register", "Register image to ~okeanos"),
674
               ("Extract", "Dump image to local file system"),
675
               ("Reset", "Reset everything and start over again"),
676
               ("Help", "Get help for using snf-image-creator")]
677

    
678
    default_item = "Customize"
679

    
680
    actions = {"Customize": customization_menu, "Register": kamaki_menu,
681
               "Extract": extract_image}
682
    while 1:
683
        (code, choice) = d.menu(
684
            text="Choose one of the following or press <Exit> to exit.",
685
            width=MENU_WIDTH, choices=choices, cancel="Exit", height=13,
686
            default_item=default_item, menu_height=len(choices),
687
            title="Image Creator for ~okeanos (snf-image-creator version %s)" %
688
                  version)
689

    
690
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
691
            if confirm_exit(d):
692
                break
693
        elif choice == "Reset":
694
            if confirm_reset(d):
695
                d.infobox("Resetting snf-image-creator. Please wait...",
696
                          width=INFOBOX_WIDTH)
697
                raise Reset
698
        elif choice in actions:
699
            actions[choice](session)
700

    
701

    
702
def select_file(d, media):
703
    root = os.sep
704
    while 1:
705
        if media is not None:
706
            if not os.path.exists(media):
707
                d.msgbox("The file you choose does not exist",
708
                         width=MSGBOX_WIDTH)
709
            else:
710
                break
711

    
712
        (code, media) = d.fselect(root, 10, 50,
713
                                 title="Please select input media")
714
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
715
            if confirm_exit(d, "You canceled the media selection dialog box."):
716
                sys.exit(0)
717
            else:
718
                media = None
719
                continue
720

    
721
    return media
722

    
723

    
724
def image_creator(d):
725
    basename = os.path.basename(sys.argv[0])
726
    usage = "Usage: %s [input_media]" % basename
727
    if len(sys.argv) > 2:
728
        sys.stderr.write("%s\n" % usage)
729
        return 1
730

    
731
    d.setBackgroundTitle('snf-image-creator')
732

    
733
    if os.geteuid() != 0:
734
        raise FatalError("You must run %s as root" % basename)
735

    
736
    media = select_file(d, sys.argv[1] if len(sys.argv) == 2 else None)
737

    
738
    out = GaugeOutput(d, "Initialization", "Initializing...")
739
    disk = Disk(media, out)
740

    
741
    def signal_handler(signum, frame):
742
        out.cleanup()
743
        disk.cleanup()
744

    
745
    signal.signal(signal.SIGINT, signal_handler)
746
    try:
747
        snapshot = disk.snapshot()
748
        dev = disk.get_device(snapshot)
749

    
750
        out.output("Collecting image metadata...")
751

    
752
        metadata = {}
753
        for (key, value) in dev.meta.items():
754
            metadata[str(key)] = str(value)
755

    
756
        dev.mount(readonly=True)
757
        cls = os_cls(dev.distro, dev.ostype)
758
        image_os = cls(dev.root, dev.g, out)
759
        dev.umount()
760

    
761
        for (key, value) in image_os.meta.items():
762
            metadata[str(key)] = str(value)
763

    
764
        out.success("done")
765
        out.cleanup()
766

    
767
        # Make sure the signal handler does not call out.cleanup again
768
        def dummy(self):
769
            pass
770
        out.cleanup = type(GaugeOutput.cleanup)(dummy, out, GaugeOutput)
771

    
772
        session = {"dialog": d,
773
                   "disk": disk,
774
                   "snapshot": snapshot,
775
                   "device": dev,
776
                   "image_os": image_os,
777
                   "metadata": metadata}
778

    
779
        main_menu(session)
780
        d.infobox("Thank you for using snf-image-creator. Bye", width=53)
781
    finally:
782
        disk.cleanup()
783

    
784
    return 0
785

    
786

    
787
def main():
788

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

    
791
    # Add extra button in dialog library
792
    dialog._common_args_syntax["extra_button"] = \
793
        lambda enable: dialog._simple_option("--extra-button", enable)
794

    
795
    dialog._common_args_syntax["extra_label"] = \
796
        lambda string: ("--extra-label", string)
797

    
798
    while 1:
799
        try:
800
            try:
801
                ret = image_creator(d)
802
                sys.exit(ret)
803
            except FatalError as e:
804
                msg = textwrap.fill(str(e), width=70)
805
                d.infobox(msg, width=INFOBOX_WIDTH, title="Fatal Error")
806
                sys.exit(1)
807
        except Reset:
808
            continue
809

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