Statistics
| Branch: | Tag: | Revision:

root / image_creator / main.py @ 825fe2a6

History | View | Annotate | Download (11 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 4e58b51b Nikos Skalkotos
from image_creator.output.cli import SilentOutput, SimpleOutput, \
41 4e58b51b 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 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 4e58b51b Nikos Skalkotos
        out = SilentOutput()
164 e77e66a9 Nikos Skalkotos
    else:
165 4e58b51b Nikos Skalkotos
        out = OutputWthProgress(True) if sys.stderr.isatty() else \
166 4e58b51b Nikos Skalkotos
                                                            SimpleOutput(False)
167 e77e66a9 Nikos Skalkotos
168 e108efd2 Nikos Skalkotos
    title = 'snf-image-creator %s' % version
169 e77e66a9 Nikos Skalkotos
    out.output(title)
170 e77e66a9 Nikos Skalkotos
    out.output('=' * len(title))
171 979096dd Nikos Skalkotos
172 c408053f Nikos Skalkotos
    if os.geteuid() != 0:
173 c408053f Nikos Skalkotos
        raise FatalError("You must run %s as root" \
174 c408053f Nikos Skalkotos
                        % os.path.basename(sys.argv[0]))
175 c408053f Nikos Skalkotos
176 69aa33fa Nikos Skalkotos
    if not options.force and options.outfile is not None:
177 8e3065a0 Nikos Skalkotos
        for extension in ('', '.meta', '.md5sum'):
178 0ae01e26 Nikos Skalkotos
            filename = "%s%s" % (options.outfile, extension)
179 c408053f Nikos Skalkotos
            if os.path.exists(filename):
180 c408053f Nikos Skalkotos
                raise FatalError("Output file %s exists "
181 c408053f Nikos Skalkotos
                    "(use --force to overwrite it)." % filename)
182 c408053f Nikos Skalkotos
183 e77e66a9 Nikos Skalkotos
    disk = Disk(options.source, out)
184 d57775d4 Nikos Skalkotos
    try:
185 e22aa3a9 Nikos Skalkotos
        snapshot = disk.snapshot()
186 e22aa3a9 Nikos Skalkotos
187 e22aa3a9 Nikos Skalkotos
        dev = disk.get_device(snapshot)
188 df499fea Nikos Skalkotos
189 df499fea Nikos Skalkotos
        # If no customization is to be applied, the image should be mounted ro
190 df499fea Nikos Skalkotos
        readonly = not (options.sysprep or options.shrink)
191 df499fea Nikos Skalkotos
        dev.mount(readonly)
192 22a6d232 Nikos Skalkotos
193 bb4db5a8 Nikos Skalkotos
        cls = os_cls(dev.distro, dev.ostype)
194 bb4db5a8 Nikos Skalkotos
        image_os = cls(dev.root, dev.g, out)
195 e77e66a9 Nikos Skalkotos
        out.output()
196 3f70f242 Nikos Skalkotos
197 f165adc0 Nikos Skalkotos
        for sysprep in options.disabled_syspreps:
198 f165adc0 Nikos Skalkotos
            image_os.disable_sysprep(sysprep)
199 f165adc0 Nikos Skalkotos
200 f165adc0 Nikos Skalkotos
        for sysprep in options.enabled_syspreps:
201 f165adc0 Nikos Skalkotos
            image_os.enable_sysprep(sysprep)
202 76d4a1c9 Nikos Skalkotos
203 f165adc0 Nikos Skalkotos
        if options.print_sysprep:
204 f165adc0 Nikos Skalkotos
            image_os.print_syspreps()
205 e77e66a9 Nikos Skalkotos
            out.output()
206 76d4a1c9 Nikos Skalkotos
207 76d4a1c9 Nikos Skalkotos
        if options.outfile is None and not options.upload:
208 76d4a1c9 Nikos Skalkotos
            return 0
209 76d4a1c9 Nikos Skalkotos
210 9cbb5794 Nikos Skalkotos
        if options.sysprep:
211 f165adc0 Nikos Skalkotos
            image_os.do_sysprep()
212 1a3f1298 Nikos Skalkotos
213 5886f568 Nikos Skalkotos
        metadata = image_os.meta
214 8c574358 Nikos Skalkotos
        dev.umount()
215 1a3f1298 Nikos Skalkotos
216 e77e66a9 Nikos Skalkotos
        size = options.shrink and dev.shrink() or dev.meta['SIZE']
217 e8b1b48b Nikos Skalkotos
        metadata.update(dev.meta)
218 ae48a082 Nikos Skalkotos
219 7d3dc857 Nikos Skalkotos
        # Add command line metadata to the collected ones...
220 7d3dc857 Nikos Skalkotos
        metadata.update(options.metadata)
221 7d3dc857 Nikos Skalkotos
222 e77e66a9 Nikos Skalkotos
        md5 = MD5(out)
223 e77e66a9 Nikos Skalkotos
        checksum = md5.compute(snapshot, size)
224 8e3065a0 Nikos Skalkotos
225 143e9484 Nikos Skalkotos
        metastring = '\n'.join(
226 5b801534 Nikos Skalkotos
                ['%s=%s' % (key, value) for (key, value) in metadata.items()])
227 143e9484 Nikos Skalkotos
        metastring += '\n'
228 0ae01e26 Nikos Skalkotos
229 b1395967 Nikos Skalkotos
        if options.outfile is not None:
230 d603d80d Nikos Skalkotos
            dev.dump(options.outfile)
231 997ac76a Nikos Skalkotos
232 e77e66a9 Nikos Skalkotos
            out.output('Dumping metadata file...', False)
233 b1395967 Nikos Skalkotos
            with open('%s.%s' % (options.outfile, 'meta'), 'w') as f:
234 5b801534 Nikos Skalkotos
                f.write(metastring)
235 e77e66a9 Nikos Skalkotos
            out.success('done')
236 b1395967 Nikos Skalkotos
237 e77e66a9 Nikos Skalkotos
            out.output('Dumping md5sum file...', False)
238 b1395967 Nikos Skalkotos
            with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f:
239 5b801534 Nikos Skalkotos
                f.write('%s %s\n' % (checksum, \
240 5b801534 Nikos Skalkotos
                                            os.path.basename(options.outfile)))
241 e77e66a9 Nikos Skalkotos
            out.success('done')
242 b1395967 Nikos Skalkotos
243 e22aa3a9 Nikos Skalkotos
        # Destroy the device. We only need the snapshot from now on
244 e22aa3a9 Nikos Skalkotos
        disk.destroy_device(dev)
245 e22aa3a9 Nikos Skalkotos
246 e77e66a9 Nikos Skalkotos
        out.output()
247 3b7d3fc7 Nikos Skalkotos
        try:
248 3b7d3fc7 Nikos Skalkotos
            uploaded_obj = ""
249 3b7d3fc7 Nikos Skalkotos
            if options.upload:
250 3b7d3fc7 Nikos Skalkotos
                out.output("Uploading image to pithos:")
251 3b7d3fc7 Nikos Skalkotos
                kamaki = Kamaki(options.account, options.token, out)
252 825fe2a6 Nikos Skalkotos
                with open(snapshot, 'rb') as f:
253 3b7d3fc7 Nikos Skalkotos
                    uploaded_obj = kamaki.upload(f, size, options.upload,
254 3b7d3fc7 Nikos Skalkotos
                                            "(1/4)  Calculating block hashes",
255 3b7d3fc7 Nikos Skalkotos
                                            "(2/4)  Uploading missing blocks")
256 3b7d3fc7 Nikos Skalkotos
257 3b7d3fc7 Nikos Skalkotos
                out.output("(3/4)  Uploading metadata file...", False)
258 3b7d3fc7 Nikos Skalkotos
                kamaki.upload(StringIO.StringIO(metastring),
259 3b7d3fc7 Nikos Skalkotos
                              size=len(metastring),
260 3b7d3fc7 Nikos Skalkotos
                              remote_path="%s.%s" % (options.upload, 'meta'))
261 3b7d3fc7 Nikos Skalkotos
                out.success('done')
262 3b7d3fc7 Nikos Skalkotos
                out.output("(4/4)  Uploading md5sum file...", False)
263 3b7d3fc7 Nikos Skalkotos
                md5sumstr = '%s %s\n' % (
264 3b7d3fc7 Nikos Skalkotos
                                    checksum, os.path.basename(options.upload))
265 3b7d3fc7 Nikos Skalkotos
                kamaki.upload(StringIO.StringIO(md5sumstr),
266 3b7d3fc7 Nikos Skalkotos
                              size=len(md5sumstr),
267 3b7d3fc7 Nikos Skalkotos
                              remote_path="%s.%s" % (options.upload, 'md5sum'))
268 3b7d3fc7 Nikos Skalkotos
                out.success('done')
269 3b7d3fc7 Nikos Skalkotos
                out.output()
270 3b7d3fc7 Nikos Skalkotos
271 3b7d3fc7 Nikos Skalkotos
            if options.register:
272 3b7d3fc7 Nikos Skalkotos
                out.output('Registring image to ~okeanos...', False)
273 3b7d3fc7 Nikos Skalkotos
                kamaki.register(options.register, uploaded_obj, metadata)
274 3b7d3fc7 Nikos Skalkotos
                out.success('done')
275 3b7d3fc7 Nikos Skalkotos
                out.output()
276 3b7d3fc7 Nikos Skalkotos
        except ClientError as e:
277 3b7d3fc7 Nikos Skalkotos
            raise FatalError("Pithos client: %d %s" % (e.status, e.message))
278 997ac76a Nikos Skalkotos
279 d57775d4 Nikos Skalkotos
    finally:
280 e77e66a9 Nikos Skalkotos
        out.output('cleaning up...')
281 d57775d4 Nikos Skalkotos
        disk.cleanup()
282 d57775d4 Nikos Skalkotos
283 e77e66a9 Nikos Skalkotos
    out.success("snf-image-creator exited without errors")
284 b1395967 Nikos Skalkotos
285 c408053f Nikos Skalkotos
    return 0
286 c408053f Nikos Skalkotos
287 ae48a082 Nikos Skalkotos
288 0ae01e26 Nikos Skalkotos
def main():
289 c408053f Nikos Skalkotos
    try:
290 0ae01e26 Nikos Skalkotos
        ret = image_creator()
291 c408053f Nikos Skalkotos
        sys.exit(ret)
292 c408053f Nikos Skalkotos
    except FatalError as e:
293 bb4db5a8 Nikos Skalkotos
        colored = sys.stderr.isatty()
294 bb4db5a8 Nikos Skalkotos
        SimpleOutput(colored).error(e)
295 c408053f Nikos Skalkotos
        sys.exit(1)
296 d57775d4 Nikos Skalkotos
297 0ae01e26 Nikos Skalkotos
if __name__ == '__main__':
298 0ae01e26 Nikos Skalkotos
    main()
299 ae48a082 Nikos Skalkotos
300 d57775d4 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :