Package VMBuilder :: Package contrib :: Module cli
[frames] | no frames]

Source Code for Module VMBuilder.contrib.cli

  1  #    Uncomplicated VM Builder 
  2  #    Copyright (C) 2007-2009 Canonical Ltd. 
  3  # 
  4  #    See AUTHORS for list of contributors 
  5  # 
  6  #    This program is free software: you can redistribute it and/or modify 
  7  #    it under the terms of the GNU General Public License version 3, as 
  8  #    published by the Free Software Foundation. 
  9  # 
 10  #    This program is distributed in the hope that it will be useful, 
 11  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  #    GNU General Public License for more details. 
 14  # 
 15  #    You should have received a copy of the GNU General Public License 
 16  #    along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 17  # 
 18  #    CLI plugin 
 19  import logging 
 20  import optparse 
 21  import os 
 22  import pwd 
 23  import shutil 
 24  import sys 
 25  import tempfile 
 26  import VMBuilder 
 27  import VMBuilder.util as util 
 28  from   VMBuilder.disk import parse_size 
 29  import VMBuilder.hypervisor 
 30  from   VMBuilder.exception import VMBuilderUserError, VMBuilderException 
 31   
32 -class CLI(object):
33 arg = 'cli' 34
35 - def main(self):
36 tmpfs_mount_point = None 37 try: 38 optparser = optparse.OptionParser() 39 40 self.set_usage(optparser) 41 42 optparser.add_option('--version', 43 action='callback', 44 callback=self.versioninfo, 45 help='Show version information') 46 47 group = optparse.OptionGroup(optparser, 'Build options') 48 group.add_option('--debug', 49 action='callback', 50 callback=self.set_verbosity, 51 help='Show debug information') 52 group.add_option('--verbose', 53 '-v', 54 action='callback', 55 callback=self.set_verbosity, 56 help='Show progress information') 57 group.add_option('--quiet', 58 '-q', 59 action='callback', 60 callback=self.set_verbosity, 61 help='Silent operation') 62 group.add_option('--overwrite', 63 '-o', 64 action='store_true', 65 help='Remove destination directory before starting build') 66 group.add_option('--config', 67 '-c', 68 type='str', 69 help='Configuration file') 70 group.add_option('--templates', 71 metavar='DIR', 72 help='Prepend DIR to template search path.') 73 group.add_option('--destdir', 74 '-d', 75 type='str', 76 help='Destination directory') 77 group.add_option('--only-chroot', 78 action='store_true', 79 help=("Only build the chroot. Don't install it " 80 "on disk images or anything.")) 81 group.add_option('--chroot-dir', 82 help="Build the chroot in directory.") 83 group.add_option('--existing-chroot', 84 help="Use existing chroot.") 85 group.add_option('--tmp', 86 '-t', 87 metavar='DIR', 88 dest='tmp_root', 89 default=tempfile.gettempdir(), 90 help=('Use TMP as temporary working space for ' 91 'image generation. Defaults to $TMPDIR if ' 92 'it is defined or /tmp otherwise. ' 93 '[default: %default]')) 94 group.add_option('--tmpfs', 95 metavar="SIZE", 96 help=('Use a tmpfs as the working directory, ' 97 'specifying its size or "-" to use tmpfs ' 98 'default (suid,dev,size=1G).')) 99 optparser.add_option_group(group) 100 101 group = optparse.OptionGroup(optparser, 'Disk') 102 group.add_option('--rootsize', 103 metavar='SIZE', 104 default=4096, 105 help=('Size (in MB) of the root filesystem ' 106 '[default: %default]')) 107 group.add_option('--optsize', 108 metavar='SIZE', 109 default=0, 110 help=('Size (in MB) of the /opt filesystem. If not' 111 ' set, no /opt filesystem will be added.')) 112 group.add_option('--swapsize', 113 metavar='SIZE', 114 default=1024, 115 help=('Size (in MB) of the swap partition ' 116 '[default: %default]')) 117 group.add_option('--raw', 118 metavar='PATH', 119 type='str', 120 action='append', 121 help=("Specify a file (or block device) to use as " 122 "first disk image (can be specified multiple" 123 " times).")) 124 group.add_option('--part', 125 metavar='PATH', 126 type='str', 127 help=("Specify a partition table in PATH. Each " 128 "line of partfile should specify (root " 129 "first): \n mountpoint size \none per " 130 "line, separated by space, where size is " 131 "in megabytes. You can have up to 4 " 132 "virtual disks, a new disk starts on a " 133 "line containing only '---'. ie: \n root " 134 "2000 \n /boot 512 \n swap 1000 \n " 135 "--- \n /var 8000 \n /var/log 2000")) 136 optparser.add_option_group(group) 137 138 optparser.disable_interspersed_args() 139 (dummy, args) = optparser.parse_args(sys.argv[1:]) 140 optparser.enable_interspersed_args() 141 142 hypervisor, distro = self.handle_args(optparser, args) 143 144 self.add_settings_from_context(optparser, distro) 145 self.add_settings_from_context(optparser, hypervisor) 146 147 hypervisor.register_hook('fix_ownership', self.fix_ownership) 148 149 config_files = ['/etc/vmbuilder.cfg', 150 os.path.expanduser('~/.vmbuilder.cfg')] 151 (self.options, args) = optparser.parse_args(sys.argv[2:]) 152 153 if os.geteuid() != 0: 154 raise VMBuilderUserError('Must run as root') 155 156 distro.overwrite = hypervisor.overwrite = self.options.overwrite 157 destdir = self.options.destdir or ('%s-%s' % (distro.arg, 158 hypervisor.arg)) 159 160 if self.options.tmpfs and self.options.chroot_dir: 161 raise VMBuilderUserError('--chroot-dir and --tmpfs can not be used together.') 162 163 if os.path.exists(destdir): 164 if os.path.realpath(destdir) == os.getcwd(): 165 raise VMBuilderUserError('Current working directory cannot be used as a destination directory') 166 if self.options.overwrite: 167 logging.debug('%s existed, but -o was specified. ' 168 'Nuking it.' % destdir) 169 shutil.rmtree(destdir) 170 else: 171 raise VMBuilderUserError('%s already exists' % destdir) 172 173 if self.options.config: 174 config_files.append(self.options.config) 175 util.apply_config_files_to_context(config_files, distro) 176 util.apply_config_files_to_context(config_files, hypervisor) 177 178 if self.options.templates: 179 distro.template_dirs.insert(0, '%s/%%s' 180 % self.options.templates) 181 hypervisor.template_dirs.insert(0, '%s/%%s' 182 % self.options.templates) 183 184 for option in dir(self.options): 185 if option.startswith('_') or option in ['ensure_value', 186 'read_module', 187 'read_file']: 188 continue 189 val = getattr(self.options, option) 190 option = option.replace('_', '-') 191 if val: 192 if (distro.has_setting(option) and 193 distro.get_setting_default(option) != val): 194 distro.set_setting_fuzzy(option, val) 195 elif (hypervisor.has_setting(option) and 196 hypervisor.get_setting_default(option) != val): 197 hypervisor.set_setting_fuzzy(option, val) 198 199 chroot_dir = None 200 if self.options.existing_chroot: 201 distro.set_chroot_dir(self.options.existing_chroot) 202 distro.call_hooks('preflight_check') 203 else: 204 if self.options.tmpfs is not None: 205 if str(self.options.tmpfs) == '-': 206 tmpfs_size = 1024 207 else: 208 tmpfs_size = int(self.options.tmpfs) 209 tmpfs_mount_point = util.set_up_tmpfs( 210 tmp_root=self.options.tmp_root, size=tmpfs_size) 211 chroot_dir = tmpfs_mount_point 212 elif self.options.chroot_dir: 213 os.mkdir(self.options.chroot_dir) 214 chroot_dir = self.options.chroot_dir 215 else: 216 chroot_dir = util.tmpdir(tmp_root=self.options.tmp_root) 217 distro.set_chroot_dir(chroot_dir) 218 distro.build_chroot() 219 220 if self.options.only_chroot: 221 print 'Chroot can be found in %s' % distro.chroot_dir 222 sys.exit(0) 223 224 self.set_disk_layout(optparser, hypervisor) 225 hypervisor.install_os() 226 227 os.mkdir(destdir) 228 self.fix_ownership(destdir) 229 hypervisor.finalise(destdir) 230 # If chroot_dir is not None, it means we created it, 231 # and if we reach here, it means the user didn't pass 232 # --only-chroot. Hence, we need to remove it to clean 233 # up after ourselves. 234 if chroot_dir is not None and tmpfs_mount_point is None: 235 util.run_cmd('rm', '-rf', '--one-file-system', chroot_dir) 236 except VMBuilderException, e: 237 logging.error(e) 238 raise 239 finally: 240 if tmpfs_mount_point is not None: 241 util.clean_up_tmpfs(tmpfs_mount_point) 242 util.run_cmd('rmdir', tmpfs_mount_point)
243
244 - def fix_ownership(self, filename):
245 """ 246 Change ownership of file to $SUDO_USER. 247 248 @type path: string 249 @param path: file or directory to give to $SUDO_USER 250 """ 251 if 'SUDO_USER' in os.environ: 252 logging.debug('Changing ownership of %s to %s' % 253 (filename, os.environ['SUDO_USER'])) 254 (uid, gid) = pwd.getpwnam(os.environ['SUDO_USER'])[2:4] 255 os.chown(filename, uid, gid)
256
257 - def add_settings_from_context(self, optparser, context):
258 setting_groups = set([setting.setting_group for setting 259 in context._config.values()]) 260 for setting_group in setting_groups: 261 optgroup = optparse.OptionGroup(optparser, setting_group.name) 262 for setting in setting_group._settings: 263 args = ['--%s' % setting.name] 264 args += setting.extra_args 265 kwargs = {} 266 if setting.help: 267 kwargs['help'] = setting.help 268 if len(setting.extra_args) > 0: 269 setting.help += " Config option: %s" % setting.name 270 if setting.metavar: 271 kwargs['metavar'] = setting.metavar 272 if setting.get_default(): 273 kwargs['default'] = setting.get_default() 274 if type(setting) == VMBuilder.plugins.Plugin.BooleanSetting: 275 kwargs['action'] = 'store_true' 276 if type(setting) == VMBuilder.plugins.Plugin.ListSetting: 277 kwargs['action'] = 'append' 278 optgroup.add_option(*args, **kwargs) 279 optparser.add_option_group(optgroup)
280
281 - def versioninfo(self, option, opt, value, parser):
282 print ('%(major)d.%(minor)d.%(micro)s' % 283 VMBuilder.get_version_info()) 284 sys.exit(0)
285
286 - def set_usage(self, optparser):
287 optparser.set_usage('%prog hypervisor distro [options]')
288 # optparser.arg_help = (('hypervisor', vm.hypervisor_help), ('distro', vm.distro_help)) 289
290 - def handle_args(self, optparser, args):
291 if len(args) < 2: 292 optparser.error("You need to specify at least the hypervisor type " 293 "and the distro") 294 distro = VMBuilder.get_distro(args[1])() 295 hypervisor = VMBuilder.get_hypervisor(args[0])(distro) 296 return hypervisor, distro
297
298 - def set_verbosity(self, option, opt_str, value, parser):
299 if opt_str == '--debug': 300 VMBuilder.set_console_loglevel(logging.DEBUG) 301 elif opt_str == '--verbose': 302 VMBuilder.set_console_loglevel(logging.INFO) 303 elif opt_str == '--quiet': 304 VMBuilder.set_console_loglevel(logging.CRITICAL)
305
306 - def set_disk_layout(self, optparser, hypervisor):
307 default_filesystem = hypervisor.distro.preferred_filesystem() 308 if not self.options.part: 309 rootsize = parse_size(self.options.rootsize) 310 swapsize = parse_size(self.options.swapsize) 311 optsize = parse_size(self.options.optsize) 312 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE: 313 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 314 hypervisor.add_filesystem(filename=tmpfile, 315 size='%dM' % rootsize, 316 type='ext3', 317 mntpnt='/') 318 if swapsize > 0: 319 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 320 hypervisor.add_filesystem(filename=tmpfile, 321 size='%dM' % swapsize, 322 type='swap', 323 mntpnt=None) 324 if optsize > 0: 325 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 326 hypervisor.add_filesystem(filename=tmpfile, 327 size='%dM' % optsize, 328 type='ext3', 329 mntpnt='/opt') 330 else: 331 if self.options.raw: 332 for raw_disk in self.options.raw: 333 hypervisor.add_disk(filename=raw_disk) 334 disk = hypervisor.disks[0] 335 else: 336 size = rootsize + swapsize + optsize 337 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 338 disk = hypervisor.add_disk(tmpfile, size='%dM' % size) 339 offset = 0 340 disk.add_part(offset, rootsize, default_filesystem, '/') 341 offset += rootsize 342 if swapsize > 0: 343 disk.add_part(offset, swapsize, 'swap', 'swap') 344 offset += swapsize 345 if optsize > 0: 346 disk.add_part(offset, optsize, default_filesystem, '/opt') 347 else: 348 # We need to parse the file specified 349 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE: 350 try: 351 for line in file(self.options.part): 352 elements = line.strip().split(' ') 353 if len(elements) < 4: 354 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 355 else: 356 tmpfile = elements[3] 357 358 if elements[0] == 'root': 359 hypervisor.add_filesystem(elements[1], 360 default_filesystem, 361 filename=tmpfile, 362 mntpnt='/') 363 elif elements[0] == 'swap': 364 hypervisor.add_filesystem(elements[1], 365 type='swap', 366 filename=tmpfile, 367 mntpnt=None) 368 elif elements[0] == '---': 369 # We just ignore the user's attempt to specify multiple disks 370 pass 371 elif len(elements) == 3: 372 hypervisor.add_filesystem(elements[1], 373 type=default_filesystem, 374 filename=tmpfile, 375 mntpnt=elements[0], 376 devletter='', 377 device=elements[2], 378 dummy=(int(elements[1]) == 0)) 379 else: 380 hypervisor.add_filesystem(elements[1], 381 type=default_filesystem, 382 filename=tmpfile, 383 mntpnt=elements[0]) 384 except IOError, (errno, strerror): 385 optparser.error("%s parsing --part option: %s" % 386 (errno, strerror)) 387 else: 388 try: 389 curdisk = list() 390 size = 0 391 disk_idx = 0 392 for line in file(self.options.part): 393 pair = line.strip().split(' ',1) 394 if pair[0] == '---': 395 self.do_disk(hypervisor, curdisk, size, disk_idx) 396 curdisk = list() 397 size = 0 398 disk_idx += 1 399 elif pair[0] != '': 400 logging.debug("part: %s, size: %d" % (pair[0], 401 int(pair[1]))) 402 curdisk.append((pair[0], pair[1])) 403 size += int(pair[1]) 404 405 self.do_disk(hypervisor, curdisk, size, disk_idx) 406 407 except IOError, (errno, strerror): 408 optparser.error("%s parsing --part option: %s" % 409 (errno, strerror))
410
411 - def do_disk(self, hypervisor, curdisk, size, disk_idx):
412 default_filesystem = hypervisor.distro.preferred_filesystem() 413 414 if self.options.raw: 415 disk = hypervisor.add_disk(filename=self.options.raw[disk_idx]) 416 else: 417 disk = hypervisor.add_disk( 418 util.tmp_filename(tmp_root=self.options.tmp_root), 419 size+1) 420 421 logging.debug("do_disk #%i - size: %d" % (disk_idx, size)) 422 offset = 0 423 for pair in curdisk: 424 logging.debug("do_disk #%i - part: %s, size: %s, offset: %d" % 425 (disk_idx, pair[0], pair[1], offset)) 426 if pair[0] == 'root': 427 disk.add_part(offset, int(pair[1]), default_filesystem, '/') 428 elif pair[0] == 'swap': 429 disk.add_part(offset, int(pair[1]), pair[0], pair[0]) 430 else: 431 disk.add_part(offset, int(pair[1]), default_filesystem, pair[0]) 432 offset += int(pair[1])
433
434 -class UVB(CLI):
435 arg = 'ubuntu-vm-builder' 436
437 - def set_usage(self, optparser):
438 optparser.set_usage('%prog hypervisor suite [options]')
439 # optparser.arg_help = (('hypervisor', vm.hypervisor_help), ('suite', self.suite_help)) 440
441 - def suite_help(self):
442 return ('Suite. Valid options: %s' % 443 " ".join(VMBuilder.plugins.ubuntu.distro.Ubuntu.suites))
444
445 - def handle_args(self, optparser, args):
446 if len(args) < 2: 447 optparser.error("You need to specify at least the hypervisor type " 448 "and the series") 449 distro = VMBuilder.get_distro('ubuntu')() 450 hypervisor = VMBuilder.get_hypervisor(args[0])(distro) 451 distro.set_setting('suite', args[1]) 452 return hypervisor, distro
453