636292e025dc99e8dded2c812b6c33133e5eb70e
[snf-image-creator] / image_creator / dialog_wizard.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 time
38 import StringIO
39
40 from image_creator.kamaki_wrapper import Kamaki, ClientError
41 from image_creator.util import MD5, FatalError
42 from image_creator.output.cli import OutputWthProgress
43
44 PAGE_WIDTH = 70
45
46
47 class WizardExit(Exception):
48     pass
49
50
51 class Wizard:
52     def __init__(self, session):
53         self.session = session
54         self.pages = []
55         self.session['wizard'] = {}
56
57     def add_page(self, page):
58         self.pages.append(page)
59
60     def run(self):
61         idx = 0
62         while True:
63             try:
64                 idx += self.pages[idx].run(self.session, idx, len(self.pages))
65             except WizardExit:
66                 return False
67
68             if idx >= len(self.pages):
69                 break
70
71             if idx < 0:
72                 return False
73         return True
74
75
76 class WizardPage:
77     NEXT = 1
78     PREV = -1
79
80     def run(self, session, index, total):
81         raise NotImplementedError
82
83
84 class WizardInputPage(WizardPage):
85
86     def __init__(self, name, message, **kargs):
87         self.name = name
88         self.message = message
89         self.title = kargs['title'] if 'title' in kargs else ''
90         self.init_value = kargs['init'] if 'init' in kargs else ''
91         self.allow_empty = kargs['empty'] if 'empty' in kargs else False
92
93     def run(self, session, index, total):
94         d = session['dialog']
95         w = session['wizard']
96
97         init = w[self.name] if self.name in w else self.init_value
98         while True:
99             (code, answer) = d.inputbox(self.message, init=init,
100                 width=PAGE_WIDTH, ok_label="Next", cancel="Back",
101                 title="(%d/%d) %s" % (index + 1, total, self.title))
102
103             if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
104                 return self.PREV
105
106             value = answer.strip()
107             if len(value) == 0 and self.allow_empty is False:
108                 d.msgbox("The value cannot be empty!", width=PAGE_WIDTH)
109                 continue
110             w[self.name] = value
111             break
112
113         return self.NEXT
114
115
116 class WizardYesNoPage(WizardPage):
117
118     def __init__(self, message, **kargs):
119         self.message = message
120         self.title = kargs['title'] if 'title' in kargs else ''
121
122     def run(self, session, index, total):
123         d = session['dialog']
124
125         while True:
126             ret = d.yesno(self.message, width=PAGE_WIDTH, ok_label="Yes",
127                     cancel="Back", extra_button=1, extra_label="Quit",
128                     title="(%d/%d) %s" % (index + 1, total, self.title))
129
130             if ret == d.DIALOG_CANCEL:
131                 return self.PREV
132             elif ret == d.DIALOG_EXTRA:
133                 raise WizardExit
134             elif ret == d.DIALOG_OK:
135                 return self.NEXT
136
137
138 def wizard(session):
139
140     name = WizardInputPage("ImageName", "Please provide a name for the image:",
141                       title="Image Name", init=session['device'].distro)
142     descr = WizardInputPage("ImageDescription",
143         "Please provide a description for the image:",
144         title="Image Description", empty=True,
145         init=session['metadata']['DESCRIPTION'] if 'DESCRIPTION' in
146         session['metadata'] else '')
147     account = WizardInputPage("account",
148         "Please provide your ~okeanos account e-mail:",
149         title="~okeanos account information", init=Kamaki.get_account())
150     token = WizardInputPage("token",
151         "Please provide your ~okeanos account token:",
152         title="~okeanos account token", init=Kamaki.get_token())
153
154     msg = "Do you wish to continue with the image extraction process?"
155     proceed = WizardYesNoPage(msg, title="Confirmation")
156
157     w = Wizard(session)
158
159     w.add_page(name)
160     w.add_page(descr)
161     w.add_page(account)
162     w.add_page(token)
163     w.add_page(proceed)
164
165     if w.run():
166         extract_image(session)
167     else:
168         return False
169
170     return True
171
172
173 def extract_image(session):
174     disk = session['disk']
175     device = session['device']
176     snapshot = session['snapshot']
177     image_os = session['image_os']
178     wizard = session['wizard']
179
180     out = OutputWthProgress(True)
181     #Initialize the output
182     disk.out = out
183     device.out = out
184     image_os.out = out
185
186     out.clear()
187
188     #Sysprep
189     device.mount(False)
190     image_os.do_sysprep()
191     metadata = image_os.meta
192     device.umount()
193
194     #Shrink
195     size = device.shrink()
196
197     metadata.update(device.meta)
198     metadata['DESCRIPTION'] = wizard['ImageDescription']
199
200     #MD5
201     md5 = MD5(out)
202     checksum = md5.compute(snapshot, size)
203
204     #Metadata
205     metastring = '\n'.join(
206         ['%s=%s' % (key, value) for (key, value) in metadata.items()])
207     metastring += '\n'
208
209     out.output()
210     try:
211         out.output("Uploading image to pithos:")
212         kamaki = Kamaki(wizard['account'], wizard['token'], out)
213
214         name = "%s-%s.diskdump" % (wizard['ImageName'],
215                                    time.strftime("%Y%m%d%H%M"))
216         pithos_file = ""
217         with open(snapshot, 'rb') as f:
218             pithos_file = kamaki.upload(f, size, name,
219                                          "(1/4)  Calculating block hashes",
220                                          "(2/4)  Uploading missing blocks")
221
222         out.output("(3/4)  Uploading metadata file...", False)
223         kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
224                       remote_path="%s.%s" % (name, 'meta'))
225         out.success('done')
226         out.output("(4/4)  Uploading md5sum file...", False)
227         md5sumstr = '%s %s\n' % (checksum, name)
228         kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr),
229                       remote_path="%s.%s" % (name, 'md5sum'))
230         out.success('done')
231         out.output()
232
233         out.output('Registring image to ~okeanos...', False)
234         kamaki.register(wizard['ImageName'], pithos_file, metadata)
235         out.success('done')
236         out.output()
237     except ClientError as e:
238         raise FatalError("Pithos client: %d %s" % (e.status, e.message))
239
240 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :