Browse Source

Copy application menu on VM clone

The qubesd daemon have no information about clone source - from that
side it looks like a new VM.  This means application menu is created as
for a new VM.
To fix this re-initialize menu with --source option as part of the clone
operation. It will copy both list of available applications (if
applicable) and selected applications.

This fixes both qvm-clone case and rename.

Fixes QubesOS/qubes-issues#3902
Fixes QubesOS/qubes-issues#4124
Marek Marczykowski-Górecki 5 years ago
parent
commit
67897e3f9f
3 changed files with 43 additions and 2 deletions
  1. 20 0
      qubesadmin/app.py
  2. 15 0
      qubesadmin/tests/app.py
  3. 8 2
      qubesadmin/tests/tools/qvm_create.py

+ 20 - 0
qubesadmin/app.py

@@ -385,6 +385,26 @@ class QubesBase(qubesadmin.base.PropertyHolder):
                 if not ignore_errors:
                     raise
 
+            try:
+                # FIXME: convert to qrexec calls to dom0/GUI VM
+                appmenus_cmd = \
+                    ['qvm-appmenus', '--init', '--update',
+                        '--source', src_vm.name, dst_vm.name]
+                subprocess.check_output(appmenus_cmd, stderr=subprocess.STDOUT)
+            except OSError:
+                # this file needs to be python 2.7 compatible,
+                # so no FileNotFoundError
+                self.log.error('Failed to clone appmenus, qvm-appmenus missing')
+                if not ignore_errors:
+                    raise qubesadmin.exc.QubesException(
+                        'Failed to clone appmenus')
+            except subprocess.CalledProcessError as e:
+                self.log.error('Failed to clone appmenus: %s',
+                    e.output.decode())
+                if not ignore_errors:
+                    raise qubesadmin.exc.QubesException(
+                        'Failed to clone appmenus')
+
         except qubesadmin.exc.QubesException:
             if not ignore_errors:
                 del self.domains[dst_vm.name]

+ 15 - 0
qubesadmin/tests/app.py

@@ -135,6 +135,16 @@ class TC_00_VMCollection(qubesadmin.tests.QubesTestCase):
 
 
 class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
+    def setUp(self):
+        super(TC_10_QubesBase, self).setUp()
+        self.check_output_patch = mock.patch(
+            'subprocess.check_output')
+        self.check_output_mock = self.check_output_patch.start()
+
+    def tearDown(self):
+        self.check_output_patch.stop()
+        super(TC_10_QubesBase, self).tearDown()
+
     def test_010_new_simple(self):
         self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM', None,
                 b'name=new-vm label=red')] = b'0\x00'
@@ -355,6 +365,11 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
             'test-template', b'name=new-name label=red')] = b'0\x00'
         new_vm = self.app.clone_vm('test-vm', 'new-name')
         self.assertEqual(new_vm.name, 'new-name')
+        self.check_output_mock.assert_called_once_with(
+            ['qvm-appmenus', '--init', '--update',
+                '--source', 'test-vm', 'new-name'],
+            stderr=subprocess.STDOUT
+        )
         self.assertAllCalled()
 
     def test_031_clone_object(self):

+ 8 - 2
qubesadmin/tests/tools/qvm_create.py

@@ -19,6 +19,8 @@
 # with this program; if not, see <http://www.gnu.org/licenses/>.
 import os
 import tempfile
+import unittest.mock
+import subprocess
 
 import qubesadmin.tests
 import qubesadmin.tests.tools
@@ -210,7 +212,8 @@ class TC_00_qvm_create(qubesadmin.tests.QubesTestCase):
             self.assertAllCalled()
             self.assertTrue(os.path.exists(root_file.name))
 
-    def test_011_standalonevm(self):
+    @unittest.mock.patch('subprocess.check_output')
+    def test_011_standalonevm(self, check_output_mock):
         self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
             b'0\x00red\nblue\n'
         self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
@@ -310,6 +313,10 @@ class TC_00_qvm_create(qubesadmin.tests.QubesTestCase):
         qubesadmin.tools.qvm_create.main(['-C', 'StandaloneVM',
             '-t', 'template', '-l', 'red', 'new-vm'],
             app=self.app)
+        check_output_mock.assert_called_once_with(
+            ['qvm-appmenus', '--init', '--update',
+                '--source', 'template', 'new-vm'],
+            stderr=subprocess.STDOUT)
         self.assertAllCalled()
 
     def test_012_invalid_label(self):
@@ -321,4 +328,3 @@ class TC_00_qvm_create(qubesadmin.tests.QubesTestCase):
                     app=self.app)
         self.assertIn('red, blue', stderr.getvalue())
         self.assertAllCalled()
-