Statistics
| Branch: | Tag: | Revision:

root / image_creator / dialog_main.py @ ea947a64

History | View | Annotate | Download (29.7 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"],
68
        ["windows"]),
69
    ("SELinux relabeling at next boot", ["SELinuxAutorelabel"], ["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
class metadata_monitor(object):
82
    def __init__(self, session, meta):
83
        self.session = session
84
        self.meta = meta
85

    
86
    def __enter__(self):
87
        self.old = {}
88
        for (k, v) in self.meta.items():
89
            self.old[k] = v
90

    
91
    def __exit__(self, type, value, traceback):
92
        d = self.session['dialog']
93

    
94
        altered = {}
95
        added = {}
96

    
97
        for (k, v) in self.meta.items():
98
            if k not in self.old:
99
                added[k] = v
100
            elif self.old[k] != v:
101
                altered[k] = v
102

    
103
        if not (len(added) or len(altered)):
104
            return
105

    
106
        msg = "The last action has changed some image properties:\n\n"
107
        if len(added):
108
            msg += "New image properties:\n"
109
            for (k, v) in added.items():
110
                msg += '    %s: "%s"\n' % (k, v)
111
            msg += "\n"
112
        if len(altered):
113
            msg += "Updated image properties:\n"
114
            for (k, v) in altered.items():
115
                msg += '    %s: "%s" -> "%s"\n' % (k, self.old[k], v)
116
            msg += "\n"
117

    
118
        self.session['metadata'].update(added)
119
        self.session['metadata'].update(altered)
120
        d.msgbox(msg, title="Image Property Changes", width=MSGBOX_WIDTH)
121

    
122

    
123
def extract_metadata_string(session):
124
    metadata = ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()]
125

    
126
    if 'task_metadata' in session:
127
        metadata.extend("%s=yes" % m for m in session['task_metadata'])
128

    
129
    return '\n'.join(metadata) + '\n'
130

    
131

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

    
135

    
136
def confirm_reset(d):
137
    return not d.yesno("Are you sure you want to reset everything?",
138
                       width=YESNO_WIDTH, defaultno=1)
139

    
140

    
141
def update_background_title(session):
142
    d = session['dialog']
143
    dev = session['device']
144

    
145
    MB = 2 ** 20
146

    
147
    size = (dev.meta['SIZE'] + MB - 1) // MB
148
    shrinked = 'shrinked' in session and session['shrinked'] == True
149
    postfix = " (shrinked)" if shrinked else ''
150

    
151
    title = "OS: %s, Distro: %s, Size: %dMB%s" % \
152
            (dev.ostype, dev.distro, size, postfix)
153

    
154
    d.setBackgroundTitle(title)
155

    
156

    
157
def extract_image(session):
158
    d = session['dialog']
159
    dir = os.getcwd()
160
    while 1:
161
        if dir and dir[-1] != os.sep:
162
            dir = dir + os.sep
163

    
164
        (code, path) = d.fselect(dir, 10, 50, title="Save image as...")
165
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
166
            return False
167

    
168
        if os.path.isdir(path):
169
            dir = path
170
            continue
171

    
172
        if os.path.isdir("%s.meta" % path):
173
            d.msgbox("Can't overwrite directory `%s.meta'" % path,
174
                     width=MSGBOX_WIDTH)
175
            continue
176

    
177
        if os.path.isdir("%s.md5sum" % path):
178
            d.msgbox("Can't overwrite directory `%s.md5sum'" % path,
179
                     width=MSGBOX_WIDTH)
180
            continue
181

    
182
        basedir = os.path.dirname(path)
183
        name = os.path.basename(path)
184
        if not os.path.exists(basedir):
185
            d.msgbox("Directory `%s' does not exist" % basedir,
186
                     width=MSGBOX_WIDTH)
187
            continue
188

    
189
        dir = basedir
190
        if len(name) == 0:
191
            continue
192

    
193
        files = ["%s%s" % (path, ext) for ext in ('', '.meta', '.md5sum')]
194
        overwrite = filter(os.path.exists, files)
195

    
196
        if len(overwrite) > 0:
197
            if d.yesno("The following file(s) exist:\n"
198
                       "%s\nDo you want to overwrite them?" %
199
                       "\n".join(overwrite), width=YESNO_WIDTH):
200
                continue
201

    
202
        out = GaugeOutput(d, "Image Extraction", "Extracting image...")
203
        try:
204
            dev = session['device']
205
            if "checksum" not in session:
206
                size = dev.meta['SIZE']
207
                md5 = MD5(out)
208
                session['checksum'] = md5.compute(session['snapshot'], size)
209

    
210
            # Extract image file
211
            dev.out = out
212
            dev.dump(path)
213

    
214
            # Extract metadata file
215
            out.output("Extracting metadata file...")
216
            with open('%s.meta' % path, 'w') as f:
217
                f.write(extract_metadata_string(session))
218
            out.success('done')
219

    
220
            # Extract md5sum file
221
            out.output("Extracting md5sum file...")
222
            md5str = "%s %s\n" % (session['checksum'], name)
223
            with open('%s.md5sum' % path, 'w') as f:
224
                f.write(md5str)
225
            out.success("done")
226

    
227
        finally:
228
            out.cleanup()
229
        d.msgbox("Image file `%s' was successfully extracted!" % path,
230
                 width=MSGBOX_WIDTH)
231
        break
232

    
233
    return True
234

    
235

    
236
def upload_image(session):
237
    d = session["dialog"]
238
    size = session['device'].meta['SIZE']
239

    
240
    if "account" not in session:
241
        d.msgbox("You need to provide your ~okeanos login username before you "
242
                 "can upload images to pithos+", width=MSGBOX_WIDTH)
243
        return False
244

    
245
    if "token" not in session:
246
        d.msgbox("You need to provide your ~okeanos account authentication "
247
                 "token before you can upload images to pithos+",
248
                 width=MSGBOX_WIDTH)
249
        return False
250

    
251
    while 1:
252
        init = session["upload"] if "upload" in session else ''
253
        (code, answer) = d.inputbox("Please provide a filename:", init=init,
254
                                    width=INPUTBOX_WIDTH)
255

    
256
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
257
            return False
258

    
259
        filename = answer.strip()
260
        if len(filename) == 0:
261
            d.msgbox("Filename cannot be empty", width=MSGBOX_WIDTH)
262
            continue
263

    
264
        break
265

    
266
    out = GaugeOutput(d, "Image Upload", "Uploading...")
267
    if 'checksum' not in session:
268
        md5 = MD5(out)
269
        session['checksum'] = md5.compute(session['snapshot'], size)
270
    try:
271
        kamaki = Kamaki(session['account'], session['token'], out)
272
        try:
273
            # Upload image file
274
            with open(session['snapshot'], 'rb') as f:
275
                session["upload"] = kamaki.upload(f, size, filename,
276
                                                  "Calculating block hashes",
277
                                                  "Uploading missing blocks")
278
            # Upload metadata file
279
            out.output("Uploading metadata file...")
280
            metastring = extract_metadata_string(session)
281
            kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
282
                          remote_path="%s.meta" % filename)
283
            out.success("done")
284

    
285
            # Upload md5sum file
286
            out.output("Uploading md5sum file...")
287
            md5str = "%s %s\n" % (session['checksum'], filename)
288
            kamaki.upload(StringIO.StringIO(md5str), size=len(md5str),
289
                          remote_path="%s.md5sum" % filename)
290
            out.success("done")
291

    
292
        except ClientError as e:
293
            d.msgbox("Error in pithos+ client: %s" % e.message,
294
                     title="Pithos+ Client Error", width=MSGBOX_WIDTH)
295
            if 'upload' in session:
296
                del session['upload']
297
            return False
298
    finally:
299
        out.cleanup()
300

    
301
    d.msgbox("Image file `%s' was successfully uploaded to pithos+" % filename,
302
             width=MSGBOX_WIDTH)
303

    
304
    return True
305

    
306

    
307
def register_image(session):
308
    d = session["dialog"]
309

    
310
    if "account" not in session:
311
        d.msgbox("You need to provide your ~okeanos login username before you "
312
                 "can register an images to cyclades",
313
                 width=MSGBOX_WIDTH)
314
        return False
315

    
316
    if "token" not in session:
317
        d.msgbox("You need to provide your ~okeanos account authentication "
318
                 "token before you can register an images to cyclades",
319
                 width=MSGBOX_WIDTH)
320
        return False
321

    
322
    if "upload" not in session:
323
        d.msgbox("You need to upload the image to pithos+ before you can "
324
                 "register it to cyclades", width=MSGBOX_WIDTH)
325
        return False
326

    
327
    while 1:
328
        (code, answer) = d.inputbox("Please provide a registration name:",
329
                                    width=INPUTBOX_WIDTH)
330
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
331
            return False
332

    
333
        name = answer.strip()
334
        if len(name) == 0:
335
            d.msgbox("Registration name cannot be empty", width=MSGBOX_WIDTH)
336
            continue
337
        break
338

    
339
    metadata = {}
340
    metadata.update(session['metadata'])
341
    if 'task_metadata' in session:
342
        for key in session['task_metadata']:
343
            metadata[key] = 'yes'
344

    
345
    out = GaugeOutput(d, "Image Registration", "Registrating image...")
346
    try:
347
        out.output("Registring image to cyclades...")
348
        try:
349
            kamaki = Kamaki(session['account'], session['token'], out)
350
            kamaki.register(name, session['upload'], metadata)
351
            out.success('done')
352
        except ClientError as e:
353
            d.msgbox("Error in pithos+ client: %s" % e.message)
354
            return False
355
    finally:
356
        out.cleanup()
357

    
358
    d.msgbox("Image `%s' was successfully registered to cyclades as `%s'" %
359
             (session['upload'], name), width=MSGBOX_WIDTH)
360
    return True
361

    
362

    
363
def kamaki_menu(session):
364
    d = session['dialog']
365
    default_item = "Account"
366
    while 1:
367
        account = session["account"] if "account" in session else "<none>"
368
        token = session["token"] if "token" in session else "<none>"
369
        upload = session["upload"] if "upload" in session else "<none>"
370

    
371
        choices = [("Account", "Change your ~okeanos username: %s" % account),
372
                   ("Token", "Change your ~okeanos token: %s" % token),
373
                   ("Upload", "Upload image to pithos+"),
374
                   ("Register", "Register the image to cyclades: %s" % upload)]
375

    
376
        (code, choice) = d.menu(
377
            text="Choose one of the following or press <Back> to go back.",
378
            width=MENU_WIDTH, choices=choices, cancel="Back",
379
            default_item=default_item, title="Image Registration Menu")
380

    
381
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
382
            return False
383

    
384
        if choice == "Account":
385
            default_item = "Account"
386
            (code, answer) = d.inputbox(
387
                "Please provide your ~okeanos account e-mail address:",
388
                init=session["account"] if "account" in session else '',
389
                width=70)
390
            if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
391
                continue
392
            if len(answer) == 0 and "account" in session:
393
                    del session["account"]
394
            else:
395
                session["account"] = answer.strip()
396
                default_item = "Token"
397
        elif choice == "Token":
398
            default_item = "Token"
399
            (code, answer) = d.inputbox(
400
                "Please provide your ~okeanos account authetication token:",
401
                init=session["token"] if "token" in session else '',
402
                width=70)
403
            if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
404
                continue
405
            if len(answer) == 0 and "token" in session:
406
                del session["token"]
407
            else:
408
                session["token"] = answer.strip()
409
                default_item = "Upload"
410
        elif choice == "Upload":
411
            if upload_image(session):
412
                default_item = "Register"
413
            else:
414
                default_item = "Upload"
415
        elif choice == "Register":
416
            if register_image(session):
417
                return True
418
            else:
419
                default_item = "Register"
420

    
421

    
422
def add_property(session):
423
    d = session['dialog']
424

    
425
    while 1:
426
        (code, answer) = d.inputbox("Please provide a name for a new image"
427
                                    " property:", width=INPUTBOX_WIDTH)
428
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
429
            return False
430

    
431
        name = answer.strip()
432
        if len(name) == 0:
433
            d.msgbox("A property name cannot be empty", width=MSGBOX_WIDTH)
434
            continue
435

    
436
        break
437

    
438
    while 1:
439
        (code, answer) = d.inputbox("Please provide a value for image "
440
                                    "property %s" % name, width=INPUTBOX_WIDTH)
441
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
442
            return False
443

    
444
        value = answer.strip()
445
        if len(value) == 0:
446
            d.msgbox("Value cannot be empty", width=MSGBOX_WIDTH)
447
            continue
448

    
449
        break
450

    
451
    session['metadata'][name] = value
452

    
453
    return True
454

    
455

    
456
def modify_properties(session):
457
    d = session['dialog']
458

    
459
    while 1:
460
        choices = []
461
        for (key, val) in session['metadata'].items():
462
            choices.append((str(key), str(val)))
463

    
464
        (code, choice) = d.menu(
465
            "In this menu you can edit existing image properties or add new "
466
            "ones. Be careful! Most properties have special meaning and "
467
            "alter the image deployment behaviour. Press <HELP> to see more "
468
            "information about image properties. Press <BACK> when done.",
469
            height=18, width=MENU_WIDTH, choices=choices, menu_height=10,
470
            ok_label="Edit", extra_button=1, extra_label="Add", cancel="Back",
471
            help_button=1, title="Image Properties")
472

    
473
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
474
            return True
475
        # Edit button
476
        elif code == d.DIALOG_OK:
477
            (code, answer) = d.inputbox("Please provide a new value for the "
478
                                        "image property with name `%s':" %
479
                                        choice,
480
                                        init=session['metadata'][choice],
481
                                        width=INPUTBOX_WIDTH)
482
            if code not in (d.DIALOG_CANCEL, d.DIALOG_ESC):
483
                value = answer.strip()
484
                if len(value) == 0:
485
                    d.msgbox("Value cannot be empty!")
486
                    continue
487
                else:
488
                    session['metadata'][choice] = value
489
        # ADD button
490
        elif code == d.DIALOG_EXTRA:
491
            add_property(session)
492
        elif code == 'help':
493
            help_file = get_help_file("image_properties")
494
            assert os.path.exists(help_file)
495
            d.textbox(help_file, title="Image Properties", width=70, height=40)
496

    
497

    
498
def delete_properties(session):
499
    d = session['dialog']
500

    
501
    choices = []
502
    for (key, val) in session['metadata'].items():
503
        choices.append((key, "%s" % val, 0))
504

    
505
    (code, to_delete) = d.checklist("Choose which properties to delete:",
506
                                    choices=choices, width=CHECKBOX_WIDTH)
507

    
508
    # If the user exits with ESC or CANCEL, the returned tag list is empty.
509
    for i in to_delete:
510
        del session['metadata'][i]
511

    
512
    cnt = len(to_delete)
513
    if cnt > 0:
514
        d.msgbox("%d image properties were deleted." % cnt, width=MSGBOX_WIDTH)
515
        return True
516
    else:
517
        return False
518

    
519

    
520
def exclude_tasks(session):
521
    d = session['dialog']
522

    
523
    index = 0
524
    displayed_index = 1
525
    choices = []
526
    mapping = {}
527
    if 'excluded_tasks' not in session:
528
        session['excluded_tasks'] = []
529

    
530
    if -1 in session['excluded_tasks']:
531
        if not d.yesno("Image deployment configuration is disabled. "
532
                       "Do you wish to enable it?", width=YESNO_WIDTH):
533
            session['excluded_tasks'].remove(-1)
534
        else:
535
            return False
536

    
537
    for (msg, task, osfamily) in CONFIGURATION_TASKS:
538
        if session['metadata']['OSFAMILY'] in osfamily:
539
            checked = 1 if index in session['excluded_tasks'] else 0
540
            choices.append((str(displayed_index), msg, checked))
541
            mapping[displayed_index] = index
542
            displayed_index += 1
543
        index += 1
544

    
545
    while 1:
546
        (code, tags) = d.checklist(
547
            text="Please choose which configuration tasks you would like to "
548
                 "prevent from running during image deployment. "
549
                 "Press <No Config> to supress any configuration. "
550
                 "Press <Help> for more help on the image deployment "
551
                 "configuration tasks.",
552
            choices=choices, height=19, list_height=8, width=CHECKBOX_WIDTH,
553
            help_button=1, extra_button=1, extra_label="No Config",
554
            title="Exclude Configuration Tasks")
555

    
556
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
557
            return False
558
        elif code == d.DIALOG_HELP:
559
            help_file = get_help_file("configuration_tasks")
560
            assert os.path.exists(help_file)
561
            d.textbox(help_file, title="Configuration Tasks",
562
                      width=70, height=40)
563
        # No Config button
564
        elif code == d.DIALOG_EXTRA:
565
            session['excluded_tasks'] = [-1]
566
            session['task_metadata'] = ["EXCLUDE_ALL_TASKS"]
567
            break
568
        elif code == d.DIALOG_OK:
569
            session['excluded_tasks'] = []
570
            for tag in tags:
571
                session['excluded_tasks'].append(mapping[int(tag)])
572

    
573
            exclude_metadata = []
574
            for task in session['excluded_tasks']:
575
                exclude_metadata.extend(CONFIGURATION_TASKS[task][1])
576

    
577
            session['task_metadata'] = map(lambda x: "EXCLUDE_TASK_%s" % x,
578
                                           exclude_metadata)
579
            break
580

    
581
    return True
582

    
583

    
584
def sysprep(session):
585
    d = session['dialog']
586
    image_os = session['image_os']
587

    
588
    # Is the image already shrinked?
589
    if 'shrinked' in session and session['shrinked'] == True:
590
        msg = "It seems you have shrinked the image. Running system " \
591
              "preparation tasks on a shrinked image is dangerous."
592

    
593
        if d.yesno("%s\n\nDo you really want to continue?" % msg,
594
                   width=YESNO_WIDTH, defaultno=1):
595
            return
596

    
597
    wrapper = textwrap.TextWrapper(width=65)
598

    
599
    help_title = "System Preperation Tasks"
600
    sysprep_help = "%s\n%s\n\n" % (help_title, '=' * len(help_title))
601

    
602
    if 'exec_syspreps' not in session:
603
        session['exec_syspreps'] = []
604

    
605
    all_syspreps = image_os.list_syspreps()
606
    # Only give the user the choice between syspreps that have not ran yet
607
    syspreps = [s for s in all_syspreps if s not in session['exec_syspreps']]
608

    
609
    if len(syspreps) == 0:
610
        d.msgbox("No system preparation task left to run!", width=MSGBOX_WIDTH)
611
        return
612

    
613
    while 1:
614
        choices = []
615
        index = 0
616
        for sysprep in syspreps:
617
            name, descr = image_os.sysprep_info(sysprep)
618
            display_name = name.replace('-', ' ').capitalize()
619
            sysprep_help += "%s\n" % display_name
620
            sysprep_help += "%s\n" % ('-' * len(display_name))
621
            sysprep_help += "%s\n\n" % wrapper.fill(" ".join(descr.split()))
622
            enabled = 1 if sysprep.enabled else 0
623
            choices.append((str(index + 1), display_name, enabled))
624
            index += 1
625

    
626
        (code, tags) = d.checklist(
627
            "Please choose which system preperation tasks you would like to "
628
            "run on the image. Press <Help> to see details about the system "
629
            "preperation tasks.", title="Run system preperation tasks",
630
            choices=choices, width=70, ok_label="Run", help_button=1)
631

    
632
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
633
            return False
634
        elif code == d.DIALOG_HELP:
635
            d.scrollbox(sysprep_help, width=HELP_WIDTH)
636
        elif code == d.DIALOG_OK:
637
            # Enable selected syspreps and disable the rest
638
            for i in range(len(syspreps)):
639
                if str(i + 1) in tags:
640
                    image_os.enable_sysprep(syspreps[i])
641
                    session['exec_syspreps'].append(syspreps[i])
642
                else:
643
                    image_os.disable_sysprep(syspreps[i])
644

    
645
            out = InfoBoxOutput(d, "Image Configuration")
646
            try:
647
                dev = session['device']
648
                dev.out = out
649
                dev.mount(readonly=False)
650
                try:
651
                    # The checksum is invalid. We have mounted the image rw
652
                    if 'checksum' in session:
653
                        del session['checksum']
654

    
655
                    # Monitor the metadata changes during syspreps
656
                    with metadata_monitor(session, image_os.meta):
657
                        image_os.out = out
658
                        image_os.do_sysprep()
659
                        image_os.out.finalize()
660

    
661
                    # Disable syspreps that have ran
662
                    for sysprep in session['exec_syspreps']:
663
                        image_os.disable_sysprep(sysprep)
664

    
665
                finally:
666
                    dev.umount()
667
            finally:
668
                out.cleanup()
669
            break
670
    return True
671

    
672

    
673
def shrink(session):
674
    d = session['dialog']
675
    dev = session['device']
676

    
677
    shrinked = 'shrinked' in session and session['shrinked'] == True
678

    
679
    if shrinked:
680
        d.msgbox("You have already shrinked your image!")
681
        return True
682

    
683
    msg = "This operation will shrink the last partition of the image to " \
684
          "reduce the total image size. If the last partition is a swap " \
685
          "partition, then this partition is removed and the partition " \
686
          "before that is shrinked. The removed swap partition will be " \
687
          "recreated during image deployment."
688

    
689
    if not d.yesno("%s\n\nDo you want to continue?" % msg, width=70,
690
                   height=12, title="Image Shrinking"):
691
        with metadata_monitor(session, dev.meta):
692
            dev.out = InfoBoxOutput(d, "Image Shrinking", height=4)
693
            dev.shrink()
694
            dev.out.finalize()
695

    
696
        session['shrinked'] = True
697
        update_background_title(session)
698
    else:
699
        return False
700

    
701
    return True
702

    
703

    
704
def customization_menu(session):
705
    d = session['dialog']
706

    
707
    choices = [("Sysprep", "Run various image preperation tasks"),
708
               ("Shrink", "Shrink image"),
709
               ("View/Modify", "View/Modify image properties"),
710
               ("Delete", "Delete image properties"),
711
               ("Exclude", "Exclude various deployment tasks from running")]
712

    
713
    default_item = 0
714

    
715
    actions = {"Sysprep": sysprep,
716
               "Shrink": shrink,
717
               "View/Modify": modify_properties,
718
               "Delete": delete_properties,
719
               "Exclude": exclude_tasks}
720
    while 1:
721
        (code, choice) = d.menu(
722
            text="Choose one of the following or press <Back> to exit.",
723
            width=MENU_WIDTH, choices=choices, cancel="Back", height=13,
724
            menu_height=len(choices), default_item=choices[default_item][0],
725
            title="Image Customization Menu")
726

    
727
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
728
            break
729
        elif choice in actions:
730
            default_item = [entry[0] for entry in choices].index(choice)
731
            if actions[choice](session):
732
                default_item = (default_item + 1) % len(choices)
733

    
734

    
735
def main_menu(session):
736
    d = session['dialog']
737
    dev = session['device']
738

    
739
    update_background_title(session)
740

    
741
    choices = [("Customize", "Customize image & ~okeanos deployment options"),
742
               ("Register", "Register image to ~okeanos"),
743
               ("Extract", "Dump image to local file system"),
744
               ("Reset", "Reset everything and start over again"),
745
               ("Help", "Get help for using snf-image-creator")]
746

    
747
    default_item = "Customize"
748

    
749
    actions = {"Customize": customization_menu, "Register": kamaki_menu,
750
               "Extract": extract_image}
751
    while 1:
752
        (code, choice) = d.menu(
753
            text="Choose one of the following or press <Exit> to exit.",
754
            width=MENU_WIDTH, choices=choices, cancel="Exit", height=13,
755
            default_item=default_item, menu_height=len(choices),
756
            title="Image Creator for ~okeanos (snf-image-creator version %s)" %
757
                  version)
758

    
759
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
760
            if confirm_exit(d):
761
                break
762
        elif choice == "Reset":
763
            if confirm_reset(d):
764
                d.infobox("Resetting snf-image-creator. Please wait...",
765
                          width=INFOBOX_WIDTH)
766
                raise Reset
767
        elif choice in actions:
768
            actions[choice](session)
769

    
770

    
771
def select_file(d, media):
772
    root = os.sep
773
    while 1:
774
        if media is not None:
775
            if not os.path.exists(media):
776
                d.msgbox("The file you choose does not exist",
777
                         width=MSGBOX_WIDTH)
778
            else:
779
                break
780

    
781
        (code, media) = d.fselect(root, 10, 50,
782
                                  title="Please select input media")
783
        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
784
            if confirm_exit(d, "You canceled the media selection dialog box."):
785
                sys.exit(0)
786
            else:
787
                media = None
788
                continue
789

    
790
    return media
791

    
792

    
793
def image_creator(d):
794
    basename = os.path.basename(sys.argv[0])
795
    usage = "Usage: %s [input_media]" % basename
796
    if len(sys.argv) > 2:
797
        sys.stderr.write("%s\n" % usage)
798
        return 1
799

    
800
    d.setBackgroundTitle('snf-image-creator')
801

    
802
    if os.geteuid() != 0:
803
        raise FatalError("You must run %s as root" % basename)
804

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

    
807
    out = GaugeOutput(d, "Initialization", "Initializing...")
808
    disk = Disk(media, out)
809

    
810
    def signal_handler(signum, frame):
811
        out.cleanup()
812
        disk.cleanup()
813

    
814
    signal.signal(signal.SIGINT, signal_handler)
815
    try:
816
        snapshot = disk.snapshot()
817
        dev = disk.get_device(snapshot)
818

    
819

    
820
        metadata = {}
821
        for (key, value) in dev.meta.items():
822
            metadata[str(key)] = str(value)
823

    
824
        dev.mount(readonly=True)
825
        out.output("Collecting image metadata...")
826
        cls = os_cls(dev.distro, dev.ostype)
827
        image_os = cls(dev.root, dev.g, out)
828
        dev.umount()
829

    
830
        for (key, value) in image_os.meta.items():
831
            metadata[str(key)] = str(value)
832

    
833
        out.success("done")
834
        out.cleanup()
835

    
836
        # Make sure the signal handler does not call out.cleanup again
837
        def dummy(self):
838
            pass
839
        out.cleanup = type(GaugeOutput.cleanup)(dummy, out, GaugeOutput)
840

    
841
        session = {"dialog": d,
842
                   "disk": disk,
843
                   "snapshot": snapshot,
844
                   "device": dev,
845
                   "image_os": image_os,
846
                   "metadata": metadata}
847

    
848
        main_menu(session)
849
        d.infobox("Thank you for using snf-image-creator. Bye", width=53)
850
    finally:
851
        disk.cleanup()
852

    
853
    return 0
854

    
855

    
856
def main():
857

    
858
    d = dialog.Dialog(dialog="dialog")
859

    
860
    # Add extra button in dialog library
861
    dialog._common_args_syntax["extra_button"] = \
862
        lambda enable: dialog._simple_option("--extra-button", enable)
863

    
864
    dialog._common_args_syntax["extra_label"] = \
865
        lambda string: ("--extra-label", string)
866

    
867
    while 1:
868
        try:
869
            try:
870
                ret = image_creator(d)
871
                sys.exit(ret)
872
            except FatalError as e:
873
                msg = textwrap.fill(str(e), width=70)
874
                d.infobox(msg, width=INFOBOX_WIDTH, title="Fatal Error")
875
                sys.exit(1)
876
        except Reset:
877
            continue
878

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