Преглед изворни кода

Remove the nested shell wrapper from the `apt-get` command

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu пре 3 недеља
родитељ
комит
9960a8978a
2 измењених фајлова са 57 додато и 4 уклоњено
  1. 16 2
      coriolis/osmorphing/debian.py
  2. 41 2
      coriolis/tests/osmorphing/test_debian.py

+ 16 - 2
coriolis/osmorphing/debian.py

@@ -33,6 +33,21 @@ class BaseDebianMorphingTools(base.BaseLinuxOSMorphingTools):
 
     netplan_base = "etc/netplan"
 
+    def __init__(self, conn, os_root_dir, os_root_dev, hypervisor,
+                 event_manager, detected_os_info, osmorphing_parameters,
+                 operation_timeout=None):
+        super(BaseDebianMorphingTools, self).__init__(
+            conn, os_root_dir, os_root_dev, hypervisor, event_manager,
+            detected_os_info, osmorphing_parameters, operation_timeout)
+
+        # NOTE: every dpkg invocation may run maintainer scripts which prompt
+        # through debconf (e.g. asking for a keyboard layout, or acknowledging
+        # a pending kernel upgrade), which would hang the OSMorphing operation
+        # indefinitely. Both the install and the uninstall paths are affected,
+        # so the frontend is declared non-interactive for all the commands run
+        # by these tools instead of being set on individual operations.
+        self.set_environment({'DEBIAN_FRONTEND': 'noninteractive'})
+
     @classmethod
     def check_os_supported(cls, detected_os_info):
         if detected_os_info['distribution_name'] != (
@@ -242,9 +257,8 @@ class BaseDebianMorphingTools(base.BaseLinuxOSMorphingTools):
             self._exec_cmd_chroot(deb_reconfigure_cmd)
 
             apt_get_cmd = (
-                '/bin/bash -c "DEBIAN_FRONTEND=noninteractive '
                 'apt-get install %s -y '
-                '-o Dpkg::Options::=\'--force-confdef\'"' % (
+                '-o Dpkg::Options::=--force-confdef' % (
                     " ".join(package_names)))
             self._exec_cmd_chroot(apt_get_cmd)
         except Exception as err:

+ 41 - 2
coriolis/tests/osmorphing/test_debian.py

@@ -48,6 +48,24 @@ class BaseDebianMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
 
         self.assertFalse(result)
 
+    def test_init_declares_noninteractive_frontend(self):
+        """dpkg maintainer scripts must never prompt through debconf."""
+        self.assertEqual(
+            'noninteractive', self.morpher._environment['DEBIAN_FRONTEND'])
+
+    def test_noninteractive_frontend_survives_set_environment(self):
+        """The OSMount environment must not drop the constructor's variables.
+
+        'set_environment' is called by the OSMorphing manager right after the
+        tools are instantiated, with the environment of the OSMount tools.
+        """
+        self.morpher.set_environment({'http_proxy': 'http://10.0.0.1:3128'})
+
+        self.assertEqual(
+            {'DEBIAN_FRONTEND': 'noninteractive',
+             'http_proxy': 'http://10.0.0.1:3128'},
+            self.morpher._environment)
+
     @mock.patch.object(
         debian.BaseDebianMorphingTools, '_schedule_grub2_update')
     @mock.patch('coriolis.utils.Grub2ConfigEditor')
@@ -346,9 +364,8 @@ class BaseDebianMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
         self.morpher.install_packages(self.package_names)
 
         apt_get_cmd = (
-            '/bin/bash -c "DEBIAN_FRONTEND=noninteractive '
             'apt-get install %s -y '
-            '-o Dpkg::Options::=\'--force-confdef\'"' % (
+            '-o Dpkg::Options::=--force-confdef' % (
                 " ".join(self.package_names)))
         deb_reconfigure_cmd = "dpkg --configure --force-confold -a"
 
@@ -373,6 +390,28 @@ class BaseDebianMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
             mock.call('apt-get remove %s -y || true' % self.package_names[1])
         ])
 
+    @ddt.data('install_packages', 'uninstall_packages')
+    @mock.patch.object(base.utils, 'exec_ssh_cmd_chroot')
+    def test_packages_operations_environment(
+            self, operation, mock_exec_ssh_cmd_chroot):
+        """Both package operations run with the environment of the tools.
+
+        Removing a package runs maintainer scripts just like installing one
+        does, so both must be shielded from debconf prompts, and both must
+        reach the package manager with the proxy configuration.
+        """
+        self.morpher.set_environment({'http_proxy': 'http://10.0.0.1:3128'})
+
+        getattr(self.morpher, operation)(self.package_names)
+
+        expected_environment = {
+            'DEBIAN_FRONTEND': 'noninteractive',
+            'http_proxy': 'http://10.0.0.1:3128'}
+        mock_exec_ssh_cmd_chroot.assert_called()
+        for call in mock_exec_ssh_cmd_chroot.call_args_list:
+            self.assertEqual(
+                expected_environment, call.kwargs['environment'])
+
     @mock.patch.object(debian.BaseDebianMorphingTools, '_exec_cmd_chroot')
     def test_uninstall_packages_with_exception(self, mock_exec_cmd_chroot):
         mock_exec_cmd_chroot.side_effect = exception.CoriolisException()