Browse Source

qubes/tools: port qvm-create

Wojtek Porczyk 9 years ago
parent
commit
ff7d89700a
6 changed files with 220 additions and 256 deletions
  1. 31 49
      doc/manpages/qvm-create.rst
  2. 1 0
      qubes/Makefile
  3. 33 5
      qubes/__init__.py
  4. 150 0
      qubes/tools/qvm_create.py
  5. 4 202
      qvm-tools/qvm-create
  6. 1 0
      rpm_spec/core-dom0.spec

+ 31 - 49
doc/manpages/qvm-create.rst

@@ -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
-
-.. option:: --template=TEMPLATE, -t TEMPLATE
-
-    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
+   show help message and exit
 
-    Create NetVM
+.. option:: --xml=XMLFILE
 
-.. option:: --hvm, -H
+   Qubes OS store file
 
-    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:: --force-root
 
-.. option:: --root-copy-from=ROOT_COPY, -r ROOT_COPY
+   Force to run as root.
 
-    Use provided root.img instead of default/empty one
-    (file will be *copied*)
+.. option:: --class, -C
 
-.. option:: --standalone, -s
+   The class of the new domain (default: AppVM).
 
-    Create standalone VM --- independent of template
+.. option:: --prop=NAME=VALUE, --property=NAME=VALUE, -p NAME=VALUE
 
-.. option:: --mem=MEM, -m MEM
+   Set domain's property, like "internal", "memory" or "vcpus". Any property may
+   be set this way, even "qid".
 
-    Initial memory size (in MB)
+.. option:: --template=VALUE, -t VALUE
 
-.. option:: --vcpus=VCPUS, -c VCPUS
+   Specify the TemplateVM to use, when applicable. This is an alias for
+   ``--property template=VALUE``.
 
-    VCPUs count
+.. option:: --label=VALUE, -l VALUE
 
-.. option:: --internal, -i
+   Specify the label to use for the new domain (e.g. red, yellow, green, ...).
+   This in an alias for ``--property label=VALUE``.
 
-    Create VM for internal use only (hidden in qubes-manager, no appmenus)
+.. option:: --root-copy-from=FILENAME, -r FILENAME
 
-.. option:: --force-root
+   Use provided root.img instead of default/empty one (file will be *copied*).
 
-    Force to run, even with root privileges
+.. option:: --root-move-from=FILENAME, -R FILENAME
 
-.. option:: --quiet, -q
+   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

+ 1 - 0
qubes/Makefile

@@ -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
 

+ 33 - 5
qubes/__init__.py

@@ -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 - 0
qubes/tools/qvm_create.py

@@ -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())

+ 4 - 202
qvm-tools/qvm-create

@@ -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())

+ 1 - 0
rpm_spec/core-dom0.spec

@@ -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