Statistics
| Branch: | Tag: | Revision:

root / image_creator / main.py @ 710adfb2

History | View | Annotate | Download (10.6 kB)

1 ae48a082 Nikos Skalkotos
#!/usr/bin/env python
2 ae48a082 Nikos Skalkotos
3 574f2712 Nikos Skalkotos
# Copyright 2012 GRNET S.A. All rights reserved.
4 d57775d4 Nikos Skalkotos
#
5 d57775d4 Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
6 d57775d4 Nikos Skalkotos
# without modification, are permitted provided that the following
7 d57775d4 Nikos Skalkotos
# conditions are met:
8 d57775d4 Nikos Skalkotos
#
9 d57775d4 Nikos Skalkotos
#   1. Redistributions of source code must retain the above
10 d57775d4 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
11 d57775d4 Nikos Skalkotos
#      disclaimer.
12 d57775d4 Nikos Skalkotos
#
13 d57775d4 Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
14 d57775d4 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
15 d57775d4 Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
16 d57775d4 Nikos Skalkotos
#      provided with the distribution.
17 d57775d4 Nikos Skalkotos
#
18 d57775d4 Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 d57775d4 Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 d57775d4 Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 d57775d4 Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 d57775d4 Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 d57775d4 Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 d57775d4 Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 d57775d4 Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 d57775d4 Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 d57775d4 Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 d57775d4 Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 d57775d4 Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
30 d57775d4 Nikos Skalkotos
#
31 d57775d4 Nikos Skalkotos
# The views and conclusions contained in the software and
32 d57775d4 Nikos Skalkotos
# documentation are those of the authors and should not be
33 d57775d4 Nikos Skalkotos
# interpreted as representing official policies, either expressed
34 d57775d4 Nikos Skalkotos
# or implied, of GRNET S.A.
35 d57775d4 Nikos Skalkotos
36 c408053f Nikos Skalkotos
from image_creator import __version__ as version
37 997ac76a Nikos Skalkotos
from image_creator import util
38 d57775d4 Nikos Skalkotos
from image_creator.disk import Disk
39 e77e66a9 Nikos Skalkotos
from image_creator.util import get_command, FatalError, MD5
40 46544c32 Nikos Skalkotos
from image_creator.output import Output, Output_wth_progress, Silent, \
41 46544c32 Nikos Skalkotos
                                                    Silent_wth_colors, error
42 5b801534 Nikos Skalkotos
from image_creator.os_type import get_os_class
43 997ac76a Nikos Skalkotos
from image_creator.kamaki_wrapper import Kamaki
44 d57775d4 Nikos Skalkotos
import sys
45 d57775d4 Nikos Skalkotos
import os
46 c408053f Nikos Skalkotos
import optparse
47 b1395967 Nikos Skalkotos
import StringIO
48 3ccb2618 Nikos Skalkotos
49 c408053f Nikos Skalkotos
50 0ae01e26 Nikos Skalkotos
def check_writable_dir(option, opt_str, value, parser):
51 0ae01e26 Nikos Skalkotos
    dirname = os.path.dirname(value)
52 0ae01e26 Nikos Skalkotos
    name = os.path.basename(value)
53 0ae01e26 Nikos Skalkotos
    if dirname and not os.path.isdir(dirname):
54 b5430a9f Nikos Skalkotos
        raise FatalError("`%s' is not an existing directory" % dirname)
55 c408053f Nikos Skalkotos
56 0ae01e26 Nikos Skalkotos
    if not name:
57 b5430a9f Nikos Skalkotos
        raise FatalError("`%s' is not a valid file name" % dirname)
58 c408053f Nikos Skalkotos
59 c408053f Nikos Skalkotos
    setattr(parser.values, option.dest, value)
60 c408053f Nikos Skalkotos
61 c408053f Nikos Skalkotos
62 c408053f Nikos Skalkotos
def parse_options(input_args):
63 0ae01e26 Nikos Skalkotos
    usage = "Usage: %prog [options] <input_media>"
64 c408053f Nikos Skalkotos
    parser = optparse.OptionParser(version=version, usage=usage)
65 c408053f Nikos Skalkotos
66 997ac76a Nikos Skalkotos
    account = os.environ["OKEANOS_USER"] if "OKEANOS_USER" in os.environ \
67 997ac76a Nikos Skalkotos
        else None
68 997ac76a Nikos Skalkotos
    token = os.environ["OKEANOS_TOKEN"] if "OKEANOS_TOKEN" in os.environ \
69 997ac76a Nikos Skalkotos
        else None
70 9cbb5794 Nikos Skalkotos
71 0ae01e26 Nikos Skalkotos
    parser.add_option("-o", "--outfile", type="string", dest="outfile",
72 0ae01e26 Nikos Skalkotos
        default=None, action="callback", callback=check_writable_dir,
73 979096dd Nikos Skalkotos
        help="dump image to FILE", metavar="FILE")
74 979096dd Nikos Skalkotos
75 997ac76a Nikos Skalkotos
    parser.add_option("-f", "--force", dest="force", default=False,
76 997ac76a Nikos Skalkotos
        action="store_true", help="overwrite output files if they exist")
77 76d4a1c9 Nikos Skalkotos
78 979096dd Nikos Skalkotos
    parser.add_option("-s", "--silent", dest="silent", default=False,
79 f165adc0 Nikos Skalkotos
        help="silent mode, only output errors", action="store_true")
80 0ae01e26 Nikos Skalkotos
81 b5430a9f Nikos Skalkotos
    parser.add_option("-u", "--upload", dest="upload", type="string",
82 b5430a9f Nikos Skalkotos
        default=False, help="upload the image to pithos with name FILENAME",
83 b5430a9f Nikos Skalkotos
        metavar="FILENAME")
84 1a3f1298 Nikos Skalkotos
85 b5430a9f Nikos Skalkotos
    parser.add_option("-r", "--register", dest="register", type="string",
86 b5430a9f Nikos Skalkotos
        default=False, help="register the image to ~okeanos as IMAGENAME",
87 b5430a9f Nikos Skalkotos
        metavar="IMAGENAME")
88 1a3f1298 Nikos Skalkotos
89 997ac76a Nikos Skalkotos
    parser.add_option("-a", "--account", dest="account", type="string",
90 997ac76a Nikos Skalkotos
        default=account,
91 997ac76a Nikos Skalkotos
        help="Use this ACCOUNT when uploading/registring images [Default: %s]"\
92 997ac76a Nikos Skalkotos
        % account)
93 997ac76a Nikos Skalkotos
94 7d3dc857 Nikos Skalkotos
    parser.add_option("-m", "--metadata", dest="metadata", default=[],
95 7d3dc857 Nikos Skalkotos
        help="Add custom KEY=VALUE metadata to the image", action="append",
96 7d3dc857 Nikos Skalkotos
        metavar="KEY=VALUE")
97 7d3dc857 Nikos Skalkotos
98 997ac76a Nikos Skalkotos
    parser.add_option("-t", "--token", dest="token", type="string",
99 997ac76a Nikos Skalkotos
        default=token,
100 997ac76a Nikos Skalkotos
        help="Use this token when uploading/registring images [Default: %s]"\
101 997ac76a Nikos Skalkotos
        % token)
102 997ac76a Nikos Skalkotos
103 997ac76a Nikos Skalkotos
    parser.add_option("--print-sysprep", dest="print_sysprep", default=False,
104 997ac76a Nikos Skalkotos
        help="print the enabled and disabled system preparation operations "
105 997ac76a Nikos Skalkotos
        "for this input media", action="store_true")
106 997ac76a Nikos Skalkotos
107 997ac76a Nikos Skalkotos
    parser.add_option("--enable-sysprep", dest="enabled_syspreps", default=[],
108 997ac76a Nikos Skalkotos
        help="run SYSPREP operation on the input media",
109 997ac76a Nikos Skalkotos
        action="append", metavar="SYSPREP")
110 997ac76a Nikos Skalkotos
111 997ac76a Nikos Skalkotos
    parser.add_option("--disable-sysprep", dest="disabled_syspreps",
112 997ac76a Nikos Skalkotos
        help="prevent SYSPREP operation from running on the input media",
113 997ac76a Nikos Skalkotos
        default=[], action="append", metavar="SYSPREP")
114 997ac76a Nikos Skalkotos
115 997ac76a Nikos Skalkotos
    parser.add_option("--no-sysprep", dest="sysprep", default=True,
116 997ac76a Nikos Skalkotos
        help="don't perform system preperation", action="store_false")
117 997ac76a Nikos Skalkotos
118 997ac76a Nikos Skalkotos
    parser.add_option("--no-shrink", dest="shrink", default=True,
119 997ac76a Nikos Skalkotos
        help="don't shrink any partition", action="store_false")
120 997ac76a Nikos Skalkotos
121 c408053f Nikos Skalkotos
    options, args = parser.parse_args(input_args)
122 c408053f Nikos Skalkotos
123 0ae01e26 Nikos Skalkotos
    if len(args) != 1:
124 0ae01e26 Nikos Skalkotos
        parser.error('Wrong number of arguments')
125 7d3dc857 Nikos Skalkotos
126 c408053f Nikos Skalkotos
    options.source = args[0]
127 c408053f Nikos Skalkotos
    if not os.path.exists(options.source):
128 b5430a9f Nikos Skalkotos
        raise FatalError("Input media `%s' is not accessible" % options.source)
129 c408053f Nikos Skalkotos
130 997ac76a Nikos Skalkotos
    if options.register and options.upload == False:
131 b5430a9f Nikos Skalkotos
        raise FatalError("You also need to set -u when -r option is set")
132 1a3f1298 Nikos Skalkotos
133 997ac76a Nikos Skalkotos
    if options.upload and options.account is None:
134 997ac76a Nikos Skalkotos
        raise FatalError("Image uploading cannot be performed. No ~okeanos "
135 997ac76a Nikos Skalkotos
        "account name is specified. Use -a to set an account name.")
136 997ac76a Nikos Skalkotos
137 997ac76a Nikos Skalkotos
    if options.upload and options.token is None:
138 997ac76a Nikos Skalkotos
        raise FatalError("Image uploading cannot be performed. No ~okeanos "
139 997ac76a Nikos Skalkotos
        "token is specified. User -t to set a token.")
140 997ac76a Nikos Skalkotos
141 7d3dc857 Nikos Skalkotos
    meta = {}
142 7d3dc857 Nikos Skalkotos
    for m in options.metadata:
143 7d3dc857 Nikos Skalkotos
        try:
144 7d3dc857 Nikos Skalkotos
            key, value = m.split('=', 1)
145 7d3dc857 Nikos Skalkotos
        except ValueError:
146 7d3dc857 Nikos Skalkotos
            raise FatalError("Metadata option: `%s' is not in "\
147 7d3dc857 Nikos Skalkotos
                                                    "KEY=VALUE format." % m)
148 7d3dc857 Nikos Skalkotos
        meta[key] = value
149 7d3dc857 Nikos Skalkotos
    options.metadata = meta
150 7d3dc857 Nikos Skalkotos
151 c408053f Nikos Skalkotos
    return options
152 d57775d4 Nikos Skalkotos
153 8c574358 Nikos Skalkotos
154 22a6d232 Nikos Skalkotos
def image_creator():
155 c408053f Nikos Skalkotos
    options = parse_options(sys.argv[1:])
156 c408053f Nikos Skalkotos
157 76d4a1c9 Nikos Skalkotos
    if options.outfile is None and not options.upload \
158 f165adc0 Nikos Skalkotos
                                            and not options.print_sysprep:
159 5b801534 Nikos Skalkotos
        raise FatalError("At least one of `-o', `-u' or `--print-sysprep' " \
160 5b801534 Nikos Skalkotos
                                                                "must be set")
161 76d4a1c9 Nikos Skalkotos
162 e77e66a9 Nikos Skalkotos
    if options.silent:
163 46544c32 Nikos Skalkotos
        out = Silent_wth_colors() if sys.stdout.isatty() else Silent()
164 e77e66a9 Nikos Skalkotos
    else:
165 46544c32 Nikos Skalkotos
        out = Output_wth_progress() if sys.stdout.isatty() else Output()
166 e77e66a9 Nikos Skalkotos
167 e108efd2 Nikos Skalkotos
    title = 'snf-image-creator %s' % version
168 e77e66a9 Nikos Skalkotos
    out.output(title)
169 e77e66a9 Nikos Skalkotos
    out.output('=' * len(title))
170 979096dd Nikos Skalkotos
171 c408053f Nikos Skalkotos
    if os.geteuid() != 0:
172 c408053f Nikos Skalkotos
        raise FatalError("You must run %s as root" \
173 c408053f Nikos Skalkotos
                        % os.path.basename(sys.argv[0]))
174 c408053f Nikos Skalkotos
175 69aa33fa Nikos Skalkotos
    if not options.force and options.outfile is not None:
176 8e3065a0 Nikos Skalkotos
        for extension in ('', '.meta', '.md5sum'):
177 0ae01e26 Nikos Skalkotos
            filename = "%s%s" % (options.outfile, extension)
178 c408053f Nikos Skalkotos
            if os.path.exists(filename):
179 c408053f Nikos Skalkotos
                raise FatalError("Output file %s exists "
180 c408053f Nikos Skalkotos
                    "(use --force to overwrite it)." % filename)
181 c408053f Nikos Skalkotos
182 e77e66a9 Nikos Skalkotos
    disk = Disk(options.source, out)
183 d57775d4 Nikos Skalkotos
    try:
184 e22aa3a9 Nikos Skalkotos
        snapshot = disk.snapshot()
185 e22aa3a9 Nikos Skalkotos
186 e22aa3a9 Nikos Skalkotos
        dev = disk.get_device(snapshot)
187 0d5a999d Nikos Skalkotos
        dev.mount()
188 22a6d232 Nikos Skalkotos
189 aa2062ba Nikos Skalkotos
        osclass = get_os_class(dev.distro, dev.ostype)
190 e77e66a9 Nikos Skalkotos
        image_os = osclass(dev.root, dev.g, out)
191 e77e66a9 Nikos Skalkotos
        out.output()
192 3f70f242 Nikos Skalkotos
193 f165adc0 Nikos Skalkotos
        for sysprep in options.disabled_syspreps:
194 f165adc0 Nikos Skalkotos
            image_os.disable_sysprep(sysprep)
195 f165adc0 Nikos Skalkotos
196 f165adc0 Nikos Skalkotos
        for sysprep in options.enabled_syspreps:
197 f165adc0 Nikos Skalkotos
            image_os.enable_sysprep(sysprep)
198 76d4a1c9 Nikos Skalkotos
199 f165adc0 Nikos Skalkotos
        if options.print_sysprep:
200 f165adc0 Nikos Skalkotos
            image_os.print_syspreps()
201 e77e66a9 Nikos Skalkotos
            out.output()
202 76d4a1c9 Nikos Skalkotos
203 76d4a1c9 Nikos Skalkotos
        if options.outfile is None and not options.upload:
204 76d4a1c9 Nikos Skalkotos
            return 0
205 76d4a1c9 Nikos Skalkotos
206 9cbb5794 Nikos Skalkotos
        if options.sysprep:
207 f165adc0 Nikos Skalkotos
            image_os.do_sysprep()
208 1a3f1298 Nikos Skalkotos
209 5886f568 Nikos Skalkotos
        metadata = image_os.meta
210 8c574358 Nikos Skalkotos
        dev.umount()
211 1a3f1298 Nikos Skalkotos
212 e77e66a9 Nikos Skalkotos
        size = options.shrink and dev.shrink() or dev.meta['SIZE']
213 e8b1b48b Nikos Skalkotos
        metadata.update(dev.meta)
214 ae48a082 Nikos Skalkotos
215 7d3dc857 Nikos Skalkotos
        # Add command line metadata to the collected ones...
216 7d3dc857 Nikos Skalkotos
        metadata.update(options.metadata)
217 7d3dc857 Nikos Skalkotos
218 e77e66a9 Nikos Skalkotos
        md5 = MD5(out)
219 e77e66a9 Nikos Skalkotos
        checksum = md5.compute(snapshot, size)
220 8e3065a0 Nikos Skalkotos
221 143e9484 Nikos Skalkotos
        metastring = '\n'.join(
222 5b801534 Nikos Skalkotos
                ['%s=%s' % (key, value) for (key, value) in metadata.items()])
223 143e9484 Nikos Skalkotos
        metastring += '\n'
224 0ae01e26 Nikos Skalkotos
225 b1395967 Nikos Skalkotos
        if options.outfile is not None:
226 d603d80d Nikos Skalkotos
            dev.dump(options.outfile)
227 997ac76a Nikos Skalkotos
228 e77e66a9 Nikos Skalkotos
            out.output('Dumping metadata file...', False)
229 b1395967 Nikos Skalkotos
            with open('%s.%s' % (options.outfile, 'meta'), 'w') as f:
230 5b801534 Nikos Skalkotos
                f.write(metastring)
231 e77e66a9 Nikos Skalkotos
            out.success('done')
232 b1395967 Nikos Skalkotos
233 e77e66a9 Nikos Skalkotos
            out.output('Dumping md5sum file...', False)
234 b1395967 Nikos Skalkotos
            with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f:
235 5b801534 Nikos Skalkotos
                f.write('%s %s\n' % (checksum, \
236 5b801534 Nikos Skalkotos
                                            os.path.basename(options.outfile)))
237 e77e66a9 Nikos Skalkotos
            out.success('done')
238 b1395967 Nikos Skalkotos
239 e22aa3a9 Nikos Skalkotos
        # Destroy the device. We only need the snapshot from now on
240 e22aa3a9 Nikos Skalkotos
        disk.destroy_device(dev)
241 e22aa3a9 Nikos Skalkotos
242 e77e66a9 Nikos Skalkotos
        out.output()
243 b1395967 Nikos Skalkotos
244 b1395967 Nikos Skalkotos
        uploaded_obj = ""
245 997ac76a Nikos Skalkotos
        if options.upload:
246 e77e66a9 Nikos Skalkotos
            out.output("Uploading image to pithos:")
247 e77e66a9 Nikos Skalkotos
            kamaki = Kamaki(options.account, options.token, out)
248 b1395967 Nikos Skalkotos
            with open(snapshot) as f:
249 b1395967 Nikos Skalkotos
                uploaded_obj = kamaki.upload(f, size, options.upload,
250 b1395967 Nikos Skalkotos
                                "(1/4)  Calculating block hashes",
251 b1395967 Nikos Skalkotos
                                "(2/4)  Uploading missing blocks")
252 b1395967 Nikos Skalkotos
253 e77e66a9 Nikos Skalkotos
            out.output("(3/4)  Uploading metadata file...", False)
254 b1395967 Nikos Skalkotos
            kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
255 b1395967 Nikos Skalkotos
                                remote_path="%s.%s" % (options.upload, 'meta'))
256 e77e66a9 Nikos Skalkotos
            out.success('done')
257 e77e66a9 Nikos Skalkotos
            out.output("(4/4)  Uploading md5sum file...", False)
258 143e9484 Nikos Skalkotos
            md5sumstr = '%s %s\n' % (
259 143e9484 Nikos Skalkotos
                checksum, os.path.basename(options.upload))
260 b1395967 Nikos Skalkotos
            kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr),
261 b1395967 Nikos Skalkotos
                            remote_path="%s.%s" % (options.upload, 'md5sum'))
262 e77e66a9 Nikos Skalkotos
            out.success('done')
263 e77e66a9 Nikos Skalkotos
            out.output()
264 b1395967 Nikos Skalkotos
265 b1395967 Nikos Skalkotos
        if options.register:
266 e77e66a9 Nikos Skalkotos
            out.output('Registring image to ~okeanos...', False)
267 b1395967 Nikos Skalkotos
            kamaki.register(options.register, uploaded_obj, metadata)
268 e77e66a9 Nikos Skalkotos
            out.success('done')
269 e77e66a9 Nikos Skalkotos
            out.output()
270 997ac76a Nikos Skalkotos
271 d57775d4 Nikos Skalkotos
    finally:
272 e77e66a9 Nikos Skalkotos
        out.output('cleaning up...')
273 d57775d4 Nikos Skalkotos
        disk.cleanup()
274 d57775d4 Nikos Skalkotos
275 e77e66a9 Nikos Skalkotos
    out.success("snf-image-creator exited without errors")
276 b1395967 Nikos Skalkotos
277 c408053f Nikos Skalkotos
    return 0
278 c408053f Nikos Skalkotos
279 ae48a082 Nikos Skalkotos
280 0ae01e26 Nikos Skalkotos
def main():
281 c408053f Nikos Skalkotos
    try:
282 0ae01e26 Nikos Skalkotos
        ret = image_creator()
283 c408053f Nikos Skalkotos
        sys.exit(ret)
284 c408053f Nikos Skalkotos
    except FatalError as e:
285 46544c32 Nikos Skalkotos
        if sys.stdout.isatty():
286 46544c32 Nikos Skalkotos
            error(e)
287 46544c32 Nikos Skalkotos
        else:
288 46544c32 Nikos Skalkotos
            error(e, True, False)
289 c408053f Nikos Skalkotos
        sys.exit(1)
290 d57775d4 Nikos Skalkotos
291 0ae01e26 Nikos Skalkotos
292 0ae01e26 Nikos Skalkotos
if __name__ == '__main__':
293 0ae01e26 Nikos Skalkotos
    main()
294 ae48a082 Nikos Skalkotos
295 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :