af236d6e6af23c0ff79cf5b71a4b1ce3facda3f4
[snf-image-creator] / image_creator / main.py
1 #!/usr/bin/env python
2
3 # Copyright 2011 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 from image_creator import get_os_class
37 from image_creator import __version__ as version
38 from image_creator import util
39 from image_creator.disk import Disk
40 from image_creator.util import get_command, error, success, output, \
41                                                     FatalError, progress, md5
42 from image_creator.kamaki_wrapper import Kamaki
43 import sys
44 import os
45 import optparse
46
47 dd = get_command('dd')
48
49
50 def check_writable_dir(option, opt_str, value, parser):
51     dirname = os.path.dirname(value)
52     name = os.path.basename(value)
53     if dirname and not os.path.isdir(dirname):
54         raise FatalError("`%s' is not an existing directory" % dirname)
55
56     if not name:
57         raise FatalError("`%s' is not a valid file name" % dirname)
58
59     setattr(parser.values, option.dest, value)
60
61
62 def parse_options(input_args):
63     usage = "Usage: %prog [options] <input_media>"
64     parser = optparse.OptionParser(version=version, usage=usage)
65
66     account = os.environ["OKEANOS_USER"] if "OKEANOS_USER" in os.environ \
67         else None
68     token = os.environ["OKEANOS_TOKEN"] if "OKEANOS_TOKEN" in os.environ \
69         else None
70
71     parser.add_option("-o", "--outfile", type="string", dest="outfile",
72         default=None, action="callback", callback=check_writable_dir,
73         help="dump image to FILE", metavar="FILE")
74
75     parser.add_option("-f", "--force", dest="force", default=False,
76         action="store_true", help="overwrite output files if they exist")
77
78     parser.add_option("-s", "--silent", dest="silent", default=False,
79         help="silent mode, only output errors", action="store_true")
80
81     parser.add_option("-u", "--upload", dest="upload", type="string",
82         default=False, help="upload the image to pithos with name FILENAME",
83         metavar="FILENAME")
84
85     parser.add_option("-r", "--register", dest="register", type="string",
86         default=False, help="register the image to ~okeanos as IMAGENAME",
87         metavar="IMAGENAME")
88
89     parser.add_option("-a", "--account", dest="account", type="string",
90         default=account,
91         help="Use this ACCOUNT when uploading/registring images [Default: %s]"\
92         % account)
93
94     parser.add_option("-t", "--token", dest="token", type="string",
95         default=token,
96         help="Use this token when uploading/registring images [Default: %s]"\
97         % token)
98
99     parser.add_option("--print-sysprep", dest="print_sysprep", default=False,
100         help="print the enabled and disabled system preparation operations "
101         "for this input media", action="store_true")
102
103     parser.add_option("--enable-sysprep", dest="enabled_syspreps", default=[],
104         help="run SYSPREP operation on the input media",
105         action="append", metavar="SYSPREP")
106
107     parser.add_option("--disable-sysprep", dest="disabled_syspreps",
108         help="prevent SYSPREP operation from running on the input media",
109         default=[], action="append", metavar="SYSPREP")
110
111     parser.add_option("--no-sysprep", dest="sysprep", default=True,
112         help="don't perform system preperation", action="store_false")
113
114     parser.add_option("--no-shrink", dest="shrink", default=True,
115         help="don't shrink any partition", action="store_false")
116
117     options, args = parser.parse_args(input_args)
118
119     if len(args) != 1:
120         parser.error('Wrong number of arguments')
121     options.source = args[0]
122     if not os.path.exists(options.source):
123         raise FatalError("Input media `%s' is not accessible" % options.source)
124
125     if options.register and options.upload == False:
126         raise FatalError("You also need to set -u when -r option is set")
127
128     if options.upload and options.account is None:
129         raise FatalError("Image uploading cannot be performed. No ~okeanos "
130         "account name is specified. Use -a to set an account name.")
131
132     if options.upload and options.token is None:
133         raise FatalError("Image uploading cannot be performed. No ~okeanos "
134         "token is specified. User -t to set a token.")
135
136     return options
137
138
139 def image_creator():
140     options = parse_options(sys.argv[1:])
141
142     if options.silent:
143         util.silent = True
144
145     if options.outfile is None and not options.upload \
146                                             and not options.print_sysprep:
147         raise FatalError("At least one of `-o', `-u' or" \
148                             "`--print-sysprep' must be set")
149
150     output('snf-image-creator %s\n' % version)
151
152     if os.geteuid() != 0:
153         raise FatalError("You must run %s as root" \
154                         % os.path.basename(sys.argv[0]))
155
156     if not options.force and options.outfile is not None:
157         for extension in ('', '.meta', '.md5sum'):
158             filename = "%s%s" % (options.outfile, extension)
159             if os.path.exists(filename):
160                 raise FatalError("Output file %s exists "
161                     "(use --force to overwrite it)." % filename)
162
163     disk = Disk(options.source)
164     try:
165         snapshot = disk.snapshot()
166
167         dev = disk.get_device(snapshot)
168         dev.mount()
169
170         osclass = get_os_class(dev.distro, dev.ostype)
171         image_os = osclass(dev.root, dev.g)
172         metadata = image_os.get_metadata()
173
174         output()
175
176         for sysprep in options.disabled_syspreps:
177             image_os.disable_sysprep(sysprep)
178
179         for sysprep in options.enabled_syspreps:
180             image_os.enable_sysprep(sysprep)
181
182         if options.print_sysprep:
183             image_os.print_syspreps()
184             output()
185
186         if options.outfile is None and not options.upload:
187             return 0
188
189         if options.sysprep:
190             image_os.do_sysprep()
191
192         dev.umount()
193
194         size = options.shrink and dev.shrink() or dev.size()
195         metadata['SIZE'] = str(size // 2 ** 20)
196
197         #Calculating MD5sum
198         output("Calculating md5sum...", False)
199         checksum = md5(snapshot, size)
200         success(checksum)
201         output()
202
203         if options.outfile is not None:
204             f = open('%s.%s' % (options.outfile, 'meta'), 'w')
205             try:
206                 for key in metadata.keys():
207                     f.write("%s=%s\n" % (key, metadata[key]))
208             finally:
209                 f.close()
210
211             dev.dump(options.outfile)
212
213         # Destroy the device. We only need the snapshot from now on
214         disk.destroy_device(dev)
215
216         if options.upload:
217             output("Uploading image to pithos:")
218             kamaki = Kamaki(options.account, options.token)
219             kamaki.upload(snapshot, size, options.upload)
220
221     finally:
222         output('cleaning up...')
223         disk.cleanup()
224
225     return 0
226
227
228 def main():
229     try:
230         ret = image_creator()
231         sys.exit(ret)
232     except FatalError as e:
233         error(e)
234         sys.exit(1)
235
236
237 if __name__ == '__main__':
238     main()
239
240 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :