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 logging.debug("Launch directory: {}".format(os.getcwd())) 157 158 distro.overwrite = hypervisor.overwrite = self.options.overwrite 159 destdir = self.options.destdir or ('%s-%s' % (distro.arg, 160 hypervisor.arg)) 161 logging.debug("Output destdir: {}".format(destdir)) 162 163 if self.options.tmpfs and self.options.chroot_dir: 164 raise VMBuilderUserError('--chroot-dir and --tmpfs can not be used together.') 165 166 if os.path.exists(destdir): 167 if os.path.realpath(destdir) == os.getcwd(): 168 raise VMBuilderUserError('Current working directory cannot be used as a destination directory') 169 if self.options.overwrite: 170 logging.debug('%s existed, but -o was specified. ' 171 'Nuking it.' % destdir) 172 shutil.rmtree(destdir) 173 else: 174 raise VMBuilderUserError('%s already exists' % destdir) 175 176 if self.options.config: 177 config_files.append(self.options.config) 178 util.apply_config_files_to_context(config_files, distro) 179 util.apply_config_files_to_context(config_files, hypervisor) 180 181 if self.options.templates: 182 distro.template_dirs.insert(0, '%s/%%s' 183 % self.options.templates) 184 hypervisor.template_dirs.insert(0, '%s/%%s' 185 % self.options.templates) 186 187 for option in dir(self.options): 188 if option.startswith('_') or option in ['ensure_value', 189 'read_module', 190 'read_file']: 191 continue 192 val = getattr(self.options, option) 193 option = option.replace('_', '-') 194 if val: 195 if (distro.has_setting(option) and 196 distro.get_setting_default(option) != val): 197 distro.set_setting_fuzzy(option, val) 198 elif (hypervisor.has_setting(option) and 199 hypervisor.get_setting_default(option) != val): 200 hypervisor.set_setting_fuzzy(option, val) 201 202 chroot_dir = None 203 if self.options.existing_chroot: 204 distro.set_chroot_dir(self.options.existing_chroot) 205 distro.call_hooks('preflight_check') 206 else: 207 if self.options.tmpfs is not None: 208 if str(self.options.tmpfs) == '-': 209 tmpfs_size = 1024 210 else: 211 tmpfs_size = int(self.options.tmpfs) 212 tmpfs_mount_point = util.set_up_tmpfs( 213 tmp_root=self.options.tmp_root, size=tmpfs_size) 214 chroot_dir = tmpfs_mount_point 215 elif self.options.chroot_dir: 216 os.mkdir(self.options.chroot_dir) 217 chroot_dir = self.options.chroot_dir 218 else: 219 chroot_dir = util.tmpdir(tmp_root=self.options.tmp_root) 220 distro.set_chroot_dir(chroot_dir) 221 distro.build_chroot() 222 223 if self.options.only_chroot: 224 print 'Chroot can be found in %s' % distro.chroot_dir 225 sys.exit(0) 226 227 self.set_disk_layout(optparser, hypervisor) 228 hypervisor.install_os() 229 230 os.mkdir(destdir) 231 self.fix_ownership(destdir) 232 hypervisor.finalise(destdir) 233 # If chroot_dir is not None, it means we created it, 234 # and if we reach here, it means the user didn't pass 235 # --only-chroot. Hence, we need to remove it to clean 236 # up after ourselves. 237 if chroot_dir is not None and tmpfs_mount_point is None: 238 util.run_cmd('rm', '-rf', '--one-file-system', chroot_dir) 239 except VMBuilderException, e: 240 logging.error(e) 241 raise 242 finally: 243 if tmpfs_mount_point is not None: 244 util.clean_up_tmpfs(tmpfs_mount_point) 245 util.run_cmd('rmdir', tmpfs_mount_point)
246
247 - def fix_ownership(self, filename):
248 """ 249 Change ownership of file to $SUDO_USER. 250 251 @type path: string 252 @param path: file or directory to give to $SUDO_USER 253 """ 254 if 'SUDO_USER' in os.environ: 255 logging.debug('Changing ownership of %s to %s' % 256 (filename, os.environ['SUDO_USER'])) 257 (uid, gid) = pwd.getpwnam(os.environ['SUDO_USER'])[2:4] 258 os.chown(filename, uid, gid)
259
260 - def add_settings_from_context(self, optparser, context):
261 setting_groups = set([setting.setting_group for setting 262 in context._config.values()]) 263 for setting_group in setting_groups: 264 optgroup = optparse.OptionGroup(optparser, setting_group.name) 265 for setting in setting_group._settings: 266 args = ['--%s' % setting.name] 267 args += setting.extra_args 268 kwargs = {} 269 if setting.help: 270 kwargs['help'] = setting.help 271 if len(setting.extra_args) > 0: 272 setting.help += " Config option: %s" % setting.name 273 if setting.metavar: 274 kwargs['metavar'] = setting.metavar 275 if setting.get_default(): 276 kwargs['default'] = setting.get_default() 277 if type(setting) == VMBuilder.plugins.Plugin.BooleanSetting: 278 kwargs['action'] = 'store_true' 279 if type(setting) == VMBuilder.plugins.Plugin.ListSetting: 280 kwargs['action'] = 'append' 281 optgroup.add_option(*args, **kwargs) 282 optparser.add_option_group(optgroup)
283
284 - def versioninfo(self, option, opt, value, parser):
285 print ('%(major)d.%(minor)d.%(micro)s' % 286 VMBuilder.get_version_info()) 287 sys.exit(0)
288
289 - def set_usage(self, optparser):
290 optparser.set_usage('%prog hypervisor distro [options]')
291 # optparser.arg_help = (('hypervisor', vm.hypervisor_help), ('distro', vm.distro_help)) 292
293 - def handle_args(self, optparser, args):
294 if len(args) < 2: 295 optparser.error("You need to specify at least the hypervisor type " 296 "and the distro") 297 distro = VMBuilder.get_distro(args[1])() 298 hypervisor = VMBuilder.get_hypervisor(args[0])(distro) 299 return hypervisor, distro
300
301 - def set_verbosity(self, option, opt_str, value, parser):
302 if opt_str == '--debug': 303 VMBuilder.set_console_loglevel(logging.DEBUG) 304 elif opt_str == '--verbose': 305 VMBuilder.set_console_loglevel(logging.INFO) 306 elif opt_str == '--quiet': 307 VMBuilder.set_console_loglevel(logging.CRITICAL)
308
309 - def set_disk_layout(self, optparser, hypervisor):
310 default_filesystem = hypervisor.distro.preferred_filesystem() 311 if not self.options.part: 312 rootsize = parse_size(self.options.rootsize) 313 swapsize = parse_size(self.options.swapsize) 314 optsize = parse_size(self.options.optsize) 315 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE: 316 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 317 hypervisor.add_filesystem(filename=tmpfile, 318 size='%dM' % rootsize, 319 type='ext3', 320 mntpnt='/') 321 if swapsize > 0: 322 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 323 hypervisor.add_filesystem(filename=tmpfile, 324 size='%dM' % swapsize, 325 type='swap', 326 mntpnt=None) 327 if optsize > 0: 328 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 329 hypervisor.add_filesystem(filename=tmpfile, 330 size='%dM' % optsize, 331 type='ext3', 332 mntpnt='/opt') 333 else: 334 if self.options.raw: 335 for raw_disk in self.options.raw: 336 hypervisor.add_disk(filename=raw_disk) 337 disk = hypervisor.disks[0] 338 else: 339 size = rootsize + swapsize + optsize 340 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 341 disk = hypervisor.add_disk(tmpfile, size='%dM' % size) 342 offset = 0 343 disk.add_part(offset, rootsize, default_filesystem, '/') 344 offset += rootsize 345 if swapsize > 0: 346 disk.add_part(offset, swapsize, 'swap', 'swap') 347 offset += swapsize 348 if optsize > 0: 349 disk.add_part(offset, optsize, default_filesystem, '/opt') 350 else: 351 # We need to parse the file specified 352 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE: 353 try: 354 for line in file(self.options.part): 355 elements = line.strip().split(' ') 356 if len(elements) < 4: 357 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 358 else: 359 tmpfile = elements[3] 360 361 if elements[0] == 'root': 362 hypervisor.add_filesystem(elements[1], 363 default_filesystem, 364 filename=tmpfile, 365 mntpnt='/') 366 elif elements[0] == 'swap': 367 hypervisor.add_filesystem(elements[1], 368 type='swap', 369 filename=tmpfile, 370 mntpnt=None) 371 elif elements[0] == '---': 372 # We just ignore the user's attempt to specify multiple disks 373 pass 374 elif len(elements) == 3: 375 hypervisor.add_filesystem(elements[1], 376 type=default_filesystem, 377 filename=tmpfile, 378 mntpnt=elements[0], 379 devletter='', 380 device=elements[2], 381 dummy=(int(elements[1]) == 0)) 382 else: 383 hypervisor.add_filesystem(elements[1], 384 type=default_filesystem, 385 filename=tmpfile, 386 mntpnt=elements[0]) 387 except IOError, (errno, strerror): 388 optparser.error("%s parsing --part option: %s" % 389 (errno, strerror)) 390 else: 391 try: 392 curdisk = list() 393 size = 0 394 disk_idx = 0 395 for line in file(self.options.part): 396 pair = line.strip().split(' ',1) 397 if pair[0] == '---': 398 self.do_disk(hypervisor, curdisk, size, disk_idx) 399 curdisk = list() 400 size = 0 401 disk_idx += 1 402 elif pair[0] != '': 403 logging.debug("part: %s, size: %d" % (pair[0], 404 int(pair[1]))) 405 curdisk.append((pair[0], pair[1])) 406 size += int(pair[1]) 407 408 self.do_disk(hypervisor, curdisk, size, disk_idx) 409 410 except IOError, (errno, strerror): 411 optparser.error("%s parsing --part option: %s" % 412 (errno, strerror))
413
414 - def do_disk(self, hypervisor, curdisk, size, disk_idx):
415 default_filesystem = hypervisor.distro.preferred_filesystem() 416 417 if self.options.raw: 418 disk = hypervisor.add_disk(filename=self.options.raw[disk_idx]) 419 else: 420 disk = hypervisor.add_disk( 421 util.tmp_filename(tmp_root=self.options.tmp_root), 422 size+1) 423 424 logging.debug("do_disk #%i - size: %d" % (disk_idx, size)) 425 offset = 0 426 for pair in curdisk: 427 logging.debug("do_disk #%i - part: %s, size: %s, offset: %d" % 428 (disk_idx, pair[0], pair[1], offset)) 429 if pair[0] == 'root': 430 disk.add_part(offset, int(pair[1]), default_filesystem, '/') 431 elif pair[0] == 'swap': 432 disk.add_part(offset, int(pair[1]), pair[0], pair[0]) 433 else: 434 disk.add_part(offset, int(pair[1]), default_filesystem, pair[0]) 435 offset += int(pair[1])
436
437 -class UVB(CLI):
438 arg = 'ubuntu-vm-builder' 439
440 - def set_usage(self, optparser):
441 optparser.set_usage('%prog hypervisor suite [options]')
442 # optparser.arg_help = (('hypervisor', vm.hypervisor_help), ('suite', self.suite_help)) 443
444 - def suite_help(self):
445 return ('Suite. Valid options: %s' % 446 " ".join(VMBuilder.plugins.ubuntu.distro.Ubuntu.suites))
447
448 - def handle_args(self, optparser, args):
449 if len(args) < 2: 450 optparser.error("You need to specify at least the hypervisor type " 451 "and the series") 452 distro = VMBuilder.get_distro('ubuntu')() 453 hypervisor = VMBuilder.get_hypervisor(args[0])(distro) 454 distro.set_setting('suite', args[1]) 455 return hypervisor, distro
456