diff -Nru curtin-19.2-735-g12e4123/curtin/util.py curtin-19.2-736-g9bc9136/curtin/util.py --- curtin-19.2-735-g12e4123/curtin/util.py 2019-07-17 20:31:41.000000000 +0000 +++ curtin-19.2-736-g9bc9136/curtin/util.py 2019-07-23 17:31:46.000000000 +0000 @@ -638,6 +638,7 @@ self.allow_daemons = allow_daemons self.sys_resolvconf = sys_resolvconf self.rconf_d = None + self.rc_tmp = None def __enter__(self): for p in self.mounts: @@ -656,14 +657,20 @@ rtd = None try: rtd = tempfile.mkdtemp(dir=target_etc) - tmp = os.path.join(rtd, "resolv.conf") - os.rename(rconf, tmp) + if os.path.lexists(rconf): + self.rc_tmp = os.path.join(rtd, "resolv.conf") + os.rename(rconf, self.rc_tmp) self.rconf_d = rtd shutil.copy("/etc/resolv.conf", rconf) except Exception: if rtd: + # if we renamed, but failed later we need to restore + if self.rc_tmp and os.path.lexists(self.rc_tmp): + os.rename(os.path.join(self.rconf_d, "resolv.conf"), + rconf) shutil.rmtree(rtd) self.rconf_d = None + self.rc_tmp = None raise return self @@ -681,7 +688,8 @@ rconf = paths.target_path(self.target, "/etc/resolv.conf") if self.sys_resolvconf and self.rconf_d: - os.rename(os.path.join(self.rconf_d, "resolv.conf"), rconf) + if self.rc_tmp and os.path.lexists(self.rc_tmp): + os.rename(os.path.join(self.rconf_d, "resolv.conf"), rconf) shutil.rmtree(self.rconf_d) def subp(self, *args, **kwargs): diff -Nru curtin-19.2-735-g12e4123/debian/changelog curtin-19.2-736-g9bc9136/debian/changelog --- curtin-19.2-735-g12e4123/debian/changelog 2019-07-17 20:31:42.000000000 +0000 +++ curtin-19.2-736-g9bc9136/debian/changelog 2019-07-23 17:31:47.000000000 +0000 @@ -1,8 +1,8 @@ -curtin (19.2-735-g12e4123-0ubuntu1+251~trunk~ubuntu18.10.1) cosmic; urgency=low +curtin (19.2-736-g9bc9136-0ubuntu1+251~trunk~ubuntu18.10.1) cosmic; urgency=low * Auto build. - -- Launchpad Package Builder Wed, 17 Jul 2019 20:31:42 +0000 + -- Launchpad Package Builder Tue, 23 Jul 2019 17:31:47 +0000 curtin (19.1-7-g37a7a0f4-0ubuntu1~18.10.1) cosmic; urgency=medium diff -Nru curtin-19.2-735-g12e4123/debian/git-build-recipe.manifest curtin-19.2-736-g9bc9136/debian/git-build-recipe.manifest --- curtin-19.2-735-g12e4123/debian/git-build-recipe.manifest 2019-07-17 20:31:42.000000000 +0000 +++ curtin-19.2-736-g9bc9136/debian/git-build-recipe.manifest 2019-07-23 17:31:47.000000000 +0000 @@ -1,3 +1,3 @@ -# git-build-recipe format 0.4 deb-version 19.2-735-g12e4123-0ubuntu1+251~trunk -lp:curtin git-commit:12e412335b248b4a4a88556ff13b5dbafefba154 +# git-build-recipe format 0.4 deb-version 19.2-736-g9bc9136-0ubuntu1+251~trunk +lp:curtin git-commit:9bc91364ca9ba5cf6fa618ac1f10d397873588ba merge ubuntu-pkg lp:curtin git-commit:f964495628aec19bb9287ead95d248c1822c2ef1 diff -Nru curtin-19.2-735-g12e4123/tests/unittests/test_util.py curtin-19.2-736-g9bc9136/tests/unittests/test_util.py --- curtin-19.2-735-g12e4123/tests/unittests/test_util.py 2019-07-17 20:31:41.000000000 +0000 +++ curtin-19.2-736-g9bc9136/tests/unittests/test_util.py 2019-07-23 17:31:46.000000000 +0000 @@ -604,6 +604,84 @@ self.assertEqual(sorted(my_mounts), sorted(in_chroot.mounts)) +class TestChrootableTargetResolvConf(CiTestCase): + """Test ChrootableTargets handles target /etc/resolv.conf gracefully""" + + def setUp(self): + super(TestChrootableTargetResolvConf, self).setUp() + self.target = self.tmp_dir() + self.rconf = os.path.join(self.target, 'etc/resolv.conf') + os.makedirs(os.path.dirname(self.rconf)) + self.add_patch('curtin.util.shutil', 'm_shutil') + self.add_patch('curtin.util.do_mount', 'm_do_mount') + self.add_patch('curtin.util.do_umount', 'm_do_umount') + self.host_content = "host_resolvconf" + + def tearDown(self): + # manually remove resolv.conf since we're patching shutil + if os.path.exists(self.rconf): + os.unlink(self.rconf) + + def mycopy(self, src, dst): + print('mycopy(src=%s dst=%s)' % (src, dst)) + util.write_file(dst, self.host_content) + + def mydel(self, tdir): + print('mydel(tdir=%s)' % tdir) + print(os.listdir(tdir)) + + def test_chrootable_target_renames_and_copies_resolvconf(self): + content = "target_resolvconf" + util.write_file(os.path.join(self.target, 'etc/resolv.conf'), content) + + self.m_shutil.copy.side_effect = self.mycopy + self.m_shutil.rmtree.side_effect = self.mydel + + with util.ChrootableTarget(self.target): + target_conf = util.load_file( + paths.target_path(self.target, path='/etc/resolv.conf')) + self.assertEqual(self.host_content, target_conf) + + def test_chrootable_target_renames_and_copies_resolvconf_if_symlink(self): + target_rconf = os.path.join(self.target, 'etc/resolv.conf') + os.symlink('../run/foobar/wark.conf', target_rconf) + + self.m_shutil.copy.side_effect = self.mycopy + self.m_shutil.rmtree.side_effect = self.mydel + with util.ChrootableTarget(self.target): + target_conf = util.load_file( + paths.target_path(self.target, path='/etc/resolv.conf')) + self.assertEqual(self.host_content, target_conf) + + @mock.patch('curtin.util.os.rename') + def test_skip_rename_resolvconf_gone(self, m_rename): + self.m_shutil.copy.side_effect = self.mycopy + self.m_shutil.rmtree.side_effect = self.mydel + with util.ChrootableTarget(self.target): + tp = paths.target_path(self.target, path='/etc/resolv.conf') + target_conf = util.load_file(tp) + self.assertEqual(self.host_content, target_conf) + + self.assertEqual(0, m_rename.call_count) + + def test_chrootable_target_restores_resolvconf_on_copy_fail(self): + content = "target_resolvconf" + tconf = os.path.join(self.target, 'etc/resolv.conf') + util.write_file(tconf, content) + + self.m_shutil.copy.side_effect = OSError('Failed to copy') + self.m_shutil.rmtree.side_effect = self.mydel + + try: + with util.ChrootableTarget(self.target): + pass + except OSError: + pass + + target_conf = util.load_file(tconf) + self.assertEqual(content, target_conf) + + class TestLoadFile(CiTestCase): """Test utility 'load_file'"""