Statistics
| Branch: | Tag: | Revision:

root / image_creator / main.py @ f5174d2c

History | View | Annotate | Download (12 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 d57775d4 Nikos Skalkotos
from image_creator.disk import Disk
38 3582e34d Nikos Skalkotos
from image_creator.util import FatalError, MD5
39 4e58b51b Nikos Skalkotos
from image_creator.output.cli import SilentOutput, SimpleOutput, \
40 f99fe99d Nikos Skalkotos
    OutputWthProgress
41 3b7d3fc7 Nikos Skalkotos
from image_creator.kamaki_wrapper import Kamaki, ClientError
42 d57775d4 Nikos Skalkotos
import sys
43 d57775d4 Nikos Skalkotos
import os
44 c408053f Nikos Skalkotos
import optparse
45 b1395967 Nikos Skalkotos
import StringIO
46 9c354f13 Nikos Skalkotos
import signal
47 3ccb2618 Nikos Skalkotos
48 c408053f Nikos Skalkotos
49 0ae01e26 Nikos Skalkotos
def check_writable_dir(option, opt_str, value, parser):
50 0ae01e26 Nikos Skalkotos
    dirname = os.path.dirname(value)
51 0ae01e26 Nikos Skalkotos
    name = os.path.basename(value)
52 0ae01e26 Nikos Skalkotos
    if dirname and not os.path.isdir(dirname):
53 b5430a9f Nikos Skalkotos
        raise FatalError("`%s' is not an existing directory" % dirname)
54 c408053f Nikos Skalkotos
55 0ae01e26 Nikos Skalkotos
    if not name:
56 b5430a9f Nikos Skalkotos
        raise FatalError("`%s' is not a valid file name" % dirname)
57 c408053f Nikos Skalkotos
58 c408053f Nikos Skalkotos
    setattr(parser.values, option.dest, value)
59 c408053f Nikos Skalkotos
60 c408053f Nikos Skalkotos
61 c408053f Nikos Skalkotos
def parse_options(input_args):
62 0ae01e26 Nikos Skalkotos
    usage = "Usage: %prog [options] <input_media>"
63 c408053f Nikos Skalkotos
    parser = optparse.OptionParser(version=version, usage=usage)
64 c408053f Nikos Skalkotos
65 0ae01e26 Nikos Skalkotos
    parser.add_option("-o", "--outfile", type="string", dest="outfile",
66 f99fe99d Nikos Skalkotos
                      default=None, action="callback",
67 f99fe99d Nikos Skalkotos
                      callback=check_writable_dir, help="dump image to FILE",
68 f99fe99d Nikos Skalkotos
                      metavar="FILE")
69 979096dd Nikos Skalkotos
70 997ac76a Nikos Skalkotos
    parser.add_option("-f", "--force", dest="force", default=False,
71 f99fe99d Nikos Skalkotos
                      action="store_true",
72 f99fe99d Nikos Skalkotos
                      help="overwrite output files if they exist")
73 76d4a1c9 Nikos Skalkotos
74 979096dd Nikos Skalkotos
    parser.add_option("-s", "--silent", dest="silent", default=False,
75 c16850ae Nikos Skalkotos
                      help="output only errors",
76 f99fe99d Nikos Skalkotos
                      action="store_true")
77 0ae01e26 Nikos Skalkotos
78 b5430a9f Nikos Skalkotos
    parser.add_option("-u", "--upload", dest="upload", type="string",
79 f99fe99d Nikos Skalkotos
                      default=False,
80 f99fe99d Nikos Skalkotos
                      help="upload the image to pithos with name FILENAME",
81 f99fe99d Nikos Skalkotos
                      metavar="FILENAME")
82 1a3f1298 Nikos Skalkotos
83 b5430a9f Nikos Skalkotos
    parser.add_option("-r", "--register", dest="register", type="string",
84 f99fe99d Nikos Skalkotos
                      default=False,
85 dcf9274b Vangelis Koukis
                      help="register the image with ~okeanos as IMAGENAME",
86 f99fe99d Nikos Skalkotos
                      metavar="IMAGENAME")
87 1a3f1298 Nikos Skalkotos
88 7d3dc857 Nikos Skalkotos
    parser.add_option("-m", "--metadata", dest="metadata", default=[],
89 c16850ae Nikos Skalkotos
                      help="add custom KEY=VALUE metadata to the image",
90 f99fe99d Nikos Skalkotos
                      action="append", metavar="KEY=VALUE")
91 7d3dc857 Nikos Skalkotos
92 997ac76a Nikos Skalkotos
    parser.add_option("-t", "--token", dest="token", type="string",
93 c2cf27e8 Nikos Skalkotos
                      default=None, help="use this authentication token when "
94 c2cf27e8 Nikos Skalkotos
                      "uploading/registering images")
95 997ac76a Nikos Skalkotos
96 997ac76a Nikos Skalkotos
    parser.add_option("--print-sysprep", dest="print_sysprep", default=False,
97 f99fe99d Nikos Skalkotos
                      help="print the enabled and disabled system preparation "
98 f99fe99d Nikos Skalkotos
                      "operations for this input media", action="store_true")
99 997ac76a Nikos Skalkotos
100 997ac76a Nikos Skalkotos
    parser.add_option("--enable-sysprep", dest="enabled_syspreps", default=[],
101 f99fe99d Nikos Skalkotos
                      help="run SYSPREP operation on the input media",
102 f99fe99d Nikos Skalkotos
                      action="append", metavar="SYSPREP")
103 997ac76a Nikos Skalkotos
104 997ac76a Nikos Skalkotos
    parser.add_option("--disable-sysprep", dest="disabled_syspreps",
105 f99fe99d Nikos Skalkotos
                      help="prevent SYSPREP operation from running on the "
106 f99fe99d Nikos Skalkotos
                      "input media", default=[], action="append",
107 f99fe99d Nikos Skalkotos
                      metavar="SYSPREP")
108 997ac76a Nikos Skalkotos
109 997ac76a Nikos Skalkotos
    parser.add_option("--no-sysprep", dest="sysprep", default=True,
110 c16850ae Nikos Skalkotos
                      help="don't perform any system preparation operation",
111 f99fe99d Nikos Skalkotos
                      action="store_false")
112 997ac76a Nikos Skalkotos
113 997ac76a Nikos Skalkotos
    parser.add_option("--no-shrink", dest="shrink", default=True,
114 f99fe99d Nikos Skalkotos
                      help="don't shrink any partition", action="store_false")
115 997ac76a Nikos Skalkotos
116 37d581b8 Nikos Skalkotos
    parser.add_option("--public", dest="public", default=False,
117 aa486e93 Nikos Skalkotos
                      help="register image with cyclades as public",
118 37d581b8 Nikos Skalkotos
                      action="store_true")
119 37d581b8 Nikos Skalkotos
120 c16850ae Nikos Skalkotos
    parser.add_option("--tmpdir", dest="tmp", type="string", default=None,
121 c16850ae Nikos Skalkotos
                      help="create large temporary image files under DIR",
122 c16850ae Nikos Skalkotos
                      metavar="DIR")
123 c16850ae Nikos Skalkotos
124 c408053f Nikos Skalkotos
    options, args = parser.parse_args(input_args)
125 c408053f Nikos Skalkotos
126 0ae01e26 Nikos Skalkotos
    if len(args) != 1:
127 0ae01e26 Nikos Skalkotos
        parser.error('Wrong number of arguments')
128 7d3dc857 Nikos Skalkotos
129 c408053f Nikos Skalkotos
    options.source = args[0]
130 c408053f Nikos Skalkotos
    if not os.path.exists(options.source):
131 b5430a9f Nikos Skalkotos
        raise FatalError("Input media `%s' is not accessible" % options.source)
132 c408053f Nikos Skalkotos
133 f99fe99d Nikos Skalkotos
    if options.register and not options.upload:
134 b5430a9f Nikos Skalkotos
        raise FatalError("You also need to set -u when -r option is set")
135 1a3f1298 Nikos Skalkotos
136 997ac76a Nikos Skalkotos
    if options.upload and options.token is None:
137 37d581b8 Nikos Skalkotos
        raise FatalError(
138 37d581b8 Nikos Skalkotos
            "Image uploading cannot be performed. "
139 31160dc8 Nikos Skalkotos
            "No authentication token is specified. Use -t to set a token")
140 997ac76a Nikos Skalkotos
141 c16850ae Nikos Skalkotos
    if options.tmp is not None and not os.path.isdir(options.tmp):
142 c16850ae Nikos Skalkotos
        raise FatalError("The directory `%s' specified with --tmpdir is not "
143 31160dc8 Nikos Skalkotos
                         "valid" % options.tmp)
144 c16850ae Nikos Skalkotos
145 7d3dc857 Nikos Skalkotos
    meta = {}
146 7d3dc857 Nikos Skalkotos
    for m in options.metadata:
147 7d3dc857 Nikos Skalkotos
        try:
148 7d3dc857 Nikos Skalkotos
            key, value = m.split('=', 1)
149 7d3dc857 Nikos Skalkotos
        except ValueError:
150 31160dc8 Nikos Skalkotos
            raise FatalError("Metadata option: `%s' is not in KEY=VALUE "
151 31160dc8 Nikos Skalkotos
                             "format." % m)
152 7d3dc857 Nikos Skalkotos
        meta[key] = value
153 7d3dc857 Nikos Skalkotos
    options.metadata = meta
154 7d3dc857 Nikos Skalkotos
155 c408053f Nikos Skalkotos
    return options
156 d57775d4 Nikos Skalkotos
157 8c574358 Nikos Skalkotos
158 22a6d232 Nikos Skalkotos
def image_creator():
159 c408053f Nikos Skalkotos
    options = parse_options(sys.argv[1:])
160 c408053f Nikos Skalkotos
161 f99fe99d Nikos Skalkotos
    if options.outfile is None and not options.upload and not \
162 f99fe99d Nikos Skalkotos
            options.print_sysprep:
163 f99fe99d Nikos Skalkotos
        raise FatalError("At least one of `-o', `-u' or `--print-sysprep' "
164 f99fe99d Nikos Skalkotos
                         "must be set")
165 76d4a1c9 Nikos Skalkotos
166 e77e66a9 Nikos Skalkotos
    if options.silent:
167 4e58b51b Nikos Skalkotos
        out = SilentOutput()
168 e77e66a9 Nikos Skalkotos
    else:
169 4e58b51b Nikos Skalkotos
        out = OutputWthProgress(True) if sys.stderr.isatty() else \
170 f99fe99d Nikos Skalkotos
            SimpleOutput(False)
171 e77e66a9 Nikos Skalkotos
172 e108efd2 Nikos Skalkotos
    title = 'snf-image-creator %s' % version
173 e77e66a9 Nikos Skalkotos
    out.output(title)
174 e77e66a9 Nikos Skalkotos
    out.output('=' * len(title))
175 979096dd Nikos Skalkotos
176 c408053f Nikos Skalkotos
    if os.geteuid() != 0:
177 f99fe99d Nikos Skalkotos
        raise FatalError("You must run %s as root"
178 f99fe99d Nikos Skalkotos
                         % os.path.basename(sys.argv[0]))
179 c408053f Nikos Skalkotos
180 69aa33fa Nikos Skalkotos
    if not options.force and options.outfile is not None:
181 8e3065a0 Nikos Skalkotos
        for extension in ('', '.meta', '.md5sum'):
182 0ae01e26 Nikos Skalkotos
            filename = "%s%s" % (options.outfile, extension)
183 c408053f Nikos Skalkotos
            if os.path.exists(filename):
184 c408053f Nikos Skalkotos
                raise FatalError("Output file %s exists "
185 31160dc8 Nikos Skalkotos
                                 "(use --force to overwrite it)" % filename)
186 31160dc8 Nikos Skalkotos
187 31160dc8 Nikos Skalkotos
    # Check if the authentication token is valid. The earlier the better
188 c2cf27e8 Nikos Skalkotos
    if options.token is not None:
189 c2cf27e8 Nikos Skalkotos
        try:
190 c2cf27e8 Nikos Skalkotos
            account = Kamaki.get_account(options.token)
191 c2cf27e8 Nikos Skalkotos
            if account is None:
192 c2cf27e8 Nikos Skalkotos
                raise FatalError("The authentication token you provided is not"
193 c2cf27e8 Nikos Skalkotos
                                 " valid!")
194 c2cf27e8 Nikos Skalkotos
        except ClientError as e:
195 c2cf27e8 Nikos Skalkotos
            raise FatalError("Astakos client: %d %s" % (e.status, e.message))
196 c2cf27e8 Nikos Skalkotos
197 c16850ae Nikos Skalkotos
    disk = Disk(options.source, out, options.tmp)
198 9c354f13 Nikos Skalkotos
199 9c354f13 Nikos Skalkotos
    def signal_handler(signum, frame):
200 9c354f13 Nikos Skalkotos
        disk.cleanup()
201 9c354f13 Nikos Skalkotos
202 9c354f13 Nikos Skalkotos
    signal.signal(signal.SIGINT, signal_handler)
203 9c354f13 Nikos Skalkotos
    signal.signal(signal.SIGTERM, signal_handler)
204 d57775d4 Nikos Skalkotos
    try:
205 e22aa3a9 Nikos Skalkotos
        snapshot = disk.snapshot()
206 e22aa3a9 Nikos Skalkotos
207 f5174d2c Nikos Skalkotos
        image = disk.get_image(snapshot)
208 df499fea Nikos Skalkotos
209 df499fea Nikos Skalkotos
        # If no customization is to be applied, the image should be mounted ro
210 f5174d2c Nikos Skalkotos
        ro = (not (options.sysprep or options.shrink) or options.print_sysprep)
211 f5174d2c Nikos Skalkotos
        image.mount(ro)
212 f5174d2c Nikos Skalkotos
        try:
213 f5174d2c Nikos Skalkotos
            for sysprep in options.disabled_syspreps:
214 f5174d2c Nikos Skalkotos
                image.os.disable_sysprep(image.os.get_sysprep_by_name(sysprep))
215 f165adc0 Nikos Skalkotos
216 f5174d2c Nikos Skalkotos
            for sysprep in options.enabled_syspreps:
217 f5174d2c Nikos Skalkotos
                image.os.enable_sysprep(image.os.get_sysprep_by_name(sysprep))
218 76d4a1c9 Nikos Skalkotos
219 f5174d2c Nikos Skalkotos
            if options.print_sysprep:
220 f5174d2c Nikos Skalkotos
                image.os.print_syspreps()
221 f5174d2c Nikos Skalkotos
                out.output()
222 76d4a1c9 Nikos Skalkotos
223 f5174d2c Nikos Skalkotos
            if options.outfile is None and not options.upload:
224 f5174d2c Nikos Skalkotos
                return 0
225 76d4a1c9 Nikos Skalkotos
226 f5174d2c Nikos Skalkotos
            if options.sysprep:
227 f5174d2c Nikos Skalkotos
                image.os.do_sysprep()
228 1a3f1298 Nikos Skalkotos
229 f5174d2c Nikos Skalkotos
            metadata = image.os.meta
230 f5174d2c Nikos Skalkotos
        finally:
231 f5174d2c Nikos Skalkotos
            image.umount()
232 1a3f1298 Nikos Skalkotos
233 f5174d2c Nikos Skalkotos
        size = options.shrink and image.shrink() or image.size
234 f5174d2c Nikos Skalkotos
        metadata.update(image.meta)
235 ae48a082 Nikos Skalkotos
236 7d3dc857 Nikos Skalkotos
        # Add command line metadata to the collected ones...
237 7d3dc857 Nikos Skalkotos
        metadata.update(options.metadata)
238 7d3dc857 Nikos Skalkotos
239 e77e66a9 Nikos Skalkotos
        md5 = MD5(out)
240 f5174d2c Nikos Skalkotos
        checksum = md5.compute(image.device, size)
241 8e3065a0 Nikos Skalkotos
242 143e9484 Nikos Skalkotos
        metastring = '\n'.join(
243 f99fe99d Nikos Skalkotos
            ['%s=%s' % (key, value) for (key, value) in metadata.items()])
244 143e9484 Nikos Skalkotos
        metastring += '\n'
245 0ae01e26 Nikos Skalkotos
246 b1395967 Nikos Skalkotos
        if options.outfile is not None:
247 f5174d2c Nikos Skalkotos
            image.dump(options.outfile)
248 997ac76a Nikos Skalkotos
249 663f5f80 Nikos Skalkotos
            out.output('Dumping metadata file ...', False)
250 b1395967 Nikos Skalkotos
            with open('%s.%s' % (options.outfile, 'meta'), 'w') as f:
251 5b801534 Nikos Skalkotos
                f.write(metastring)
252 e77e66a9 Nikos Skalkotos
            out.success('done')
253 b1395967 Nikos Skalkotos
254 663f5f80 Nikos Skalkotos
            out.output('Dumping md5sum file ...', False)
255 b1395967 Nikos Skalkotos
            with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f:
256 f99fe99d Nikos Skalkotos
                f.write('%s %s\n' % (checksum,
257 f99fe99d Nikos Skalkotos
                                     os.path.basename(options.outfile)))
258 e77e66a9 Nikos Skalkotos
            out.success('done')
259 b1395967 Nikos Skalkotos
260 f5174d2c Nikos Skalkotos
        # Destroy the image instance. We only need the snapshot from now on
261 f5174d2c Nikos Skalkotos
        disk.destroy_image(image)
262 e22aa3a9 Nikos Skalkotos
263 e77e66a9 Nikos Skalkotos
        out.output()
264 3b7d3fc7 Nikos Skalkotos
        try:
265 3b7d3fc7 Nikos Skalkotos
            uploaded_obj = ""
266 3b7d3fc7 Nikos Skalkotos
            if options.upload:
267 3b7d3fc7 Nikos Skalkotos
                out.output("Uploading image to pithos:")
268 31160dc8 Nikos Skalkotos
                kamaki = Kamaki(account, out)
269 825fe2a6 Nikos Skalkotos
                with open(snapshot, 'rb') as f:
270 37d581b8 Nikos Skalkotos
                    uploaded_obj = kamaki.upload(
271 37d581b8 Nikos Skalkotos
                        f, size, options.upload,
272 31160dc8 Nikos Skalkotos
                        "(1/4)  Calculating block hashes",
273 31160dc8 Nikos Skalkotos
                        "(2/4)  Uploading missing blocks")
274 3b7d3fc7 Nikos Skalkotos
275 fd9af948 Nikos Skalkotos
                out.output("(3/4)  Uploading metadata file ...", False)
276 3b7d3fc7 Nikos Skalkotos
                kamaki.upload(StringIO.StringIO(metastring),
277 3b7d3fc7 Nikos Skalkotos
                              size=len(metastring),
278 3b7d3fc7 Nikos Skalkotos
                              remote_path="%s.%s" % (options.upload, 'meta'))
279 3b7d3fc7 Nikos Skalkotos
                out.success('done')
280 663f5f80 Nikos Skalkotos
                out.output("(4/4)  Uploading md5sum file ...", False)
281 f99fe99d Nikos Skalkotos
                md5sumstr = '%s %s\n' % (checksum,
282 f99fe99d Nikos Skalkotos
                                         os.path.basename(options.upload))
283 3b7d3fc7 Nikos Skalkotos
                kamaki.upload(StringIO.StringIO(md5sumstr),
284 3b7d3fc7 Nikos Skalkotos
                              size=len(md5sumstr),
285 3b7d3fc7 Nikos Skalkotos
                              remote_path="%s.%s" % (options.upload, 'md5sum'))
286 3b7d3fc7 Nikos Skalkotos
                out.success('done')
287 3b7d3fc7 Nikos Skalkotos
                out.output()
288 3b7d3fc7 Nikos Skalkotos
289 3b7d3fc7 Nikos Skalkotos
            if options.register:
290 37d581b8 Nikos Skalkotos
                img_type = 'public' if options.public else 'private'
291 37d581b8 Nikos Skalkotos
                out.output('Registering %s image with ~okeanos ...' % img_type,
292 37d581b8 Nikos Skalkotos
                           False)
293 37d581b8 Nikos Skalkotos
                kamaki.register(options.register, uploaded_obj, metadata,
294 37d581b8 Nikos Skalkotos
                                options.public)
295 3b7d3fc7 Nikos Skalkotos
                out.success('done')
296 3b7d3fc7 Nikos Skalkotos
                out.output()
297 3b7d3fc7 Nikos Skalkotos
        except ClientError as e:
298 3b7d3fc7 Nikos Skalkotos
            raise FatalError("Pithos client: %d %s" % (e.status, e.message))
299 997ac76a Nikos Skalkotos
300 d57775d4 Nikos Skalkotos
    finally:
301 fd9af948 Nikos Skalkotos
        out.output('cleaning up ...')
302 d57775d4 Nikos Skalkotos
        disk.cleanup()
303 d57775d4 Nikos Skalkotos
304 e77e66a9 Nikos Skalkotos
    out.success("snf-image-creator exited without errors")
305 b1395967 Nikos Skalkotos
306 c408053f Nikos Skalkotos
    return 0
307 c408053f Nikos Skalkotos
308 ae48a082 Nikos Skalkotos
309 0ae01e26 Nikos Skalkotos
def main():
310 c408053f Nikos Skalkotos
    try:
311 0ae01e26 Nikos Skalkotos
        ret = image_creator()
312 c408053f Nikos Skalkotos
        sys.exit(ret)
313 c408053f Nikos Skalkotos
    except FatalError as e:
314 bb4db5a8 Nikos Skalkotos
        colored = sys.stderr.isatty()
315 bb4db5a8 Nikos Skalkotos
        SimpleOutput(colored).error(e)
316 c408053f Nikos Skalkotos
        sys.exit(1)
317 d57775d4 Nikos Skalkotos
318 0ae01e26 Nikos Skalkotos
if __name__ == '__main__':
319 0ae01e26 Nikos Skalkotos
    main()
320 ae48a082 Nikos Skalkotos
321 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :