qubes/tools: port qvm-create
This commit is contained in:
parent
669a976d4e
commit
ff7d89700a
@ -1,80 +1,62 @@
|
||||
.. program:: qvm-create
|
||||
|
||||
=========================================
|
||||
:program:`qvm-create` -- Creates a new VM
|
||||
=========================================
|
||||
:program:`qvm-create` -- create new domain
|
||||
==========================================
|
||||
|
||||
Synopsis
|
||||
========
|
||||
:command:`qvm-create` [*options*] <*vm-name*>
|
||||
--------
|
||||
|
||||
:command:`qvm-create` skel-manpage.py [-h] [--xml *XMLFILE*] [--force-root] [--class *CLS*] [--property *NAME*=*VALUE*] [--template *VALUE*] [--label *VALUE*] [--root-copy-from *FILENAME* | --root-move-from *FILENAME*] *VMNAME*
|
||||
|
||||
Options
|
||||
=======
|
||||
-------
|
||||
|
||||
.. option:: --help, -h
|
||||
|
||||
Show this help message and exit
|
||||
show help message and exit
|
||||
|
||||
.. option:: --template=TEMPLATE, -t TEMPLATE
|
||||
.. option:: --xml=XMLFILE
|
||||
|
||||
Specify the TemplateVM to use
|
||||
|
||||
.. option:: --label=LABEL, -l LABEL
|
||||
|
||||
Specify the label to use for the new VM (e.g. red, yellow, green, ...)
|
||||
|
||||
.. option:: --proxy, -p
|
||||
|
||||
Create ProxyVM
|
||||
|
||||
.. option:: --net, -n
|
||||
|
||||
Create NetVM
|
||||
|
||||
.. option:: --hvm, -H
|
||||
|
||||
Create HVM (standalone, unless :option:`--template` option used)
|
||||
|
||||
.. option:: --hvm-template
|
||||
|
||||
Create HVM template
|
||||
|
||||
.. option:: --root-move-from=ROOT_MOVE, -R ROOT_MOVE
|
||||
|
||||
Use provided root.img instead of default/empty one
|
||||
(file will be *moved*)
|
||||
|
||||
.. option:: --root-copy-from=ROOT_COPY, -r ROOT_COPY
|
||||
|
||||
Use provided root.img instead of default/empty one
|
||||
(file will be *copied*)
|
||||
|
||||
.. option:: --standalone, -s
|
||||
|
||||
Create standalone VM --- independent of template
|
||||
|
||||
.. option:: --mem=MEM, -m MEM
|
||||
|
||||
Initial memory size (in MB)
|
||||
|
||||
.. option:: --vcpus=VCPUS, -c VCPUS
|
||||
|
||||
VCPUs count
|
||||
|
||||
.. option:: --internal, -i
|
||||
|
||||
Create VM for internal use only (hidden in qubes-manager, no appmenus)
|
||||
Qubes OS store file
|
||||
|
||||
.. option:: --force-root
|
||||
|
||||
Force to run, even with root privileges
|
||||
Force to run as root.
|
||||
|
||||
.. option:: --quiet, -q
|
||||
.. option:: --class, -C
|
||||
|
||||
The class of the new domain (default: AppVM).
|
||||
|
||||
.. option:: --prop=NAME=VALUE, --property=NAME=VALUE, -p NAME=VALUE
|
||||
|
||||
Set domain's property, like "internal", "memory" or "vcpus". Any property may
|
||||
be set this way, even "qid".
|
||||
|
||||
.. option:: --template=VALUE, -t VALUE
|
||||
|
||||
Specify the TemplateVM to use, when applicable. This is an alias for
|
||||
``--property template=VALUE``.
|
||||
|
||||
.. option:: --label=VALUE, -l VALUE
|
||||
|
||||
Specify the label to use for the new domain (e.g. red, yellow, green, ...).
|
||||
This in an alias for ``--property label=VALUE``.
|
||||
|
||||
.. option:: --root-copy-from=FILENAME, -r FILENAME
|
||||
|
||||
Use provided root.img instead of default/empty one (file will be *copied*).
|
||||
|
||||
.. option:: --root-move-from=FILENAME, -R FILENAME
|
||||
|
||||
use provided root.img instead of default/empty one (file will be *moved*).
|
||||
|
||||
Be quiet
|
||||
|
||||
Authors
|
||||
=======
|
||||
-------
|
||||
|
||||
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
|
||||
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
|
||||
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
|
||||
| Wojtek Porczyk <woju at invisiblethingslab dot com>
|
||||
|
||||
.. vim: ts=3 sw=3 et tw=80
|
||||
|
@ -54,6 +54,7 @@ endif
|
||||
cp \
|
||||
tools/__init__.py* \
|
||||
tools/qubes_create.py* \
|
||||
tools/qvm_create.py* \
|
||||
tools/qvm_ls.py* \
|
||||
$(DESTDIR)$(PYTHON_QUBESPATH)/tools
|
||||
|
||||
|
@ -439,9 +439,6 @@ class VMCollection(object):
|
||||
raise TypeError('{} holds only BaseVM instances'.format(
|
||||
self.__class__.__name__))
|
||||
|
||||
if not hasattr(value, 'qid'):
|
||||
value.qid = self.get_new_unused_qid()
|
||||
|
||||
if value.qid in self:
|
||||
raise ValueError('This collection already holds VM that has '
|
||||
'qid={!r} ({!r})'.format(value.qid, self[value.qid]))
|
||||
@ -453,6 +450,8 @@ class VMCollection(object):
|
||||
value.events_enabled = True
|
||||
self.app.fire_event('domain-added', value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
@ -1296,12 +1295,41 @@ class Qubes(PropertyHolder):
|
||||
return labels
|
||||
|
||||
|
||||
def add_new_vm(self, vm):
|
||||
def add_new_vm(self, cls, qid=None, **kwargs):
|
||||
'''Add new Virtual Machine to colletion
|
||||
|
||||
'''
|
||||
|
||||
self.domains.add(vm)
|
||||
if qid is None:
|
||||
qid = self.get_new_unused_qid()
|
||||
|
||||
return self.domains.add(cls(self, None, qid=qid, **kwargs))
|
||||
|
||||
|
||||
def get_label(self, label):
|
||||
'''Get label as identified by index or name
|
||||
|
||||
:throws KeyError: when label is not found
|
||||
'''
|
||||
|
||||
# first search for index, verbatim
|
||||
try:
|
||||
return self.labels[label]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# then search for name
|
||||
for l in self.labels.values():
|
||||
if l.name == label:
|
||||
return label
|
||||
|
||||
# last call, if label is a number represented as str, search in indices
|
||||
try:
|
||||
return self.labels[int(label)]
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
raise KeyError(label)
|
||||
|
||||
|
||||
@qubes.events.handler('domain-pre-deleted')
|
||||
|
150
qubes/tools/qvm_create.py
Normal file
150
qubes/tools/qvm_create.py
Normal file
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/python2
|
||||
# -*- encoding: utf8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2010-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
# TODO list available classes
|
||||
# TODO list labels (maybe in qvm-prefs)
|
||||
# TODO services, devices, tags
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import qubes
|
||||
import qubes.tools
|
||||
|
||||
|
||||
parser = qubes.tools.get_parser_base(want_force_root=True)
|
||||
|
||||
parser.add_argument('--class', '-C', dest='cls',
|
||||
default='AppVM',
|
||||
help='specify the class of the new domain (default: %(default)s)')
|
||||
|
||||
parser.add_argument('--property', '--prop', '-p',
|
||||
action=qubes.tools.PropertyAction,
|
||||
help='set domain\'s property, like "internal", "memory" or "vcpus"')
|
||||
|
||||
parser.add_argument('--template', '-t',
|
||||
action=qubes.tools.SinglePropertyAction,
|
||||
help='specify the TemplateVM to use')
|
||||
|
||||
parser.add_argument('--label', '-l',
|
||||
action=qubes.tools.SinglePropertyAction,
|
||||
help='specify the label to use for the new domain'
|
||||
' (e.g. red, yellow, green, ...)')
|
||||
|
||||
parser_root = parser.add_mutually_exclusive_group()
|
||||
parser_root.add_argument('--root-copy-from', '-r', metavar='FILENAME',
|
||||
help='use provided root.img instead of default/empty one'
|
||||
' (file will be COPIED)')
|
||||
parser_root.add_argument('--root-move-from', '-R', metavar='FILENAME',
|
||||
help='use provided root.img instead of default/empty one'
|
||||
' (file will be MOVED)')
|
||||
|
||||
parser.add_argument('name', metavar='VMNAME',
|
||||
action=qubes.tools.SinglePropertyAction,
|
||||
nargs='?',
|
||||
help='name of the domain to create')
|
||||
|
||||
#parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True)
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
qubes.tools.dont_run_as_root(parser, args)
|
||||
|
||||
if 'label' not in args.properties:
|
||||
parser.error('--label option is mandatory')
|
||||
|
||||
if 'name' not in args.properties:
|
||||
parser.error('VMNAME is mandatory')
|
||||
|
||||
app = qubes.Qubes(args.xml)
|
||||
try:
|
||||
label = app.get_label(args.properties['label'])
|
||||
except KeyError:
|
||||
parser.error('no such label: {!r}; available: {}'.format(args.label,
|
||||
', '.join(repr(l.name) for l in app.labels)))
|
||||
|
||||
try:
|
||||
cls = qubes.vm.BaseVM.register[args.cls]
|
||||
except KeyError:
|
||||
parser.error('no such domain class: {!r}'.format(args.cls))
|
||||
|
||||
if 'template' in args.properties \
|
||||
and 'template' not in cls.list_properties():
|
||||
parser.error('this domain class does not support template')
|
||||
|
||||
vm = app.add_new_vm(cls, **args.properties)
|
||||
|
||||
# if not options.standalone and any([options.root_copy_from, options.root_move_from]):
|
||||
# print >> sys.stderr, "root.img can be specified only for standalone VMs"
|
||||
# exit (1)
|
||||
|
||||
# if options.hvm_template and options.template is not None:
|
||||
# print >> sys.stderr, "Template VM cannot be based on another template"
|
||||
# exit (1)
|
||||
|
||||
# if options.root_copy_from is not None and not os.path.exists(options.root_copy_from):
|
||||
# print >> sys.stderr, "File specified as root.img does not exists"
|
||||
# exit (1)
|
||||
|
||||
# if options.root_move_from is not None and not os.path.exists(options.root_move_from):
|
||||
# print >> sys.stderr, "File specified as root.img does not exists"
|
||||
# exit (1)
|
||||
|
||||
# elif not options.hvm and not options.hvm_template:
|
||||
# if qvm_collection.get_default_template() is None:
|
||||
# print >> sys.stderr, "No default TemplateVM defined!"
|
||||
# exit (1)
|
||||
# else:
|
||||
# template = qvm_collection.get_default_template()
|
||||
# if (options.verbose):
|
||||
# print('--> Using default TemplateVM: {0}'.format(template.name))
|
||||
|
||||
try:
|
||||
vm.create_on_disk()
|
||||
|
||||
if args.root_move_from is not None:
|
||||
# if (options.verbose):
|
||||
# print "--> Replacing root.img with provided file"
|
||||
os.unlink(vm.root_img)
|
||||
os.rename(options.root_move_from, vm.root_img)
|
||||
elif args.root_copy_from is not None:
|
||||
# if (options.verbose):
|
||||
# print "--> Replacing root.img with provided file"
|
||||
os.unlink(vm.root_img)
|
||||
# use 'cp' to preserve sparse file
|
||||
subprocess.check_call(['cp', options.root_copy_from, vm.root_img])
|
||||
|
||||
except (IOError, OSError) as err:
|
||||
parser.error(str(err))
|
||||
|
||||
app.save()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(not main())
|
@ -1,205 +1,7 @@
|
||||
#!/usr/bin/python2
|
||||
# -*- encoding: utf8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
#
|
||||
#!/usr/bin/python2 -O
|
||||
# vim: fileencoding=utf-8
|
||||
|
||||
from qubes.qubes import QubesVmCollection
|
||||
from qubes.qubes import QubesVmLabels
|
||||
from qubes.qubes import QubesException
|
||||
from optparse import OptionParser;
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import qubes.tools.qvm_create
|
||||
|
||||
def main():
|
||||
usage = "usage: %prog [options] <vm-name>"
|
||||
parser = OptionParser (usage)
|
||||
parser.add_option ("-t", "--template", dest="template",
|
||||
help="Specify the TemplateVM to use")
|
||||
parser.add_option ("-l", "--label", dest="label",
|
||||
help="Specify the label to use for the new VM (e.g. red, yellow, green, ...)")
|
||||
parser.add_option ("-p", "--proxy", action="store_true", dest="proxyvm", default=False,
|
||||
help="Create ProxyVM")
|
||||
parser.add_option ("-H", "--hvm", action="store_true", dest="hvm", default=False,
|
||||
help="Create HVM (standalone unless --template option used)")
|
||||
parser.add_option ("--hvm-template", action="store_true", dest="hvm_template", default=False,
|
||||
help="Create HVM template")
|
||||
parser.add_option ("-n", "--net", action="store_true", dest="netvm", default=False,
|
||||
help="Create NetVM")
|
||||
parser.add_option ("-s", "--standalone", action="store_true", dest="standalone", default=False,
|
||||
help="Create standalone VM - independent of template ")
|
||||
parser.add_option ("-R", "--root-move-from", dest="root_move", default=None,
|
||||
help="Use provided root.img instead of default/empty one (file will be MOVED)")
|
||||
parser.add_option ("-r", "--root-copy-from", dest="root_copy", default=None,
|
||||
help="Use provided root.img instead of default/empty one (file will be COPIED)")
|
||||
parser.add_option ("-m", "--mem", dest="mem", default=None,
|
||||
help="Initial memory size (in MB)")
|
||||
parser.add_option ("-c", "--vcpus", dest="vcpus", default=None,
|
||||
help="VCPUs count")
|
||||
parser.add_option ("-i", "--internal", action="store_true", dest="internal", default=False,
|
||||
help="Create VM for internal use only (hidden in qubes-manager, no appmenus)")
|
||||
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
|
||||
help="Force to run, even with root privileges")
|
||||
|
||||
parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True)
|
||||
(options, args) = parser.parse_args ()
|
||||
if (len (args) != 1):
|
||||
parser.error ("You must specify VM name!")
|
||||
vmname = args[0]
|
||||
|
||||
if (options.netvm + options.proxyvm + options.hvm + options.hvm_template) > 1:
|
||||
parser.error ("You must specify at most one VM type switch")
|
||||
|
||||
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
||||
print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems."
|
||||
if options.force_root:
|
||||
print >> sys.stderr, "Continuing as commanded. You have been warned."
|
||||
else:
|
||||
print >> sys.stderr, "Retry as unprivileged user."
|
||||
print >> sys.stderr, "... or use --force-root to continue anyway."
|
||||
exit(1)
|
||||
|
||||
if options.label is None:
|
||||
print >> sys.stderr, "You must choose a label for the new VM by passing the --label option."
|
||||
print >> sys.stderr, "Possible values are:"
|
||||
for l in QubesVmLabels.values():
|
||||
print >> sys.stderr, "* {0}".format(l.name)
|
||||
exit (1)
|
||||
|
||||
if options.label not in QubesVmLabels:
|
||||
print >> sys.stderr, "Wrong label name, supported values are the following:"
|
||||
for l in QubesVmLabels.values():
|
||||
print >> sys.stderr, "* {0}".format(l.name)
|
||||
exit (1)
|
||||
label = QubesVmLabels[options.label]
|
||||
|
||||
if options.hvm and not options.template:
|
||||
options.standalone = True
|
||||
|
||||
if options.hvm_template:
|
||||
options.standalone = True
|
||||
|
||||
if not options.standalone and any([options.root_copy, options.root_move]):
|
||||
print >> sys.stderr, "root.img can be specified only for standalone VMs"
|
||||
exit (1)
|
||||
|
||||
if options.hvm_template and options.template is not None:
|
||||
print >> sys.stderr, "Template VM cannot be based on another template"
|
||||
exit (1)
|
||||
|
||||
if options.root_copy and options.root_move:
|
||||
print >> sys.stderr, "Only one of --root-move-from and --root-copy from can be specified"
|
||||
exit(1)
|
||||
|
||||
if options.root_copy is not None and not os.path.exists(options.root_copy):
|
||||
print >> sys.stderr, "File specified as root.img does not exists"
|
||||
exit (1)
|
||||
|
||||
if options.root_move is not None and not os.path.exists(options.root_move):
|
||||
print >> sys.stderr, "File specified as root.img does not exists"
|
||||
exit (1)
|
||||
|
||||
qvm_collection = QubesVmCollection()
|
||||
qvm_collection.lock_db_for_writing()
|
||||
qvm_collection.load()
|
||||
|
||||
if qvm_collection.get_vm_by_name(vmname) is not None:
|
||||
print >> sys.stderr, "A VM with the name '{0}' already exists in the system.".format(vmname)
|
||||
exit(1)
|
||||
|
||||
template = None
|
||||
if options.template is not None:
|
||||
template = qvm_collection.get_vm_by_name(options.template)
|
||||
if template is None:
|
||||
print >> sys.stderr, "There is no (Template)VM with the name '{0}'".format(options.template)
|
||||
exit (1)
|
||||
if not template.is_template():
|
||||
print >> sys.stderr, "VM '{0}' is not a TemplateVM".format(options.template)
|
||||
exit (1)
|
||||
if (options.verbose):
|
||||
print "--> Using TemplateVM: {0}".format(template.name)
|
||||
|
||||
elif not options.hvm and not options.hvm_template:
|
||||
if qvm_collection.get_default_template() is None:
|
||||
print >> sys.stderr, "No default TemplateVM defined!"
|
||||
exit (1)
|
||||
else:
|
||||
template = qvm_collection.get_default_template()
|
||||
if (options.verbose):
|
||||
print "--> Using default TemplateVM: {0}".format(template.name)
|
||||
|
||||
if options.standalone:
|
||||
new_vm_template = None
|
||||
else:
|
||||
new_vm_template = template
|
||||
|
||||
vm = None
|
||||
if options.netvm:
|
||||
vmtype = "QubesNetVm"
|
||||
elif options.proxyvm:
|
||||
vmtype = "QubesProxyVm"
|
||||
elif options.hvm:
|
||||
vmtype = "QubesHVm"
|
||||
elif options.hvm_template:
|
||||
vmtype = "QubesTemplateHVm"
|
||||
else:
|
||||
vmtype = "QubesAppVm"
|
||||
|
||||
try:
|
||||
vm = qvm_collection.add_new_vm(vmtype, name=vmname, template=new_vm_template, label = label)
|
||||
except QubesException as err:
|
||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
||||
exit (1)
|
||||
|
||||
if options.internal:
|
||||
vm.internal = True
|
||||
|
||||
if options.mem is not None:
|
||||
vm.memory = options.mem
|
||||
|
||||
if options.vcpus is not None:
|
||||
vm.vcpus = options.vcpus
|
||||
|
||||
try:
|
||||
vm.create_on_disk(verbose=options.verbose, source_template=template)
|
||||
if options.root_move:
|
||||
if (options.verbose):
|
||||
print "--> Replacing root.img with provided file"
|
||||
os.unlink(vm.root_img)
|
||||
os.rename(options.root_move, vm.root_img)
|
||||
elif options.root_copy:
|
||||
if (options.verbose):
|
||||
print "--> Replacing root.img with provided file"
|
||||
os.unlink(vm.root_img)
|
||||
# use "cp" to preserve sparse file
|
||||
subprocess.check_call(["cp", options.root_copy, vm.root_img])
|
||||
|
||||
except (IOError, OSError) as err:
|
||||
print >> sys.stderr, "ERROR: {0}".format(err)
|
||||
exit (1)
|
||||
|
||||
|
||||
qvm_collection.save()
|
||||
qvm_collection.unlock_db()
|
||||
|
||||
|
||||
main()
|
||||
sys.exit(not qubes.tools.qvm_create.main())
|
||||
|
@ -217,6 +217,7 @@ fi
|
||||
%dir %{python_sitearch}/qubes/tools
|
||||
%{python_sitearch}/qubes/tools/__init__.py*
|
||||
%{python_sitearch}/qubes/tools/qubes_create.py*
|
||||
%{python_sitearch}/qubes/tools/qvm_create.py*
|
||||
%{python_sitearch}/qubes/tools/qvm_ls.py*
|
||||
|
||||
%dir %{python_sitearch}/qubes/ext
|
||||
|
Loading…
Reference in New Issue
Block a user