Statistics
| Branch: | Tag: | Revision:

root / image_creator / main.py @ d15368cc

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