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', action='callback', callback=self.versioninfo, help='Show version information') 43 44 group = optparse.OptionGroup(optparser, 'Build options') 45 group.add_option('--debug', action='callback', callback=self.set_verbosity, help='Show debug information') 46 group.add_option('--verbose', '-v', action='callback', callback=self.set_verbosity, help='Show progress information') 47 group.add_option('--quiet', '-q', action='callback', callback=self.set_verbosity, help='Silent operation') 48 group.add_option('--overwrite', '-o', action='store_true', help='Configuration file') 49 group.add_option('--config', '-c', type='str', help='Configuration file') 50 group.add_option('--templates', metavar='DIR', help='Prepend DIR to template search path.') 51 group.add_option('--destdir', '-d', type='str', help='Destination directory') 52 group.add_option('--only-chroot', action='store_true', help="Only build the chroot. Don't install it on disk images or anything.") 53 group.add_option('--existing-chroot', help="Use existing chroot.") 54 group.add_option('--tmp', 55 '-t', 56 metavar='DIR', 57 dest='tmp_root', 58 default=tempfile.gettempdir(), 59 help=('Use TMP as temporary working space for ' 60 'image generation. Defaults to $TMPDIR if ' 61 'it is defined or /tmp otherwise. ' 62 '[default: %default]')) 63 group.add_option('--tmpfs', 64 metavar="SIZE", 65 help=('Use a tmpfs as the working directory, ' 66 'specifying its size or "-" to use tmpfs ' 67 'default (suid,dev,size=1G).')) 68 69 optparser.add_option_group(group) 70 71 group = optparse.OptionGroup(optparser, 'Disk') 72 group.add_option('--rootsize', metavar='SIZE', default=4096, help='Size (in MB) of the root filesystem [default: %default]') 73 group.add_option('--optsize', metavar='SIZE', default=0, help='Size (in MB) of the /opt filesystem. If not set, no /opt filesystem will be added.') 74 group.add_option('--swapsize', metavar='SIZE', default=1024, help='Size (in MB) of the swap partition [default: %default]') 75 group.add_option('--raw', metavar='PATH', type='str', help="Specify a file (or block device) to as first disk image.") 76 group.add_option('--part', metavar='PATH', type='str', help="Allows to specify a partition table in PATH each line of partfile should specify (root first): \n mountpoint size \none per line, separated by space, where size is in megabytes. You can have up to 4 virtual disks, a new disk starts on a line containing only '---'. ie: \n root 2000 \n /boot 512 \n swap 1000 \n --- \n /var 8000 \n /var/log 2000") 77 optparser.add_option_group(group) 78 79 hypervisor, distro = self.handle_args(optparser, sys.argv[1:]) 80 81 self.add_settings_from_context(optparser, distro) 82 self.add_settings_from_context(optparser, hypervisor) 83 84 hypervisor.register_hook('fix_ownership', self.fix_ownership) 85 86 config_files = ['/etc/vmbuilder.cfg', os.path.expanduser('~/.vmbuilder.cfg')] 87 (self.options, args) = optparser.parse_args(sys.argv[2:]) 88 89 if os.geteuid() != 0: 90 raise VMBuilderUserError('Must run as root') 91 92 destdir = self.options.destdir or ('%s-%s' % (distro.arg, hypervisor.arg)) 93 if os.path.exists(destdir): 94 if self.options.overwrite: 95 logging.debug('%s existed, but -o was specified. Nuking it.' % destdir) 96 shutil.rmtree(destdir) 97 else: 98 raise VMBuilderUserError('%s already exists' % destdir) 99 100 if self.options.config: 101 config_files.append(self.options.config) 102 util.apply_config_files_to_context(config_files, distro) 103 util.apply_config_files_to_context(config_files, hypervisor) 104 105 if self.options.templates: 106 distro.template_dirs.insert(0,'%s/%%s' % self.options.templates) 107 hypervisor.template_dirs.insert(0,'%s/%%s' % self.options.templates) 108 109 for option in dir(self.options): 110 if option.startswith('_') or option in ['ensure_value', 'read_module', 'read_file']: 111 continue 112 val = getattr(self.options, option) 113 option = option.replace('_', '-') 114 if val: 115 if distro.has_setting(option) and distro.get_setting_default(option) != val: 116 distro.set_setting_fuzzy(option, val) 117 elif hypervisor.has_setting(option) and hypervisor.get_setting_default(option) != val: 118 hypervisor.set_setting_fuzzy(option, val) 119 120 chroot_dir = None 121 if self.options.existing_chroot: 122 distro.set_chroot_dir(self.options.existing_chroot) 123 distro.call_hooks('preflight_check') 124 else: 125 if self.options.tmpfs is not None: 126 if str(self.options.tmpfs) == '-': 127 tmpfs_size = 1024 128 else: 129 tmpfs_size = int(self.options.tmpfs) 130 tmpfs_mount_point = util.set_up_tmpfs( 131 tmp_root=self.options.tmp_root, size=tmpfs_size) 132 chroot_dir = tmpfs_mount_point 133 else: 134 chroot_dir = util.tmpdir(tmp_root=self.options.tmp_root) 135 136 distro.set_chroot_dir(chroot_dir) 137 distro.build_chroot() 138 139 if self.options.only_chroot: 140 print 'Chroot can be found in %s' % distro.chroot_dir 141 sys.exit(0) 142 143 self.set_disk_layout(hypervisor) 144 hypervisor.install_os() 145 146 os.mkdir(destdir) 147 self.fix_ownership(destdir) 148 hypervisor.finalise(destdir) 149 # If chroot_dir is not None, it means we created it, 150 # and if we reach here, it means the user didn't pass 151 # --only-chroot. Hence, we need to remove it to clean 152 # up after ourselves. 153 if chroot_dir is not None and tmpfs_mount_point is None: 154 util.run_cmd('rm', '-rf', '--one-file-system', chroot_dir) 155 except VMBuilderException, e: 156 logging.error(e) 157 raise 158 finally: 159 if tmpfs_mount_point is not None: 160 util.clean_up_tmpfs(tmpfs_mount_point) 161 util.run_cmd('rmdir', tmpfs_mount_point)
162
163 - def fix_ownership(self, filename):
164 """ 165 Change ownership of file to $SUDO_USER. 166 167 @type path: string 168 @param path: file or directory to give to $SUDO_USER 169 """ 170 if 'SUDO_USER' in os.environ: 171 logging.debug('Changing ownership of %s to %s' % (filename, os.environ['SUDO_USER'])) 172 (uid, gid) = pwd.getpwnam(os.environ['SUDO_USER'])[2:4] 173 os.chown(filename, uid, gid)
174
175 - def add_settings_from_context(self, optparser, context):
176 setting_groups = set([setting.setting_group for setting in context._config.values()]) 177 for setting_group in setting_groups: 178 optgroup = optparse.OptionGroup(optparser, setting_group.name) 179 for setting in setting_group._settings: 180 args = ['--%s' % setting.name] 181 args += setting.extra_args 182 kwargs = {} 183 if setting.help: 184 kwargs['help'] = setting.help 185 if len(setting.extra_args) > 0: 186 setting.help += " Config option: %s" % setting.name 187 if setting.metavar: 188 kwargs['metavar'] = setting.metavar 189 if setting.get_default(): 190 kwargs['default'] = setting.get_default() 191 if type(setting) == VMBuilder.plugins.Plugin.BooleanSetting: 192 kwargs['action'] = 'store_true' 193 if type(setting) == VMBuilder.plugins.Plugin.ListSetting: 194 kwargs['action'] = 'append' 195 optgroup.add_option(*args, **kwargs) 196 optparser.add_option_group(optgroup)
197
198 - def versioninfo(self, option, opt, value, parser):
199 print '%(major)d.%(minor)d.%(micro)s.r%(revno)d' % VMBuilder.get_version_info() 200 sys.exit(0)
201
202 - def set_usage(self, optparser):
203 optparser.set_usage('%prog hypervisor distro [options]')
204 # optparser.arg_help = (('hypervisor', vm.hypervisor_help), ('distro', vm.distro_help)) 205
206 - def handle_args(self, optparser, args):
207 if len(args) < 2: 208 optparser.error("You need to specify at least the hypervisor type and the distro") 209 distro = VMBuilder.get_distro(args[1])() 210 hypervisor = VMBuilder.get_hypervisor(args[0])(distro) 211 return hypervisor, distro
212
213 - def set_verbosity(self, option, opt_str, value, parser):
214 if opt_str == '--debug': 215 VMBuilder.set_console_loglevel(logging.DEBUG) 216 elif opt_str == '--verbose': 217 VMBuilder.set_console_loglevel(logging.INFO) 218 elif opt_str == '--quiet': 219 VMBuilder.set_console_loglevel(logging.CRITICAL)
220
221 - def set_disk_layout(self, hypervisor):
222 default_filesystem = hypervisor.distro.preferred_filesystem() 223 if not self.options.part: 224 rootsize = parse_size(self.options.rootsize) 225 swapsize = parse_size(self.options.swapsize) 226 optsize = parse_size(self.options.optsize) 227 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE: 228 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 229 hypervisor.add_filesystem(filename=tmpfile, 230 size='%dM' % rootsize, 231 type='ext3', 232 mntpnt='/') 233 if swapsize > 0: 234 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 235 hypervisor.add_filesystem(filename=tmpfile, 236 size='%dM' % swapsize, 237 type='swap', 238 mntpnt=None) 239 if optsize > 0: 240 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 241 hypervisor.add_filesystem(filename=tmpfile, 242 size='%dM' % optsize, 243 type='ext3', 244 mntpnt='/opt') 245 else: 246 if self.options.raw: 247 for raw_disk in self.options.raw: 248 hypervisor.add_disk(filename=raw_disk) 249 disk = hypervisor.disks[0] 250 else: 251 size = rootsize + swapsize + optsize 252 tmpfile = util.tmp_filename(tmp_root=self.options.tmp_root) 253 disk = hypervisor.add_disk(tmpfile, size='%dM' % size) 254 offset = 0 255 disk.add_part(offset, rootsize, default_filesystem, '/') 256 offset += rootsize 257 if swapsize > 0: 258 disk.add_part(offset, swapsize, 'swap', 'swap') 259 offset += swapsize 260 if optsize > 0: 261 disk.add_part(offset, optsize, default_filesystem, '/opt') 262 else: 263 # We need to parse the file specified 264 if hypervisor.preferred_storage == VMBuilder.hypervisor.STORAGE_FS_IMAGE: 265 try: 266 for line in file(self.options.part): 267 elements = line.strip().split(' ') 268 tmpfile = util.tmp_filename( 269 tmp_root=self.options.tmp_root) 270 if elements[0] == 'root': 271 hypervisor.add_filesystem(elements[1], default_filesystem, filename=tmpfile, mntpnt='/') 272 elif elements[0] == 'swap': 273 hypervisor.add_filesystem(elements[1], type='swap', filename=tmpfile, mntpnt=None) 274 elif elements[0] == '---': 275 # We just ignore the user's attempt to specify multiple disks 276 pass 277 elif len(elements) == 3: 278 hypervisor.add_filesystem(elements[1], type=default_filesystem, filename=tmpfile, mntpnt=elements[0], devletter='', device=elements[2], dummy=(int(elements[1]) == 0)) 279 else: 280 hypervisor.add_filesystem(elements[1], type=default_filesystem, filename=tmpfile, mntpnt=elements[0]) 281 282 except IOError, (errno, strerror): 283 self.optparser.error("%s parsing --part option: %s" % (errno, strerror)) 284 else: 285 try: 286 curdisk = list() 287 size = 0 288 for line in file(self.options.part): 289 pair = line.strip().split(' ',1) 290 if pair[0] == '---': 291 self.do_disk(hypervisor, curdisk, size) 292 curdisk = list() 293 size = 0 294 elif pair[0] != '': 295 logging.debug("part: %s, size: %d" % (pair[0], int(pair[1]))) 296 curdisk.append((pair[0], pair[1])) 297 size += int(pair[1]) 298 299 self.do_disk(hypervisor, curdisk, size) 300 301 except IOError, (errno, strerror): 302 hypervisor.optparser.error("%s parsing --part option: %s" % (errno, strerror))
303
304 - def do_disk(self, hypervisor, curdisk, size):
305 default_filesystem = hypervisor.distro.preferred_filesystem() 306 disk = hypervisor.add_disk(util.tmpfile(keep=False), size+1) 307 if self.options.raw: 308 disk = hypervisor.add_disk(filename=self.options.raw[disk_idx]) 309 else: 310 disk = hypervisor.add_disk( 311 util.tmp_filename(tmp_root=self.options.tmp_root), 312 size+1) 313 logging.debug("do_disk - size: %d" % size) 314 offset = 0 315 for pair in curdisk: 316 logging.debug("do_disk - part: %s, size: %s, offset: %d" % (pair[0], pair[1], offset)) 317 if pair[0] == 'root': 318 disk.add_part(offset, int(pair[1]), default_filesystem, '/') 319 elif pair[0] == 'swap': 320 disk.add_part(offset, int(pair[1]), pair[0], pair[0]) 321 else: 322 disk.add_part(offset, int(pair[1]), default_filesystem, pair[0]) 323 offset += int(pair[1])
324
325 -class UVB(CLI):
326 arg = 'ubuntu-vm-builder' 327
328 - def set_usage(self, optparser):
329 optparser.set_usage('%prog hypervisor suite [options]')
330 # optparser.arg_help = (('hypervisor', vm.hypervisor_help), ('suite', self.suite_help)) 331
332 - def suite_help(self):
333 return 'Suite. Valid options: %s' % " ".join(VMBuilder.plugins.ubuntu.distro.Ubuntu.suites)
334
335 - def handle_args(self, optparser, args):
336 if len(args) < 2: 337 optparser.error("You need to specify at least the hypervisor type and the series") 338 distro = VMBuilder.get_distro('ubuntu')() 339 hypervisor = VMBuilder.get_hypervisor(args[0])(distro) 340 distro.set_setting('suite', args[1]) 341 return hypervisor, distro
342