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