diff options
Diffstat (limited to 'tests/unittests/config')
48 files changed, 6070 insertions, 4302 deletions
diff --git a/tests/unittests/config/test_apt_conf_v1.py b/tests/unittests/config/test_apt_conf_v1.py index 98d99945..5a75cf0a 100644 --- a/tests/unittests/config/test_apt_conf_v1.py +++ b/tests/unittests/config/test_apt_conf_v1.py @@ -1,16 +1,15 @@ # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit.config import cc_apt_configure -from cloudinit import util - -from tests.unittests.helpers import TestCase - import copy import os import re import shutil import tempfile +from cloudinit import util +from cloudinit.config import cc_apt_configure +from tests.unittests.helpers import TestCase + class TestAptProxyConfig(TestCase): def setUp(self): @@ -23,10 +22,12 @@ class TestAptProxyConfig(TestCase): def _search_apt_config(self, contents, ptype, value): return re.search( r"acquire::%s::proxy\s+[\"']%s[\"'];\n" % (ptype, value), - contents, flags=re.IGNORECASE) + contents, + flags=re.IGNORECASE, + ) def test_apt_proxy_written(self): - cfg = {'proxy': 'myproxy'} + cfg = {"proxy": "myproxy"} cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) self.assertTrue(os.path.isfile(self.pfile)) @@ -36,7 +37,7 @@ class TestAptProxyConfig(TestCase): self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) def test_apt_http_proxy_written(self): - cfg = {'http_proxy': 'myproxy'} + cfg = {"http_proxy": "myproxy"} cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) self.assertTrue(os.path.isfile(self.pfile)) @@ -46,14 +47,17 @@ class TestAptProxyConfig(TestCase): self.assertTrue(self._search_apt_config(contents, "http", "myproxy")) def test_apt_all_proxy_written(self): - cfg = {'http_proxy': 'myproxy_http_proxy', - 'https_proxy': 'myproxy_https_proxy', - 'ftp_proxy': 'myproxy_ftp_proxy'} - - values = {'http': cfg['http_proxy'], - 'https': cfg['https_proxy'], - 'ftp': cfg['ftp_proxy'], - } + cfg = { + "http_proxy": "myproxy_http_proxy", + "https_proxy": "myproxy_https_proxy", + "ftp_proxy": "myproxy_ftp_proxy", + } + + values = { + "http": cfg["http_proxy"], + "https": cfg["https_proxy"], + "ftp": cfg["ftp_proxy"], + } cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) @@ -73,15 +77,16 @@ class TestAptProxyConfig(TestCase): def test_proxy_replaced(self): util.write_file(self.cfile, "content doesnt matter") - cc_apt_configure.apply_apt_config({'proxy': "foo"}, - self.pfile, self.cfile) + cc_apt_configure.apply_apt_config( + {"proxy": "foo"}, self.pfile, self.cfile + ) self.assertTrue(os.path.isfile(self.pfile)) contents = util.load_file(self.pfile) self.assertTrue(self._search_apt_config(contents, "http", "foo")) def test_config_written(self): - payload = 'this is my apt config' - cfg = {'conf': payload} + payload = "this is my apt config" + cfg = {"conf": payload} cc_apt_configure.apply_apt_config(cfg, self.pfile, self.cfile) @@ -92,8 +97,9 @@ class TestAptProxyConfig(TestCase): def test_config_replaced(self): util.write_file(self.pfile, "content doesnt matter") - cc_apt_configure.apply_apt_config({'conf': "foo"}, - self.pfile, self.cfile) + cc_apt_configure.apply_apt_config( + {"conf": "foo"}, self.pfile, self.cfile + ) self.assertTrue(os.path.isfile(self.cfile)) self.assertEqual(util.load_file(self.cfile), "foo") @@ -109,21 +115,23 @@ class TestConversion(TestCase): def test_convert_with_apt_mirror_as_empty_string(self): # an empty apt_mirror is the same as no apt_mirror empty_m_found = cc_apt_configure.convert_to_v3_apt_format( - {'apt_mirror': ''}) + {"apt_mirror": ""} + ) default_found = cc_apt_configure.convert_to_v3_apt_format({}) self.assertEqual(default_found, empty_m_found) def test_convert_with_apt_mirror(self): - mirror = 'http://my.mirror/ubuntu' - f = cc_apt_configure.convert_to_v3_apt_format({'apt_mirror': mirror}) - self.assertIn(mirror, set(m['uri'] for m in f['apt']['primary'])) + mirror = "http://my.mirror/ubuntu" + f = cc_apt_configure.convert_to_v3_apt_format({"apt_mirror": mirror}) + self.assertIn(mirror, set(m["uri"] for m in f["apt"]["primary"])) def test_no_old_content(self): - mirror = 'http://my.mirror/ubuntu' - mydata = {'apt': {'primary': {'arches': ['default'], 'uri': mirror}}} + mirror = "http://my.mirror/ubuntu" + mydata = {"apt": {"primary": {"arches": ["default"], "uri": mirror}}} expected = copy.deepcopy(mydata) - self.assertEqual(expected, - cc_apt_configure.convert_to_v3_apt_format(mydata)) + self.assertEqual( + expected, cc_apt_configure.convert_to_v3_apt_format(mydata) + ) # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_apt_configure_sources_list_v1.py b/tests/unittests/config/test_apt_configure_sources_list_v1.py index 4aeaea24..d4ade106 100644 --- a/tests/unittests/config/test_apt_configure_sources_list_v1.py +++ b/tests/unittests/config/test_apt_configure_sources_list_v1.py @@ -9,14 +9,9 @@ import shutil import tempfile from unittest import mock -from cloudinit import templater -from cloudinit import subp -from cloudinit import util - +from cloudinit import subp, templater, util from cloudinit.config import cc_apt_configure - from cloudinit.distros.debian import Distro - from tests.unittests import helpers as t_help from tests.unittests.util import get_cloud @@ -41,8 +36,7 @@ apt_custom_sources_list: | # FIND_SOMETHING_SPECIAL """ -EXPECTED_CONVERTED_CONTENT = ( - """## Note, this file is written by cloud-init on first boot of an instance +EXPECTED_CONVERTED_CONTENT = """## Note, this file is written by cloud-init on first boot of an instance ## modifications made here will not survive a re-bundle. ## if you wish to make changes you can: ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg @@ -55,13 +49,14 @@ EXPECTED_CONVERTED_CONTENT = ( deb http://archive.ubuntu.com/ubuntu/ fakerelease main restricted deb-src http://archive.ubuntu.com/ubuntu/ fakerelease main restricted # FIND_SOMETHING_SPECIAL -""") +""" class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """TestAptSourceConfigSourceList Main Class to test sources list rendering """ + def setUp(self): super(TestAptSourceConfigSourceList, self).setUp() self.subp = subp.subp @@ -70,11 +65,11 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): rpatcher = mock.patch("cloudinit.util.lsb_release") get_rel = rpatcher.start() - get_rel.return_value = {'codename': "fakerelease"} + get_rel.return_value = {"codename": "fakerelease"} self.addCleanup(rpatcher.stop) apatcher = mock.patch("cloudinit.util.get_dpkg_architecture") get_arch = apatcher.start() - get_arch.return_value = 'amd64' + get_arch.return_value = "amd64" self.addCleanup(apatcher.stop) def apt_source_list(self, distro, mirror, mirrorcheck=None): @@ -85,47 +80,57 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): mirrorcheck = mirror if isinstance(mirror, list): - cfg = {'apt_mirror_search': mirror} + cfg = {"apt_mirror_search": mirror} else: - cfg = {'apt_mirror': mirror} + cfg = {"apt_mirror": mirror} mycloud = get_cloud(distro) - with mock.patch.object(util, 'write_file') as mockwf: - with mock.patch.object(util, 'load_file', - return_value="faketmpl") as mocklf: - with mock.patch.object(os.path, 'isfile', - return_value=True) as mockisfile: + with mock.patch.object(util, "write_file") as mockwf: + with mock.patch.object( + util, "load_file", return_value="faketmpl" + ) as mocklf: + with mock.patch.object( + os.path, "isfile", return_value=True + ) as mockisfile: with mock.patch.object( - templater, 'render_string', - return_value='fake') as mockrnd: - with mock.patch.object(util, 'rename'): - cc_apt_configure.handle("test", cfg, mycloud, - LOG, None) + templater, "render_string", return_value="fake" + ) as mockrnd: + with mock.patch.object(util, "rename"): + cc_apt_configure.handle( + "test", cfg, mycloud, LOG, None + ) mockisfile.assert_any_call( - ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) + "/etc/cloud/templates/sources.list.%s.tmpl" % distro + ) mocklf.assert_any_call( - ('/etc/cloud/templates/sources.list.%s.tmpl' % distro)) - mockrnd.assert_called_once_with('faketmpl', - {'RELEASE': 'fakerelease', - 'PRIMARY': mirrorcheck, - 'MIRROR': mirrorcheck, - 'SECURITY': mirrorcheck, - 'codename': 'fakerelease', - 'primary': mirrorcheck, - 'mirror': mirrorcheck, - 'security': mirrorcheck}) - mockwf.assert_called_once_with('/etc/apt/sources.list', 'fake', - mode=0o644) + "/etc/cloud/templates/sources.list.%s.tmpl" % distro + ) + mockrnd.assert_called_once_with( + "faketmpl", + { + "RELEASE": "fakerelease", + "PRIMARY": mirrorcheck, + "MIRROR": mirrorcheck, + "SECURITY": mirrorcheck, + "codename": "fakerelease", + "primary": mirrorcheck, + "mirror": mirrorcheck, + "security": mirrorcheck, + }, + ) + mockwf.assert_called_once_with( + "/etc/apt/sources.list", "fake", mode=0o644 + ) def test_apt_v1_source_list_debian(self): """Test rendering of a source.list from template for debian""" - self.apt_source_list('debian', 'http://httpredir.debian.org/debian') + self.apt_source_list("debian", "http://httpredir.debian.org/debian") def test_apt_v1_source_list_ubuntu(self): """Test rendering of a source.list from template for ubuntu""" - self.apt_source_list('ubuntu', 'http://archive.ubuntu.com/ubuntu/') + self.apt_source_list("ubuntu", "http://archive.ubuntu.com/ubuntu/") @staticmethod def myresolve(name): @@ -139,23 +144,30 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): def test_apt_v1_srcl_debian_mirrorfail(self): """Test rendering of a source.list from template for debian""" - with mock.patch.object(util, 'is_resolvable', - side_effect=self.myresolve) as mockresolve: - self.apt_source_list('debian', - ['http://does.not.exist', - 'http://httpredir.debian.org/debian'], - 'http://httpredir.debian.org/debian') + with mock.patch.object( + util, "is_resolvable", side_effect=self.myresolve + ) as mockresolve: + self.apt_source_list( + "debian", + [ + "http://does.not.exist", + "http://httpredir.debian.org/debian", + ], + "http://httpredir.debian.org/debian", + ) mockresolve.assert_any_call("does.not.exist") mockresolve.assert_any_call("httpredir.debian.org") def test_apt_v1_srcl_ubuntu_mirrorfail(self): """Test rendering of a source.list from template for ubuntu""" - with mock.patch.object(util, 'is_resolvable', - side_effect=self.myresolve) as mockresolve: - self.apt_source_list('ubuntu', - ['http://does.not.exist', - 'http://archive.ubuntu.com/ubuntu/'], - 'http://archive.ubuntu.com/ubuntu/') + with mock.patch.object( + util, "is_resolvable", side_effect=self.myresolve + ) as mockresolve: + self.apt_source_list( + "ubuntu", + ["http://does.not.exist", "http://archive.ubuntu.com/ubuntu/"], + "http://archive.ubuntu.com/ubuntu/", + ) mockresolve.assert_any_call("does.not.exist") mockresolve.assert_any_call("archive.ubuntu.com") @@ -165,17 +177,18 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): mycloud = get_cloud() # the second mock restores the original subp - with mock.patch.object(util, 'write_file') as mockwrite: - with mock.patch.object(subp, 'subp', self.subp): - with mock.patch.object(Distro, 'get_primary_arch', - return_value='amd64'): - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) + with mock.patch.object(util, "write_file") as mockwrite: + with mock.patch.object(subp, "subp", self.subp): + with mock.patch.object( + Distro, "get_primary_arch", return_value="amd64" + ): + cc_apt_configure.handle( + "notimportant", cfg, mycloud, LOG, None + ) mockwrite.assert_called_once_with( - '/etc/apt/sources.list', - EXPECTED_CONVERTED_CONTENT, - mode=420) + "/etc/apt/sources.list", EXPECTED_CONVERTED_CONTENT, mode=420 + ) # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_apt_configure_sources_list_v3.py b/tests/unittests/config/test_apt_configure_sources_list_v3.py index a8087bd1..d9ec6f74 100644 --- a/tests/unittests/config/test_apt_configure_sources_list_v3.py +++ b/tests/unittests/config/test_apt_configure_sources_list_v3.py @@ -3,20 +3,18 @@ """ test_apt_custom_sources_list Test templating of custom sources list """ -from contextlib import ExitStack import logging import os import shutil import tempfile +from contextlib import ExitStack from unittest import mock from unittest.mock import call -from cloudinit import subp -from cloudinit import util +from cloudinit import subp, util from cloudinit.config import cc_apt_configure from cloudinit.distros.debian import Distro from tests.unittests import helpers as t_help - from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -65,30 +63,31 @@ deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted """ -EXPECTED_BASE_CONTENT = (""" +EXPECTED_BASE_CONTENT = """ deb http://test.ubuntu.com/ubuntu/ notouched main restricted deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted -""") +""" -EXPECTED_MIRROR_CONTENT = (""" +EXPECTED_MIRROR_CONTENT = """ deb http://test.ubuntu.com/ubuntu/ notouched main restricted deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted deb http://test.ubuntu.com/ubuntu/ notouched-security main restricted -""") +""" -EXPECTED_PRIMSEC_CONTENT = (""" +EXPECTED_PRIMSEC_CONTENT = """ deb http://test.ubuntu.com/ubuntu/ notouched main restricted deb-src http://test.ubuntu.com/ubuntu/ notouched main restricted deb http://test.ubuntu.com/ubuntu/ notouched-updates main restricted deb http://testsec.ubuntu.com/ubuntu/ notouched-security main restricted -""") +""" class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): """TestAptSourceConfigSourceList - Class to test sources list rendering""" + def setUp(self): super(TestAptSourceConfigSourceList, self).setUp() self.subp = subp.subp @@ -97,33 +96,39 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): rpatcher = mock.patch("cloudinit.util.lsb_release") get_rel = rpatcher.start() - get_rel.return_value = {'codename': "fakerel"} + get_rel.return_value = {"codename": "fakerel"} self.addCleanup(rpatcher.stop) apatcher = mock.patch("cloudinit.util.get_dpkg_architecture") get_arch = apatcher.start() - get_arch.return_value = 'amd64' + get_arch.return_value = "amd64" self.addCleanup(apatcher.stop) def _apt_source_list(self, distro, cfg, cfg_on_empty=False): """_apt_source_list - Test rendering from template (generic)""" # entry at top level now, wrap in 'apt' key - cfg = {'apt': cfg} + cfg = {"apt": cfg} mycloud = get_cloud(distro) with ExitStack() as stack: - mock_writefile = stack.enter_context(mock.patch.object( - util, 'write_file')) - mock_loadfile = stack.enter_context(mock.patch.object( - util, 'load_file', return_value=MOCKED_APT_SRC_LIST)) - mock_isfile = stack.enter_context(mock.patch.object( - os.path, 'isfile', return_value=True)) - stack.enter_context(mock.patch.object( - util, 'del_file')) - cfg_func = ('cloudinit.config.cc_apt_configure.' - '_should_configure_on_empty_apt') - mock_shouldcfg = stack.enter_context(mock.patch( - cfg_func, return_value=(cfg_on_empty, 'test') - )) + mock_writefile = stack.enter_context( + mock.patch.object(util, "write_file") + ) + mock_loadfile = stack.enter_context( + mock.patch.object( + util, "load_file", return_value=MOCKED_APT_SRC_LIST + ) + ) + mock_isfile = stack.enter_context( + mock.patch.object(os.path, "isfile", return_value=True) + ) + stack.enter_context(mock.patch.object(util, "del_file")) + cfg_func = ( + "cloudinit.config.cc_apt_configure." + "_should_configure_on_empty_apt" + ) + mock_shouldcfg = stack.enter_context( + mock.patch(cfg_func, return_value=(cfg_on_empty, "test")) + ) cc_apt_configure.handle("test", cfg, mycloud, LOG, None) return mock_writefile, mock_loadfile, mock_isfile, mock_shouldcfg @@ -131,15 +136,20 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): def test_apt_v3_source_list_debian(self): """test_apt_v3_source_list_debian - without custom sources or parms""" cfg = {} - distro = 'debian' + distro = "debian" expected = EXPECTED_BASE_CONTENT - mock_writefile, mock_load_file, mock_isfile, mock_shouldcfg = ( - self._apt_source_list(distro, cfg, cfg_on_empty=True)) - - template = '/etc/cloud/templates/sources.list.%s.tmpl' % distro - mock_writefile.assert_called_once_with('/etc/apt/sources.list', - expected, mode=0o644) + ( + mock_writefile, + mock_load_file, + mock_isfile, + mock_shouldcfg, + ) = self._apt_source_list(distro, cfg, cfg_on_empty=True) + + template = "/etc/cloud/templates/sources.list.%s.tmpl" % distro + mock_writefile.assert_called_once_with( + "/etc/apt/sources.list", expected, mode=0o644 + ) mock_load_file.assert_called_with(template) mock_isfile.assert_any_call(template) self.assertEqual(1, mock_shouldcfg.call_count) @@ -147,15 +157,20 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): def test_apt_v3_source_list_ubuntu(self): """test_apt_v3_source_list_ubuntu - without custom sources or parms""" cfg = {} - distro = 'ubuntu' + distro = "ubuntu" expected = EXPECTED_BASE_CONTENT - mock_writefile, mock_load_file, mock_isfile, mock_shouldcfg = ( - self._apt_source_list(distro, cfg, cfg_on_empty=True)) - - template = '/etc/cloud/templates/sources.list.%s.tmpl' % distro - mock_writefile.assert_called_once_with('/etc/apt/sources.list', - expected, mode=0o644) + ( + mock_writefile, + mock_load_file, + mock_isfile, + mock_shouldcfg, + ) = self._apt_source_list(distro, cfg, cfg_on_empty=True) + + template = "/etc/cloud/templates/sources.list.%s.tmpl" % distro + mock_writefile.assert_called_once_with( + "/etc/apt/sources.list", expected, mode=0o644 + ) mock_load_file.assert_called_with(template) mock_isfile.assert_any_call(template) self.assertEqual(1, mock_shouldcfg.call_count) @@ -163,12 +178,13 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): def test_apt_v3_source_list_ubuntu_snappy(self): """test_apt_v3_source_list_ubuntu_snappy - without custom sources or parms""" - cfg = {'apt': {}} + cfg = {"apt": {}} mycloud = get_cloud() - with mock.patch.object(util, 'write_file') as mock_writefile: - with mock.patch.object(util, 'system_is_snappy', - return_value=True) as mock_issnappy: + with mock.patch.object(util, "write_file") as mock_writefile: + with mock.patch.object( + util, "system_is_snappy", return_value=True + ) as mock_issnappy: cc_apt_configure.handle("test", cfg, mycloud, LOG, None) self.assertEqual(0, mock_writefile.call_count) @@ -177,7 +193,7 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): def test_apt_v3_source_list_centos(self): """test_apt_v3_source_list_centos - without custom sources or parms""" cfg = {} - distro = 'rhel' + distro = "rhel" mock_writefile, _, _, _ = self._apt_source_list(distro, cfg) @@ -185,22 +201,24 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): def test_apt_v3_source_list_psm(self): """test_apt_v3_source_list_psm - Test specifying prim+sec mirrors""" - pm = 'http://test.ubuntu.com/ubuntu/' - sm = 'http://testsec.ubuntu.com/ubuntu/' - cfg = {'preserve_sources_list': False, - 'primary': [{'arches': ["default"], - 'uri': pm}], - 'security': [{'arches': ["default"], - 'uri': sm}]} - distro = 'ubuntu' + pm = "http://test.ubuntu.com/ubuntu/" + sm = "http://testsec.ubuntu.com/ubuntu/" + cfg = { + "preserve_sources_list": False, + "primary": [{"arches": ["default"], "uri": pm}], + "security": [{"arches": ["default"], "uri": sm}], + } + distro = "ubuntu" expected = EXPECTED_PRIMSEC_CONTENT - mock_writefile, mock_load_file, mock_isfile, _ = ( - self._apt_source_list(distro, cfg, cfg_on_empty=True)) + mock_writefile, mock_load_file, mock_isfile, _ = self._apt_source_list( + distro, cfg, cfg_on_empty=True + ) - template = '/etc/cloud/templates/sources.list.%s.tmpl' % distro - mock_writefile.assert_called_once_with('/etc/apt/sources.list', - expected, mode=0o644) + template = "/etc/cloud/templates/sources.list.%s.tmpl" % distro + mock_writefile.assert_called_once_with( + "/etc/apt/sources.list", expected, mode=0o644 + ) mock_load_file.assert_called_with(template) mock_isfile.assert_any_call(template) @@ -210,16 +228,20 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase): mycloud = get_cloud() # the second mock restores the original subp - with mock.patch.object(util, 'write_file') as mockwrite: - with mock.patch.object(subp, 'subp', self.subp): - with mock.patch.object(Distro, 'get_primary_arch', - return_value='amd64'): - cc_apt_configure.handle("notimportant", cfg, mycloud, - LOG, None) - - calls = [call('/etc/apt/sources.list', - EXPECTED_CONVERTED_CONTENT, - mode=0o644)] + with mock.patch.object(util, "write_file") as mockwrite: + with mock.patch.object(subp, "subp", self.subp): + with mock.patch.object( + Distro, "get_primary_arch", return_value="amd64" + ): + cc_apt_configure.handle( + "notimportant", cfg, mycloud, LOG, None + ) + + calls = [ + call( + "/etc/apt/sources.list", EXPECTED_CONVERTED_CONTENT, mode=0o644 + ) + ] mockwrite.assert_has_calls(calls) diff --git a/tests/unittests/config/test_apt_key.py b/tests/unittests/config/test_apt_key.py index 00e5a38d..9fcf3039 100644 --- a/tests/unittests/config/test_apt_key.py +++ b/tests/unittests/config/test_apt_key.py @@ -1,11 +1,10 @@ import os from unittest import mock +from cloudinit import subp, util from cloudinit.config import cc_apt_configure -from cloudinit import subp -from cloudinit import util -TEST_KEY_HUMAN = ''' +TEST_KEY_HUMAN = """ /etc/apt/cloud-init.gpg.d/my_key.gpg -------------------------------------------- pub rsa4096 2021-10-22 [SC] @@ -13,9 +12,9 @@ pub rsa4096 2021-10-22 [SC] uid [ unknown] Brett Holman <brett.holman@canonical.com> sub rsa4096 2021-10-22 [A] sub rsa4096 2021-10-22 [E] -''' +""" -TEST_KEY_MACHINE = ''' +TEST_KEY_MACHINE = """ tru::1:1635129362:0:3:1:5 pub:-:4096:1:F83F77129A5EBD85:1634912922:::-:::scESCA::::::23::0: fpr:::::::::3A3EF34DFDEDB3B7F3FDF603F83F77129A5EBD85: @@ -25,113 +24,101 @@ sub:-:4096:1:544B39C9A9141F04:1634912922::::::a::::::23: fpr:::::::::8BD901490D6EC986D03D6F0D544B39C9A9141F04: sub:-:4096:1:F45D9443F0A87092:1634912922::::::e::::::23: fpr:::::::::8CCCB332317324F030A45B19F45D9443F0A87092: -''' +""" -TEST_KEY_FINGERPRINT_HUMAN = \ - '3A3E F34D FDED B3B7 F3FD F603 F83F 7712 9A5E BD85' +TEST_KEY_FINGERPRINT_HUMAN = ( + "3A3E F34D FDED B3B7 F3FD F603 F83F 7712 9A5E BD85" +) -TEST_KEY_FINGERPRINT_MACHINE = \ - '3A3EF34DFDEDB3B7F3FDF603F83F77129A5EBD85' +TEST_KEY_FINGERPRINT_MACHINE = "3A3EF34DFDEDB3B7F3FDF603F83F77129A5EBD85" class TestAptKey: """TestAptKey Class to test apt-key commands """ - @mock.patch.object(subp, 'subp', return_value=('fakekey', '')) - @mock.patch.object(util, 'write_file') + + @mock.patch.object(subp, "subp", return_value=("fakekey", "")) + @mock.patch.object(util, "write_file") def _apt_key_add_success_helper(self, directory, *args, hardened=False): file = cc_apt_configure.apt_key( - 'add', - output_file='my-key', - data='fakekey', - hardened=hardened) - assert file == directory + '/my-key.gpg' + "add", output_file="my-key", data="fakekey", hardened=hardened + ) + assert file == directory + "/my-key.gpg" def test_apt_key_add_success(self): - """Verify the correct directory path gets returned for unhardened case - """ - self._apt_key_add_success_helper('/etc/apt/trusted.gpg.d') + """Verify the right directory path gets returned for unhardened case""" + self._apt_key_add_success_helper("/etc/apt/trusted.gpg.d") def test_apt_key_add_success_hardened(self): - """Verify the correct directory path gets returned for hardened case - """ + """Verify the right directory path gets returned for hardened case""" self._apt_key_add_success_helper( - '/etc/apt/cloud-init.gpg.d', - hardened=True) + "/etc/apt/cloud-init.gpg.d", hardened=True + ) def test_apt_key_add_fail_no_file_name(self): - """Verify that null filename gets handled correctly - """ - file = cc_apt_configure.apt_key( - 'add', - output_file=None, - data='') - assert '/dev/null' == file + """Verify that null filename gets handled correctly""" + file = cc_apt_configure.apt_key("add", output_file=None, data="") + assert "/dev/null" == file def _apt_key_fail_helper(self): file = cc_apt_configure.apt_key( - 'add', - output_file='my-key', - data='fakekey') - assert file == '/dev/null' + "add", output_file="my-key", data="fakekey" + ) + assert file == "/dev/null" - @mock.patch.object(subp, 'subp', side_effect=subp.ProcessExecutionError) + @mock.patch.object(subp, "subp", side_effect=subp.ProcessExecutionError) def test_apt_key_add_fail_no_file_name_subproc(self, *args): - """Verify that bad key value gets handled correctly - """ + """Verify that bad key value gets handled correctly""" self._apt_key_fail_helper() @mock.patch.object( - subp, 'subp', side_effect=UnicodeDecodeError('test', b'', 1, 1, '')) + subp, "subp", side_effect=UnicodeDecodeError("test", b"", 1, 1, "") + ) def test_apt_key_add_fail_no_file_name_unicode(self, *args): - """Verify that bad key encoding gets handled correctly - """ + """Verify that bad key encoding gets handled correctly""" self._apt_key_fail_helper() def _apt_key_list_success_helper(self, finger, key, human_output=True): - @mock.patch.object(os, 'listdir', return_value=('/fake/dir/key.gpg',)) - @mock.patch.object(subp, 'subp', return_value=(key, '')) + @mock.patch.object(os, "listdir", return_value=("/fake/dir/key.gpg",)) + @mock.patch.object(subp, "subp", return_value=(key, "")) def mocked_list(*a): - keys = cc_apt_configure.apt_key('list', human_output) + keys = cc_apt_configure.apt_key("list", human_output) assert finger in keys + mocked_list() def test_apt_key_list_success_human(self): - """Verify expected key output, human - """ + """Verify expected key output, human""" self._apt_key_list_success_helper( - TEST_KEY_FINGERPRINT_HUMAN, - TEST_KEY_HUMAN) + TEST_KEY_FINGERPRINT_HUMAN, TEST_KEY_HUMAN + ) def test_apt_key_list_success_machine(self): - """Verify expected key output, machine - """ + """Verify expected key output, machine""" self._apt_key_list_success_helper( - TEST_KEY_FINGERPRINT_MACHINE, - TEST_KEY_MACHINE, human_output=False) + TEST_KEY_FINGERPRINT_MACHINE, TEST_KEY_MACHINE, human_output=False + ) - @mock.patch.object(os, 'listdir', return_value=()) - @mock.patch.object(subp, 'subp', return_value=('', '')) + @mock.patch.object(os, "listdir", return_value=()) + @mock.patch.object(subp, "subp", return_value=("", "")) def test_apt_key_list_fail_no_keys(self, *args): - """Ensure falsy output for no keys - """ - keys = cc_apt_configure.apt_key('list') + """Ensure falsy output for no keys""" + keys = cc_apt_configure.apt_key("list") assert not keys - @mock.patch.object(os, 'listdir', return_value=('file_not_gpg_key.txt')) - @mock.patch.object(subp, 'subp', return_value=('', '')) + @mock.patch.object(os, "listdir", return_value="file_not_gpg_key.txt") + @mock.patch.object(subp, "subp", return_value=("", "")) def test_apt_key_list_fail_no_keys_file(self, *args): """Ensure non-gpg file is not returned. apt-key used file extensions for this, so we do too """ - assert not cc_apt_configure.apt_key('list') + assert not cc_apt_configure.apt_key("list") - @mock.patch.object(subp, 'subp', side_effect=subp.ProcessExecutionError) - @mock.patch.object(os, 'listdir', return_value=('bad_gpg_key.gpg')) + @mock.patch.object(subp, "subp", side_effect=subp.ProcessExecutionError) + @mock.patch.object(os, "listdir", return_value="bad_gpg_key.gpg") def test_apt_key_list_fail_bad_key_file(self, *args): - """Ensure bad gpg key doesn't throw exeption. - """ - assert not cc_apt_configure.apt_key('list') + """Ensure bad gpg key doesn't throw exeption.""" + assert not cc_apt_configure.apt_key("list") diff --git a/tests/unittests/config/test_apt_source_v1.py b/tests/unittests/config/test_apt_source_v1.py index 684c2495..fbc2bf45 100644 --- a/tests/unittests/config/test_apt_source_v1.py +++ b/tests/unittests/config/test_apt_source_v1.py @@ -6,18 +6,15 @@ This calls all things with v1 format to stress the conversion code on top of the actually tested code. """ import os +import pathlib import re import shutil import tempfile -import pathlib from unittest import mock from unittest.mock import call +from cloudinit import gpg, subp, util from cloudinit.config import cc_apt_configure -from cloudinit import gpg -from cloudinit import subp -from cloudinit import util - from tests.unittests.helpers import TestCase EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -39,6 +36,7 @@ ADD_APT_REPO_MATCH = r"^[\w-]+:\w" class FakeDistro(object): """Fake Distro helper object""" + def update_package_sources(self): """Fake update_package_sources helper method""" return @@ -46,12 +44,14 @@ class FakeDistro(object): class FakeDatasource: """Fake Datasource helper object""" + def __init__(self): - self.region = 'region' + self.region = "region" class FakeCloud(object): """Fake Cloud helper object""" + def __init__(self): self.distro = FakeDistro() self.datasource = FakeDatasource() @@ -61,6 +61,7 @@ class TestAptSourceConfig(TestCase): """TestAptSourceConfig Main Class to test apt_source configs """ + release = "fantastic" def setUp(self): @@ -73,18 +74,19 @@ class TestAptSourceConfig(TestCase): self.join = os.path.join self.matcher = re.compile(ADD_APT_REPO_MATCH).search # mock fallback filename into writable tmp dir - self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", - "cloud_config_sources.list") + self.fallbackfn = os.path.join( + self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list" + ) self.fakecloud = FakeCloud() rpatcher = mock.patch("cloudinit.util.lsb_release") get_rel = rpatcher.start() - get_rel.return_value = {'codename': self.release} + get_rel.return_value = {"codename": self.release} self.addCleanup(rpatcher.stop) apatcher = mock.patch("cloudinit.util.get_dpkg_architecture") get_arch = apatcher.start() - get_arch.return_value = 'amd64' + get_arch.return_value = "amd64" self.addCleanup(apatcher.stop) def _get_default_params(self): @@ -92,23 +94,27 @@ class TestAptSourceConfig(TestCase): Get the most basic default mrror and release info to be used in tests """ params = {} - params['RELEASE'] = self.release - params['MIRROR'] = "http://archive.ubuntu.com/ubuntu" + params["RELEASE"] = self.release + params["MIRROR"] = "http://archive.ubuntu.com/ubuntu" return params def wrapv1conf(self, cfg): params = self._get_default_params() # old v1 list format under old keys, but callabe to main handler # disable source.list rendering and set mirror to avoid other code - return {'apt_preserve_sources_list': True, - 'apt_mirror': params['MIRROR'], - 'apt_sources': cfg} + return { + "apt_preserve_sources_list": True, + "apt_mirror": params["MIRROR"], + "apt_sources": cfg, + } def myjoin(self, *args, **kwargs): """myjoin - redir into writable tmpdir""" - if (args[0] == "/etc/apt/sources.list.d/" and - args[1] == "cloud_config_sources.list" and - len(args) == 2): + if ( + args[0] == "/etc/apt/sources.list.d/" + and args[1] == "cloud_config_sources.list" + and len(args) == 2 + ): return self.join(self.tmp, args[0].lstrip("/"), args[1]) else: return self.join(*args, **kwargs) @@ -124,26 +130,43 @@ class TestAptSourceConfig(TestCase): self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://archive.ubuntu.com/ubuntu", - "karmic-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://archive.ubuntu.com/ubuntu", + "karmic-backports", + "main universe multiverse restricted", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_basic(self): """Test deb source string, overwrite mirror and filename""" - cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted'), - 'filename': self.aptlistfile} + cfg = { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ), + "filename": self.aptlistfile, + } self.apt_src_basic(self.aptlistfile, [cfg]) def test_apt_src_basic_dict(self): """Test deb source string, overwrite mirror and filename (dict)""" - cfg = {self.aptlistfile: {'source': - ('deb http://archive.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted')}} + cfg = { + self.aptlistfile: { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ) + } + } self.apt_src_basic(self.aptlistfile, cfg) def apt_src_basic_tri(self, cfg): @@ -156,56 +179,99 @@ class TestAptSourceConfig(TestCase): # extra verify on two extra files of this test contents = util.load_file(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://archive.ubuntu.com/ubuntu", - "precise-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://archive.ubuntu.com/ubuntu", + "precise-backports", + "main universe multiverse restricted", + ), + contents, + flags=re.IGNORECASE, + ) + ) contents = util.load_file(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://archive.ubuntu.com/ubuntu", - "lucid-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://archive.ubuntu.com/ubuntu", + "lucid-backports", + "main universe multiverse restricted", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_basic_tri(self): """Test Fix three deb source string with filenames""" - cfg1 = {'source': ('deb http://archive.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted'), - 'filename': self.aptlistfile} - cfg2 = {'source': ('deb http://archive.ubuntu.com/ubuntu' - ' precise-backports' - ' main universe multiverse restricted'), - 'filename': self.aptlistfile2} - cfg3 = {'source': ('deb http://archive.ubuntu.com/ubuntu' - ' lucid-backports' - ' main universe multiverse restricted'), - 'filename': self.aptlistfile3} + cfg1 = { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ), + "filename": self.aptlistfile, + } + cfg2 = { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " precise-backports" + " main universe multiverse restricted" + ), + "filename": self.aptlistfile2, + } + cfg3 = { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " lucid-backports" + " main universe multiverse restricted" + ), + "filename": self.aptlistfile3, + } self.apt_src_basic_tri([cfg1, cfg2, cfg3]) def test_apt_src_basic_dict_tri(self): """Test Fix three deb source string with filenames (dict)""" - cfg = {self.aptlistfile: {'source': - ('deb http://archive.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted')}, - self.aptlistfile2: {'source': - ('deb http://archive.ubuntu.com/ubuntu' - ' precise-backports' - ' main universe multiverse restricted')}, - self.aptlistfile3: {'source': - ('deb http://archive.ubuntu.com/ubuntu' - ' lucid-backports' - ' main universe multiverse restricted')}} + cfg = { + self.aptlistfile: { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ) + }, + self.aptlistfile2: { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " precise-backports" + " main universe multiverse restricted" + ) + }, + self.aptlistfile3: { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " lucid-backports" + " main universe multiverse restricted" + ) + }, + } self.apt_src_basic_tri(cfg) def test_apt_src_basic_nofn(self): """Test Fix three deb source string without filenames (dict)""" - cfg = {'source': ('deb http://archive.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted')} - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + cfg = { + "source": ( + "deb http://archive.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ) + } + with mock.patch.object(os.path, "join", side_effect=self.myjoin): self.apt_src_basic(self.fallbackfn, [cfg]) def apt_src_replacement(self, filename, cfg): @@ -219,15 +285,21 @@ class TestAptSourceConfig(TestCase): self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", params['MIRROR'], params['RELEASE'], - "multiverse"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ("deb", params["MIRROR"], params["RELEASE"], "multiverse"), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_replace(self): """Test Autoreplacement of MIRROR and RELEASE in source specs""" - cfg = {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile} + cfg = { + "source": "deb $MIRROR $RELEASE multiverse", + "filename": self.aptlistfile, + } self.apt_src_replacement(self.aptlistfile, [cfg]) def apt_src_replace_tri(self, cfg): @@ -240,38 +312,56 @@ class TestAptSourceConfig(TestCase): # extra verify on two extra files of this test params = self._get_default_params() contents = util.load_file(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", params['MIRROR'], params['RELEASE'], - "main"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ("deb", params["MIRROR"], params["RELEASE"], "main"), + contents, + flags=re.IGNORECASE, + ) + ) contents = util.load_file(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", params['MIRROR'], params['RELEASE'], - "universe"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ("deb", params["MIRROR"], params["RELEASE"], "universe"), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_replace_tri(self): """Test triple Autoreplacement of MIRROR and RELEASE in source specs""" - cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile} - cfg2 = {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2} - cfg3 = {'source': 'deb $MIRROR $RELEASE universe', - 'filename': self.aptlistfile3} + cfg1 = { + "source": "deb $MIRROR $RELEASE multiverse", + "filename": self.aptlistfile, + } + cfg2 = { + "source": "deb $MIRROR $RELEASE main", + "filename": self.aptlistfile2, + } + cfg3 = { + "source": "deb $MIRROR $RELEASE universe", + "filename": self.aptlistfile3, + } self.apt_src_replace_tri([cfg1, cfg2, cfg3]) def test_apt_src_replace_dict_tri(self): """Test triple Autoreplacement in source specs (dict)""" - cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, - 'notused': {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2}, - self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} + cfg = { + self.aptlistfile: {"source": "deb $MIRROR $RELEASE multiverse"}, + "notused": { + "source": "deb $MIRROR $RELEASE main", + "filename": self.aptlistfile2, + }, + self.aptlistfile3: {"source": "deb $MIRROR $RELEASE universe"}, + } self.apt_src_replace_tri(cfg) def test_apt_src_replace_nofn(self): """Test Autoreplacement of MIRROR and RELEASE in source specs nofile""" - cfg = {'source': 'deb $MIRROR $RELEASE multiverse'} - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + cfg = {"source": "deb $MIRROR $RELEASE multiverse"} + with mock.patch.object(os.path, "join", side_effect=self.myjoin): self.apt_src_replacement(self.fallbackfn, [cfg]) def apt_src_keyid(self, filename, cfg, keynum): @@ -280,12 +370,12 @@ class TestAptSourceConfig(TestCase): """ cfg = self.wrapv1conf(cfg) - with mock.patch.object(cc_apt_configure, 'add_apt_key') as mockobj: + with mock.patch.object(cc_apt_configure, "add_apt_key") as mockobj: cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) # check if it added the right number of keys calls = [] - sources = cfg['apt']['sources'] + sources = cfg["apt"]["sources"] for src in sources: print(sources[src]) calls.append(call(sources[src], None)) @@ -295,68 +385,109 @@ class TestAptSourceConfig(TestCase): self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "main"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "main", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_keyid(self): """Test specification of a source + keyid with filename being set""" - cfg = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'keyid': "03683F77", - 'filename': self.aptlistfile} + cfg = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "keyid": "03683F77", + "filename": self.aptlistfile, + } self.apt_src_keyid(self.aptlistfile, [cfg], 1) def test_apt_src_keyid_tri(self): """Test 3x specification of a source + keyid with filename being set""" - cfg1 = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'keyid': "03683F77", - 'filename': self.aptlistfile} - cfg2 = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial universe'), - 'keyid': "03683F77", - 'filename': self.aptlistfile2} - cfg3 = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial multiverse'), - 'keyid': "03683F77", - 'filename': self.aptlistfile3} + cfg1 = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "keyid": "03683F77", + "filename": self.aptlistfile, + } + cfg2 = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial universe" + ), + "keyid": "03683F77", + "filename": self.aptlistfile2, + } + cfg3 = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial multiverse" + ), + "keyid": "03683F77", + "filename": self.aptlistfile3, + } self.apt_src_keyid(self.aptlistfile, [cfg1, cfg2, cfg3], 3) contents = util.load_file(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "universe"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "universe", + ), + contents, + flags=re.IGNORECASE, + ) + ) contents = util.load_file(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "multiverse"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "multiverse", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_keyid_nofn(self): """Test specification of a source + keyid without filename being set""" - cfg = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'keyid': "03683F77"} - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + cfg = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "keyid": "03683F77", + } + with mock.patch.object(os.path, "join", side_effect=self.myjoin): self.apt_src_keyid(self.fallbackfn, [cfg], 1) def apt_src_key(self, filename, cfg): @@ -365,11 +496,11 @@ class TestAptSourceConfig(TestCase): """ cfg = self.wrapv1conf([cfg]) - with mock.patch.object(cc_apt_configure, 'add_apt_key') as mockobj: + with mock.patch.object(cc_apt_configure, "add_apt_key") as mockobj: cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) # check if it added the right amount of keys - sources = cfg['apt']['sources'] + sources = cfg["apt"]["sources"] calls = [] for src in sources: print(sources[src]) @@ -380,46 +511,63 @@ class TestAptSourceConfig(TestCase): self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "main"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "main", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_src_key(self): """Test specification of a source + key with filename being set""" - cfg = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'key': "fakekey 4321", - 'filename': self.aptlistfile} + cfg = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "key": "fakekey 4321", + "filename": self.aptlistfile, + } self.apt_src_key(self.aptlistfile, cfg) def test_apt_src_key_nofn(self): """Test specification of a source + key without filename being set""" - cfg = {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'key': "fakekey 4321"} - with mock.patch.object(os.path, 'join', side_effect=self.myjoin): + cfg = { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "key": "fakekey 4321", + } + with mock.patch.object(os.path, "join", side_effect=self.myjoin): self.apt_src_key(self.fallbackfn, cfg) def test_apt_src_keyonly(self): """Test specifying key without source""" - cfg = {'key': "fakekey 4242", - 'filename': self.aptlistfile} + cfg = {"key": "fakekey 4242", "filename": self.aptlistfile} cfg = self.wrapv1conf([cfg]) - with mock.patch.object(cc_apt_configure, 'apt_key') as mockobj: + with mock.patch.object(cc_apt_configure, "apt_key") as mockobj: cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) - calls = (call( - 'add', - output_file=pathlib.Path(self.aptlistfile).stem, - data='fakekey 4242', - hardened=False),) + calls = ( + call( + "add", + output_file=pathlib.Path(self.aptlistfile).stem, + data="fakekey 4242", + hardened=False, + ), + ) mockobj.assert_has_calls(calls, any_order=True) # filename should be ignored on key only @@ -427,25 +575,25 @@ class TestAptSourceConfig(TestCase): def test_apt_src_keyidonly(self): """Test specification of a keyid without source""" - cfg = {'keyid': "03683F77", - 'filename': self.aptlistfile} + cfg = {"keyid": "03683F77", "filename": self.aptlistfile} cfg = self.wrapv1conf([cfg]) - with mock.patch.object(subp, 'subp', - return_value=('fakekey 1212', '')): - with mock.patch.object(cc_apt_configure, 'apt_key') as mockobj: + with mock.patch.object( + subp, "subp", return_value=("fakekey 1212", "") + ): + with mock.patch.object(cc_apt_configure, "apt_key") as mockobj: cc_apt_configure.handle( - "test", - cfg, - self.fakecloud, - None, - None) - - calls = (call( - 'add', - output_file=pathlib.Path(self.aptlistfile).stem, - data='fakekey 1212', - hardened=False),) + "test", cfg, self.fakecloud, None, None + ) + + calls = ( + call( + "add", + output_file=pathlib.Path(self.aptlistfile).stem, + data="fakekey 1212", + hardened=False, + ), + ) mockobj.assert_has_calls(calls, any_order=True) # filename should be ignored on key only @@ -457,20 +605,21 @@ class TestAptSourceConfig(TestCase): up to addition of the key (add_apt_key_raw mocked to keep the environment as is) """ - key = cfg['keyid'] - keyserver = cfg.get('keyserver', 'keyserver.ubuntu.com') + key = cfg["keyid"] + keyserver = cfg.get("keyserver", "keyserver.ubuntu.com") cfg = self.wrapv1conf([cfg]) - with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(gpg, 'getkeybyid', - return_value=expectedkey) as mockgetkey: - cc_apt_configure.handle("test", cfg, self.fakecloud, - None, None) + with mock.patch.object(cc_apt_configure, "add_apt_key_raw") as mockkey: + with mock.patch.object( + gpg, "getkeybyid", return_value=expectedkey + ) as mockgetkey: + cc_apt_configure.handle( + "test", cfg, self.fakecloud, None, None + ) if is_hardened is not None: mockkey.assert_called_with( - expectedkey, - self.aptlistfile, - hardened=is_hardened) + expectedkey, self.aptlistfile, hardened=is_hardened + ) else: mockkey.assert_called_with(expectedkey, self.aptlistfile) mockgetkey.assert_called_with(key, keyserver) @@ -481,62 +630,77 @@ class TestAptSourceConfig(TestCase): def test_apt_src_keyid_real(self): """test_apt_src_keyid_real - Test keyid including key add""" keyid = "03683F77" - cfg = {'keyid': keyid, - 'filename': self.aptlistfile} + cfg = {"keyid": keyid, "filename": self.aptlistfile} self.apt_src_keyid_real(cfg, EXPECTEDKEY, is_hardened=False) def test_apt_src_longkeyid_real(self): """test_apt_src_longkeyid_real - Test long keyid including key add""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" - cfg = {'keyid': keyid, - 'filename': self.aptlistfile} + cfg = {"keyid": keyid, "filename": self.aptlistfile} self.apt_src_keyid_real(cfg, EXPECTEDKEY, is_hardened=False) def test_apt_src_longkeyid_ks_real(self): """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" - cfg = {'keyid': keyid, - 'keyserver': 'keys.gnupg.net', - 'filename': self.aptlistfile} + cfg = { + "keyid": keyid, + "keyserver": "keys.gnupg.net", + "filename": self.aptlistfile, + } self.apt_src_keyid_real(cfg, EXPECTEDKEY, is_hardened=False) def test_apt_src_ppa(self): """Test adding a ppa""" - cfg = {'source': 'ppa:smoser/cloud-init-test', - 'filename': self.aptlistfile} + cfg = { + "source": "ppa:smoser/cloud-init-test", + "filename": self.aptlistfile, + } cfg = self.wrapv1conf([cfg]) - with mock.patch.object(subp, 'subp') as mockobj: + with mock.patch.object(subp, "subp") as mockobj: cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) - mockobj.assert_called_once_with(['add-apt-repository', - 'ppa:smoser/cloud-init-test'], - target=None) + mockobj.assert_called_once_with( + ["add-apt-repository", "ppa:smoser/cloud-init-test"], target=None + ) # adding ppa should ignore filename (uses add-apt-repository) self.assertFalse(os.path.isfile(self.aptlistfile)) def test_apt_src_ppa_tri(self): """Test adding three ppa's""" - cfg1 = {'source': 'ppa:smoser/cloud-init-test', - 'filename': self.aptlistfile} - cfg2 = {'source': 'ppa:smoser/cloud-init-test2', - 'filename': self.aptlistfile2} - cfg3 = {'source': 'ppa:smoser/cloud-init-test3', - 'filename': self.aptlistfile3} + cfg1 = { + "source": "ppa:smoser/cloud-init-test", + "filename": self.aptlistfile, + } + cfg2 = { + "source": "ppa:smoser/cloud-init-test2", + "filename": self.aptlistfile2, + } + cfg3 = { + "source": "ppa:smoser/cloud-init-test3", + "filename": self.aptlistfile3, + } cfg = self.wrapv1conf([cfg1, cfg2, cfg3]) - with mock.patch.object(subp, 'subp') as mockobj: - cc_apt_configure.handle("test", cfg, self.fakecloud, - None, None) - calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'], - target=None), - call(['add-apt-repository', 'ppa:smoser/cloud-init-test2'], - target=None), - call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'], - target=None)] + with mock.patch.object(subp, "subp") as mockobj: + cc_apt_configure.handle("test", cfg, self.fakecloud, None, None) + calls = [ + call( + ["add-apt-repository", "ppa:smoser/cloud-init-test"], + target=None, + ), + call( + ["add-apt-repository", "ppa:smoser/cloud-init-test2"], + target=None, + ), + call( + ["add-apt-repository", "ppa:smoser/cloud-init-test3"], + target=None, + ), + ] mockobj.assert_has_calls(calls, any_order=True) # adding ppa should ignore all filenames (uses add-apt-repository) @@ -546,43 +710,59 @@ class TestAptSourceConfig(TestCase): def test_convert_to_new_format(self): """Test the conversion of old to new format""" - cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile} - cfg2 = {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2} - cfg3 = {'source': 'deb $MIRROR $RELEASE universe', - 'filename': self.aptlistfile3} - cfg = {'apt_sources': [cfg1, cfg2, cfg3]} - checkcfg = {self.aptlistfile: {'filename': self.aptlistfile, - 'source': 'deb $MIRROR $RELEASE ' - 'multiverse'}, - self.aptlistfile2: {'filename': self.aptlistfile2, - 'source': 'deb $MIRROR $RELEASE main'}, - self.aptlistfile3: {'filename': self.aptlistfile3, - 'source': 'deb $MIRROR $RELEASE ' - 'universe'}} + cfg1 = { + "source": "deb $MIRROR $RELEASE multiverse", + "filename": self.aptlistfile, + } + cfg2 = { + "source": "deb $MIRROR $RELEASE main", + "filename": self.aptlistfile2, + } + cfg3 = { + "source": "deb $MIRROR $RELEASE universe", + "filename": self.aptlistfile3, + } + cfg = {"apt_sources": [cfg1, cfg2, cfg3]} + checkcfg = { + self.aptlistfile: { + "filename": self.aptlistfile, + "source": "deb $MIRROR $RELEASE multiverse", + }, + self.aptlistfile2: { + "filename": self.aptlistfile2, + "source": "deb $MIRROR $RELEASE main", + }, + self.aptlistfile3: { + "filename": self.aptlistfile3, + "source": "deb $MIRROR $RELEASE universe", + }, + } newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg) - self.assertEqual(newcfg['apt']['sources'], checkcfg) + self.assertEqual(newcfg["apt"]["sources"], checkcfg) # convert again, should stay the same newcfg2 = cc_apt_configure.convert_to_v3_apt_format(newcfg) - self.assertEqual(newcfg2['apt']['sources'], checkcfg) + self.assertEqual(newcfg2["apt"]["sources"], checkcfg) # should work without raising an exception cc_apt_configure.convert_to_v3_apt_format({}) with self.assertRaises(ValueError): - cc_apt_configure.convert_to_v3_apt_format({'apt_sources': 5}) + cc_apt_configure.convert_to_v3_apt_format({"apt_sources": 5}) def test_convert_to_new_format_collision(self): """Test the conversion of old to new format with collisions - That matches e.g. the MAAS case specifying old and new config""" - cfg_1_and_3 = {'apt': {'proxy': 'http://192.168.122.1:8000/'}, - 'apt_proxy': 'http://192.168.122.1:8000/'} - cfg_3_only = {'apt': {'proxy': 'http://192.168.122.1:8000/'}} - cfgconflict = {'apt': {'proxy': 'http://192.168.122.1:8000/'}, - 'apt_proxy': 'ftp://192.168.122.1:8000/'} + That matches e.g. the MAAS case specifying old and new config""" + cfg_1_and_3 = { + "apt": {"proxy": "http://192.168.122.1:8000/"}, + "apt_proxy": "http://192.168.122.1:8000/", + } + cfg_3_only = {"apt": {"proxy": "http://192.168.122.1:8000/"}} + cfgconflict = { + "apt": {"proxy": "http://192.168.122.1:8000/"}, + "apt_proxy": "ftp://192.168.122.1:8000/", + } # collision (equal) newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg_1_and_3) @@ -596,22 +776,34 @@ class TestAptSourceConfig(TestCase): cc_apt_configure.convert_to_v3_apt_format(cfgconflict) def test_convert_to_new_format_dict_collision(self): - cfg1 = {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile} - cfg2 = {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2} - cfg3 = {'source': 'deb $MIRROR $RELEASE universe', - 'filename': self.aptlistfile3} - fullv3 = {self.aptlistfile: {'filename': self.aptlistfile, - 'source': 'deb $MIRROR $RELEASE ' - 'multiverse'}, - self.aptlistfile2: {'filename': self.aptlistfile2, - 'source': 'deb $MIRROR $RELEASE main'}, - self.aptlistfile3: {'filename': self.aptlistfile3, - 'source': 'deb $MIRROR $RELEASE ' - 'universe'}} - cfg_3_only = {'apt': {'sources': fullv3}} - cfg_1_and_3 = {'apt_sources': [cfg1, cfg2, cfg3]} + cfg1 = { + "source": "deb $MIRROR $RELEASE multiverse", + "filename": self.aptlistfile, + } + cfg2 = { + "source": "deb $MIRROR $RELEASE main", + "filename": self.aptlistfile2, + } + cfg3 = { + "source": "deb $MIRROR $RELEASE universe", + "filename": self.aptlistfile3, + } + fullv3 = { + self.aptlistfile: { + "filename": self.aptlistfile, + "source": "deb $MIRROR $RELEASE multiverse", + }, + self.aptlistfile2: { + "filename": self.aptlistfile2, + "source": "deb $MIRROR $RELEASE main", + }, + self.aptlistfile3: { + "filename": self.aptlistfile3, + "source": "deb $MIRROR $RELEASE universe", + }, + } + cfg_3_only = {"apt": {"sources": fullv3}} + cfg_1_and_3 = {"apt_sources": [cfg1, cfg2, cfg3]} cfg_1_and_3.update(cfg_3_only) # collision (equal, so ok to remove) @@ -621,27 +813,36 @@ class TestAptSourceConfig(TestCase): newcfg = cc_apt_configure.convert_to_v3_apt_format(cfg_3_only) self.assertEqual(newcfg, cfg_3_only) - diff = {self.aptlistfile: {'filename': self.aptlistfile, - 'source': 'deb $MIRROR $RELEASE ' - 'DIFFERENTVERSE'}, - self.aptlistfile2: {'filename': self.aptlistfile2, - 'source': 'deb $MIRROR $RELEASE main'}, - self.aptlistfile3: {'filename': self.aptlistfile3, - 'source': 'deb $MIRROR $RELEASE ' - 'universe'}} - cfg_3_only = {'apt': {'sources': diff}} - cfg_1_and_3_different = {'apt_sources': [cfg1, cfg2, cfg3]} + diff = { + self.aptlistfile: { + "filename": self.aptlistfile, + "source": "deb $MIRROR $RELEASE DIFFERENTVERSE", + }, + self.aptlistfile2: { + "filename": self.aptlistfile2, + "source": "deb $MIRROR $RELEASE main", + }, + self.aptlistfile3: { + "filename": self.aptlistfile3, + "source": "deb $MIRROR $RELEASE universe", + }, + } + cfg_3_only = {"apt": {"sources": diff}} + cfg_1_and_3_different = {"apt_sources": [cfg1, cfg2, cfg3]} cfg_1_and_3_different.update(cfg_3_only) # collision (unequal by dict having a different entry) with self.assertRaises(ValueError): cc_apt_configure.convert_to_v3_apt_format(cfg_1_and_3_different) - missing = {self.aptlistfile: {'filename': self.aptlistfile, - 'source': 'deb $MIRROR $RELEASE ' - 'multiverse'}} - cfg_3_only = {'apt': {'sources': missing}} - cfg_1_and_3_missing = {'apt_sources': [cfg1, cfg2, cfg3]} + missing = { + self.aptlistfile: { + "filename": self.aptlistfile, + "source": "deb $MIRROR $RELEASE multiverse", + } + } + cfg_3_only = {"apt": {"sources": missing}} + cfg_1_and_3_missing = {"apt_sources": [cfg1, cfg2, cfg3]} cfg_1_and_3_missing.update(cfg_3_only) # collision (unequal by dict missing an entry) with self.assertRaises(ValueError): diff --git a/tests/unittests/config/test_apt_source_v3.py b/tests/unittests/config/test_apt_source_v3.py index 0b78037e..75adc647 100644 --- a/tests/unittests/config/test_apt_source_v3.py +++ b/tests/unittests/config/test_apt_source_v3.py @@ -6,21 +6,17 @@ This tries to call all in the new v3 format and cares about new features """ import glob import os +import pathlib import re import shutil import socket import tempfile -import pathlib - from unittest import TestCase, mock from unittest.mock import call -from cloudinit import gpg -from cloudinit import subp -from cloudinit import util +from cloudinit import gpg, subp, util from cloudinit.config import cc_apt_configure from tests.unittests import helpers as t_help - from tests.unittests.util import get_cloud EXPECTEDKEY = """-----BEGIN PGP PUBLIC KEY BLOCK----- @@ -42,18 +38,23 @@ ADD_APT_REPO_MATCH = r"^[\w-]+:\w" TARGET = None MOCK_LSB_RELEASE_DATA = { - 'id': 'Ubuntu', 'description': 'Ubuntu 18.04.1 LTS', - 'release': '18.04', 'codename': 'bionic'} + "id": "Ubuntu", + "description": "Ubuntu 18.04.1 LTS", + "release": "18.04", + "codename": "bionic", +} class FakeDatasource: """Fake Datasource helper object""" + def __init__(self): - self.region = 'region' + self.region = "region" class FakeCloud: """Fake Cloud helper object""" + def __init__(self): self.datasource = FakeDatasource() @@ -62,6 +63,7 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): """TestAptSourceConfig Main Class to test apt configs """ + def setUp(self): super(TestAptSourceConfig, self).setUp() self.tmp = tempfile.mkdtemp() @@ -74,12 +76,14 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): self.join = os.path.join self.matcher = re.compile(ADD_APT_REPO_MATCH).search self.add_patch( - 'cloudinit.config.cc_apt_configure.util.lsb_release', - 'm_lsb_release', return_value=MOCK_LSB_RELEASE_DATA.copy()) + "cloudinit.config.cc_apt_configure.util.lsb_release", + "m_lsb_release", + return_value=MOCK_LSB_RELEASE_DATA.copy(), + ) @staticmethod def _add_apt_sources(*args, **kwargs): - with mock.patch.object(cc_apt_configure, 'update_packages'): + with mock.patch.object(cc_apt_configure, "update_packages"): cc_apt_configure.add_apt_sources(*args, **kwargs) @staticmethod @@ -88,17 +92,20 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): Get the most basic default mrror and release info to be used in tests """ params = {} - params['RELEASE'] = MOCK_LSB_RELEASE_DATA['release'] - arch = 'amd64' - params['MIRROR'] = cc_apt_configure.\ - get_default_mirrors(arch)["PRIMARY"] + params["RELEASE"] = MOCK_LSB_RELEASE_DATA["release"] + arch = "amd64" + params["MIRROR"] = cc_apt_configure.get_default_mirrors(arch)[ + "PRIMARY" + ] return params def _myjoin(self, *args, **kwargs): """_myjoin - redir into writable tmpdir""" - if (args[0] == "/etc/apt/sources.list.d/" and - args[1] == "cloud_config_sources.list" and - len(args) == 2): + if ( + args[0] == "/etc/apt/sources.list.d/" + and args[1] == "cloud_config_sources.list" + and len(args) == 2 + ): return self.join(self.tmp, args[0].lstrip("/"), args[1]) else: return self.join(*args, **kwargs) @@ -109,81 +116,131 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): """ params = self._get_default_params() - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://test.ubuntu.com/ubuntu", - "karmic-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://test.ubuntu.com/ubuntu", + "karmic-backports", + "main universe multiverse restricted", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_v3_src_basic(self): """test_apt_v3_src_basic - Test fix deb source string""" - cfg = {self.aptlistfile: {'source': - ('deb http://test.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted')}} + cfg = { + self.aptlistfile: { + "source": ( + "deb http://test.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ) + } + } self._apt_src_basic(self.aptlistfile, cfg) def test_apt_v3_src_basic_tri(self): """test_apt_v3_src_basic_tri - Test multiple fix deb source strings""" - cfg = {self.aptlistfile: {'source': - ('deb http://test.ubuntu.com/ubuntu' - ' karmic-backports' - ' main universe multiverse restricted')}, - self.aptlistfile2: {'source': - ('deb http://test.ubuntu.com/ubuntu' - ' precise-backports' - ' main universe multiverse restricted')}, - self.aptlistfile3: {'source': - ('deb http://test.ubuntu.com/ubuntu' - ' lucid-backports' - ' main universe multiverse restricted')}} + cfg = { + self.aptlistfile: { + "source": ( + "deb http://test.ubuntu.com/ubuntu" + " karmic-backports" + " main universe multiverse restricted" + ) + }, + self.aptlistfile2: { + "source": ( + "deb http://test.ubuntu.com/ubuntu" + " precise-backports" + " main universe multiverse restricted" + ) + }, + self.aptlistfile3: { + "source": ( + "deb http://test.ubuntu.com/ubuntu" + " lucid-backports" + " main universe multiverse restricted" + ) + }, + } self._apt_src_basic(self.aptlistfile, cfg) # extra verify on two extra files of this test contents = util.load_file(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://test.ubuntu.com/ubuntu", - "precise-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://test.ubuntu.com/ubuntu", + "precise-backports", + "main universe multiverse restricted", + ), + contents, + flags=re.IGNORECASE, + ) + ) contents = util.load_file(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", "http://test.ubuntu.com/ubuntu", - "lucid-backports", - "main universe multiverse restricted"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://test.ubuntu.com/ubuntu", + "lucid-backports", + "main universe multiverse restricted", + ), + contents, + flags=re.IGNORECASE, + ) + ) def _apt_src_replacement(self, filename, cfg): """apt_src_replace Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", params['MIRROR'], params['RELEASE'], - "multiverse"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ("deb", params["MIRROR"], params["RELEASE"], "multiverse"), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_v3_src_replace(self): """test_apt_v3_src_replace - Test replacement of MIRROR & RELEASE""" - cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}} + cfg = {self.aptlistfile: {"source": "deb $MIRROR $RELEASE multiverse"}} self._apt_src_replacement(self.aptlistfile, cfg) def test_apt_v3_src_replace_fn(self): """test_apt_v3_src_replace_fn - Test filename overwritten in dict""" - cfg = {'ignored': {'source': 'deb $MIRROR $RELEASE multiverse', - 'filename': self.aptlistfile}} + cfg = { + "ignored": { + "source": "deb $MIRROR $RELEASE multiverse", + "filename": self.aptlistfile, + } + } # second file should overwrite the dict key self._apt_src_replacement(self.aptlistfile, cfg) @@ -197,22 +254,34 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): # extra verify on two extra files of this test params = self._get_default_params() contents = util.load_file(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", params['MIRROR'], params['RELEASE'], - "main"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ("deb", params["MIRROR"], params["RELEASE"], "main"), + contents, + flags=re.IGNORECASE, + ) + ) contents = util.load_file(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", params['MIRROR'], params['RELEASE'], - "universe"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ("deb", params["MIRROR"], params["RELEASE"], "universe"), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_v3_src_replace_tri(self): """test_apt_v3_src_replace_tri - Test multiple replace/overwrites""" - cfg = {self.aptlistfile: {'source': 'deb $MIRROR $RELEASE multiverse'}, - 'notused': {'source': 'deb $MIRROR $RELEASE main', - 'filename': self.aptlistfile2}, - self.aptlistfile3: {'source': 'deb $MIRROR $RELEASE universe'}} + cfg = { + self.aptlistfile: {"source": "deb $MIRROR $RELEASE multiverse"}, + "notused": { + "source": "deb $MIRROR $RELEASE main", + "filename": self.aptlistfile2, + }, + self.aptlistfile3: {"source": "deb $MIRROR $RELEASE universe"}, + } self._apt_src_replace_tri(cfg) def _apt_src_keyid(self, filename, cfg, keynum, is_hardened=None): @@ -221,9 +290,10 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): """ params = self._get_default_params() - with mock.patch.object(cc_apt_configure, 'add_apt_key') as mockobj: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) + with mock.patch.object(cc_apt_configure, "add_apt_key") as mockobj: + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) # check if it added the right number of keys calls = [] @@ -238,103 +308,165 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): self.assertTrue(os.path.isfile(filename)) contents = util.load_file(filename) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "main"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "main", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_v3_src_keyid(self): """test_apt_v3_src_keyid - Test source + keyid with filename""" - cfg = {self.aptlistfile: {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'filename': self.aptlistfile, - 'keyid': "03683F77"}} + cfg = { + self.aptlistfile: { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "filename": self.aptlistfile, + "keyid": "03683F77", + } + } self._apt_src_keyid(self.aptlistfile, cfg, 1) def test_apt_v3_src_keyid_tri(self): """test_apt_v3_src_keyid_tri - Test multiple src+key+filen writes""" - cfg = {self.aptlistfile: {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'keyid': "03683F77"}, - 'ignored': {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial universe'), - 'keyid': "03683F77", - 'filename': self.aptlistfile2}, - self.aptlistfile3: {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial multiverse'), - 'filename': self.aptlistfile3, - 'keyid': "03683F77"}} + cfg = { + self.aptlistfile: { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "keyid": "03683F77", + }, + "ignored": { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial universe" + ), + "keyid": "03683F77", + "filename": self.aptlistfile2, + }, + self.aptlistfile3: { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial multiverse" + ), + "filename": self.aptlistfile3, + "keyid": "03683F77", + }, + } self._apt_src_keyid(self.aptlistfile, cfg, 3) contents = util.load_file(self.aptlistfile2) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "universe"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "universe", + ), + contents, + flags=re.IGNORECASE, + ) + ) contents = util.load_file(self.aptlistfile3) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "multiverse"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "multiverse", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_v3_src_key(self): """test_apt_v3_src_key - Test source + key""" params = self._get_default_params() - cfg = {self.aptlistfile: {'source': ('deb ' - 'http://ppa.launchpad.net/' - 'smoser/cloud-init-test/ubuntu' - ' xenial main'), - 'filename': self.aptlistfile, - 'key': "fakekey 4321"}} - - with mock.patch.object(cc_apt_configure, 'apt_key') as mockobj: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) - - calls = (call( - 'add', - output_file=pathlib.Path(self.aptlistfile).stem, - data='fakekey 4321', - hardened=False),) + cfg = { + self.aptlistfile: { + "source": ( + "deb " + "http://ppa.launchpad.net/" + "smoser/cloud-init-test/ubuntu" + " xenial main" + ), + "filename": self.aptlistfile, + "key": "fakekey 4321", + } + } + + with mock.patch.object(cc_apt_configure, "apt_key") as mockobj: + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) + + calls = ( + call( + "add", + output_file=pathlib.Path(self.aptlistfile).stem, + data="fakekey 4321", + hardened=False, + ), + ) mockobj.assert_has_calls(calls, any_order=True) self.assertTrue(os.path.isfile(self.aptlistfile)) contents = util.load_file(self.aptlistfile) - self.assertTrue(re.search(r"%s %s %s %s\n" % - ("deb", - ('http://ppa.launchpad.net/smoser/' - 'cloud-init-test/ubuntu'), - "xenial", "main"), - contents, flags=re.IGNORECASE)) + self.assertTrue( + re.search( + r"%s %s %s %s\n" + % ( + "deb", + "http://ppa.launchpad.net/smoser/cloud-init-test/ubuntu", + "xenial", + "main", + ), + contents, + flags=re.IGNORECASE, + ) + ) def test_apt_v3_src_keyonly(self): """test_apt_v3_src_keyonly - Test key without source""" params = self._get_default_params() - cfg = {self.aptlistfile: {'key': "fakekey 4242"}} - - with mock.patch.object(cc_apt_configure, 'apt_key') as mockobj: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) - - calls = (call( - 'add', - output_file=pathlib.Path(self.aptlistfile).stem, - data='fakekey 4242', - hardened=False),) + cfg = {self.aptlistfile: {"key": "fakekey 4242"}} + + with mock.patch.object(cc_apt_configure, "apt_key") as mockobj: + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) + + calls = ( + call( + "add", + output_file=pathlib.Path(self.aptlistfile).stem, + data="fakekey 4242", + hardened=False, + ), + ) mockobj.assert_has_calls(calls, any_order=True) # filename should be ignored on key only @@ -343,18 +475,26 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): def test_apt_v3_src_keyidonly(self): """test_apt_v3_src_keyidonly - Test keyid without source""" params = self._get_default_params() - cfg = {self.aptlistfile: {'keyid': "03683F77"}} - with mock.patch.object(subp, 'subp', - return_value=('fakekey 1212', '')): - with mock.patch.object(cc_apt_configure, 'apt_key') as mockobj: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) - - calls = (call( - 'add', - output_file=pathlib.Path(self.aptlistfile).stem, - data='fakekey 1212', - hardened=False),) + cfg = {self.aptlistfile: {"keyid": "03683F77"}} + with mock.patch.object( + subp, "subp", return_value=("fakekey 1212", "") + ): + with mock.patch.object(cc_apt_configure, "apt_key") as mockobj: + self._add_apt_sources( + cfg, + TARGET, + template_params=params, + aa_repo_match=self.matcher, + ) + + calls = ( + call( + "add", + output_file=pathlib.Path(self.aptlistfile).stem, + data="fakekey 1212", + hardened=False, + ), + ) mockobj.assert_has_calls(calls, any_order=True) # filename should be ignored on key only @@ -368,21 +508,25 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): """ params = self._get_default_params() - with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(gpg, 'getkeybyid', - return_value=expectedkey) as mockgetkey: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) + with mock.patch.object(cc_apt_configure, "add_apt_key_raw") as mockkey: + with mock.patch.object( + gpg, "getkeybyid", return_value=expectedkey + ) as mockgetkey: + self._add_apt_sources( + cfg, + TARGET, + template_params=params, + aa_repo_match=self.matcher, + ) keycfg = cfg[self.aptlistfile] - mockgetkey.assert_called_with(keycfg['keyid'], - keycfg.get('keyserver', - 'keyserver.ubuntu.com')) + mockgetkey.assert_called_with( + keycfg["keyid"], keycfg.get("keyserver", "keyserver.ubuntu.com") + ) if is_hardened is not None: mockkey.assert_called_with( - expectedkey, - keycfg['keyfile'], - hardened=is_hardened) + expectedkey, keycfg["keyfile"], hardened=is_hardened + ) # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) @@ -390,25 +534,27 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): def test_apt_v3_src_keyid_real(self): """test_apt_v3_src_keyid_real - Test keyid including key add""" keyid = "03683F77" - cfg = {self.aptlistfile: {'keyid': keyid, - 'keyfile': self.aptlistfile}} + cfg = {self.aptlistfile: {"keyid": keyid, "keyfile": self.aptlistfile}} self.apt_src_keyid_real(cfg, EXPECTEDKEY, is_hardened=False) def test_apt_v3_src_longkeyid_real(self): """test_apt_v3_src_longkeyid_real Test long keyid including key add""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" - cfg = {self.aptlistfile: {'keyid': keyid, - 'keyfile': self.aptlistfile}} + cfg = {self.aptlistfile: {"keyid": keyid, "keyfile": self.aptlistfile}} self.apt_src_keyid_real(cfg, EXPECTEDKEY, is_hardened=False) def test_apt_v3_src_longkeyid_ks_real(self): """test_apt_v3_src_longkeyid_ks_real Test long keyid from other ks""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" - cfg = {self.aptlistfile: {'keyid': keyid, - 'keyfile': self.aptlistfile, - 'keyserver': 'keys.gnupg.net'}} + cfg = { + self.aptlistfile: { + "keyid": keyid, + "keyfile": self.aptlistfile, + "keyserver": "keys.gnupg.net", + } + } self.apt_src_keyid_real(cfg, EXPECTEDKEY) @@ -416,21 +562,31 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): """test_apt_v3_src_keyid_keyserver - Test custom keyserver""" keyid = "03683F77" params = self._get_default_params() - cfg = {self.aptlistfile: {'keyid': keyid, - 'keyfile': self.aptlistfile, - 'keyserver': 'test.random.com'}} + cfg = { + self.aptlistfile: { + "keyid": keyid, + "keyfile": self.aptlistfile, + "keyserver": "test.random.com", + } + } # in some test environments only *.ubuntu.com is reachable # so mock the call and check if the config got there - with mock.patch.object(gpg, 'getkeybyid', - return_value="fakekey") as mockgetkey: - with mock.patch.object(cc_apt_configure, - 'add_apt_key_raw') as mockadd: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) - - mockgetkey.assert_called_with('03683F77', 'test.random.com') - mockadd.assert_called_with('fakekey', self.aptlistfile, hardened=False) + with mock.patch.object( + gpg, "getkeybyid", return_value="fakekey" + ) as mockgetkey: + with mock.patch.object( + cc_apt_configure, "add_apt_key_raw" + ) as mockadd: + self._add_apt_sources( + cfg, + TARGET, + template_params=params, + aa_repo_match=self.matcher, + ) + + mockgetkey.assert_called_with("03683F77", "test.random.com") + mockadd.assert_called_with("fakekey", self.aptlistfile, hardened=False) # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) @@ -438,13 +594,15 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): def test_apt_v3_src_ppa(self): """test_apt_v3_src_ppa - Test specification of a ppa""" params = self._get_default_params() - cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'}} + cfg = {self.aptlistfile: {"source": "ppa:smoser/cloud-init-test"}} with mock.patch("cloudinit.subp.subp") as mockobj: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) - mockobj.assert_any_call(['add-apt-repository', - 'ppa:smoser/cloud-init-test'], target=TARGET) + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) + mockobj.assert_any_call( + ["add-apt-repository", "ppa:smoser/cloud-init-test"], target=TARGET + ) # adding ppa should ignore filename (uses add-apt-repository) self.assertFalse(os.path.isfile(self.aptlistfile)) @@ -452,19 +610,30 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): def test_apt_v3_src_ppa_tri(self): """test_apt_v3_src_ppa_tri - Test specification of multiple ppa's""" params = self._get_default_params() - cfg = {self.aptlistfile: {'source': 'ppa:smoser/cloud-init-test'}, - self.aptlistfile2: {'source': 'ppa:smoser/cloud-init-test2'}, - self.aptlistfile3: {'source': 'ppa:smoser/cloud-init-test3'}} + cfg = { + self.aptlistfile: {"source": "ppa:smoser/cloud-init-test"}, + self.aptlistfile2: {"source": "ppa:smoser/cloud-init-test2"}, + self.aptlistfile3: {"source": "ppa:smoser/cloud-init-test3"}, + } with mock.patch("cloudinit.subp.subp") as mockobj: - self._add_apt_sources(cfg, TARGET, template_params=params, - aa_repo_match=self.matcher) - calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test'], - target=TARGET), - call(['add-apt-repository', 'ppa:smoser/cloud-init-test2'], - target=TARGET), - call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'], - target=TARGET)] + self._add_apt_sources( + cfg, TARGET, template_params=params, aa_repo_match=self.matcher + ) + calls = [ + call( + ["add-apt-repository", "ppa:smoser/cloud-init-test"], + target=TARGET, + ), + call( + ["add-apt-repository", "ppa:smoser/cloud-init-test2"], + target=TARGET, + ), + call( + ["add-apt-repository", "ppa:smoser/cloud-init-test3"], + target=TARGET, + ), + ] mockobj.assert_has_calls(calls, any_order=True) # adding ppa should ignore all filenames (uses add-apt-repository) @@ -478,34 +647,46 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): pre = "/var/lib/apt/lists" # filenames are archive dependent - arch = 's390x' + arch = "s390x" m_get_dpkg_architecture.return_value = arch component = "ubuntu-ports" archive = "ports.ubuntu.com" - cfg = {'primary': [{'arches': ["default"], - 'uri': - 'http://test.ubuntu.com/%s/' % component}], - 'security': [{'arches': ["default"], - 'uri': - 'http://testsec.ubuntu.com/%s/' % component}]} - post = ("%s_dists_%s-updates_InRelease" % - (component, MOCK_LSB_RELEASE_DATA['codename'])) - fromfn = ("%s/%s_%s" % (pre, archive, post)) - tofn = ("%s/test.ubuntu.com_%s" % (pre, post)) + cfg = { + "primary": [ + { + "arches": ["default"], + "uri": "http://test.ubuntu.com/%s/" % component, + } + ], + "security": [ + { + "arches": ["default"], + "uri": "http://testsec.ubuntu.com/%s/" % component, + } + ], + } + post = "%s_dists_%s-updates_InRelease" % ( + component, + MOCK_LSB_RELEASE_DATA["codename"], + ) + fromfn = "%s/%s_%s" % (pre, archive, post) + tofn = "%s/test.ubuntu.com_%s" % (pre, post) mirrors = cc_apt_configure.find_apt_mirror_info(cfg, FakeCloud(), arch) - self.assertEqual(mirrors['MIRROR'], - "http://test.ubuntu.com/%s/" % component) - self.assertEqual(mirrors['PRIMARY'], - "http://test.ubuntu.com/%s/" % component) - self.assertEqual(mirrors['SECURITY'], - "http://testsec.ubuntu.com/%s/" % component) + self.assertEqual( + mirrors["MIRROR"], "http://test.ubuntu.com/%s/" % component + ) + self.assertEqual( + mirrors["PRIMARY"], "http://test.ubuntu.com/%s/" % component + ) + self.assertEqual( + mirrors["SECURITY"], "http://testsec.ubuntu.com/%s/" % component + ) - with mock.patch.object(os, 'rename') as mockren: - with mock.patch.object(glob, 'glob', - return_value=[fromfn]): + with mock.patch.object(os, "rename") as mockren: + with mock.patch.object(glob, "glob", return_value=[fromfn]): cc_apt_configure.rename_apt_lists(mirrors, TARGET, arch) mockren.assert_any_call(fromfn, tofn) @@ -515,13 +696,13 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): target = os.path.join(self.tmp, "rename_non_slash") apt_lists_d = os.path.join(target, "./" + cc_apt_configure.APT_LISTS) - arch = 'amd64' + arch = "amd64" m_get_dpkg_architecture.return_value = arch mirror_path = "some/random/path/" primary = "http://test.ubuntu.com/" + mirror_path security = "http://test-security.ubuntu.com/" + mirror_path - mirrors = {'PRIMARY': primary, 'SECURITY': security} + mirrors = {"PRIMARY": primary, "SECURITY": security} # these match default archive prefixes opri_pre = "archive.ubuntu.com_ubuntu_dists_xenial" @@ -559,203 +740,226 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): @staticmethod def test_apt_v3_proxy(): """test_apt_v3_proxy - Test apt_*proxy configuration""" - cfg = {"proxy": "foobar1", - "http_proxy": "foobar2", - "ftp_proxy": "foobar3", - "https_proxy": "foobar4"} + cfg = { + "proxy": "foobar1", + "http_proxy": "foobar2", + "ftp_proxy": "foobar3", + "https_proxy": "foobar4", + } - with mock.patch.object(util, 'write_file') as mockobj: + with mock.patch.object(util, "write_file") as mockobj: cc_apt_configure.apply_apt_config(cfg, "proxyfn", "notused") - mockobj.assert_called_with('proxyfn', - ('Acquire::http::Proxy "foobar1";\n' - 'Acquire::http::Proxy "foobar2";\n' - 'Acquire::ftp::Proxy "foobar3";\n' - 'Acquire::https::Proxy "foobar4";\n')) + mockobj.assert_called_with( + "proxyfn", + 'Acquire::http::Proxy "foobar1";\n' + 'Acquire::http::Proxy "foobar2";\n' + 'Acquire::ftp::Proxy "foobar3";\n' + 'Acquire::https::Proxy "foobar4";\n', + ) def test_apt_v3_mirror(self): """test_apt_v3_mirror - Test defining a mirror""" pmir = "http://us.archive.ubuntu.com/ubuntu/" smir = "http://security.ubuntu.com/ubuntu/" - cfg = {"primary": [{'arches': ["default"], - "uri": pmir}], - "security": [{'arches': ["default"], - "uri": smir}]} + cfg = { + "primary": [{"arches": ["default"], "uri": pmir}], + "security": [{"arches": ["default"], "uri": smir}], + } mirrors = cc_apt_configure.find_apt_mirror_info( - cfg, FakeCloud(), 'amd64') + cfg, FakeCloud(), "amd64" + ) - self.assertEqual(mirrors['MIRROR'], - pmir) - self.assertEqual(mirrors['PRIMARY'], - pmir) - self.assertEqual(mirrors['SECURITY'], - smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_mirror_default(self): """test_apt_v3_mirror_default - Test without defining a mirror""" - arch = 'amd64' + arch = "amd64" default_mirrors = cc_apt_configure.get_default_mirrors(arch) pmir = default_mirrors["PRIMARY"] smir = default_mirrors["SECURITY"] mycloud = get_cloud() mirrors = cc_apt_configure.find_apt_mirror_info({}, mycloud, arch) - self.assertEqual(mirrors['MIRROR'], - pmir) - self.assertEqual(mirrors['PRIMARY'], - pmir) - self.assertEqual(mirrors['SECURITY'], - smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_mirror_arches(self): """test_apt_v3_mirror_arches - Test arches selection of mirror""" pmir = "http://my-primary.ubuntu.com/ubuntu/" smir = "http://my-security.ubuntu.com/ubuntu/" - arch = 'ppc64el' - cfg = {"primary": [{'arches': ["default"], "uri": "notthis-primary"}, - {'arches': [arch], "uri": pmir}], - "security": [{'arches': ["default"], "uri": "nothis-security"}, - {'arches': [arch], "uri": smir}]} + arch = "ppc64el" + cfg = { + "primary": [ + {"arches": ["default"], "uri": "notthis-primary"}, + {"arches": [arch], "uri": pmir}, + ], + "security": [ + {"arches": ["default"], "uri": "nothis-security"}, + {"arches": [arch], "uri": smir}, + ], + } mirrors = cc_apt_configure.find_apt_mirror_info(cfg, FakeCloud(), arch) - self.assertEqual(mirrors['PRIMARY'], pmir) - self.assertEqual(mirrors['MIRROR'], pmir) - self.assertEqual(mirrors['SECURITY'], smir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_mirror_arches_default(self): """test_apt_v3_mirror_arches - Test falling back to default arch""" pmir = "http://us.archive.ubuntu.com/ubuntu/" smir = "http://security.ubuntu.com/ubuntu/" - cfg = {"primary": [{'arches': ["default"], - "uri": pmir}, - {'arches': ["thisarchdoesntexist"], - "uri": "notthis"}], - "security": [{'arches': ["thisarchdoesntexist"], - "uri": "nothat"}, - {'arches': ["default"], - "uri": smir}]} + cfg = { + "primary": [ + {"arches": ["default"], "uri": pmir}, + {"arches": ["thisarchdoesntexist"], "uri": "notthis"}, + ], + "security": [ + {"arches": ["thisarchdoesntexist"], "uri": "nothat"}, + {"arches": ["default"], "uri": smir}, + ], + } mirrors = cc_apt_configure.find_apt_mirror_info( - cfg, FakeCloud(), 'amd64') + cfg, FakeCloud(), "amd64" + ) - self.assertEqual(mirrors['MIRROR'], - pmir) - self.assertEqual(mirrors['PRIMARY'], - pmir) - self.assertEqual(mirrors['SECURITY'], - smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) @mock.patch("cloudinit.config.cc_apt_configure.util.get_dpkg_architecture") def test_apt_v3_get_def_mir_non_intel_no_arch( self, m_get_dpkg_architecture ): - arch = 'ppc64el' + arch = "ppc64el" m_get_dpkg_architecture.return_value = arch - expected = {'PRIMARY': 'http://ports.ubuntu.com/ubuntu-ports', - 'SECURITY': 'http://ports.ubuntu.com/ubuntu-ports'} + expected = { + "PRIMARY": "http://ports.ubuntu.com/ubuntu-ports", + "SECURITY": "http://ports.ubuntu.com/ubuntu-ports", + } self.assertEqual(expected, cc_apt_configure.get_default_mirrors()) def test_apt_v3_get_default_mirrors_non_intel_with_arch(self): - found = cc_apt_configure.get_default_mirrors('ppc64el') + found = cc_apt_configure.get_default_mirrors("ppc64el") - expected = {'PRIMARY': 'http://ports.ubuntu.com/ubuntu-ports', - 'SECURITY': 'http://ports.ubuntu.com/ubuntu-ports'} + expected = { + "PRIMARY": "http://ports.ubuntu.com/ubuntu-ports", + "SECURITY": "http://ports.ubuntu.com/ubuntu-ports", + } self.assertEqual(expected, found) def test_apt_v3_mirror_arches_sysdefault(self): """test_apt_v3_mirror_arches - Test arches fallback to sys default""" - arch = 'amd64' + arch = "amd64" default_mirrors = cc_apt_configure.get_default_mirrors(arch) pmir = default_mirrors["PRIMARY"] smir = default_mirrors["SECURITY"] mycloud = get_cloud() - cfg = {"primary": [{'arches': ["thisarchdoesntexist_64"], - "uri": "notthis"}, - {'arches': ["thisarchdoesntexist"], - "uri": "notthiseither"}], - "security": [{'arches': ["thisarchdoesntexist"], - "uri": "nothat"}, - {'arches': ["thisarchdoesntexist_64"], - "uri": "nothateither"}]} + cfg = { + "primary": [ + {"arches": ["thisarchdoesntexist_64"], "uri": "notthis"}, + {"arches": ["thisarchdoesntexist"], "uri": "notthiseither"}, + ], + "security": [ + {"arches": ["thisarchdoesntexist"], "uri": "nothat"}, + {"arches": ["thisarchdoesntexist_64"], "uri": "nothateither"}, + ], + } mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) - self.assertEqual(mirrors['MIRROR'], pmir) - self.assertEqual(mirrors['PRIMARY'], pmir) - self.assertEqual(mirrors['SECURITY'], smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_mirror_search(self): """test_apt_v3_mirror_search - Test searching mirrors in a list - mock checks to avoid relying on network connectivity""" + mock checks to avoid relying on network connectivity""" pmir = "http://us.archive.ubuntu.com/ubuntu/" smir = "http://security.ubuntu.com/ubuntu/" - cfg = {"primary": [{'arches': ["default"], - "search": ["pfailme", pmir]}], - "security": [{'arches': ["default"], - "search": ["sfailme", smir]}]} - - with mock.patch.object(cc_apt_configure.util, 'search_for_mirror', - side_effect=[pmir, smir]) as mocksearch: - mirrors = cc_apt_configure.find_apt_mirror_info(cfg, FakeCloud(), - 'amd64') - - calls = [call(["pfailme", pmir]), - call(["sfailme", smir])] + cfg = { + "primary": [{"arches": ["default"], "search": ["pfailme", pmir]}], + "security": [{"arches": ["default"], "search": ["sfailme", smir]}], + } + + with mock.patch.object( + cc_apt_configure.util, + "search_for_mirror", + side_effect=[pmir, smir], + ) as mocksearch: + mirrors = cc_apt_configure.find_apt_mirror_info( + cfg, FakeCloud(), "amd64" + ) + + calls = [call(["pfailme", pmir]), call(["sfailme", smir])] mocksearch.assert_has_calls(calls) - self.assertEqual(mirrors['MIRROR'], - pmir) - self.assertEqual(mirrors['PRIMARY'], - pmir) - self.assertEqual(mirrors['SECURITY'], - smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_mirror_search_many2(self): """test_apt_v3_mirror_search_many3 - Test both mirrors specs at once""" pmir = "http://us.archive.ubuntu.com/ubuntu/" smir = "http://security.ubuntu.com/ubuntu/" - cfg = {"primary": [{'arches': ["default"], - "uri": pmir, - "search": ["pfailme", "foo"]}], - "security": [{'arches': ["default"], - "uri": smir, - "search": ["sfailme", "bar"]}]} + cfg = { + "primary": [ + { + "arches": ["default"], + "uri": pmir, + "search": ["pfailme", "foo"], + } + ], + "security": [ + { + "arches": ["default"], + "uri": smir, + "search": ["sfailme", "bar"], + } + ], + } - arch = 'amd64' + arch = "amd64" # should be called only once per type, despite two mirror configs mycloud = None - with mock.patch.object(cc_apt_configure, 'get_mirror', - return_value="http://mocked/foo") as mockgm: + with mock.patch.object( + cc_apt_configure, "get_mirror", return_value="http://mocked/foo" + ) as mockgm: mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) - calls = [call(cfg, 'primary', arch, mycloud), - call(cfg, 'security', arch, mycloud)] + calls = [ + call(cfg, "primary", arch, mycloud), + call(cfg, "security", arch, mycloud), + ] mockgm.assert_has_calls(calls) # should not be called, since primary is specified - with mock.patch.object(cc_apt_configure.util, - 'search_for_mirror') as mockse: + with mock.patch.object( + cc_apt_configure.util, "search_for_mirror" + ) as mockse: mirrors = cc_apt_configure.find_apt_mirror_info( - cfg, FakeCloud(), arch) + cfg, FakeCloud(), arch + ) mockse.assert_not_called() - self.assertEqual(mirrors['MIRROR'], - pmir) - self.assertEqual(mirrors['PRIMARY'], - pmir) - self.assertEqual(mirrors['SECURITY'], - smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_url_resolvable(self): """test_apt_v3_url_resolvable - Test resolving urls""" - with mock.patch.object(util, 'is_resolvable') as mockresolve: + with mock.patch.object(util, "is_resolvable") as mockresolve: util.is_resolvable_url("http://1.2.3.4/ubuntu") mockresolve.assert_called_with("1.2.3.4") - with mock.patch.object(util, 'is_resolvable') as mockresolve: + with mock.patch.object(util, "is_resolvable") as mockresolve: util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu") mockresolve.assert_called_with("us.archive.ubuntu.com") @@ -764,25 +968,27 @@ class TestAptSourceConfig(t_help.FilesystemMockingTestCase): util._DNS_REDIRECT_IP = None bad = [(None, None, None, "badname", ["10.3.2.1"])] good = [(None, None, None, "goodname", ["10.2.3.4"])] - with mock.patch.object(socket, 'getaddrinfo', - side_effect=[bad, bad, bad, good, - good]) as mocksock: + with mock.patch.object( + socket, "getaddrinfo", side_effect=[bad, bad, bad, good, good] + ) as mocksock: ret = util.is_resolvable_url("http://us.archive.ubuntu.com/ubuntu") ret2 = util.is_resolvable_url("http://1.2.3.4/ubuntu") - mocksock.assert_any_call('does-not-exist.example.com.', None, - 0, 0, 1, 2) - mocksock.assert_any_call('example.invalid.', None, 0, 0, 1, 2) - mocksock.assert_any_call('us.archive.ubuntu.com', None) - mocksock.assert_any_call('1.2.3.4', None) + mocksock.assert_any_call( + "does-not-exist.example.com.", None, 0, 0, 1, 2 + ) + mocksock.assert_any_call("example.invalid.", None, 0, 0, 1, 2) + mocksock.assert_any_call("us.archive.ubuntu.com", None) + mocksock.assert_any_call("1.2.3.4", None) self.assertTrue(ret) self.assertTrue(ret2) # side effect need only bad ret after initial call - with mock.patch.object(socket, 'getaddrinfo', - side_effect=[bad]) as mocksock: + with mock.patch.object( + socket, "getaddrinfo", side_effect=[bad] + ) as mocksock: ret3 = util.is_resolvable_url("http://failme.com/ubuntu") - calls = [call('failme.com', None)] + calls = [call("failme.com", None)] mocksock.assert_has_calls(calls) self.assertFalse(ret3) @@ -818,24 +1024,28 @@ deb http://ubuntu.com/ubuntu/ xenial-proposed main""" # single disable other suite disabled = ["$RELEASE-updates"] - expect = ("""deb http://ubuntu.com//ubuntu xenial main + expect = ( + """deb http://ubuntu.com//ubuntu xenial main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu""" - """ xenial-updates main + """ xenial-updates main deb http://ubuntu.com//ubuntu xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse -deb http://ubuntu.com/ubuntu/ xenial-proposed main""") +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" + ) result = cc_apt_configure.disable_suites(disabled, orig, release) self.assertEqual(expect, result) # multi disable disabled = ["$RELEASE-updates", "$RELEASE-security"] - expect = ("""deb http://ubuntu.com//ubuntu xenial main + expect = ( + """deb http://ubuntu.com//ubuntu xenial main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-updates main + """xenial-updates main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-security main + """xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse -deb http://ubuntu.com/ubuntu/ xenial-proposed main""") +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" + ) result = cc_apt_configure.disable_suites(disabled, orig, release) self.assertEqual(expect, result) @@ -848,17 +1058,19 @@ deb-src http://ubuntu.com//ubuntu universe multiverse deb http://UBUNTU.com//ubuntu xenial-updates main deb http://UBUNTU.COM//ubuntu xenial-updates main deb http://ubuntu.com/ubuntu/ xenial-proposed main""" - expect = ("""deb http://ubuntu.com//ubuntu xenial main + expect = ( + """deb http://ubuntu.com//ubuntu xenial main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-updates main + """xenial-updates main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-security main + """xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse # suite disabled by cloud-init: deb http://UBUNTU.com//ubuntu """ - """xenial-updates main + """xenial-updates main # suite disabled by cloud-init: deb http://UBUNTU.COM//ubuntu """ - """xenial-updates main -deb http://ubuntu.com/ubuntu/ xenial-proposed main""") + """xenial-updates main +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" + ) result = cc_apt_configure.disable_suites(disabled, orig, release) self.assertEqual(expect, result) @@ -872,17 +1084,19 @@ deb-src http://ubuntu.com//ubuntu universe multiverse #deb http://UBUNTU.com//ubuntu xenial-updates main deb http://UBUNTU.COM//ubuntu xenial-updates main deb http://ubuntu.com/ubuntu/ xenial-proposed main""" - expect = ("""deb http://ubuntu.com//ubuntu xenial main + expect = ( + """deb http://ubuntu.com//ubuntu xenial main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-updates main + """xenial-updates main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-security main + """xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse #foo #deb http://UBUNTU.com//ubuntu xenial-updates main # suite disabled by cloud-init: deb http://UBUNTU.COM//ubuntu """ - """xenial-updates main -deb http://ubuntu.com/ubuntu/ xenial-proposed main""") + """xenial-updates main +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" + ) result = cc_apt_configure.disable_suites(disabled, orig, release) self.assertEqual(expect, result) @@ -919,12 +1133,14 @@ deb [a=b] http://ubu.com//ubu xenial-updates main deb http://ubuntu.com//ubuntu xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse deb http://ubuntu.com/ubuntu/ xenial-proposed main""" - expect = ("""deb http://ubuntu.com//ubuntu xenial main + expect = ( + """deb http://ubuntu.com//ubuntu xenial main # suite disabled by cloud-init: deb [a=b] http://ubu.com//ubu """ - """xenial-updates main + """xenial-updates main deb http://ubuntu.com//ubuntu xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse -deb http://ubuntu.com/ubuntu/ xenial-proposed main""") +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" + ) result = cc_apt_configure.disable_suites(disabled, orig, release) self.assertEqual(expect, result) @@ -951,134 +1167,167 @@ deb [arch=foo] http://ubuntu.com//ubuntu xenial-updates main deb http://ubuntu.com//ubuntu xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse deb http://ubuntu.com/ubuntu/ xenial-proposed main""" - expect = ("""deb http://ubuntu.com//ubuntu xenial main + expect = ( + """deb http://ubuntu.com//ubuntu xenial main deb [arch=foo] http://ubuntu.com//ubuntu xenial-updates main # suite disabled by cloud-init: deb http://ubuntu.com//ubuntu """ - """xenial-security main + """xenial-security main deb-src http://ubuntu.com//ubuntu universe multiverse -deb http://ubuntu.com/ubuntu/ xenial-proposed main""") +deb http://ubuntu.com/ubuntu/ xenial-proposed main""" + ) result = cc_apt_configure.disable_suites(disabled, orig, release) self.assertEqual(expect, result) def test_disable_suites_blank_lines(self): """test_disable_suites_blank_lines - ensure blank lines allowed""" - lines = ["deb %(repo)s %(rel)s main universe", - "", - "deb %(repo)s %(rel)s-updates main universe", - " # random comment", - "#comment here", - ""] + lines = [ + "deb %(repo)s %(rel)s main universe", + "", + "deb %(repo)s %(rel)s-updates main universe", + " # random comment", + "#comment here", + "", + ] rel = "trusty" - repo = 'http://example.com/mirrors/ubuntu' - orig = "\n".join(lines) % {'repo': repo, 'rel': rel} + repo = "http://example.com/mirrors/ubuntu" + orig = "\n".join(lines) % {"repo": repo, "rel": rel} self.assertEqual( - orig, cc_apt_configure.disable_suites(["proposed"], orig, rel)) + orig, cc_apt_configure.disable_suites(["proposed"], orig, rel) + ) - @mock.patch("cloudinit.util.get_hostname", return_value='abc.localdomain') + @mock.patch("cloudinit.util.get_hostname", return_value="abc.localdomain") def test_apt_v3_mirror_search_dns(self, m_get_hostname): """test_apt_v3_mirror_search_dns - Test searching dns patterns""" pmir = "phit" smir = "shit" - arch = 'amd64' - mycloud = get_cloud('ubuntu') - cfg = {"primary": [{'arches': ["default"], - "search_dns": True}], - "security": [{'arches': ["default"], - "search_dns": True}]} - - with mock.patch.object(cc_apt_configure, 'get_mirror', - return_value="http://mocked/foo") as mockgm: + arch = "amd64" + mycloud = get_cloud("ubuntu") + cfg = { + "primary": [{"arches": ["default"], "search_dns": True}], + "security": [{"arches": ["default"], "search_dns": True}], + } + + with mock.patch.object( + cc_apt_configure, "get_mirror", return_value="http://mocked/foo" + ) as mockgm: mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) - calls = [call(cfg, 'primary', arch, mycloud), - call(cfg, 'security', arch, mycloud)] + calls = [ + call(cfg, "primary", arch, mycloud), + call(cfg, "security", arch, mycloud), + ] mockgm.assert_has_calls(calls) - with mock.patch.object(cc_apt_configure, 'search_for_mirror_dns', - return_value="http://mocked/foo") as mocksdns: + with mock.patch.object( + cc_apt_configure, + "search_for_mirror_dns", + return_value="http://mocked/foo", + ) as mocksdns: mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) - calls = [call(True, 'primary', cfg, mycloud), - call(True, 'security', cfg, mycloud)] + calls = [ + call(True, "primary", cfg, mycloud), + call(True, "security", cfg, mycloud), + ] mocksdns.assert_has_calls(calls) # first return is for the non-dns call before - with mock.patch.object(cc_apt_configure.util, 'search_for_mirror', - side_effect=[None, pmir, None, smir]) as mockse: + with mock.patch.object( + cc_apt_configure.util, + "search_for_mirror", + side_effect=[None, pmir, None, smir], + ) as mockse: mirrors = cc_apt_configure.find_apt_mirror_info(cfg, mycloud, arch) - calls = [call(None), - call(['http://ubuntu-mirror.localdomain/ubuntu', - 'http://ubuntu-mirror/ubuntu']), - call(None), - call(['http://ubuntu-security-mirror.localdomain/ubuntu', - 'http://ubuntu-security-mirror/ubuntu'])] + calls = [ + call(None), + call( + [ + "http://ubuntu-mirror.localdomain/ubuntu", + "http://ubuntu-mirror/ubuntu", + ] + ), + call(None), + call( + [ + "http://ubuntu-security-mirror.localdomain/ubuntu", + "http://ubuntu-security-mirror/ubuntu", + ] + ), + ] mockse.assert_has_calls(calls) - self.assertEqual(mirrors['MIRROR'], - pmir) - self.assertEqual(mirrors['PRIMARY'], - pmir) - self.assertEqual(mirrors['SECURITY'], - smir) + self.assertEqual(mirrors["MIRROR"], pmir) + self.assertEqual(mirrors["PRIMARY"], pmir) + self.assertEqual(mirrors["SECURITY"], smir) def test_apt_v3_add_mirror_keys(self): """test_apt_v3_add_mirror_keys - Test adding key for mirrors""" - arch = 'amd64' + arch = "amd64" cfg = { - 'primary': [ - {'arches': [arch], - 'uri': 'http://test.ubuntu.com/', - 'filename': 'primary', - 'key': 'fakekey_primary'}], - 'security': [ - {'arches': [arch], - 'uri': 'http://testsec.ubuntu.com/', - 'filename': 'security', - 'key': 'fakekey_security'}] + "primary": [ + { + "arches": [arch], + "uri": "http://test.ubuntu.com/", + "filename": "primary", + "key": "fakekey_primary", + } + ], + "security": [ + { + "arches": [arch], + "uri": "http://testsec.ubuntu.com/", + "filename": "security", + "key": "fakekey_security", + } + ], } - with mock.patch.object(cc_apt_configure, - 'add_apt_key_raw') as mockadd: + with mock.patch.object(cc_apt_configure, "add_apt_key_raw") as mockadd: cc_apt_configure.add_mirror_keys(cfg, TARGET) calls = [ - mock.call('fakekey_primary', 'primary', hardened=False), - mock.call('fakekey_security', 'security', hardened=False), + mock.call("fakekey_primary", "primary", hardened=False), + mock.call("fakekey_security", "security", hardened=False), ] mockadd.assert_has_calls(calls, any_order=True) class TestDebconfSelections(TestCase): - @mock.patch("cloudinit.config.cc_apt_configure.subp.subp") def test_set_sel_appends_newline_if_absent(self, m_subp): """Automatically append a newline to debconf-set-selections config.""" - selections = b'some/setting boolean true' + selections = b"some/setting boolean true" cc_apt_configure.debconf_set_selections(selections=selections) - cc_apt_configure.debconf_set_selections(selections=selections + b'\n') + cc_apt_configure.debconf_set_selections(selections=selections + b"\n") m_call = mock.call( - ['debconf-set-selections'], data=selections + b'\n', capture=True, - target=None) + ["debconf-set-selections"], + data=selections + b"\n", + capture=True, + target=None, + ) self.assertEqual([m_call, m_call], m_subp.call_args_list) @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") def test_no_set_sel_if_none_to_set(self, m_set_sel): - cc_apt_configure.apply_debconf_selections({'foo': 'bar'}) + cc_apt_configure.apply_debconf_selections({"foo": "bar"}) m_set_sel.assert_not_called() - @mock.patch("cloudinit.config.cc_apt_configure." - "debconf_set_selections") - @mock.patch("cloudinit.config.cc_apt_configure." - "util.get_installed_packages") + @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") + @mock.patch( + "cloudinit.config.cc_apt_configure.util.get_installed_packages" + ) def test_set_sel_call_has_expected_input(self, m_get_inst, m_set_sel): data = { - 'set1': 'pkga pkga/q1 mybool false', - 'set2': ('pkgb\tpkgb/b1\tstr\tthis is a string\n' - 'pkgc\tpkgc/ip\tstring\t10.0.0.1')} - lines = '\n'.join(data.values()).split('\n') + "set1": "pkga pkga/q1 mybool false", + "set2": ( + "pkgb\tpkgb/b1\tstr\tthis is a string\n" + "pkgc\tpkgc/ip\tstring\t10.0.0.1" + ), + } + lines = "\n".join(data.values()).split("\n") m_get_inst.return_value = ["adduser", "apparmor"] m_set_sel.return_value = None - cc_apt_configure.apply_debconf_selections({'debconf_selections': data}) + cc_apt_configure.apply_debconf_selections({"debconf_selections": data}) self.assertTrue(m_get_inst.called) self.assertEqual(m_set_sel.call_count, 1) @@ -1092,43 +1341,59 @@ class TestDebconfSelections(TestCase): @mock.patch("cloudinit.config.cc_apt_configure.dpkg_reconfigure") @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") - @mock.patch("cloudinit.config.cc_apt_configure." - "util.get_installed_packages") - def test_reconfigure_if_intersection(self, m_get_inst, m_set_sel, - m_dpkg_r): + @mock.patch( + "cloudinit.config.cc_apt_configure.util.get_installed_packages" + ) + def test_reconfigure_if_intersection( + self, m_get_inst, m_set_sel, m_dpkg_r + ): data = { - 'set1': 'pkga pkga/q1 mybool false', - 'set2': ('pkgb\tpkgb/b1\tstr\tthis is a string\n' - 'pkgc\tpkgc/ip\tstring\t10.0.0.1'), - 'cloud-init': ('cloud-init cloud-init/datasources' - 'multiselect MAAS')} + "set1": "pkga pkga/q1 mybool false", + "set2": ( + "pkgb\tpkgb/b1\tstr\tthis is a string\n" + "pkgc\tpkgc/ip\tstring\t10.0.0.1" + ), + "cloud-init": "cloud-init cloud-init/datasourcesmultiselect MAAS", + } m_set_sel.return_value = None - m_get_inst.return_value = ["adduser", "apparmor", "pkgb", - "cloud-init", 'zdog'] + m_get_inst.return_value = [ + "adduser", + "apparmor", + "pkgb", + "cloud-init", + "zdog", + ] - cc_apt_configure.apply_debconf_selections({'debconf_selections': data}) + cc_apt_configure.apply_debconf_selections({"debconf_selections": data}) # reconfigure should be called with the intersection # of (packages in config, packages installed) self.assertEqual(m_dpkg_r.call_count, 1) # assumes called with *args (dpkg_reconfigure([a,b,c], target=)) packages = m_dpkg_r.call_args_list[0][0][0] - self.assertEqual(set(['cloud-init', 'pkgb']), set(packages)) + self.assertEqual(set(["cloud-init", "pkgb"]), set(packages)) @mock.patch("cloudinit.config.cc_apt_configure.dpkg_reconfigure") @mock.patch("cloudinit.config.cc_apt_configure.debconf_set_selections") - @mock.patch("cloudinit.config.cc_apt_configure." - "util.get_installed_packages") - def test_reconfigure_if_no_intersection(self, m_get_inst, m_set_sel, - m_dpkg_r): - data = {'set1': 'pkga pkga/q1 mybool false'} - - m_get_inst.return_value = ["adduser", "apparmor", "pkgb", - "cloud-init", 'zdog'] + @mock.patch( + "cloudinit.config.cc_apt_configure.util.get_installed_packages" + ) + def test_reconfigure_if_no_intersection( + self, m_get_inst, m_set_sel, m_dpkg_r + ): + data = {"set1": "pkga pkga/q1 mybool false"} + + m_get_inst.return_value = [ + "adduser", + "apparmor", + "pkgb", + "cloud-init", + "zdog", + ] m_set_sel.return_value = None - cc_apt_configure.apply_debconf_selections({'debconf_selections': data}) + cc_apt_configure.apply_debconf_selections({"debconf_selections": data}) self.assertTrue(m_get_inst.called) self.assertEqual(m_dpkg_r.call_count, 0) @@ -1141,19 +1406,25 @@ class TestDebconfSelections(TestCase): # mocking clean_cloud_init directly does not work. So we mock # the CONFIG_CLEANERS dictionary and assert our cleaner is called. ci_cleaner = mock.MagicMock() - with mock.patch.dict(("cloudinit.config.cc_apt_configure." - "CONFIG_CLEANERS"), - values={'cloud-init': ci_cleaner}, clear=True): - cc_apt_configure.dpkg_reconfigure(['pkga', 'cloud-init'], - target=target) + with mock.patch.dict( + "cloudinit.config.cc_apt_configure.CONFIG_CLEANERS", + values={"cloud-init": ci_cleaner}, + clear=True, + ): + cc_apt_configure.dpkg_reconfigure( + ["pkga", "cloud-init"], target=target + ) # cloud-init is actually the only package we have a cleaner for # so for now, its the only one that should reconfigured self.assertTrue(m_subp.called) ci_cleaner.assert_called_with(target) self.assertEqual(m_subp.call_count, 1) found = m_subp.call_args_list[0][0][0] - expected = ['dpkg-reconfigure', '--frontend=noninteractive', - 'cloud-init'] + expected = [ + "dpkg-reconfigure", + "--frontend=noninteractive", + "cloud-init", + ] self.assertEqual(expected, found) @mock.patch("cloudinit.config.cc_apt_configure.subp.subp") @@ -1163,8 +1434,9 @@ class TestDebconfSelections(TestCase): @mock.patch("cloudinit.config.cc_apt_configure.subp.subp") def test_dpkg_reconfigure_not_done_if_no_cleaners(self, m_subp): - cc_apt_configure.dpkg_reconfigure(['pkgfoo', 'pkgbar']) + cc_apt_configure.dpkg_reconfigure(["pkgfoo", "pkgbar"]) m_subp.assert_not_called() + # # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_apk_configure.py b/tests/unittests/config/test_cc_apk_configure.py index 70139451..6fbc3dec 100644 --- a/tests/unittests/config/test_cc_apk_configure.py +++ b/tests/unittests/config/test_cc_apk_configure.py @@ -8,20 +8,19 @@ import logging import os import textwrap -from cloudinit import (cloud, helpers, util) - +from cloudinit import cloud, helpers, util from cloudinit.config import cc_apk_configure -from tests.unittests.helpers import (FilesystemMockingTestCase, mock) +from tests.unittests.helpers import FilesystemMockingTestCase, mock REPO_FILE = "/etc/apk/repositories" DEFAULT_MIRROR_URL = "https://alpine.global.ssl.fastly.net/alpine" -CC_APK = 'cloudinit.config.cc_apk_configure' +CC_APK = "cloudinit.config.cc_apk_configure" class TestNoConfig(FilesystemMockingTestCase): def setUp(self): super(TestNoConfig, self).setUp() - self.add_patch(CC_APK + '._write_repositories_file', 'm_write_repos') + self.add_patch(CC_APK + "._write_repositories_file", "m_write_repos") self.name = "apk-configure" self.cloud_init = None self.log = logging.getLogger("TestNoConfig") @@ -34,8 +33,9 @@ class TestNoConfig(FilesystemMockingTestCase): """ config = util.get_builtin_cfg() - cc_apk_configure.handle(self.name, config, self.cloud_init, - self.log, self.args) + cc_apk_configure.handle( + self.name, config, self.cloud_init, self.log, self.args + ) self.assertEqual(0, self.m_write_repos.call_count) @@ -45,15 +45,15 @@ class TestConfig(FilesystemMockingTestCase): super(TestConfig, self).setUp() self.new_root = self.tmp_dir() self.new_root = self.reRoot(root=self.new_root) - for dirname in ['tmp', 'etc/apk']: + for dirname in ["tmp", "etc/apk"]: util.ensure_dir(os.path.join(self.new_root, dirname)) - self.paths = helpers.Paths({'templates_dir': self.new_root}) + self.paths = helpers.Paths({"templates_dir": self.new_root}) self.name = "apk-configure" self.cloud = cloud.Cloud(None, self.paths, None, None, None) self.log = logging.getLogger("TestNoConfig") self.args = [] - @mock.patch(CC_APK + '._write_repositories_file') + @mock.patch(CC_APK + "._write_repositories_file") def test_no_repo_settings(self, m_write_repos): """ Test that nothing is written if the 'alpine-repo' key @@ -61,20 +61,22 @@ class TestConfig(FilesystemMockingTestCase): """ config = {"apk_repos": {}} - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) self.assertEqual(0, m_write_repos.call_count) - @mock.patch(CC_APK + '._write_repositories_file') + @mock.patch(CC_APK + "._write_repositories_file") def test_empty_repo_settings(self, m_write_repos): """ Test that nothing is written if 'alpine_repo' list is empty. """ config = {"apk_repos": {"alpine_repo": []}} - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) self.assertEqual(0, m_write_repos.call_count) @@ -82,19 +84,15 @@ class TestConfig(FilesystemMockingTestCase): """ Test when only details of main repo is written to file. """ - alpine_version = 'v3.12' - config = { - "apk_repos": { - "alpine_repo": { - "version": alpine_version - } - } - } + alpine_version = "v3.12" + config = {"apk_repos": {"alpine_repo": {"version": alpine_version}}} - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) - expected_content = textwrap.dedent("""\ + expected_content = textwrap.dedent( + """\ # # Created by cloud-init # @@ -103,7 +101,10 @@ class TestConfig(FilesystemMockingTestCase): {0}/{1}/main - """.format(DEFAULT_MIRROR_URL, alpine_version)) + """.format( + DEFAULT_MIRROR_URL, alpine_version + ) + ) self.assertEqual(expected_content, util.load_file(REPO_FILE)) @@ -112,20 +113,22 @@ class TestConfig(FilesystemMockingTestCase): Test when only details of main and community repos are written to file. """ - alpine_version = 'edge' + alpine_version = "edge" config = { "apk_repos": { "alpine_repo": { "version": alpine_version, - "community_enabled": True + "community_enabled": True, } } } - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) - expected_content = textwrap.dedent("""\ + expected_content = textwrap.dedent( + """\ # # Created by cloud-init # @@ -135,7 +138,10 @@ class TestConfig(FilesystemMockingTestCase): {0}/{1}/main {0}/{1}/community - """.format(DEFAULT_MIRROR_URL, alpine_version)) + """.format( + DEFAULT_MIRROR_URL, alpine_version + ) + ) self.assertEqual(expected_content, util.load_file(REPO_FILE)) @@ -144,21 +150,23 @@ class TestConfig(FilesystemMockingTestCase): Test when details of main, community and testing repos are written to file. """ - alpine_version = 'v3.12' + alpine_version = "v3.12" config = { "apk_repos": { "alpine_repo": { "version": alpine_version, "community_enabled": True, - "testing_enabled": True + "testing_enabled": True, } } } - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) - expected_content = textwrap.dedent("""\ + expected_content = textwrap.dedent( + """\ # # Created by cloud-init # @@ -172,7 +180,10 @@ class TestConfig(FilesystemMockingTestCase): # {0}/edge/testing - """.format(DEFAULT_MIRROR_URL, alpine_version)) + """.format( + DEFAULT_MIRROR_URL, alpine_version + ) + ) self.assertEqual(expected_content, util.load_file(REPO_FILE)) @@ -181,21 +192,23 @@ class TestConfig(FilesystemMockingTestCase): Test when details of main, community and testing repos for Edge version of Alpine are written to file. """ - alpine_version = 'edge' + alpine_version = "edge" config = { "apk_repos": { "alpine_repo": { "version": alpine_version, "community_enabled": True, - "testing_enabled": True + "testing_enabled": True, } } } - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) - expected_content = textwrap.dedent("""\ + expected_content = textwrap.dedent( + """\ # # Created by cloud-init # @@ -206,7 +219,10 @@ class TestConfig(FilesystemMockingTestCase): {0}/{1}/community {0}/{1}/testing - """.format(DEFAULT_MIRROR_URL, alpine_version)) + """.format( + DEFAULT_MIRROR_URL, alpine_version + ) + ) self.assertEqual(expected_content, util.load_file(REPO_FILE)) @@ -215,23 +231,25 @@ class TestConfig(FilesystemMockingTestCase): Test when details of main, community, testing and local repos are written to file. """ - alpine_version = 'v3.12' - local_repo_url = 'http://some.mirror/whereever' + alpine_version = "v3.12" + local_repo_url = "http://some.mirror/whereever" config = { "apk_repos": { "alpine_repo": { "version": alpine_version, "community_enabled": True, - "testing_enabled": True + "testing_enabled": True, }, - "local_repo_base_url": local_repo_url + "local_repo_base_url": local_repo_url, } } - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) - expected_content = textwrap.dedent("""\ + expected_content = textwrap.dedent( + """\ # # Created by cloud-init # @@ -250,7 +268,10 @@ class TestConfig(FilesystemMockingTestCase): # {2}/{1} - """.format(DEFAULT_MIRROR_URL, alpine_version, local_repo_url)) + """.format( + DEFAULT_MIRROR_URL, alpine_version, local_repo_url + ) + ) self.assertEqual(expected_content, util.load_file(REPO_FILE)) @@ -259,23 +280,25 @@ class TestConfig(FilesystemMockingTestCase): Test when details of main, community, testing and local repos for Edge version of Alpine are written to file. """ - alpine_version = 'edge' - local_repo_url = 'http://some.mirror/whereever' + alpine_version = "edge" + local_repo_url = "http://some.mirror/whereever" config = { "apk_repos": { "alpine_repo": { "version": alpine_version, "community_enabled": True, - "testing_enabled": True + "testing_enabled": True, }, - "local_repo_base_url": local_repo_url + "local_repo_base_url": local_repo_url, } } - cc_apk_configure.handle(self.name, config, self.cloud, self.log, - self.args) + cc_apk_configure.handle( + self.name, config, self.cloud, self.log, self.args + ) - expected_content = textwrap.dedent("""\ + expected_content = textwrap.dedent( + """\ # # Created by cloud-init # @@ -291,7 +314,10 @@ class TestConfig(FilesystemMockingTestCase): # {2}/{1} - """.format(DEFAULT_MIRROR_URL, alpine_version, local_repo_url)) + """.format( + DEFAULT_MIRROR_URL, alpine_version, local_repo_url + ) + ) self.assertEqual(expected_content, util.load_file(REPO_FILE)) diff --git a/tests/unittests/config/test_cc_apt_pipelining.py b/tests/unittests/config/test_cc_apt_pipelining.py index d7589d35..b4497156 100644 --- a/tests/unittests/config/test_cc_apt_pipelining.py +++ b/tests/unittests/config/test_cc_apt_pipelining.py @@ -3,26 +3,26 @@ """Tests cc_apt_pipelining handler""" import cloudinit.config.cc_apt_pipelining as cc_apt_pipelining - from tests.unittests.helpers import CiTestCase, mock class TestAptPipelining(CiTestCase): - - @mock.patch('cloudinit.config.cc_apt_pipelining.util.write_file') + @mock.patch("cloudinit.config.cc_apt_pipelining.util.write_file") def test_not_disabled_by_default(self, m_write_file): """ensure that default behaviour is to not disable pipelining""" - cc_apt_pipelining.handle('foo', {}, None, mock.MagicMock(), None) + cc_apt_pipelining.handle("foo", {}, None, mock.MagicMock(), None) self.assertEqual(0, m_write_file.call_count) - @mock.patch('cloudinit.config.cc_apt_pipelining.util.write_file') + @mock.patch("cloudinit.config.cc_apt_pipelining.util.write_file") def test_false_disables_pipelining(self, m_write_file): """ensure that pipelining can be disabled with correct config""" cc_apt_pipelining.handle( - 'foo', {'apt_pipelining': 'false'}, None, mock.MagicMock(), None) + "foo", {"apt_pipelining": "false"}, None, mock.MagicMock(), None + ) self.assertEqual(1, m_write_file.call_count) args, _ = m_write_file.call_args self.assertEqual(cc_apt_pipelining.DEFAULT_FILE, args[0]) self.assertIn('Pipeline-Depth "0"', args[1]) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_bootcmd.py b/tests/unittests/config/test_cc_bootcmd.py index 6f38f12a..6d8793b9 100644 --- a/tests/unittests/config/test_cc_bootcmd.py +++ b/tests/unittests/config/test_cc_bootcmd.py @@ -2,11 +2,14 @@ import logging import tempfile +from cloudinit import subp, util from cloudinit.config.cc_bootcmd import handle, schema -from cloudinit import (subp, util) from tests.unittests.helpers import ( - CiTestCase, mock, SchemaTestCaseMixin, skipUnlessJsonSchema) - + CiTestCase, + SchemaTestCaseMixin, + mock, + skipUnlessJsonSchema, +) from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -16,7 +19,8 @@ class FakeExtendedTempFile(object): def __init__(self, suffix): self.suffix = suffix self.handle = tempfile.NamedTemporaryFile( - prefix="ci-%s." % self.__class__.__name__, delete=False) + prefix="ci-%s." % self.__class__.__name__, delete=False + ) def __enter__(self): return self.handle @@ -30,8 +34,9 @@ class TestBootcmd(CiTestCase): with_logs = True - _etmpfile_path = ('cloudinit.config.cc_bootcmd.temp_utils.' - 'ExtendedTemporaryFile') + _etmpfile_path = ( + "cloudinit.config.cc_bootcmd.temp_utils.ExtendedTemporaryFile" + ) def setUp(self): super(TestBootcmd, self).setUp() @@ -42,21 +47,23 @@ class TestBootcmd(CiTestCase): """When the provided config doesn't contain bootcmd, skip it.""" cfg = {} mycloud = get_cloud() - handle('notimportant', cfg, mycloud, LOG, None) + handle("notimportant", cfg, mycloud, LOG, None) self.assertIn( "Skipping module named notimportant, no 'bootcmd' key", - self.logs.getvalue()) + self.logs.getvalue(), + ) def test_handler_invalid_command_set(self): """Commands which can't be converted to shell will raise errors.""" - invalid_config = {'bootcmd': 1} + invalid_config = {"bootcmd": 1} cc = get_cloud() with self.assertRaises(TypeError) as context_manager: - handle('cc_bootcmd', invalid_config, cc, LOG, []) - self.assertIn('Failed to shellify bootcmd', self.logs.getvalue()) + handle("cc_bootcmd", invalid_config, cc, LOG, []) + self.assertIn("Failed to shellify bootcmd", self.logs.getvalue()) self.assertEqual( "Input to shellify was type 'int'. Expected list or tuple.", - str(context_manager.exception)) + str(context_manager.exception), + ) @skipUnlessJsonSchema() def test_handler_schema_validation_warns_non_array_type(self): @@ -65,14 +72,15 @@ class TestBootcmd(CiTestCase): Schema validation is not strict, so bootcmd attempts to shellify the invalid content. """ - invalid_config = {'bootcmd': 1} + invalid_config = {"bootcmd": 1} cc = get_cloud() with self.assertRaises(TypeError): - handle('cc_bootcmd', invalid_config, cc, LOG, []) + handle("cc_bootcmd", invalid_config, cc, LOG, []) self.assertIn( - 'Invalid config:\nbootcmd: 1 is not of type \'array\'', - self.logs.getvalue()) - self.assertIn('Failed to shellify', self.logs.getvalue()) + "Invalid config:\nbootcmd: 1 is not of type 'array'", + self.logs.getvalue(), + ) + self.assertIn("Failed to shellify", self.logs.getvalue()) @skipUnlessJsonSchema() def test_handler_schema_validation_warns_non_array_item_type(self): @@ -82,54 +90,58 @@ class TestBootcmd(CiTestCase): invalid content. """ invalid_config = { - 'bootcmd': ['ls /', 20, ['wget', 'http://stuff/blah'], {'a': 'n'}]} + "bootcmd": ["ls /", 20, ["wget", "http://stuff/blah"], {"a": "n"}] + } cc = get_cloud() with self.assertRaises(TypeError) as context_manager: - handle('cc_bootcmd', invalid_config, cc, LOG, []) + handle("cc_bootcmd", invalid_config, cc, LOG, []) expected_warnings = [ - 'bootcmd.1: 20 is not valid under any of the given schemas', - 'bootcmd.3: {\'a\': \'n\'} is not valid under any of the given' - ' schema' + "bootcmd.1: 20 is not valid under any of the given schemas", + "bootcmd.3: {'a': 'n'} is not valid under any of the given schema", ] logs = self.logs.getvalue() for warning in expected_warnings: self.assertIn(warning, logs) - self.assertIn('Failed to shellify', logs) + self.assertIn("Failed to shellify", logs) self.assertEqual( - ("Unable to shellify type 'int'. Expected list, string, tuple. " - "Got: 20"), - str(context_manager.exception)) + "Unable to shellify type 'int'. Expected list, string, tuple. " + "Got: 20", + str(context_manager.exception), + ) def test_handler_creates_and_runs_bootcmd_script_with_instance_id(self): """Valid schema runs a bootcmd script with INSTANCE_ID in the env.""" cc = get_cloud() - out_file = self.tmp_path('bootcmd.out', self.new_root) + out_file = self.tmp_path("bootcmd.out", self.new_root) my_id = "b6ea0f59-e27d-49c6-9f87-79f19765a425" - valid_config = {'bootcmd': [ - 'echo {0} $INSTANCE_ID > {1}'.format(my_id, out_file)]} + valid_config = { + "bootcmd": ["echo {0} $INSTANCE_ID > {1}".format(my_id, out_file)] + } with mock.patch(self._etmpfile_path, FakeExtendedTempFile): - with self.allow_subp(['/bin/sh']): - handle('cc_bootcmd', valid_config, cc, LOG, []) - self.assertEqual(my_id + ' iid-datasource-none\n', - util.load_file(out_file)) + with self.allow_subp(["/bin/sh"]): + handle("cc_bootcmd", valid_config, cc, LOG, []) + self.assertEqual( + my_id + " iid-datasource-none\n", util.load_file(out_file) + ) def test_handler_runs_bootcmd_script_with_error(self): """When a valid script generates an error, that error is raised.""" cc = get_cloud() - valid_config = {'bootcmd': ['exit 1']} # Script with error + valid_config = {"bootcmd": ["exit 1"]} # Script with error with mock.patch(self._etmpfile_path, FakeExtendedTempFile): - with self.allow_subp(['/bin/sh']): + with self.allow_subp(["/bin/sh"]): with self.assertRaises(subp.ProcessExecutionError) as ctxt: - handle('does-not-matter', valid_config, cc, LOG, []) + handle("does-not-matter", valid_config, cc, LOG, []) self.assertIn( - 'Unexpected error while running command.\n' - "Command: ['/bin/sh',", - str(ctxt.exception)) + "Unexpected error while running command.\nCommand: ['/bin/sh',", + str(ctxt.exception), + ) self.assertIn( - 'Failed to run bootcmd module does-not-matter', - self.logs.getvalue()) + "Failed to run bootcmd module does-not-matter", + self.logs.getvalue(), + ) @skipUnlessJsonSchema() @@ -141,12 +153,14 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): def test_duplicates_are_fine_array_array(self): """Duplicated commands array/array entries are allowed.""" self.assertSchemaValid( - ["byebye", "byebye"], 'command entries can be duplicate') + ["byebye", "byebye"], "command entries can be duplicate" + ) def test_duplicates_are_fine_array_string(self): """Duplicated commands array/string entries are allowed.""" self.assertSchemaValid( - ["echo bye", "echo bye"], "command entries can be duplicate.") + ["echo bye", "echo bye"], "command entries can be duplicate." + ) # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_ca_certs.py b/tests/unittests/config/test_cc_ca_certs.py index 91b005d0..c49922e6 100644 --- a/tests/unittests/config/test_cc_ca_certs.py +++ b/tests/unittests/config/test_cc_ca_certs.py @@ -6,13 +6,9 @@ import unittest from contextlib import ExitStack from unittest import mock -from cloudinit import distros +from cloudinit import distros, helpers, subp, util from cloudinit.config import cc_ca_certs -from cloudinit import helpers -from cloudinit import subp -from cloudinit import util from tests.unittests.helpers import TestCase - from tests.unittests.util import get_cloud @@ -31,12 +27,15 @@ class TestNoConfig(unittest.TestCase): config = util.get_builtin_cfg() with ExitStack() as mocks: util_mock = mocks.enter_context( - mock.patch.object(util, 'write_file')) + mock.patch.object(util, "write_file") + ) certs_mock = mocks.enter_context( - mock.patch.object(cc_ca_certs, 'update_ca_certs')) + mock.patch.object(cc_ca_certs, "update_ca_certs") + ) - cc_ca_certs.handle(self.name, config, self.cloud_init, self.log, - self.args) + cc_ca_certs.handle( + self.name, config, self.cloud_init, self.log, self.args + ) self.assertEqual(util_mock.call_count, 0) self.assertEqual(certs_mock.call_count, 0) @@ -61,11 +60,14 @@ class TestConfig(TestCase): # Mock out the functions that actually modify the system self.mock_add = self.mocks.enter_context( - mock.patch.object(cc_ca_certs, 'add_ca_certs')) + mock.patch.object(cc_ca_certs, "add_ca_certs") + ) self.mock_update = self.mocks.enter_context( - mock.patch.object(cc_ca_certs, 'update_ca_certs')) + mock.patch.object(cc_ca_certs, "update_ca_certs") + ) self.mock_remove = self.mocks.enter_context( - mock.patch.object(cc_ca_certs, 'remove_default_ca_certs')) + mock.patch.object(cc_ca_certs, "remove_default_ca_certs") + ) def test_no_trusted_list(self): """ @@ -106,7 +108,7 @@ class TestConfig(TestCase): conf = cc_ca_certs._distro_ca_certs_configs(distro_name) cc_ca_certs.handle(self.name, config, cloud, self.log, self.args) - self.mock_add.assert_called_once_with(conf, ['CERT1']) + self.mock_add.assert_called_once_with(conf, ["CERT1"]) self.assertEqual(self.mock_update.call_count, 1) self.assertEqual(self.mock_remove.call_count, 0) @@ -120,7 +122,7 @@ class TestConfig(TestCase): conf = cc_ca_certs._distro_ca_certs_configs(distro_name) cc_ca_certs.handle(self.name, config, cloud, self.log, self.args) - self.mock_add.assert_called_once_with(conf, ['CERT1', 'CERT2']) + self.mock_add.assert_called_once_with(conf, ["CERT1", "CERT2"]) self.assertEqual(self.mock_update.call_count, 1) self.assertEqual(self.mock_remove.call_count, 0) @@ -160,20 +162,21 @@ class TestConfig(TestCase): conf = cc_ca_certs._distro_ca_certs_configs(distro_name) cc_ca_certs.handle(self.name, config, cloud, self.log, self.args) - self.mock_add.assert_called_once_with(conf, ['CERT1']) + self.mock_add.assert_called_once_with(conf, ["CERT1"]) self.assertEqual(self.mock_update.call_count, 1) self.assertEqual(self.mock_remove.call_count, 1) class TestAddCaCerts(TestCase): - def setUp(self): super(TestAddCaCerts, self).setUp() tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmpdir) - self.paths = helpers.Paths({ - 'cloud_dir': tmpdir, - }) + self.paths = helpers.Paths( + { + "cloud_dir": tmpdir, + } + ) self.add_patch("cloudinit.config.cc_ca_certs.os.stat", "m_stat") def _fetch_distro(self, kind): @@ -185,7 +188,7 @@ class TestAddCaCerts(TestCase): """Test that no certificate are written if not provided.""" for distro_name in cc_ca_certs.distros: conf = cc_ca_certs._distro_ca_certs_configs(distro_name) - with mock.patch.object(util, 'write_file') as mockobj: + with mock.patch.object(util, "write_file") as mockobj: cc_ca_certs.add_ca_certs(conf, []) self.assertEqual(mockobj.call_count, 0) @@ -204,21 +207,28 @@ class TestAddCaCerts(TestCase): with ExitStack() as mocks: mock_write = mocks.enter_context( - mock.patch.object(util, 'write_file')) + mock.patch.object(util, "write_file") + ) mock_load = mocks.enter_context( - mock.patch.object(util, 'load_file', - return_value=ca_certs_content)) + mock.patch.object( + util, "load_file", return_value=ca_certs_content + ) + ) cc_ca_certs.add_ca_certs(conf, [cert]) - mock_write.assert_has_calls([ - mock.call(conf['ca_cert_full_path'], - cert, mode=0o644)]) - if conf['ca_cert_config'] is not None: - mock_write.assert_has_calls([ - mock.call(conf['ca_cert_config'], - expected, omode="wb")]) - mock_load.assert_called_once_with(conf['ca_cert_config']) + mock_write.assert_has_calls( + [mock.call(conf["ca_cert_full_path"], cert, mode=0o644)] + ) + if conf["ca_cert_config"] is not None: + mock_write.assert_has_calls( + [ + mock.call( + conf["ca_cert_config"], expected, omode="wb" + ) + ] + ) + mock_load.assert_called_once_with(conf["ca_cert_config"]) def test_single_cert_no_trailing_cr(self): """Test adding a single certificate to the trusted CAs @@ -234,24 +244,32 @@ class TestAddCaCerts(TestCase): with ExitStack() as mocks: mock_write = mocks.enter_context( - mock.patch.object(util, 'write_file')) + mock.patch.object(util, "write_file") + ) mock_load = mocks.enter_context( - mock.patch.object(util, 'load_file', - return_value=ca_certs_content)) + mock.patch.object( + util, "load_file", return_value=ca_certs_content + ) + ) cc_ca_certs.add_ca_certs(conf, [cert]) - mock_write.assert_has_calls([ - mock.call(conf['ca_cert_full_path'], - cert, mode=0o644)]) - if conf['ca_cert_config'] is not None: - mock_write.assert_has_calls([ - mock.call(conf['ca_cert_config'], - "%s\n%s\n" % (ca_certs_content, - conf['ca_cert_filename']), - omode="wb")]) - - mock_load.assert_called_once_with(conf['ca_cert_config']) + mock_write.assert_has_calls( + [mock.call(conf["ca_cert_full_path"], cert, mode=0o644)] + ) + if conf["ca_cert_config"] is not None: + mock_write.assert_has_calls( + [ + mock.call( + conf["ca_cert_config"], + "%s\n%s\n" + % (ca_certs_content, conf["ca_cert_filename"]), + omode="wb", + ) + ] + ) + + mock_load.assert_called_once_with(conf["ca_cert_config"]) def test_single_cert_to_empty_existing_ca_file(self): """Test adding a single certificate to the trusted CAs @@ -264,18 +282,23 @@ class TestAddCaCerts(TestCase): for distro_name in cc_ca_certs.distros: conf = cc_ca_certs._distro_ca_certs_configs(distro_name) - with mock.patch.object(util, 'write_file', - autospec=True) as m_write: + with mock.patch.object( + util, "write_file", autospec=True + ) as m_write: cc_ca_certs.add_ca_certs(conf, [cert]) - m_write.assert_has_calls([ - mock.call(conf['ca_cert_full_path'], - cert, mode=0o644)]) - if conf['ca_cert_config'] is not None: - m_write.assert_has_calls([ - mock.call(conf['ca_cert_config'], - expected, omode="wb")]) + m_write.assert_has_calls( + [mock.call(conf["ca_cert_full_path"], cert, mode=0o644)] + ) + if conf["ca_cert_config"] is not None: + m_write.assert_has_calls( + [ + mock.call( + conf["ca_cert_config"], expected, omode="wb" + ) + ] + ) def test_multiple_certs(self): """Test adding multiple certificates to the trusted CAs.""" @@ -290,45 +313,61 @@ class TestAddCaCerts(TestCase): with ExitStack() as mocks: mock_write = mocks.enter_context( - mock.patch.object(util, 'write_file')) + mock.patch.object(util, "write_file") + ) mock_load = mocks.enter_context( - mock.patch.object(util, 'load_file', - return_value=ca_certs_content)) + mock.patch.object( + util, "load_file", return_value=ca_certs_content + ) + ) cc_ca_certs.add_ca_certs(conf, certs) - mock_write.assert_has_calls([ - mock.call(conf['ca_cert_full_path'], - expected_cert_file, mode=0o644)]) - if conf['ca_cert_config'] is not None: - mock_write.assert_has_calls([ - mock.call(conf['ca_cert_config'], - "%s\n%s\n" % (ca_certs_content, - conf['ca_cert_filename']), - omode='wb')]) - - mock_load.assert_called_once_with(conf['ca_cert_config']) + mock_write.assert_has_calls( + [ + mock.call( + conf["ca_cert_full_path"], + expected_cert_file, + mode=0o644, + ) + ] + ) + if conf["ca_cert_config"] is not None: + mock_write.assert_has_calls( + [ + mock.call( + conf["ca_cert_config"], + "%s\n%s\n" + % (ca_certs_content, conf["ca_cert_filename"]), + omode="wb", + ) + ] + ) + + mock_load.assert_called_once_with(conf["ca_cert_config"]) class TestUpdateCaCerts(unittest.TestCase): def test_commands(self): for distro_name in cc_ca_certs.distros: conf = cc_ca_certs._distro_ca_certs_configs(distro_name) - with mock.patch.object(subp, 'subp') as mockobj: + with mock.patch.object(subp, "subp") as mockobj: cc_ca_certs.update_ca_certs(conf) mockobj.assert_called_once_with( - conf['ca_cert_update_cmd'], capture=False) + conf["ca_cert_update_cmd"], capture=False + ) class TestRemoveDefaultCaCerts(TestCase): - def setUp(self): super(TestRemoveDefaultCaCerts, self).setUp() tmpdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmpdir) - self.paths = helpers.Paths({ - 'cloud_dir': tmpdir, - }) + self.paths = helpers.Paths( + { + "cloud_dir": tmpdir, + } + ) def test_commands(self): for distro_name in cc_ca_certs.distros: @@ -336,26 +375,35 @@ class TestRemoveDefaultCaCerts(TestCase): with ExitStack() as mocks: mock_delete = mocks.enter_context( - mock.patch.object(util, 'delete_dir_contents')) + mock.patch.object(util, "delete_dir_contents") + ) mock_write = mocks.enter_context( - mock.patch.object(util, 'write_file')) + mock.patch.object(util, "write_file") + ) mock_subp = mocks.enter_context( - mock.patch.object(subp, 'subp')) + mock.patch.object(subp, "subp") + ) cc_ca_certs.remove_default_ca_certs(distro_name, conf) - mock_delete.assert_has_calls([ - mock.call(conf['ca_cert_path']), - mock.call(conf['ca_cert_system_path'])]) + mock_delete.assert_has_calls( + [ + mock.call(conf["ca_cert_path"]), + mock.call(conf["ca_cert_system_path"]), + ] + ) - if conf['ca_cert_config'] is not None: + if conf["ca_cert_config"] is not None: mock_write.assert_called_once_with( - conf['ca_cert_config'], "", mode=0o644) + conf["ca_cert_config"], "", mode=0o644 + ) - if distro_name in ['debian', 'ubuntu']: + if distro_name in ["debian", "ubuntu"]: mock_subp.assert_called_once_with( - ('debconf-set-selections', '-'), - "ca-certificates \ -ca-certificates/trust_new_crts select no") + ("debconf-set-selections", "-"), + "ca-certificates ca-certificates/trust_new_crts" + " select no", + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_chef.py b/tests/unittests/config/test_cc_chef.py index 1c90a4fc..835974e5 100644 --- a/tests/unittests/config/test_cc_chef.py +++ b/tests/unittests/config/test_cc_chef.py @@ -1,21 +1,20 @@ # This file is part of cloud-init. See LICENSE file for license information. -import httpretty import json import logging import os -from cloudinit.config import cc_chef -from cloudinit import util +import httpretty +from cloudinit import util +from cloudinit.config import cc_chef from tests.unittests.helpers import ( - HttprettyTestCase, FilesystemMockingTestCase, + HttprettyTestCase, + cloud_init_project_dir, mock, skipIf, - cloud_init_project_dir, ) - from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -31,7 +30,6 @@ OMNIBUS_URL_HTTP = cc_chef.OMNIBUS_URL.replace("https:", "http:") class TestInstallChefOmnibus(HttprettyTestCase): - def setUp(self): super(TestInstallChefOmnibus, self).setUp() self.new_root = self.tmp_dir() @@ -41,70 +39,81 @@ class TestInstallChefOmnibus(HttprettyTestCase): """install_chef_from_omnibus calls subp_blob_in_tempfile.""" response = b'#!/bin/bash\necho "Hi Mom"' httpretty.register_uri( - httpretty.GET, cc_chef.OMNIBUS_URL, body=response, status=200) + httpretty.GET, cc_chef.OMNIBUS_URL, body=response, status=200 + ) ret = (None, None) # stdout, stderr but capture=False - with mock.patch("cloudinit.config.cc_chef.subp_blob_in_tempfile", - return_value=ret) as m_subp_blob: + with mock.patch( + "cloudinit.config.cc_chef.subp_blob_in_tempfile", return_value=ret + ) as m_subp_blob: cc_chef.install_chef_from_omnibus() # admittedly whitebox, but assuming subp_blob_in_tempfile works # this should be fine. self.assertEqual( - [mock.call(blob=response, args=[], basename='chef-omnibus-install', - capture=False)], - m_subp_blob.call_args_list) - - @mock.patch('cloudinit.config.cc_chef.url_helper.readurl') - @mock.patch('cloudinit.config.cc_chef.subp_blob_in_tempfile') + [ + mock.call( + blob=response, + args=[], + basename="chef-omnibus-install", + capture=False, + ) + ], + m_subp_blob.call_args_list, + ) + + @mock.patch("cloudinit.config.cc_chef.url_helper.readurl") + @mock.patch("cloudinit.config.cc_chef.subp_blob_in_tempfile") def test_install_chef_from_omnibus_retries_url(self, m_subp_blob, m_rdurl): """install_chef_from_omnibus retries OMNIBUS_URL upon failure.""" class FakeURLResponse(object): contents = '#!/bin/bash\necho "Hi Mom" > {0}/chef.out'.format( - self.new_root) + self.new_root + ) m_rdurl.return_value = FakeURLResponse() cc_chef.install_chef_from_omnibus() - expected_kwargs = {'retries': cc_chef.OMNIBUS_URL_RETRIES, - 'url': cc_chef.OMNIBUS_URL} + expected_kwargs = { + "retries": cc_chef.OMNIBUS_URL_RETRIES, + "url": cc_chef.OMNIBUS_URL, + } self.assertCountEqual(expected_kwargs, m_rdurl.call_args_list[0][1]) cc_chef.install_chef_from_omnibus(retries=10) - expected_kwargs = {'retries': 10, - 'url': cc_chef.OMNIBUS_URL} + expected_kwargs = {"retries": 10, "url": cc_chef.OMNIBUS_URL} self.assertCountEqual(expected_kwargs, m_rdurl.call_args_list[1][1]) expected_subp_kwargs = { - 'args': ['-v', '2.0'], - 'basename': 'chef-omnibus-install', - 'blob': m_rdurl.return_value.contents, - 'capture': False + "args": ["-v", "2.0"], + "basename": "chef-omnibus-install", + "blob": m_rdurl.return_value.contents, + "capture": False, } self.assertCountEqual( - expected_subp_kwargs, - m_subp_blob.call_args_list[0][1]) + expected_subp_kwargs, m_subp_blob.call_args_list[0][1] + ) @mock.patch("cloudinit.config.cc_chef.OMNIBUS_URL", OMNIBUS_URL_HTTP) - @mock.patch('cloudinit.config.cc_chef.subp_blob_in_tempfile') + @mock.patch("cloudinit.config.cc_chef.subp_blob_in_tempfile") def test_install_chef_from_omnibus_has_omnibus_version(self, m_subp_blob): """install_chef_from_omnibus provides version arg to OMNIBUS_URL.""" - chef_outfile = self.tmp_path('chef.out', self.new_root) + chef_outfile = self.tmp_path("chef.out", self.new_root) response = '#!/bin/bash\necho "Hi Mom" > {0}'.format(chef_outfile) httpretty.register_uri( - httpretty.GET, cc_chef.OMNIBUS_URL, body=response) - cc_chef.install_chef_from_omnibus(omnibus_version='2.0') + httpretty.GET, cc_chef.OMNIBUS_URL, body=response + ) + cc_chef.install_chef_from_omnibus(omnibus_version="2.0") called_kwargs = m_subp_blob.call_args_list[0][1] expected_kwargs = { - 'args': ['-v', '2.0'], - 'basename': 'chef-omnibus-install', - 'blob': response, - 'capture': False + "args": ["-v", "2.0"], + "basename": "chef-omnibus-install", + "blob": response, + "capture": False, } self.assertCountEqual(expected_kwargs, called_kwargs) class TestChef(FilesystemMockingTestCase): - def setUp(self): super(TestChef, self).setUp() self.tmp = self.tmp_dir() @@ -114,12 +123,13 @@ class TestChef(FilesystemMockingTestCase): self.patchOS(self.tmp) cfg = {} - cc_chef.handle('chef', cfg, get_cloud(), LOG, []) + cc_chef.handle("chef", cfg, get_cloud(), LOG, []) for d in cc_chef.CHEF_DIRS: self.assertFalse(os.path.isdir(d)) - @skipIf(not os.path.isfile(CLIENT_TEMPL), - CLIENT_TEMPL + " is not available") + @skipIf( + not os.path.isfile(CLIENT_TEMPL), CLIENT_TEMPL + " is not available" + ) def test_basic_config(self): """ test basic config looks sane @@ -147,26 +157,27 @@ class TestChef(FilesystemMockingTestCase): self.patchUtils(self.tmp) self.patchOS(self.tmp) - util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file) + util.write_file("/etc/cloud/templates/chef_client.rb.tmpl", tpl_file) cfg = { - 'chef': { - 'chef_license': "accept", - 'server_url': 'localhost', - 'validation_name': 'bob', - 'validation_key': "/etc/chef/vkey.pem", - 'validation_cert': "this is my cert", - 'encrypted_data_bag_secret': - '/etc/chef/encrypted_data_bag_secret' + "chef": { + "chef_license": "accept", + "server_url": "localhost", + "validation_name": "bob", + "validation_key": "/etc/chef/vkey.pem", + "validation_cert": "this is my cert", + "encrypted_data_bag_secret": ( + "/etc/chef/encrypted_data_bag_secret" + ), }, } - cc_chef.handle('chef', cfg, get_cloud(), LOG, []) + cc_chef.handle("chef", cfg, get_cloud(), LOG, []) for d in cc_chef.CHEF_DIRS: self.assertTrue(os.path.isdir(d)) c = util.load_file(cc_chef.CHEF_RB_PATH) # the content of these keys is not expected to be rendered to tmpl - unrendered_keys = ('validation_cert',) - for k, v in cfg['chef'].items(): + unrendered_keys = ("validation_cert",) + for k, v in cfg["chef"].items(): if k in unrendered_keys: continue self.assertIn(v, c) @@ -174,7 +185,7 @@ class TestChef(FilesystemMockingTestCase): if k in unrendered_keys: continue # the value from the cfg overrides that in the default - val = cfg['chef'].get(k, v) + val = cfg["chef"].get(k, v) if isinstance(val, str): self.assertIn(val, c) c = util.load_file(cc_chef.CHEF_FB_PATH) @@ -185,64 +196,68 @@ class TestChef(FilesystemMockingTestCase): self.patchOS(self.tmp) cfg = { - 'chef': { - 'server_url': 'localhost', - 'validation_name': 'bob', - 'run_list': ['a', 'b', 'c'], - 'initial_attributes': { - 'c': 'd', - } + "chef": { + "server_url": "localhost", + "validation_name": "bob", + "run_list": ["a", "b", "c"], + "initial_attributes": { + "c": "d", + }, }, } - cc_chef.handle('chef', cfg, get_cloud(), LOG, []) + cc_chef.handle("chef", cfg, get_cloud(), LOG, []) c = util.load_file(cc_chef.CHEF_FB_PATH) self.assertEqual( { - 'run_list': ['a', 'b', 'c'], - 'c': 'd', - }, json.loads(c)) + "run_list": ["a", "b", "c"], + "c": "d", + }, + json.loads(c), + ) - @skipIf(not os.path.isfile(CLIENT_TEMPL), - CLIENT_TEMPL + " is not available") + @skipIf( + not os.path.isfile(CLIENT_TEMPL), CLIENT_TEMPL + " is not available" + ) def test_template_deletes(self): tpl_file = util.load_file(CLIENT_TEMPL) self.patchUtils(self.tmp) self.patchOS(self.tmp) - util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file) + util.write_file("/etc/cloud/templates/chef_client.rb.tmpl", tpl_file) cfg = { - 'chef': { - 'server_url': 'localhost', - 'validation_name': 'bob', - 'json_attribs': None, - 'show_time': None, + "chef": { + "server_url": "localhost", + "validation_name": "bob", + "json_attribs": None, + "show_time": None, }, } - cc_chef.handle('chef', cfg, get_cloud(), LOG, []) + cc_chef.handle("chef", cfg, get_cloud(), LOG, []) c = util.load_file(cc_chef.CHEF_RB_PATH) - self.assertNotIn('json_attribs', c) - self.assertNotIn('Formatter.show_time', c) + self.assertNotIn("json_attribs", c) + self.assertNotIn("Formatter.show_time", c) - @skipIf(not os.path.isfile(CLIENT_TEMPL), - CLIENT_TEMPL + " is not available") + @skipIf( + not os.path.isfile(CLIENT_TEMPL), CLIENT_TEMPL + " is not available" + ) def test_validation_cert_and_validation_key(self): # test validation_cert content is written to validation_key path tpl_file = util.load_file(CLIENT_TEMPL) self.patchUtils(self.tmp) self.patchOS(self.tmp) - util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file) - v_path = '/etc/chef/vkey.pem' - v_cert = 'this is my cert' + util.write_file("/etc/cloud/templates/chef_client.rb.tmpl", tpl_file) + v_path = "/etc/chef/vkey.pem" + v_cert = "this is my cert" cfg = { - 'chef': { - 'server_url': 'localhost', - 'validation_name': 'bob', - 'validation_key': v_path, - 'validation_cert': v_cert + "chef": { + "server_url": "localhost", + "validation_name": "bob", + "validation_key": v_path, + "validation_cert": v_cert, }, } - cc_chef.handle('chef', cfg, get_cloud(), LOG, []) + cc_chef.handle("chef", cfg, get_cloud(), LOG, []) content = util.load_file(cc_chef.CHEF_RB_PATH) self.assertIn(v_path, content) util.load_file(v_path) @@ -254,23 +269,24 @@ class TestChef(FilesystemMockingTestCase): self.patchUtils(self.tmp) self.patchOS(self.tmp) - v_path = '/etc/chef/vkey.pem' + v_path = "/etc/chef/vkey.pem" v_cert = "system" expected_cert = "this is the system file certificate" cfg = { - 'chef': { - 'server_url': 'localhost', - 'validation_name': 'bob', - 'validation_key': v_path, - 'validation_cert': v_cert + "chef": { + "server_url": "localhost", + "validation_name": "bob", + "validation_key": v_path, + "validation_cert": v_cert, }, } - util.write_file('/etc/cloud/templates/chef_client.rb.tmpl', tpl_file) + util.write_file("/etc/cloud/templates/chef_client.rb.tmpl", tpl_file) util.write_file(v_path, expected_cert) - cc_chef.handle('chef', cfg, get_cloud(), LOG, []) + cc_chef.handle("chef", cfg, get_cloud(), LOG, []) content = util.load_file(cc_chef.CHEF_RB_PATH) self.assertIn(v_path, content) util.load_file(v_path) self.assertEqual(expected_cert, util.load_file(v_path)) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_debug.py b/tests/unittests/config/test_cc_debug.py index 174f772f..79a88561 100644 --- a/tests/unittests/config/test_cc_debug.py +++ b/tests/unittests/config/test_cc_debug.py @@ -7,14 +7,13 @@ import tempfile from cloudinit import util from cloudinit.config import cc_debug -from tests.unittests.helpers import (FilesystemMockingTestCase, mock) - +from tests.unittests.helpers import FilesystemMockingTestCase, mock from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) -@mock.patch('cloudinit.distros.debian.read_system_locale') +@mock.patch("cloudinit.distros.debian.read_system_locale") class TestDebug(FilesystemMockingTestCase): def setUp(self): super(TestDebug, self).setUp() @@ -23,37 +22,39 @@ class TestDebug(FilesystemMockingTestCase): self.patchUtils(self.new_root) def test_debug_write(self, m_locale): - m_locale.return_value = 'en_US.UTF-8' + m_locale.return_value = "en_US.UTF-8" cfg = { - 'abc': '123', - 'c': '\u20a0', - 'debug': { - 'verbose': True, + "abc": "123", + "c": "\u20a0", + "debug": { + "verbose": True, # Does not actually write here due to mocking... - 'output': '/var/log/cloud-init-debug.log', + "output": "/var/log/cloud-init-debug.log", }, } cc = get_cloud() - cc_debug.handle('cc_debug', cfg, cc, LOG, []) - contents = util.load_file('/var/log/cloud-init-debug.log') + cc_debug.handle("cc_debug", cfg, cc, LOG, []) + contents = util.load_file("/var/log/cloud-init-debug.log") # Some basic sanity tests... self.assertNotEqual(0, len(contents)) for k in cfg.keys(): self.assertIn(k, contents) def test_debug_no_write(self, m_locale): - m_locale.return_value = 'en_US.UTF-8' + m_locale.return_value = "en_US.UTF-8" cfg = { - 'abc': '123', - 'debug': { - 'verbose': False, + "abc": "123", + "debug": { + "verbose": False, # Does not actually write here due to mocking... - 'output': '/var/log/cloud-init-debug.log', + "output": "/var/log/cloud-init-debug.log", }, } cc = get_cloud() - cc_debug.handle('cc_debug', cfg, cc, LOG, []) - self.assertRaises(IOError, - util.load_file, '/var/log/cloud-init-debug.log') + cc_debug.handle("cc_debug", cfg, cc, LOG, []) + self.assertRaises( + IOError, util.load_file, "/var/log/cloud-init-debug.log" + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_disable_ec2_metadata.py b/tests/unittests/config/test_cc_disable_ec2_metadata.py index 7a794845..3c3313a7 100644 --- a/tests/unittests/config/test_cc_disable_ec2_metadata.py +++ b/tests/unittests/config/test_cc_disable_ec2_metadata.py @@ -2,47 +2,49 @@ """Tests cc_disable_ec2_metadata handler""" -import cloudinit.config.cc_disable_ec2_metadata as ec2_meta +import logging +import cloudinit.config.cc_disable_ec2_metadata as ec2_meta from tests.unittests.helpers import CiTestCase, mock -import logging - LOG = logging.getLogger(__name__) -DISABLE_CFG = {'disable_ec2_metadata': 'true'} +DISABLE_CFG = {"disable_ec2_metadata": "true"} class TestEC2MetadataRoute(CiTestCase): - - @mock.patch('cloudinit.config.cc_disable_ec2_metadata.subp.which') - @mock.patch('cloudinit.config.cc_disable_ec2_metadata.subp.subp') + @mock.patch("cloudinit.config.cc_disable_ec2_metadata.subp.which") + @mock.patch("cloudinit.config.cc_disable_ec2_metadata.subp.subp") def test_disable_ifconfig(self, m_subp, m_which): """Set the route if ifconfig command is available""" - m_which.side_effect = lambda x: x if x == 'ifconfig' else None - ec2_meta.handle('foo', DISABLE_CFG, None, LOG, None) + m_which.side_effect = lambda x: x if x == "ifconfig" else None + ec2_meta.handle("foo", DISABLE_CFG, None, LOG, None) m_subp.assert_called_with( - ['route', 'add', '-host', '169.254.169.254', 'reject'], - capture=False) + ["route", "add", "-host", "169.254.169.254", "reject"], + capture=False, + ) - @mock.patch('cloudinit.config.cc_disable_ec2_metadata.subp.which') - @mock.patch('cloudinit.config.cc_disable_ec2_metadata.subp.subp') + @mock.patch("cloudinit.config.cc_disable_ec2_metadata.subp.which") + @mock.patch("cloudinit.config.cc_disable_ec2_metadata.subp.subp") def test_disable_ip(self, m_subp, m_which): """Set the route if ip command is available""" - m_which.side_effect = lambda x: x if x == 'ip' else None - ec2_meta.handle('foo', DISABLE_CFG, None, LOG, None) + m_which.side_effect = lambda x: x if x == "ip" else None + ec2_meta.handle("foo", DISABLE_CFG, None, LOG, None) m_subp.assert_called_with( - ['ip', 'route', 'add', 'prohibit', '169.254.169.254'], - capture=False) + ["ip", "route", "add", "prohibit", "169.254.169.254"], + capture=False, + ) - @mock.patch('cloudinit.config.cc_disable_ec2_metadata.subp.which') - @mock.patch('cloudinit.config.cc_disable_ec2_metadata.subp.subp') + @mock.patch("cloudinit.config.cc_disable_ec2_metadata.subp.which") + @mock.patch("cloudinit.config.cc_disable_ec2_metadata.subp.subp") def test_disable_no_tool(self, m_subp, m_which): """Log error when neither route nor ip commands are available""" m_which.return_value = None # Find neither ifconfig nor ip - ec2_meta.handle('foo', DISABLE_CFG, None, LOG, None) + ec2_meta.handle("foo", DISABLE_CFG, None, LOG, None) self.assertEqual( - [mock.call('ip'), mock.call('ifconfig')], m_which.call_args_list) + [mock.call("ip"), mock.call("ifconfig")], m_which.call_args_list + ) m_subp.assert_not_called() + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_disk_setup.py b/tests/unittests/config/test_cc_disk_setup.py index fa565559..8a8d7195 100644 --- a/tests/unittests/config/test_cc_disk_setup.py +++ b/tests/unittests/config/test_cc_disk_setup.py @@ -3,19 +3,20 @@ import random from cloudinit.config import cc_disk_setup -from tests.unittests.helpers import CiTestCase, ExitStack, mock, TestCase +from tests.unittests.helpers import CiTestCase, ExitStack, TestCase, mock class TestIsDiskUsed(TestCase): - def setUp(self): super(TestIsDiskUsed, self).setUp() self.patches = ExitStack() - mod_name = 'cloudinit.config.cc_disk_setup' + mod_name = "cloudinit.config.cc_disk_setup" self.enumerate_disk = self.patches.enter_context( - mock.patch('{0}.enumerate_disk'.format(mod_name))) + mock.patch("{0}.enumerate_disk".format(mod_name)) + ) self.check_fs = self.patches.enter_context( - mock.patch('{0}.check_fs'.format(mod_name))) + mock.patch("{0}.check_fs".format(mod_name)) + ) def tearDown(self): super(TestIsDiskUsed, self).tearDown() @@ -29,7 +30,10 @@ class TestIsDiskUsed(TestCase): def test_valid_filesystem_returns_true(self): self.enumerate_disk.return_value = (mock.MagicMock() for _ in range(1)) self.check_fs.return_value = ( - mock.MagicMock(), 'ext4', mock.MagicMock()) + mock.MagicMock(), + "ext4", + mock.MagicMock(), + ) self.assertTrue(cc_disk_setup.is_disk_used(mock.MagicMock())) def test_one_child_nodes_and_no_fs_returns_false(self): @@ -39,12 +43,12 @@ class TestIsDiskUsed(TestCase): class TestGetMbrHddSize(TestCase): - def setUp(self): super(TestGetMbrHddSize, self).setUp() self.patches = ExitStack() self.subp = self.patches.enter_context( - mock.patch.object(cc_disk_setup.subp, 'subp')) + mock.patch.object(cc_disk_setup.subp, "subp") + ) def tearDown(self): super(TestGetMbrHddSize, self).tearDown() @@ -53,11 +57,11 @@ class TestGetMbrHddSize(TestCase): def _configure_subp_mock(self, hdd_size_in_bytes, sector_size_in_bytes): def _subp(cmd, *args, **kwargs): self.assertEqual(3, len(cmd)) - if '--getsize64' in cmd: + if "--getsize64" in cmd: return hdd_size_in_bytes, None - elif '--getss' in cmd: + elif "--getss" in cmd: return sector_size_in_bytes, None - raise Exception('Unexpected blockdev command called') + raise Exception("Unexpected blockdev command called") self.subp.side_effect = _subp @@ -65,8 +69,9 @@ class TestGetMbrHddSize(TestCase): size_in_bytes = random.randint(10000, 10000000) * 512 size_in_sectors = size_in_bytes / sector_size self._configure_subp_mock(size_in_bytes, sector_size) - self.assertEqual(size_in_sectors, - cc_disk_setup.get_hdd_size('/dev/sda1')) + self.assertEqual( + size_in_sectors, cc_disk_setup.get_hdd_size("/dev/sda1") + ) def test_size_for_512_byte_sectors(self): self._test_for_sector_size(512) @@ -82,98 +87,116 @@ class TestGetMbrHddSize(TestCase): class TestGetPartitionMbrLayout(TestCase): - def test_single_partition_using_boolean(self): - self.assertEqual('0,', - cc_disk_setup.get_partition_mbr_layout(1000, True)) + self.assertEqual( + "0,", cc_disk_setup.get_partition_mbr_layout(1000, True) + ) def test_single_partition_using_list(self): disk_size = random.randint(1000000, 1000000000000) self.assertEqual( - ',,83', - cc_disk_setup.get_partition_mbr_layout(disk_size, [100])) + ",,83", cc_disk_setup.get_partition_mbr_layout(disk_size, [100]) + ) def test_half_and_half(self): disk_size = random.randint(1000000, 1000000000000) expected_partition_size = int(float(disk_size) / 2) self.assertEqual( - ',{0},83\n,,83'.format(expected_partition_size), - cc_disk_setup.get_partition_mbr_layout(disk_size, [50, 50])) + ",{0},83\n,,83".format(expected_partition_size), + cc_disk_setup.get_partition_mbr_layout(disk_size, [50, 50]), + ) def test_thirds_with_different_partition_type(self): disk_size = random.randint(1000000, 1000000000000) expected_partition_size = int(float(disk_size) * 0.33) self.assertEqual( - ',{0},83\n,,82'.format(expected_partition_size), - cc_disk_setup.get_partition_mbr_layout(disk_size, [33, [66, 82]])) + ",{0},83\n,,82".format(expected_partition_size), + cc_disk_setup.get_partition_mbr_layout(disk_size, [33, [66, 82]]), + ) class TestUpdateFsSetupDevices(TestCase): def test_regression_1634678(self): # Cf. https://bugs.launchpad.net/cloud-init/+bug/1634678 fs_setup = { - 'partition': 'auto', - 'device': '/dev/xvdb1', - 'overwrite': False, - 'label': 'test', - 'filesystem': 'ext4' + "partition": "auto", + "device": "/dev/xvdb1", + "overwrite": False, + "label": "test", + "filesystem": "ext4", } - cc_disk_setup.update_fs_setup_devices([fs_setup], - lambda device: device) + cc_disk_setup.update_fs_setup_devices( + [fs_setup], lambda device: device + ) - self.assertEqual({ - '_origname': '/dev/xvdb1', - 'partition': 'auto', - 'device': '/dev/xvdb1', - 'overwrite': False, - 'label': 'test', - 'filesystem': 'ext4' - }, fs_setup) + self.assertEqual( + { + "_origname": "/dev/xvdb1", + "partition": "auto", + "device": "/dev/xvdb1", + "overwrite": False, + "label": "test", + "filesystem": "ext4", + }, + fs_setup, + ) def test_dotted_devname(self): fs_setup = { - 'partition': 'auto', - 'device': 'ephemeral0.0', - 'label': 'test2', - 'filesystem': 'xfs' + "partition": "auto", + "device": "ephemeral0.0", + "label": "test2", + "filesystem": "xfs", } - cc_disk_setup.update_fs_setup_devices([fs_setup], - lambda device: device) + cc_disk_setup.update_fs_setup_devices( + [fs_setup], lambda device: device + ) - self.assertEqual({ - '_origname': 'ephemeral0.0', - '_partition': 'auto', - 'partition': '0', - 'device': 'ephemeral0', - 'label': 'test2', - 'filesystem': 'xfs' - }, fs_setup) + self.assertEqual( + { + "_origname": "ephemeral0.0", + "_partition": "auto", + "partition": "0", + "device": "ephemeral0", + "label": "test2", + "filesystem": "xfs", + }, + fs_setup, + ) def test_dotted_devname_populates_partition(self): fs_setup = { - 'device': 'ephemeral0.1', - 'label': 'test2', - 'filesystem': 'xfs' + "device": "ephemeral0.1", + "label": "test2", + "filesystem": "xfs", } - cc_disk_setup.update_fs_setup_devices([fs_setup], - lambda device: device) - self.assertEqual({ - '_origname': 'ephemeral0.1', - 'device': 'ephemeral0', - 'partition': '1', - 'label': 'test2', - 'filesystem': 'xfs' - }, fs_setup) - - -@mock.patch('cloudinit.config.cc_disk_setup.assert_and_settle_device', - return_value=None) -@mock.patch('cloudinit.config.cc_disk_setup.find_device_node', - return_value=('/dev/xdb1', False)) -@mock.patch('cloudinit.config.cc_disk_setup.device_type', return_value=None) -@mock.patch('cloudinit.config.cc_disk_setup.subp.subp', return_value=('', '')) + cc_disk_setup.update_fs_setup_devices( + [fs_setup], lambda device: device + ) + self.assertEqual( + { + "_origname": "ephemeral0.1", + "device": "ephemeral0", + "partition": "1", + "label": "test2", + "filesystem": "xfs", + }, + fs_setup, + ) + + +@mock.patch( + "cloudinit.config.cc_disk_setup.assert_and_settle_device", + return_value=None, +) +@mock.patch( + "cloudinit.config.cc_disk_setup.find_device_node", + return_value=("/dev/xdb1", False), +) +@mock.patch("cloudinit.config.cc_disk_setup.device_type", return_value=None) +@mock.patch("cloudinit.config.cc_disk_setup.subp.subp", return_value=("", "")) class TestMkfsCommandHandling(CiTestCase): with_logs = True @@ -181,63 +204,84 @@ class TestMkfsCommandHandling(CiTestCase): def test_with_cmd(self, subp, *args): """mkfs honors cmd and logs warnings when extra_opts or overwrite are provided.""" - cc_disk_setup.mkfs({ - 'cmd': 'mkfs -t %(filesystem)s -L %(label)s %(device)s', - 'filesystem': 'ext4', - 'device': '/dev/xdb1', - 'label': 'with_cmd', - 'extra_opts': ['should', 'generate', 'warning'], - 'overwrite': 'should generate warning too' - }) + cc_disk_setup.mkfs( + { + "cmd": "mkfs -t %(filesystem)s -L %(label)s %(device)s", + "filesystem": "ext4", + "device": "/dev/xdb1", + "label": "with_cmd", + "extra_opts": ["should", "generate", "warning"], + "overwrite": "should generate warning too", + } + ) self.assertIn( - 'extra_opts ' + - 'ignored because cmd was specified: mkfs -t ext4 -L with_cmd ' + - '/dev/xdb1', - self.logs.getvalue()) + "extra_opts " + + "ignored because cmd was specified: mkfs -t ext4 -L with_cmd " + + "/dev/xdb1", + self.logs.getvalue(), + ) self.assertIn( - 'overwrite ' + - 'ignored because cmd was specified: mkfs -t ext4 -L with_cmd ' + - '/dev/xdb1', - self.logs.getvalue()) + "overwrite " + + "ignored because cmd was specified: mkfs -t ext4 -L with_cmd " + + "/dev/xdb1", + self.logs.getvalue(), + ) subp.assert_called_once_with( - 'mkfs -t ext4 -L with_cmd /dev/xdb1', shell=True) + "mkfs -t ext4 -L with_cmd /dev/xdb1", shell=True + ) - @mock.patch('cloudinit.config.cc_disk_setup.subp.which') + @mock.patch("cloudinit.config.cc_disk_setup.subp.which") def test_overwrite_and_extra_opts_without_cmd(self, m_which, subp, *args): """mkfs observes extra_opts and overwrite settings when cmd is not present.""" - m_which.side_effect = lambda p: {'mkfs.ext4': '/sbin/mkfs.ext4'}[p] - cc_disk_setup.mkfs({ - 'filesystem': 'ext4', - 'device': '/dev/xdb1', - 'label': 'without_cmd', - 'extra_opts': ['are', 'added'], - 'overwrite': True - }) + m_which.side_effect = lambda p: {"mkfs.ext4": "/sbin/mkfs.ext4"}[p] + cc_disk_setup.mkfs( + { + "filesystem": "ext4", + "device": "/dev/xdb1", + "label": "without_cmd", + "extra_opts": ["are", "added"], + "overwrite": True, + } + ) subp.assert_called_once_with( - ['/sbin/mkfs.ext4', '/dev/xdb1', - '-L', 'without_cmd', '-F', 'are', 'added'], - shell=False) - - @mock.patch('cloudinit.config.cc_disk_setup.subp.which') + [ + "/sbin/mkfs.ext4", + "/dev/xdb1", + "-L", + "without_cmd", + "-F", + "are", + "added", + ], + shell=False, + ) + + @mock.patch("cloudinit.config.cc_disk_setup.subp.which") def test_mkswap(self, m_which, subp, *args): """mkfs observes extra_opts and overwrite settings when cmd is not present.""" - m_which.side_effect = iter([None, '/sbin/mkswap']) - cc_disk_setup.mkfs({ - 'filesystem': 'swap', - 'device': '/dev/xdb1', - 'label': 'swap', - 'overwrite': True, - }) - - self.assertEqual([mock.call('mkfs.swap'), mock.call('mkswap')], - m_which.call_args_list) + m_which.side_effect = iter([None, "/sbin/mkswap"]) + cc_disk_setup.mkfs( + { + "filesystem": "swap", + "device": "/dev/xdb1", + "label": "swap", + "overwrite": True, + } + ) + + self.assertEqual( + [mock.call("mkfs.swap"), mock.call("mkswap")], + m_which.call_args_list, + ) subp.assert_called_once_with( - ['/sbin/mkswap', '/dev/xdb1', '-L', 'swap', '-f'], shell=False) + ["/sbin/mkswap", "/dev/xdb1", "-L", "swap", "-f"], shell=False + ) + # # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_growpart.py b/tests/unittests/config/test_cc_growpart.py index b007f24f..ba66f136 100644 --- a/tests/unittests/config/test_cc_growpart.py +++ b/tests/unittests/config/test_cc_growpart.py @@ -1,21 +1,18 @@ # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit import cloud -from cloudinit.config import cc_growpart -from cloudinit import subp -from cloudinit import temp_utils - -from tests.unittests.helpers import TestCase - import errno import logging import os -import shutil import re +import shutil +import stat import unittest from contextlib import ExitStack from unittest import mock -import stat + +from cloudinit import cloud, subp, temp_utils +from cloudinit.config import cc_growpart +from tests.unittests.helpers import TestCase # growpart: # mode: auto # off, on, auto, 'growpart' @@ -62,7 +59,8 @@ usage: gpart add -t type [-a alignment] [-b start] <SNIP> geom class Dir: - '''Stub object''' + """Stub object""" + def __init__(self, name): self.name = name self.st_mode = name @@ -75,9 +73,13 @@ class Dir: class Scanner: - '''Stub object''' + """Stub object""" + def __enter__(self): - return (Dir(''), Dir(''),) + return ( + Dir(""), + Dir(""), + ) def __exit__(self, *args): pass @@ -97,11 +99,12 @@ class TestDisabled(unittest.TestCase): # Test that nothing is done if mode is off. # this really only verifies that resizer_factory isn't called - config = {'growpart': {'mode': 'off'}} + config = {"growpart": {"mode": "off"}} - with mock.patch.object(cc_growpart, 'resizer_factory') as mockobj: - self.handle(self.name, config, self.cloud_init, self.log, - self.args) + with mock.patch.object(cc_growpart, "resizer_factory") as mockobj: + self.handle( + self.name, config, self.cloud_init, self.log, self.args + ) self.assertEqual(mockobj.call_count, 0) @@ -116,9 +119,9 @@ class TestConfig(TestCase): self.cloud_init = None self.handle = cc_growpart.handle - self.tmppath = '/tmp/cloudinit-test-file' - self.tmpdir = os.scandir('/tmp') - self.tmpfile = open(self.tmppath, 'w') + self.tmppath = "/tmp/cloudinit-test-file" + self.tmpdir = os.scandir("/tmp") + self.tmpfile = open(self.tmppath, "w") def tearDown(self): self.tmpfile.close() @@ -127,110 +130,143 @@ class TestConfig(TestCase): @mock.patch.dict("os.environ", clear=True) def test_no_resizers_auto_is_fine(self): with mock.patch.object( - subp, 'subp', - return_value=(HELP_GROWPART_NO_RESIZE, "")) as mockobj: - - config = {'growpart': {'mode': 'auto'}} - self.handle(self.name, config, self.cloud_init, self.log, - self.args) - - mockobj.assert_has_calls([ - mock.call(['growpart', '--help'], env={'LANG': 'C'}), - mock.call(['gpart', 'help'], env={'LANG': 'C'}, rcs=[0, 1])]) + subp, "subp", return_value=(HELP_GROWPART_NO_RESIZE, "") + ) as mockobj: + + config = {"growpart": {"mode": "auto"}} + self.handle( + self.name, config, self.cloud_init, self.log, self.args + ) + + mockobj.assert_has_calls( + [ + mock.call(["growpart", "--help"], env={"LANG": "C"}), + mock.call( + ["gpart", "help"], env={"LANG": "C"}, rcs=[0, 1] + ), + ] + ) @mock.patch.dict("os.environ", clear=True) def test_no_resizers_mode_growpart_is_exception(self): with mock.patch.object( - subp, 'subp', - return_value=(HELP_GROWPART_NO_RESIZE, "")) as mockobj: - config = {'growpart': {'mode': "growpart"}} + subp, "subp", return_value=(HELP_GROWPART_NO_RESIZE, "") + ) as mockobj: + config = {"growpart": {"mode": "growpart"}} self.assertRaises( - ValueError, self.handle, self.name, config, - self.cloud_init, self.log, self.args) + ValueError, + self.handle, + self.name, + config, + self.cloud_init, + self.log, + self.args, + ) mockobj.assert_called_once_with( - ['growpart', '--help'], env={'LANG': 'C'}) + ["growpart", "--help"], env={"LANG": "C"} + ) @mock.patch.dict("os.environ", clear=True) def test_mode_auto_prefers_growpart(self): with mock.patch.object( - subp, 'subp', - return_value=(HELP_GROWPART_RESIZE, "")) as mockobj: + subp, "subp", return_value=(HELP_GROWPART_RESIZE, "") + ) as mockobj: ret = cc_growpart.resizer_factory(mode="auto") self.assertIsInstance(ret, cc_growpart.ResizeGrowPart) mockobj.assert_called_once_with( - ['growpart', '--help'], env={'LANG': 'C'}) - - @mock.patch.dict("os.environ", {'LANG': 'cs_CZ.UTF-8'}, clear=True) - @mock.patch.object(temp_utils, 'mkdtemp', return_value='/tmp/much-random') - @mock.patch.object(stat, 'S_ISDIR', return_value=False) - @mock.patch.object(os.path, 'samestat', return_value=True) - @mock.patch.object(os.path, "join", return_value='/tmp') - @mock.patch.object(os, 'scandir', return_value=Scanner()) - @mock.patch.object(os, 'mkdir') - @mock.patch.object(os, 'unlink') - @mock.patch.object(os, 'rmdir') - @mock.patch.object(os, 'open', return_value=1) - @mock.patch.object(os, 'close') - @mock.patch.object(shutil, 'rmtree') - @mock.patch.object(os, 'lseek', return_value=1024) - @mock.patch.object(os, 'lstat', return_value='interesting metadata') + ["growpart", "--help"], env={"LANG": "C"} + ) + + @mock.patch.dict("os.environ", {"LANG": "cs_CZ.UTF-8"}, clear=True) + @mock.patch.object(temp_utils, "mkdtemp", return_value="/tmp/much-random") + @mock.patch.object(stat, "S_ISDIR", return_value=False) + @mock.patch.object(os.path, "samestat", return_value=True) + @mock.patch.object(os.path, "join", return_value="/tmp") + @mock.patch.object(os, "scandir", return_value=Scanner()) + @mock.patch.object(os, "mkdir") + @mock.patch.object(os, "unlink") + @mock.patch.object(os, "rmdir") + @mock.patch.object(os, "open", return_value=1) + @mock.patch.object(os, "close") + @mock.patch.object(shutil, "rmtree") + @mock.patch.object(os, "lseek", return_value=1024) + @mock.patch.object(os, "lstat", return_value="interesting metadata") def test_force_lang_check_tempfile(self, *args, **kwargs): with mock.patch.object( - subp, - 'subp', - return_value=(HELP_GROWPART_RESIZE, "")) as mockobj: + subp, "subp", return_value=(HELP_GROWPART_RESIZE, "") + ) as mockobj: ret = cc_growpart.resizer_factory(mode="auto") self.assertIsInstance(ret, cc_growpart.ResizeGrowPart) - diskdev = '/dev/sdb' + diskdev = "/dev/sdb" partnum = 1 - partdev = '/dev/sdb' + partdev = "/dev/sdb" ret.resize(diskdev, partnum, partdev) - mockobj.assert_has_calls([ - mock.call( - ["growpart", '--dry-run', diskdev, partnum], - env={'LANG': 'C', 'TMPDIR': '/tmp'}), - mock.call( - ["growpart", diskdev, partnum], - env={'LANG': 'C', 'TMPDIR': '/tmp'}), - ]) - - @mock.patch.dict("os.environ", {'LANG': 'cs_CZ.UTF-8'}, clear=True) + mockobj.assert_has_calls( + [ + mock.call( + ["growpart", "--dry-run", diskdev, partnum], + env={"LANG": "C", "TMPDIR": "/tmp"}, + ), + mock.call( + ["growpart", diskdev, partnum], + env={"LANG": "C", "TMPDIR": "/tmp"}, + ), + ] + ) + + @mock.patch.dict("os.environ", {"LANG": "cs_CZ.UTF-8"}, clear=True) def test_mode_auto_falls_back_to_gpart(self): with mock.patch.object( - subp, 'subp', - return_value=("", HELP_GPART)) as mockobj: + subp, "subp", return_value=("", HELP_GPART) + ) as mockobj: ret = cc_growpart.resizer_factory(mode="auto") self.assertIsInstance(ret, cc_growpart.ResizeGpart) - mockobj.assert_has_calls([ - mock.call(['growpart', '--help'], env={'LANG': 'C'}), - mock.call(['gpart', 'help'], env={'LANG': 'C'}, rcs=[0, 1])]) + mockobj.assert_has_calls( + [ + mock.call(["growpart", "--help"], env={"LANG": "C"}), + mock.call( + ["gpart", "help"], env={"LANG": "C"}, rcs=[0, 1] + ), + ] + ) def test_handle_with_no_growpart_entry(self): # if no 'growpart' entry in config, then mode=auto should be used myresizer = object() - retval = (("/", cc_growpart.RESIZE.CHANGED, "my-message",),) + retval = ( + ( + "/", + cc_growpart.RESIZE.CHANGED, + "my-message", + ), + ) with ExitStack() as mocks: factory = mocks.enter_context( - mock.patch.object(cc_growpart, 'resizer_factory', - return_value=myresizer)) + mock.patch.object( + cc_growpart, "resizer_factory", return_value=myresizer + ) + ) rsdevs = mocks.enter_context( - mock.patch.object(cc_growpart, 'resize_devices', - return_value=retval)) + mock.patch.object( + cc_growpart, "resize_devices", return_value=retval + ) + ) mocks.enter_context( - mock.patch.object(cc_growpart, 'RESIZERS', - (('mysizer', object),) - )) + mock.patch.object( + cc_growpart, "RESIZERS", (("mysizer", object),) + ) + ) self.handle(self.name, {}, self.cloud_init, self.log, self.args) - factory.assert_called_once_with('auto') - rsdevs.assert_called_once_with(myresizer, ['/']) + factory.assert_called_once_with("auto") + rsdevs.assert_called_once_with(myresizer, ["/"]) class TestResize(unittest.TestCase): @@ -244,9 +280,18 @@ class TestResize(unittest.TestCase): # this patches out devent2dev, os.stat, and device_part_info # so in the end, doesn't test a lot devs = ["/dev/XXda1", "/dev/YYda2"] - devstat_ret = Bunch(st_mode=25008, st_ino=6078, st_dev=5, - st_nlink=1, st_uid=0, st_gid=6, st_size=0, - st_atime=0, st_mtime=0, st_ctime=0) + devstat_ret = Bunch( + st_mode=25008, + st_ino=6078, + st_dev=5, + st_nlink=1, + st_uid=0, + st_gid=6, + st_size=0, + st_atime=0, + st_mtime=0, + st_ctime=0, + ) enoent = ["/dev/NOENT"] real_stat = os.stat resize_calls = [] @@ -280,12 +325,15 @@ class TestResize(unittest.TestCase): return f return None - self.assertEqual(cc_growpart.RESIZE.NOCHANGE, - find("/dev/XXda1", resized)[1]) - self.assertEqual(cc_growpart.RESIZE.CHANGED, - find("/dev/YYda2", resized)[1]) - self.assertEqual(cc_growpart.RESIZE.SKIPPED, - find(enoent[0], resized)[1]) + self.assertEqual( + cc_growpart.RESIZE.NOCHANGE, find("/dev/XXda1", resized)[1] + ) + self.assertEqual( + cc_growpart.RESIZE.CHANGED, find("/dev/YYda2", resized)[1] + ) + self.assertEqual( + cc_growpart.RESIZE.SKIPPED, find(enoent[0], resized)[1] + ) # self.assertEqual(resize_calls, # [("/dev/XXda", "1", "/dev/XXda1"), # ("/dev/YYda", "2", "/dev/YYda2")]) diff --git a/tests/unittests/config/test_cc_grub_dpkg.py b/tests/unittests/config/test_cc_grub_dpkg.py index 99c05bb5..5151a7b5 100644 --- a/tests/unittests/config/test_cc_grub_dpkg.py +++ b/tests/unittests/config/test_cc_grub_dpkg.py @@ -1,11 +1,12 @@ # This file is part of cloud-init. See LICENSE file for license information. +from logging import Logger +from unittest import mock + import pytest -from unittest import mock -from logging import Logger -from cloudinit.subp import ProcessExecutionError from cloudinit.config.cc_grub_dpkg import fetch_idevs, handle +from cloudinit.subp import ProcessExecutionError class TestFetchIdevs: @@ -21,73 +22,78 @@ class TestFetchIdevs: ProcessExecutionError(reason=FileNotFoundError()), False, mock.call("'grub-probe' not found in $PATH"), - '', - '', + "", + "", ), # Inside a container, grub installed ( ProcessExecutionError(stderr="failed to get canonical path"), False, mock.call("grub-probe 'failed to get canonical path'"), - '', - '', + "", + "", ), # KVM Instance ( - ['/dev/vda'], + ["/dev/vda"], True, None, ( - '/dev/disk/by-path/pci-0000:00:00.0 ', - '/dev/disk/by-path/virtio-pci-0000:00:00.0 ' + "/dev/disk/by-path/pci-0000:00:00.0 ", + "/dev/disk/by-path/virtio-pci-0000:00:00.0 ", ), - '/dev/vda', + "/dev/vda", ), # Xen Instance ( - ['/dev/xvda'], + ["/dev/xvda"], True, None, - '', - '/dev/xvda', + "", + "/dev/xvda", ), # NVMe Hardware Instance ( - ['/dev/nvme1n1'], + ["/dev/nvme1n1"], True, None, ( - '/dev/disk/by-id/nvme-Company_hash000 ', - '/dev/disk/by-id/nvme-nvme.000-000-000-000-000 ', - '/dev/disk/by-path/pci-0000:00:00.0-nvme-0 ' + "/dev/disk/by-id/nvme-Company_hash000 ", + "/dev/disk/by-id/nvme-nvme.000-000-000-000-000 ", + "/dev/disk/by-path/pci-0000:00:00.0-nvme-0 ", ), - '/dev/disk/by-id/nvme-Company_hash000', + "/dev/disk/by-id/nvme-Company_hash000", ), # SCSI Hardware Instance ( - ['/dev/sda'], + ["/dev/sda"], True, None, ( - '/dev/disk/by-id/company-user-1 ', - '/dev/disk/by-id/scsi-0Company_user-1 ', - '/dev/disk/by-path/pci-0000:00:00.0-scsi-0:0:0:0 ' + "/dev/disk/by-id/company-user-1 ", + "/dev/disk/by-id/scsi-0Company_user-1 ", + "/dev/disk/by-path/pci-0000:00:00.0-scsi-0:0:0:0 ", ), - '/dev/disk/by-id/company-user-1', + "/dev/disk/by-id/company-user-1", ), ], ) @mock.patch("cloudinit.config.cc_grub_dpkg.util.logexc") @mock.patch("cloudinit.config.cc_grub_dpkg.os.path.exists") @mock.patch("cloudinit.config.cc_grub_dpkg.subp.subp") - def test_fetch_idevs(self, m_subp, m_exists, m_logexc, grub_output, - path_exists, expected_log_call, udevadm_output, - expected_idevs): + def test_fetch_idevs( + self, + m_subp, + m_exists, + m_logexc, + grub_output, + path_exists, + expected_log_call, + udevadm_output, + expected_idevs, + ): """Tests outputs from grub-probe and udevadm info against grub-dpkg""" - m_subp.side_effect = [ - grub_output, - ["".join(udevadm_output)] - ] + m_subp.side_effect = [grub_output, ["".join(udevadm_output)]] m_exists.return_value = path_exists log = mock.Mock(spec=Logger) idevs = fetch_idevs(log) @@ -106,67 +112,72 @@ class TestHandle: # No configuration None, None, - '/dev/disk/by-id/nvme-Company_hash000', + "/dev/disk/by-id/nvme-Company_hash000", ( "Setting grub debconf-set-selections with ", - "'/dev/disk/by-id/nvme-Company_hash000','false'" + "'/dev/disk/by-id/nvme-Company_hash000','false'", ), ), ( # idevs set, idevs_empty unset - '/dev/sda', + "/dev/sda", None, - '/dev/sda', + "/dev/sda", ( "Setting grub debconf-set-selections with ", - "'/dev/sda','false'" + "'/dev/sda','false'", ), ), ( # idevs unset, idevs_empty set None, - 'true', - '/dev/xvda', + "true", + "/dev/xvda", ( "Setting grub debconf-set-selections with ", - "'/dev/xvda','true'" + "'/dev/xvda','true'", ), ), ( # idevs set, idevs_empty set - '/dev/vda', - 'false', - '/dev/disk/by-id/company-user-1', + "/dev/vda", + "false", + "/dev/disk/by-id/company-user-1", ( "Setting grub debconf-set-selections with ", - "'/dev/vda','false'" + "'/dev/vda','false'", ), ), ( # idevs set, idevs_empty set # Respect what the user defines, even if its logically wrong - '/dev/nvme0n1', - 'true', - '', + "/dev/nvme0n1", + "true", + "", ( "Setting grub debconf-set-selections with ", - "'/dev/nvme0n1','true'" + "'/dev/nvme0n1','true'", ), - ) + ), ], ) @mock.patch("cloudinit.config.cc_grub_dpkg.fetch_idevs") @mock.patch("cloudinit.config.cc_grub_dpkg.util.get_cfg_option_str") @mock.patch("cloudinit.config.cc_grub_dpkg.util.logexc") @mock.patch("cloudinit.config.cc_grub_dpkg.subp.subp") - def test_handle(self, m_subp, m_logexc, m_get_cfg_str, m_fetch_idevs, - cfg_idevs, cfg_idevs_empty, fetch_idevs_output, - expected_log_output): + def test_handle( + self, + m_subp, + m_logexc, + m_get_cfg_str, + m_fetch_idevs, + cfg_idevs, + cfg_idevs_empty, + fetch_idevs_output, + expected_log_output, + ): """Test setting of correct debconf database entries""" - m_get_cfg_str.side_effect = [ - cfg_idevs, - cfg_idevs_empty - ] + m_get_cfg_str.side_effect = [cfg_idevs, cfg_idevs_empty] m_fetch_idevs.return_value = fetch_idevs_output log = mock.Mock(spec=Logger) handle(mock.Mock(), mock.Mock(), mock.Mock(), log, mock.Mock()) diff --git a/tests/unittests/config/test_cc_install_hotplug.py b/tests/unittests/config/test_cc_install_hotplug.py index 5d6b1e77..3bd44aba 100644 --- a/tests/unittests/config/test_cc_install_hotplug.py +++ b/tests/unittests/config/test_cc_install_hotplug.py @@ -5,28 +5,31 @@ from unittest import mock import pytest from cloudinit.config.cc_install_hotplug import ( - handle, HOTPLUG_UDEV_PATH, HOTPLUG_UDEV_RULES_TEMPLATE, + handle, ) from cloudinit.event import EventScope, EventType @pytest.yield_fixture() def mocks(): - m_update_enabled = mock.patch('cloudinit.stages.update_event_enabled') - m_write = mock.patch('cloudinit.util.write_file', autospec=True) - m_del = mock.patch('cloudinit.util.del_file', autospec=True) - m_subp = mock.patch('cloudinit.subp.subp') - m_which = mock.patch('cloudinit.subp.which', return_value=None) - m_path_exists = mock.patch('os.path.exists', return_value=False) + m_update_enabled = mock.patch("cloudinit.stages.update_event_enabled") + m_write = mock.patch("cloudinit.util.write_file", autospec=True) + m_del = mock.patch("cloudinit.util.del_file", autospec=True) + m_subp = mock.patch("cloudinit.subp.subp") + m_which = mock.patch("cloudinit.subp.which", return_value=None) + m_path_exists = mock.patch("os.path.exists", return_value=False) yield namedtuple( - 'Mocks', - 'm_update_enabled m_write m_del m_subp m_which m_path_exists' + "Mocks", "m_update_enabled m_write m_del m_subp m_which m_path_exists" )( - m_update_enabled.start(), m_write.start(), m_del.start(), - m_subp.start(), m_which.start(), m_path_exists.start() + m_update_enabled.start(), + m_write.start(), + m_del.start(), + m_subp.start(), + m_which.start(), + m_path_exists.start(), ) m_update_enabled.stop() @@ -38,11 +41,11 @@ def mocks(): class TestInstallHotplug: - @pytest.mark.parametrize('libexec_exists', [True, False]) + @pytest.mark.parametrize("libexec_exists", [True, False]) def test_rules_installed_when_supported_and_enabled( self, mocks, libexec_exists ): - mocks.m_which.return_value = 'udevadm' + mocks.m_which.return_value = "udevadm" mocks.m_update_enabled.return_value = True m_cloud = mock.MagicMock() m_cloud.datasource.get_supported_events.return_value = { @@ -53,16 +56,23 @@ class TestInstallHotplug: libexecdir = "/usr/libexec/cloud-init" else: libexecdir = "/usr/lib/cloud-init" - with mock.patch('os.path.exists', return_value=libexec_exists): + with mock.patch("os.path.exists", return_value=libexec_exists): handle(None, {}, m_cloud, mock.Mock(), None) mocks.m_write.assert_called_once_with( filename=HOTPLUG_UDEV_PATH, content=HOTPLUG_UDEV_RULES_TEMPLATE.format( - libexecdir=libexecdir), + libexecdir=libexecdir + ), ) - assert mocks.m_subp.call_args_list == [mock.call([ - 'udevadm', 'control', '--reload-rules', - ])] + assert mocks.m_subp.call_args_list == [ + mock.call( + [ + "udevadm", + "control", + "--reload-rules", + ] + ) + ] assert mocks.m_del.call_args_list == [] def test_rules_not_installed_when_unsupported(self, mocks): @@ -95,9 +105,15 @@ class TestInstallHotplug: handle(None, {}, m_cloud, mock.Mock(), None) mocks.m_del.assert_called_with(HOTPLUG_UDEV_PATH) - assert mocks.m_subp.call_args_list == [mock.call([ - 'udevadm', 'control', '--reload-rules', - ])] + assert mocks.m_subp.call_args_list == [ + mock.call( + [ + "udevadm", + "control", + "--reload-rules", + ] + ) + ] assert mocks.m_write.call_args_list == [] def test_rules_not_installed_when_no_udevadm(self, mocks): diff --git a/tests/unittests/config/test_cc_keys_to_console.py b/tests/unittests/config/test_cc_keys_to_console.py index 4083fc54..9efc2b48 100644 --- a/tests/unittests/config/test_cc_keys_to_console.py +++ b/tests/unittests/config/test_cc_keys_to_console.py @@ -16,12 +16,18 @@ class TestHandle: @mock.patch("cloudinit.config.cc_keys_to_console.util.multi_log") @mock.patch("cloudinit.config.cc_keys_to_console.os.path.exists") @mock.patch("cloudinit.config.cc_keys_to_console.subp.subp") - @pytest.mark.parametrize("cfg,subp_called", [ - ({}, True), # Default to emitting keys - ({"ssh": {}}, True), # Default even if we have the parent key - ({"ssh": {"emit_keys_to_console": True}}, True), # Explicitly enabled - ({"ssh": {"emit_keys_to_console": False}}, False), # Disabled - ]) + @pytest.mark.parametrize( + "cfg,subp_called", + [ + ({}, True), # Default to emitting keys + ({"ssh": {}}, True), # Default even if we have the parent key + ( + {"ssh": {"emit_keys_to_console": True}}, + True, + ), # Explicitly enabled + ({"ssh": {"emit_keys_to_console": False}}, False), # Disabled + ], + ) def test_emit_keys_to_console_config( self, m_subp, m_path_exists, _m_multi_log, cfg, subp_called ): diff --git a/tests/unittests/config/test_cc_landscape.py b/tests/unittests/config/test_cc_landscape.py index 07b3f899..efddc1b6 100644 --- a/tests/unittests/config/test_cc_landscape.py +++ b/tests/unittests/config/test_cc_landscape.py @@ -1,12 +1,15 @@ # This file is part of cloud-init. See LICENSE file for license information. import logging + from configobj import ConfigObj -from cloudinit.config import cc_landscape from cloudinit import util -from tests.unittests.helpers import (FilesystemMockingTestCase, mock, - wrap_and_call) - +from cloudinit.config import cc_landscape +from tests.unittests.helpers import ( + FilesystemMockingTestCase, + mock, + wrap_and_call, +) from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -19,108 +22,149 @@ class TestLandscape(FilesystemMockingTestCase): def setUp(self): super(TestLandscape, self).setUp() self.new_root = self.tmp_dir() - self.conf = self.tmp_path('client.conf', self.new_root) - self.default_file = self.tmp_path('default_landscape', self.new_root) + self.conf = self.tmp_path("client.conf", self.new_root) + self.default_file = self.tmp_path("default_landscape", self.new_root) self.patchUtils(self.new_root) self.add_patch( - 'cloudinit.distros.ubuntu.Distro.install_packages', - 'm_install_packages' + "cloudinit.distros.ubuntu.Distro.install_packages", + "m_install_packages", ) def test_handler_skips_empty_landscape_cloudconfig(self): """Empty landscape cloud-config section does no work.""" - mycloud = get_cloud('ubuntu') + mycloud = get_cloud("ubuntu") mycloud.distro = mock.MagicMock() - cfg = {'landscape': {}} - cc_landscape.handle('notimportant', cfg, mycloud, LOG, None) + cfg = {"landscape": {}} + cc_landscape.handle("notimportant", cfg, mycloud, LOG, None) self.assertFalse(mycloud.distro.install_packages.called) def test_handler_error_on_invalid_landscape_type(self): """Raise an error when landscape configuraiton option is invalid.""" - mycloud = get_cloud('ubuntu') - cfg = {'landscape': 'wrongtype'} + mycloud = get_cloud("ubuntu") + cfg = {"landscape": "wrongtype"} with self.assertRaises(RuntimeError) as context_manager: - cc_landscape.handle('notimportant', cfg, mycloud, LOG, None) + cc_landscape.handle("notimportant", cfg, mycloud, LOG, None) self.assertIn( "'landscape' key existed in config, but not a dict", - str(context_manager.exception)) + str(context_manager.exception), + ) - @mock.patch('cloudinit.config.cc_landscape.subp') + @mock.patch("cloudinit.config.cc_landscape.subp") def test_handler_restarts_landscape_client(self, m_subp): """handler restarts lansdscape-client after install.""" - mycloud = get_cloud('ubuntu') - cfg = {'landscape': {'client': {}}} + mycloud = get_cloud("ubuntu") + cfg = {"landscape": {"client": {}}} wrap_and_call( - 'cloudinit.config.cc_landscape', - {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, - cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) + "cloudinit.config.cc_landscape", + {"LSC_CLIENT_CFG_FILE": {"new": self.conf}}, + cc_landscape.handle, + "notimportant", + cfg, + mycloud, + LOG, + None, + ) self.assertEqual( - [mock.call(['service', 'landscape-client', 'restart'])], - m_subp.subp.call_args_list) + [mock.call(["service", "landscape-client", "restart"])], + m_subp.subp.call_args_list, + ) def test_handler_installs_client_and_creates_config_file(self): """Write landscape client.conf and install landscape-client.""" - mycloud = get_cloud('ubuntu') - cfg = {'landscape': {'client': {}}} - expected = {'client': { - 'log_level': 'info', - 'url': 'https://landscape.canonical.com/message-system', - 'ping_url': 'http://landscape.canonical.com/ping', - 'data_path': '/var/lib/landscape/client'}} + mycloud = get_cloud("ubuntu") + cfg = {"landscape": {"client": {}}} + expected = { + "client": { + "log_level": "info", + "url": "https://landscape.canonical.com/message-system", + "ping_url": "http://landscape.canonical.com/ping", + "data_path": "/var/lib/landscape/client", + } + } mycloud.distro = mock.MagicMock() wrap_and_call( - 'cloudinit.config.cc_landscape', - {'LSC_CLIENT_CFG_FILE': {'new': self.conf}, - 'LS_DEFAULT_FILE': {'new': self.default_file}}, - cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) + "cloudinit.config.cc_landscape", + { + "LSC_CLIENT_CFG_FILE": {"new": self.conf}, + "LS_DEFAULT_FILE": {"new": self.default_file}, + }, + cc_landscape.handle, + "notimportant", + cfg, + mycloud, + LOG, + None, + ) self.assertEqual( - [mock.call('landscape-client')], - mycloud.distro.install_packages.call_args) + [mock.call("landscape-client")], + mycloud.distro.install_packages.call_args, + ) self.assertEqual(expected, dict(ConfigObj(self.conf))) self.assertIn( - 'Wrote landscape config file to {0}'.format(self.conf), - self.logs.getvalue()) + "Wrote landscape config file to {0}".format(self.conf), + self.logs.getvalue(), + ) default_content = util.load_file(self.default_file) - self.assertEqual('RUN=1\n', default_content) + self.assertEqual("RUN=1\n", default_content) def test_handler_writes_merged_client_config_file_with_defaults(self): """Merge and write options from LSC_CLIENT_CFG_FILE with defaults.""" # Write existing sparse client.conf file - util.write_file(self.conf, '[client]\ncomputer_title = My PC\n') - mycloud = get_cloud('ubuntu') - cfg = {'landscape': {'client': {}}} - expected = {'client': { - 'log_level': 'info', - 'url': 'https://landscape.canonical.com/message-system', - 'ping_url': 'http://landscape.canonical.com/ping', - 'data_path': '/var/lib/landscape/client', - 'computer_title': 'My PC'}} + util.write_file(self.conf, "[client]\ncomputer_title = My PC\n") + mycloud = get_cloud("ubuntu") + cfg = {"landscape": {"client": {}}} + expected = { + "client": { + "log_level": "info", + "url": "https://landscape.canonical.com/message-system", + "ping_url": "http://landscape.canonical.com/ping", + "data_path": "/var/lib/landscape/client", + "computer_title": "My PC", + } + } wrap_and_call( - 'cloudinit.config.cc_landscape', - {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, - cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) + "cloudinit.config.cc_landscape", + {"LSC_CLIENT_CFG_FILE": {"new": self.conf}}, + cc_landscape.handle, + "notimportant", + cfg, + mycloud, + LOG, + None, + ) self.assertEqual(expected, dict(ConfigObj(self.conf))) self.assertIn( - 'Wrote landscape config file to {0}'.format(self.conf), - self.logs.getvalue()) + "Wrote landscape config file to {0}".format(self.conf), + self.logs.getvalue(), + ) def test_handler_writes_merged_provided_cloudconfig_with_defaults(self): """Merge and write options from cloud-config options with defaults.""" # Write empty sparse client.conf file - util.write_file(self.conf, '') - mycloud = get_cloud('ubuntu') - cfg = {'landscape': {'client': {'computer_title': 'My PC'}}} - expected = {'client': { - 'log_level': 'info', - 'url': 'https://landscape.canonical.com/message-system', - 'ping_url': 'http://landscape.canonical.com/ping', - 'data_path': '/var/lib/landscape/client', - 'computer_title': 'My PC'}} + util.write_file(self.conf, "") + mycloud = get_cloud("ubuntu") + cfg = {"landscape": {"client": {"computer_title": "My PC"}}} + expected = { + "client": { + "log_level": "info", + "url": "https://landscape.canonical.com/message-system", + "ping_url": "http://landscape.canonical.com/ping", + "data_path": "/var/lib/landscape/client", + "computer_title": "My PC", + } + } wrap_and_call( - 'cloudinit.config.cc_landscape', - {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, - cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) + "cloudinit.config.cc_landscape", + {"LSC_CLIENT_CFG_FILE": {"new": self.conf}}, + cc_landscape.handle, + "notimportant", + cfg, + mycloud, + LOG, + None, + ) self.assertEqual(expected, dict(ConfigObj(self.conf))) self.assertIn( - 'Wrote landscape config file to {0}'.format(self.conf), - self.logs.getvalue()) + "Wrote landscape config file to {0}".format(self.conf), + self.logs.getvalue(), + ) diff --git a/tests/unittests/config/test_cc_locale.py b/tests/unittests/config/test_cc_locale.py index 6cd95a29..7190bc68 100644 --- a/tests/unittests/config/test_cc_locale.py +++ b/tests/unittests/config/test_cc_locale.py @@ -8,21 +8,19 @@ import os import shutil import tempfile from io import BytesIO -from configobj import ConfigObj from unittest import mock +from configobj import ConfigObj + from cloudinit import util from cloudinit.config import cc_locale from tests.unittests import helpers as t_help - from tests.unittests.util import get_cloud - LOG = logging.getLogger(__name__) class TestLocale(t_help.FilesystemMockingTestCase): - def setUp(self): super(TestLocale, self).setUp() self.new_root = tempfile.mkdtemp() @@ -30,35 +28,37 @@ class TestLocale(t_help.FilesystemMockingTestCase): self.patchUtils(self.new_root) def test_set_locale_arch(self): - locale = 'en_GB.UTF-8' - locale_configfile = '/etc/invalid-locale-path' + locale = "en_GB.UTF-8" + locale_configfile = "/etc/invalid-locale-path" cfg = { - 'locale': locale, - 'locale_configfile': locale_configfile, + "locale": locale, + "locale_configfile": locale_configfile, } - cc = get_cloud('arch') - - with mock.patch('cloudinit.distros.arch.subp.subp') as m_subp: - with mock.patch('cloudinit.distros.arch.LOG.warning') as m_LOG: - cc_locale.handle('cc_locale', cfg, cc, LOG, []) - m_LOG.assert_called_with('Invalid locale_configfile %s, ' - 'only supported value is ' - '/etc/locale.conf', - locale_configfile) + cc = get_cloud("arch") + + with mock.patch("cloudinit.distros.arch.subp.subp") as m_subp: + with mock.patch("cloudinit.distros.arch.LOG.warning") as m_LOG: + cc_locale.handle("cc_locale", cfg, cc, LOG, []) + m_LOG.assert_called_with( + "Invalid locale_configfile %s, " + "only supported value is " + "/etc/locale.conf", + locale_configfile, + ) contents = util.load_file(cc.distro.locale_gen_fn) - self.assertIn('%s UTF-8' % locale, contents) - m_subp.assert_called_with(['localectl', - 'set-locale', - locale], capture=False) + self.assertIn("%s UTF-8" % locale, contents) + m_subp.assert_called_with( + ["localectl", "set-locale", locale], capture=False + ) def test_set_locale_sles(self): cfg = { - 'locale': 'My.Locale', + "locale": "My.Locale", } - cc = get_cloud('sles') - cc_locale.handle('cc_locale', cfg, cc, LOG, []) + cc = get_cloud("sles") + cc_locale.handle("cc_locale", cfg, cc, LOG, []) if cc.distro.uses_systemd(): locale_conf = cc.distro.systemd_locale_conf_fn else: @@ -66,51 +66,58 @@ class TestLocale(t_help.FilesystemMockingTestCase): contents = util.load_file(locale_conf, decode=False) n_cfg = ConfigObj(BytesIO(contents)) if cc.distro.uses_systemd(): - self.assertEqual({'LANG': cfg['locale']}, dict(n_cfg)) + self.assertEqual({"LANG": cfg["locale"]}, dict(n_cfg)) else: - self.assertEqual({'RC_LANG': cfg['locale']}, dict(n_cfg)) + self.assertEqual({"RC_LANG": cfg["locale"]}, dict(n_cfg)) def test_set_locale_sles_default(self): cfg = {} - cc = get_cloud('sles') - cc_locale.handle('cc_locale', cfg, cc, LOG, []) + cc = get_cloud("sles") + cc_locale.handle("cc_locale", cfg, cc, LOG, []) if cc.distro.uses_systemd(): locale_conf = cc.distro.systemd_locale_conf_fn - keyname = 'LANG' + keyname = "LANG" else: locale_conf = cc.distro.locale_conf_fn - keyname = 'RC_LANG' + keyname = "RC_LANG" contents = util.load_file(locale_conf, decode=False) n_cfg = ConfigObj(BytesIO(contents)) - self.assertEqual({keyname: 'en_US.UTF-8'}, dict(n_cfg)) + self.assertEqual({keyname: "en_US.UTF-8"}, dict(n_cfg)) def test_locale_update_config_if_different_than_default(self): """Test cc_locale writes updates conf if different than default""" locale_conf = os.path.join(self.new_root, "etc/default/locale") util.write_file(locale_conf, 'LANG="en_US.UTF-8"\n') - cfg = {'locale': 'C.UTF-8'} - cc = get_cloud('ubuntu') - with mock.patch('cloudinit.distros.debian.subp.subp') as m_subp: - with mock.patch('cloudinit.distros.debian.LOCALE_CONF_FN', - locale_conf): - cc_locale.handle('cc_locale', cfg, cc, LOG, []) - m_subp.assert_called_with(['update-locale', - '--locale-file=%s' % locale_conf, - 'LANG=C.UTF-8'], capture=False) + cfg = {"locale": "C.UTF-8"} + cc = get_cloud("ubuntu") + with mock.patch("cloudinit.distros.debian.subp.subp") as m_subp: + with mock.patch( + "cloudinit.distros.debian.LOCALE_CONF_FN", locale_conf + ): + cc_locale.handle("cc_locale", cfg, cc, LOG, []) + m_subp.assert_called_with( + [ + "update-locale", + "--locale-file=%s" % locale_conf, + "LANG=C.UTF-8", + ], + capture=False, + ) def test_locale_rhel_defaults_en_us_utf8(self): """Test cc_locale gets en_US.UTF-8 from distro get_locale fallback""" cfg = {} - cc = get_cloud('rhel') - update_sysconfig = 'cloudinit.distros.rhel_util.update_sysconfig_file' - with mock.patch.object(cc.distro, 'uses_systemd') as m_use_sd: + cc = get_cloud("rhel") + update_sysconfig = "cloudinit.distros.rhel_util.update_sysconfig_file" + with mock.patch.object(cc.distro, "uses_systemd") as m_use_sd: m_use_sd.return_value = True with mock.patch(update_sysconfig) as m_update_syscfg: - cc_locale.handle('cc_locale', cfg, cc, LOG, []) - m_update_syscfg.assert_called_with('/etc/locale.conf', - {'LANG': 'en_US.UTF-8'}) + cc_locale.handle("cc_locale", cfg, cc, LOG, []) + m_update_syscfg.assert_called_with( + "/etc/locale.conf", {"LANG": "en_US.UTF-8"} + ) # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_lxd.py b/tests/unittests/config/test_cc_lxd.py index 887987c0..720274d6 100644 --- a/tests/unittests/config/test_cc_lxd.py +++ b/tests/unittests/config/test_cc_lxd.py @@ -3,7 +3,6 @@ from unittest import mock from cloudinit.config import cc_lxd from tests.unittests import helpers as t_help - from tests.unittests.util import get_cloud @@ -12,11 +11,11 @@ class TestLxd(t_help.CiTestCase): with_logs = True lxd_cfg = { - 'lxd': { - 'init': { - 'network_address': '0.0.0.0', - 'storage_backend': 'zfs', - 'storage_pool': 'poolname', + "lxd": { + "init": { + "network_address": "0.0.0.0", + "storage_backend": "zfs", + "storage_pool": "poolname", } } } @@ -27,16 +26,26 @@ class TestLxd(t_help.CiTestCase): cc = get_cloud() mock_subp.which.return_value = True m_maybe_clean.return_value = None - cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, []) + cc_lxd.handle("cc_lxd", self.lxd_cfg, cc, self.logger, []) self.assertTrue(mock_subp.which.called) # no bridge config, so maybe_cleanup should not be called. self.assertFalse(m_maybe_clean.called) self.assertEqual( - [mock.call(['lxd', 'waitready', '--timeout=300']), - mock.call( - ['lxd', 'init', '--auto', '--network-address=0.0.0.0', - '--storage-backend=zfs', '--storage-pool=poolname'])], - mock_subp.subp.call_args_list) + [ + mock.call(["lxd", "waitready", "--timeout=300"]), + mock.call( + [ + "lxd", + "init", + "--auto", + "--network-address=0.0.0.0", + "--storage-backend=zfs", + "--storage-pool=poolname", + ] + ), + ], + mock_subp.subp.call_args_list, + ) @mock.patch("cloudinit.config.cc_lxd.maybe_cleanup_default") @mock.patch("cloudinit.config.cc_lxd.subp") @@ -44,20 +53,20 @@ class TestLxd(t_help.CiTestCase): cc = get_cloud() cc.distro = mock.MagicMock() mock_subp.which.return_value = None - cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, []) - self.assertNotIn('WARN', self.logs.getvalue()) + cc_lxd.handle("cc_lxd", self.lxd_cfg, cc, self.logger, []) + self.assertNotIn("WARN", self.logs.getvalue()) self.assertTrue(cc.distro.install_packages.called) - cc_lxd.handle('cc_lxd', self.lxd_cfg, cc, self.logger, []) + cc_lxd.handle("cc_lxd", self.lxd_cfg, cc, self.logger, []) self.assertFalse(m_maybe_clean.called) install_pkg = cc.distro.install_packages.call_args_list[0][0][0] - self.assertEqual(sorted(install_pkg), ['lxd', 'zfsutils-linux']) + self.assertEqual(sorted(install_pkg), ["lxd", "zfsutils-linux"]) @mock.patch("cloudinit.config.cc_lxd.maybe_cleanup_default") @mock.patch("cloudinit.config.cc_lxd.subp") def test_no_init_does_nothing(self, mock_subp, m_maybe_clean): cc = get_cloud() cc.distro = mock.MagicMock() - cc_lxd.handle('cc_lxd', {'lxd': {}}, cc, self.logger, []) + cc_lxd.handle("cc_lxd", {"lxd": {}}, cc, self.logger, []) self.assertFalse(cc.distro.install_packages.called) self.assertFalse(mock_subp.subp.called) self.assertFalse(m_maybe_clean.called) @@ -67,118 +76,150 @@ class TestLxd(t_help.CiTestCase): def test_no_lxd_does_nothing(self, mock_subp, m_maybe_clean): cc = get_cloud() cc.distro = mock.MagicMock() - cc_lxd.handle('cc_lxd', {'package_update': True}, cc, self.logger, []) + cc_lxd.handle("cc_lxd", {"package_update": True}, cc, self.logger, []) self.assertFalse(cc.distro.install_packages.called) self.assertFalse(mock_subp.subp.called) self.assertFalse(m_maybe_clean.called) def test_lxd_debconf_new_full(self): - data = {"mode": "new", - "name": "testbr0", - "ipv4_address": "10.0.8.1", - "ipv4_netmask": "24", - "ipv4_dhcp_first": "10.0.8.2", - "ipv4_dhcp_last": "10.0.8.254", - "ipv4_dhcp_leases": "250", - "ipv4_nat": "true", - "ipv6_address": "fd98:9e0:3744::1", - "ipv6_netmask": "64", - "ipv6_nat": "true", - "domain": "lxd"} + data = { + "mode": "new", + "name": "testbr0", + "ipv4_address": "10.0.8.1", + "ipv4_netmask": "24", + "ipv4_dhcp_first": "10.0.8.2", + "ipv4_dhcp_last": "10.0.8.254", + "ipv4_dhcp_leases": "250", + "ipv4_nat": "true", + "ipv6_address": "fd98:9e0:3744::1", + "ipv6_netmask": "64", + "ipv6_nat": "true", + "domain": "lxd", + } self.assertEqual( cc_lxd.bridge_to_debconf(data), - {"lxd/setup-bridge": "true", - "lxd/bridge-name": "testbr0", - "lxd/bridge-ipv4": "true", - "lxd/bridge-ipv4-address": "10.0.8.1", - "lxd/bridge-ipv4-netmask": "24", - "lxd/bridge-ipv4-dhcp-first": "10.0.8.2", - "lxd/bridge-ipv4-dhcp-last": "10.0.8.254", - "lxd/bridge-ipv4-dhcp-leases": "250", - "lxd/bridge-ipv4-nat": "true", - "lxd/bridge-ipv6": "true", - "lxd/bridge-ipv6-address": "fd98:9e0:3744::1", - "lxd/bridge-ipv6-netmask": "64", - "lxd/bridge-ipv6-nat": "true", - "lxd/bridge-domain": "lxd"}) + { + "lxd/setup-bridge": "true", + "lxd/bridge-name": "testbr0", + "lxd/bridge-ipv4": "true", + "lxd/bridge-ipv4-address": "10.0.8.1", + "lxd/bridge-ipv4-netmask": "24", + "lxd/bridge-ipv4-dhcp-first": "10.0.8.2", + "lxd/bridge-ipv4-dhcp-last": "10.0.8.254", + "lxd/bridge-ipv4-dhcp-leases": "250", + "lxd/bridge-ipv4-nat": "true", + "lxd/bridge-ipv6": "true", + "lxd/bridge-ipv6-address": "fd98:9e0:3744::1", + "lxd/bridge-ipv6-netmask": "64", + "lxd/bridge-ipv6-nat": "true", + "lxd/bridge-domain": "lxd", + }, + ) def test_lxd_debconf_new_partial(self): - data = {"mode": "new", - "ipv6_address": "fd98:9e0:3744::1", - "ipv6_netmask": "64", - "ipv6_nat": "true"} + data = { + "mode": "new", + "ipv6_address": "fd98:9e0:3744::1", + "ipv6_netmask": "64", + "ipv6_nat": "true", + } self.assertEqual( cc_lxd.bridge_to_debconf(data), - {"lxd/setup-bridge": "true", - "lxd/bridge-ipv6": "true", - "lxd/bridge-ipv6-address": "fd98:9e0:3744::1", - "lxd/bridge-ipv6-netmask": "64", - "lxd/bridge-ipv6-nat": "true"}) + { + "lxd/setup-bridge": "true", + "lxd/bridge-ipv6": "true", + "lxd/bridge-ipv6-address": "fd98:9e0:3744::1", + "lxd/bridge-ipv6-netmask": "64", + "lxd/bridge-ipv6-nat": "true", + }, + ) def test_lxd_debconf_existing(self): - data = {"mode": "existing", - "name": "testbr0"} + data = {"mode": "existing", "name": "testbr0"} self.assertEqual( cc_lxd.bridge_to_debconf(data), - {"lxd/setup-bridge": "false", - "lxd/use-existing-bridge": "true", - "lxd/bridge-name": "testbr0"}) + { + "lxd/setup-bridge": "false", + "lxd/use-existing-bridge": "true", + "lxd/bridge-name": "testbr0", + }, + ) def test_lxd_debconf_none(self): data = {"mode": "none"} self.assertEqual( cc_lxd.bridge_to_debconf(data), - {"lxd/setup-bridge": "false", - "lxd/bridge-name": ""}) + {"lxd/setup-bridge": "false", "lxd/bridge-name": ""}, + ) def test_lxd_cmd_new_full(self): - data = {"mode": "new", - "name": "testbr0", - "ipv4_address": "10.0.8.1", - "ipv4_netmask": "24", - "ipv4_dhcp_first": "10.0.8.2", - "ipv4_dhcp_last": "10.0.8.254", - "ipv4_dhcp_leases": "250", - "ipv4_nat": "true", - "ipv6_address": "fd98:9e0:3744::1", - "ipv6_netmask": "64", - "ipv6_nat": "true", - "domain": "lxd"} + data = { + "mode": "new", + "name": "testbr0", + "ipv4_address": "10.0.8.1", + "ipv4_netmask": "24", + "ipv4_dhcp_first": "10.0.8.2", + "ipv4_dhcp_last": "10.0.8.254", + "ipv4_dhcp_leases": "250", + "ipv4_nat": "true", + "ipv6_address": "fd98:9e0:3744::1", + "ipv6_netmask": "64", + "ipv6_nat": "true", + "domain": "lxd", + } self.assertEqual( cc_lxd.bridge_to_cmd(data), - (["network", "create", "testbr0", - "ipv4.address=10.0.8.1/24", "ipv4.nat=true", - "ipv4.dhcp.ranges=10.0.8.2-10.0.8.254", - "ipv6.address=fd98:9e0:3744::1/64", - "ipv6.nat=true", "dns.domain=lxd"], - ["network", "attach-profile", - "testbr0", "default", "eth0"])) + ( + [ + "network", + "create", + "testbr0", + "ipv4.address=10.0.8.1/24", + "ipv4.nat=true", + "ipv4.dhcp.ranges=10.0.8.2-10.0.8.254", + "ipv6.address=fd98:9e0:3744::1/64", + "ipv6.nat=true", + "dns.domain=lxd", + ], + ["network", "attach-profile", "testbr0", "default", "eth0"], + ), + ) def test_lxd_cmd_new_partial(self): - data = {"mode": "new", - "ipv6_address": "fd98:9e0:3744::1", - "ipv6_netmask": "64", - "ipv6_nat": "true"} + data = { + "mode": "new", + "ipv6_address": "fd98:9e0:3744::1", + "ipv6_netmask": "64", + "ipv6_nat": "true", + } self.assertEqual( cc_lxd.bridge_to_cmd(data), - (["network", "create", "lxdbr0", "ipv4.address=none", - "ipv6.address=fd98:9e0:3744::1/64", "ipv6.nat=true"], - ["network", "attach-profile", - "lxdbr0", "default", "eth0"])) + ( + [ + "network", + "create", + "lxdbr0", + "ipv4.address=none", + "ipv6.address=fd98:9e0:3744::1/64", + "ipv6.nat=true", + ], + ["network", "attach-profile", "lxdbr0", "default", "eth0"], + ), + ) def test_lxd_cmd_existing(self): - data = {"mode": "existing", - "name": "testbr0"} + data = {"mode": "existing", "name": "testbr0"} self.assertEqual( cc_lxd.bridge_to_cmd(data), - (None, ["network", "attach-profile", - "testbr0", "default", "eth0"])) + ( + None, + ["network", "attach-profile", "testbr0", "default", "eth0"], + ), + ) def test_lxd_cmd_none(self): data = {"mode": "none"} - self.assertEqual( - cc_lxd.bridge_to_cmd(data), - (None, None)) + self.assertEqual(cc_lxd.bridge_to_cmd(data), (None, None)) class TestLxdMaybeCleanupDefault(t_help.CiTestCase): @@ -190,21 +231,24 @@ class TestLxdMaybeCleanupDefault(t_help.CiTestCase): def test_network_other_than_default_not_deleted(self, m_lxc): """deletion or removal should only occur if bridge is default.""" cc_lxd.maybe_cleanup_default( - net_name="lxdbr1", did_init=True, create=True, attach=True) + net_name="lxdbr1", did_init=True, create=True, attach=True + ) m_lxc.assert_not_called() @mock.patch("cloudinit.config.cc_lxd._lxc") def test_did_init_false_does_not_delete(self, m_lxc): """deletion or removal should only occur if did_init is True.""" cc_lxd.maybe_cleanup_default( - net_name=self.defnet, did_init=False, create=True, attach=True) + net_name=self.defnet, did_init=False, create=True, attach=True + ) m_lxc.assert_not_called() @mock.patch("cloudinit.config.cc_lxd._lxc") def test_network_deleted_if_create_true(self, m_lxc): """deletion of network should occur if create is True.""" cc_lxd.maybe_cleanup_default( - net_name=self.defnet, did_init=True, create=True, attach=False) + net_name=self.defnet, did_init=True, create=True, attach=False + ) m_lxc.assert_called_with(["network", "delete", self.defnet]) @mock.patch("cloudinit.config.cc_lxd._lxc") @@ -213,10 +257,16 @@ class TestLxdMaybeCleanupDefault(t_help.CiTestCase): nic_name = "my_nic" profile = "my_profile" cc_lxd.maybe_cleanup_default( - net_name=self.defnet, did_init=True, create=False, attach=True, - profile=profile, nic_name=nic_name) + net_name=self.defnet, + did_init=True, + create=False, + attach=True, + profile=profile, + nic_name=nic_name, + ) m_lxc.assert_called_once_with( - ["profile", "device", "remove", profile, nic_name]) + ["profile", "device", "remove", profile, nic_name] + ) # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_mcollective.py b/tests/unittests/config/test_cc_mcollective.py index fff777b6..5cbdeb76 100644 --- a/tests/unittests/config/test_cc_mcollective.py +++ b/tests/unittests/config/test_cc_mcollective.py @@ -1,15 +1,15 @@ # This file is part of cloud-init. See LICENSE file for license information. -import configobj import logging import os import shutil import tempfile from io import BytesIO -from cloudinit import (util) +import configobj + +from cloudinit import util from cloudinit.config import cc_mcollective from tests.unittests import helpers as t_help - from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -47,83 +47,92 @@ class TestConfig(t_help.FilesystemMockingTestCase): self.addCleanup(shutil.rmtree, self.tmp) # "./": make os.path.join behave correctly with abs path as second arg self.server_cfg = os.path.join( - self.tmp, "./" + cc_mcollective.SERVER_CFG) + self.tmp, "./" + cc_mcollective.SERVER_CFG + ) self.pubcert_file = os.path.join( - self.tmp, "./" + cc_mcollective.PUBCERT_FILE) + self.tmp, "./" + cc_mcollective.PUBCERT_FILE + ) self.pricert_file = os.path.join( - self.tmp, self.tmp, "./" + cc_mcollective.PRICERT_FILE) + self.tmp, self.tmp, "./" + cc_mcollective.PRICERT_FILE + ) def test_basic_config(self): cfg = { - 'mcollective': { - 'conf': { - 'loglevel': 'debug', - 'connector': 'rabbitmq', - 'logfile': '/var/log/mcollective.log', - 'ttl': '4294957', - 'collectives': 'mcollective', - 'main_collective': 'mcollective', - 'securityprovider': 'psk', - 'daemonize': '1', - 'factsource': 'yaml', - 'direct_addressing': '1', - 'plugin.psk': 'unset', - 'libdir': '/usr/share/mcollective/plugins', - 'identity': '1', + "mcollective": { + "conf": { + "loglevel": "debug", + "connector": "rabbitmq", + "logfile": "/var/log/mcollective.log", + "ttl": "4294957", + "collectives": "mcollective", + "main_collective": "mcollective", + "securityprovider": "psk", + "daemonize": "1", + "factsource": "yaml", + "direct_addressing": "1", + "plugin.psk": "unset", + "libdir": "/usr/share/mcollective/plugins", + "identity": "1", }, }, } - expected = cfg['mcollective']['conf'] + expected = cfg["mcollective"]["conf"] self.patchUtils(self.tmp) - cc_mcollective.configure(cfg['mcollective']['conf']) + cc_mcollective.configure(cfg["mcollective"]["conf"]) contents = util.load_file(cc_mcollective.SERVER_CFG, decode=False) contents = configobj.ConfigObj(BytesIO(contents)) self.assertEqual(expected, dict(contents)) def test_existing_config_is_saved(self): - cfg = {'loglevel': 'warn'} + cfg = {"loglevel": "warn"} util.write_file(self.server_cfg, STOCK_CONFIG) cc_mcollective.configure(config=cfg, server_cfg=self.server_cfg) self.assertTrue(os.path.exists(self.server_cfg)) self.assertTrue(os.path.exists(self.server_cfg + ".old")) - self.assertEqual(util.load_file(self.server_cfg + ".old"), - STOCK_CONFIG) + self.assertEqual( + util.load_file(self.server_cfg + ".old"), STOCK_CONFIG + ) def test_existing_updated(self): - cfg = {'loglevel': 'warn'} + cfg = {"loglevel": "warn"} util.write_file(self.server_cfg, STOCK_CONFIG) cc_mcollective.configure(config=cfg, server_cfg=self.server_cfg) cfgobj = configobj.ConfigObj(self.server_cfg) - self.assertEqual(cfg['loglevel'], cfgobj['loglevel']) + self.assertEqual(cfg["loglevel"], cfgobj["loglevel"]) def test_certificats_written(self): # check public-cert and private-cert keys in config get written - cfg = {'loglevel': 'debug', - 'public-cert': "this is my public-certificate", - 'private-cert': "secret private certificate"} + cfg = { + "loglevel": "debug", + "public-cert": "this is my public-certificate", + "private-cert": "secret private certificate", + } cc_mcollective.configure( - config=cfg, server_cfg=self.server_cfg, - pricert_file=self.pricert_file, pubcert_file=self.pubcert_file) + config=cfg, + server_cfg=self.server_cfg, + pricert_file=self.pricert_file, + pubcert_file=self.pubcert_file, + ) found = configobj.ConfigObj(self.server_cfg) # make sure these didnt get written in - self.assertFalse('public-cert' in found) - self.assertFalse('private-cert' in found) + self.assertFalse("public-cert" in found) + self.assertFalse("private-cert" in found) # these need updating to the specified paths - self.assertEqual(found['plugin.ssl_server_public'], self.pubcert_file) - self.assertEqual(found['plugin.ssl_server_private'], self.pricert_file) + self.assertEqual(found["plugin.ssl_server_public"], self.pubcert_file) + self.assertEqual(found["plugin.ssl_server_private"], self.pricert_file) # and the security provider should be ssl - self.assertEqual(found['securityprovider'], 'ssl') + self.assertEqual(found["securityprovider"], "ssl") self.assertEqual( - util.load_file(self.pricert_file), cfg['private-cert']) - self.assertEqual( - util.load_file(self.pubcert_file), cfg['public-cert']) + util.load_file(self.pricert_file), cfg["private-cert"] + ) + self.assertEqual(util.load_file(self.pubcert_file), cfg["public-cert"]) class TestHandler(t_help.TestCase): @@ -133,14 +142,17 @@ class TestHandler(t_help.TestCase): cc = get_cloud() cc.distro = t_help.mock.MagicMock() mock_util.load_file.return_value = b"" - mycfg = {'mcollective': {'conf': {'loglevel': 'debug'}}} - cc_mcollective.handle('cc_mcollective', mycfg, cc, LOG, []) + mycfg = {"mcollective": {"conf": {"loglevel": "debug"}}} + cc_mcollective.handle("cc_mcollective", mycfg, cc, LOG, []) self.assertTrue(cc.distro.install_packages.called) install_pkg = cc.distro.install_packages.call_args_list[0][0][0] - self.assertEqual(install_pkg, ('mcollective',)) + self.assertEqual(install_pkg, ("mcollective",)) self.assertTrue(mock_subp.subp.called) - self.assertEqual(mock_subp.subp.call_args_list[0][0][0], - ['service', 'mcollective', 'restart']) + self.assertEqual( + mock_subp.subp.call_args_list[0][0][0], + ["service", "mcollective", "restart"], + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_mounts.py b/tests/unittests/config/test_cc_mounts.py index fc65f108..084faacd 100644 --- a/tests/unittests/config/test_cc_mounts.py +++ b/tests/unittests/config/test_cc_mounts.py @@ -1,302 +1,363 @@ # This file is part of cloud-init. See LICENSE file for license information. -import pytest import os.path from unittest import mock -from tests.unittests import helpers as test_helpers +import pytest + from cloudinit.config import cc_mounts from cloudinit.config.cc_mounts import create_swapfile from cloudinit.subp import ProcessExecutionError +from tests.unittests import helpers as test_helpers -M_PATH = 'cloudinit.config.cc_mounts.' +M_PATH = "cloudinit.config.cc_mounts." class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase): - def setUp(self): super(TestSanitizeDevname, self).setUp() self.new_root = self.tmp_dir() self.patchOS(self.new_root) def _touch(self, path): - path = os.path.join(self.new_root, path.lstrip('/')) + path = os.path.join(self.new_root, path.lstrip("/")) basedir = os.path.dirname(path) if not os.path.exists(basedir): os.makedirs(basedir) - open(path, 'a').close() + open(path, "a").close() def _makedirs(self, directory): - directory = os.path.join(self.new_root, directory.lstrip('/')) + directory = os.path.join(self.new_root, directory.lstrip("/")) if not os.path.exists(directory): os.makedirs(directory) def mock_existence_of_disk(self, disk_path): self._touch(disk_path) - self._makedirs(os.path.join('/sys/block', disk_path.split('/')[-1])) + self._makedirs(os.path.join("/sys/block", disk_path.split("/")[-1])) def mock_existence_of_partition(self, disk_path, partition_number): self.mock_existence_of_disk(disk_path) self._touch(disk_path + str(partition_number)) - disk_name = disk_path.split('/')[-1] - self._makedirs(os.path.join('/sys/block', - disk_name, - disk_name + str(partition_number))) + disk_name = disk_path.split("/")[-1] + self._makedirs( + os.path.join( + "/sys/block", disk_name, disk_name + str(partition_number) + ) + ) def test_existent_full_disk_path_is_returned(self): - disk_path = '/dev/sda' + disk_path = "/dev/sda" self.mock_existence_of_disk(disk_path) - self.assertEqual(disk_path, - cc_mounts.sanitize_devname(disk_path, - lambda x: None, - mock.Mock())) + self.assertEqual( + disk_path, + cc_mounts.sanitize_devname(disk_path, lambda x: None, mock.Mock()), + ) def test_existent_disk_name_returns_full_path(self): - disk_name = 'sda' - disk_path = '/dev/' + disk_name + disk_name = "sda" + disk_path = "/dev/" + disk_name self.mock_existence_of_disk(disk_path) - self.assertEqual(disk_path, - cc_mounts.sanitize_devname(disk_name, - lambda x: None, - mock.Mock())) + self.assertEqual( + disk_path, + cc_mounts.sanitize_devname(disk_name, lambda x: None, mock.Mock()), + ) def test_existent_meta_disk_is_returned(self): - actual_disk_path = '/dev/sda' + actual_disk_path = "/dev/sda" self.mock_existence_of_disk(actual_disk_path) self.assertEqual( actual_disk_path, - cc_mounts.sanitize_devname('ephemeral0', - lambda x: actual_disk_path, - mock.Mock())) + cc_mounts.sanitize_devname( + "ephemeral0", lambda x: actual_disk_path, mock.Mock() + ), + ) def test_existent_meta_partition_is_returned(self): - disk_name, partition_part = '/dev/sda', '1' + disk_name, partition_part = "/dev/sda", "1" actual_partition_path = disk_name + partition_part self.mock_existence_of_partition(disk_name, partition_part) self.assertEqual( actual_partition_path, - cc_mounts.sanitize_devname('ephemeral0.1', - lambda x: disk_name, - mock.Mock())) + cc_mounts.sanitize_devname( + "ephemeral0.1", lambda x: disk_name, mock.Mock() + ), + ) def test_existent_meta_partition_with_p_is_returned(self): - disk_name, partition_part = '/dev/sda', 'p1' + disk_name, partition_part = "/dev/sda", "p1" actual_partition_path = disk_name + partition_part self.mock_existence_of_partition(disk_name, partition_part) self.assertEqual( actual_partition_path, - cc_mounts.sanitize_devname('ephemeral0.1', - lambda x: disk_name, - mock.Mock())) + cc_mounts.sanitize_devname( + "ephemeral0.1", lambda x: disk_name, mock.Mock() + ), + ) def test_first_partition_returned_if_existent_disk_is_partitioned(self): - disk_name, partition_part = '/dev/sda', '1' + disk_name, partition_part = "/dev/sda", "1" actual_partition_path = disk_name + partition_part self.mock_existence_of_partition(disk_name, partition_part) self.assertEqual( actual_partition_path, - cc_mounts.sanitize_devname('ephemeral0', - lambda x: disk_name, - mock.Mock())) + cc_mounts.sanitize_devname( + "ephemeral0", lambda x: disk_name, mock.Mock() + ), + ) def test_nth_partition_returned_if_requested(self): - disk_name, partition_part = '/dev/sda', '3' + disk_name, partition_part = "/dev/sda", "3" actual_partition_path = disk_name + partition_part self.mock_existence_of_partition(disk_name, partition_part) self.assertEqual( actual_partition_path, - cc_mounts.sanitize_devname('ephemeral0.3', - lambda x: disk_name, - mock.Mock())) + cc_mounts.sanitize_devname( + "ephemeral0.3", lambda x: disk_name, mock.Mock() + ), + ) def test_transformer_returning_none_returns_none(self): self.assertIsNone( cc_mounts.sanitize_devname( - 'ephemeral0', lambda x: None, mock.Mock())) + "ephemeral0", lambda x: None, mock.Mock() + ) + ) def test_missing_device_returns_none(self): self.assertIsNone( - cc_mounts.sanitize_devname('/dev/sda', None, mock.Mock())) + cc_mounts.sanitize_devname("/dev/sda", None, mock.Mock()) + ) def test_missing_sys_returns_none(self): - disk_path = '/dev/sda' + disk_path = "/dev/sda" self._makedirs(disk_path) self.assertIsNone( - cc_mounts.sanitize_devname(disk_path, None, mock.Mock())) + cc_mounts.sanitize_devname(disk_path, None, mock.Mock()) + ) def test_existent_disk_but_missing_partition_returns_none(self): - disk_path = '/dev/sda' + disk_path = "/dev/sda" self.mock_existence_of_disk(disk_path) self.assertIsNone( cc_mounts.sanitize_devname( - 'ephemeral0.1', lambda x: disk_path, mock.Mock())) + "ephemeral0.1", lambda x: disk_path, mock.Mock() + ) + ) def test_network_device_returns_network_device(self): - disk_path = 'netdevice:/path' + disk_path = "netdevice:/path" self.assertEqual( - disk_path, - cc_mounts.sanitize_devname(disk_path, None, mock.Mock())) + disk_path, cc_mounts.sanitize_devname(disk_path, None, mock.Mock()) + ) def test_device_aliases_remapping(self): - disk_path = '/dev/sda' + disk_path = "/dev/sda" self.mock_existence_of_disk(disk_path) - self.assertEqual(disk_path, - cc_mounts.sanitize_devname('mydata', - lambda x: None, - mock.Mock(), - {'mydata': disk_path})) + self.assertEqual( + disk_path, + cc_mounts.sanitize_devname( + "mydata", lambda x: None, mock.Mock(), {"mydata": disk_path} + ), + ) class TestSwapFileCreation(test_helpers.FilesystemMockingTestCase): - def setUp(self): super(TestSwapFileCreation, self).setUp() self.new_root = self.tmp_dir() self.patchOS(self.new_root) - self.fstab_path = os.path.join(self.new_root, 'etc/fstab') - self.swap_path = os.path.join(self.new_root, 'swap.img') - self._makedirs('/etc') + self.fstab_path = os.path.join(self.new_root, "etc/fstab") + self.swap_path = os.path.join(self.new_root, "swap.img") + self._makedirs("/etc") - self.add_patch('cloudinit.config.cc_mounts.FSTAB_PATH', - 'mock_fstab_path', - self.fstab_path, - autospec=False) - - self.add_patch('cloudinit.config.cc_mounts.subp.subp', - 'm_subp_subp') + self.add_patch( + "cloudinit.config.cc_mounts.FSTAB_PATH", + "mock_fstab_path", + self.fstab_path, + autospec=False, + ) - self.add_patch('cloudinit.config.cc_mounts.util.mounts', - 'mock_util_mounts', - return_value={ - '/dev/sda1': {'fstype': 'ext4', - 'mountpoint': '/', - 'opts': 'rw,relatime,discard' - }}) + self.add_patch("cloudinit.config.cc_mounts.subp.subp", "m_subp_subp") + + self.add_patch( + "cloudinit.config.cc_mounts.util.mounts", + "mock_util_mounts", + return_value={ + "/dev/sda1": { + "fstype": "ext4", + "mountpoint": "/", + "opts": "rw,relatime,discard", + } + }, + ) self.mock_cloud = mock.Mock() self.mock_log = mock.Mock() self.mock_cloud.device_name_to_device = self.device_name_to_device self.cc = { - 'swap': { - 'filename': self.swap_path, - 'size': '512', - 'maxsize': '512'}} + "swap": { + "filename": self.swap_path, + "size": "512", + "maxsize": "512", + } + } def _makedirs(self, directory): - directory = os.path.join(self.new_root, directory.lstrip('/')) + directory = os.path.join(self.new_root, directory.lstrip("/")) if not os.path.exists(directory): os.makedirs(directory) def device_name_to_device(self, path): - if path == 'swap': + if path == "swap": return self.swap_path else: dev = None return dev - @mock.patch('cloudinit.util.get_mount_info') - @mock.patch('cloudinit.util.kernel_version') - def test_swap_creation_method_fallocate_on_xfs(self, m_kernel_version, - m_get_mount_info): + @mock.patch("cloudinit.util.get_mount_info") + @mock.patch("cloudinit.util.kernel_version") + def test_swap_creation_method_fallocate_on_xfs( + self, m_kernel_version, m_get_mount_info + ): m_kernel_version.return_value = (4, 20) m_get_mount_info.return_value = ["", "xfs"] cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) - self.m_subp_subp.assert_has_calls([ - mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True), - mock.call(['mkswap', self.swap_path]), - mock.call(['swapon', '-a'])]) - - @mock.patch('cloudinit.util.get_mount_info') - @mock.patch('cloudinit.util.kernel_version') - def test_swap_creation_method_xfs(self, m_kernel_version, - m_get_mount_info): + self.m_subp_subp.assert_has_calls( + [ + mock.call( + ["fallocate", "-l", "0M", self.swap_path], capture=True + ), + mock.call(["mkswap", self.swap_path]), + mock.call(["swapon", "-a"]), + ] + ) + + @mock.patch("cloudinit.util.get_mount_info") + @mock.patch("cloudinit.util.kernel_version") + def test_swap_creation_method_xfs( + self, m_kernel_version, m_get_mount_info + ): m_kernel_version.return_value = (3, 18) m_get_mount_info.return_value = ["", "xfs"] cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) - self.m_subp_subp.assert_has_calls([ - mock.call(['dd', 'if=/dev/zero', - 'of=' + self.swap_path, - 'bs=1M', 'count=0'], capture=True), - mock.call(['mkswap', self.swap_path]), - mock.call(['swapon', '-a'])]) - - @mock.patch('cloudinit.util.get_mount_info') - @mock.patch('cloudinit.util.kernel_version') - def test_swap_creation_method_btrfs(self, m_kernel_version, - m_get_mount_info): + self.m_subp_subp.assert_has_calls( + [ + mock.call( + [ + "dd", + "if=/dev/zero", + "of=" + self.swap_path, + "bs=1M", + "count=0", + ], + capture=True, + ), + mock.call(["mkswap", self.swap_path]), + mock.call(["swapon", "-a"]), + ] + ) + + @mock.patch("cloudinit.util.get_mount_info") + @mock.patch("cloudinit.util.kernel_version") + def test_swap_creation_method_btrfs( + self, m_kernel_version, m_get_mount_info + ): m_kernel_version.return_value = (4, 20) m_get_mount_info.return_value = ["", "btrfs"] cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) - self.m_subp_subp.assert_has_calls([ - mock.call(['dd', 'if=/dev/zero', - 'of=' + self.swap_path, - 'bs=1M', 'count=0'], capture=True), - mock.call(['mkswap', self.swap_path]), - mock.call(['swapon', '-a'])]) - - @mock.patch('cloudinit.util.get_mount_info') - @mock.patch('cloudinit.util.kernel_version') - def test_swap_creation_method_ext4(self, m_kernel_version, - m_get_mount_info): + self.m_subp_subp.assert_has_calls( + [ + mock.call( + [ + "dd", + "if=/dev/zero", + "of=" + self.swap_path, + "bs=1M", + "count=0", + ], + capture=True, + ), + mock.call(["mkswap", self.swap_path]), + mock.call(["swapon", "-a"]), + ] + ) + + @mock.patch("cloudinit.util.get_mount_info") + @mock.patch("cloudinit.util.kernel_version") + def test_swap_creation_method_ext4( + self, m_kernel_version, m_get_mount_info + ): m_kernel_version.return_value = (5, 14) m_get_mount_info.return_value = ["", "ext4"] cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) - self.m_subp_subp.assert_has_calls([ - mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True), - mock.call(['mkswap', self.swap_path]), - mock.call(['swapon', '-a'])]) + self.m_subp_subp.assert_has_calls( + [ + mock.call( + ["fallocate", "-l", "0M", self.swap_path], capture=True + ), + mock.call(["mkswap", self.swap_path]), + mock.call(["swapon", "-a"]), + ] + ) class TestFstabHandling(test_helpers.FilesystemMockingTestCase): - swap_path = '/dev/sdb1' + swap_path = "/dev/sdb1" def setUp(self): super(TestFstabHandling, self).setUp() self.new_root = self.tmp_dir() self.patchOS(self.new_root) - self.fstab_path = os.path.join(self.new_root, 'etc/fstab') - self._makedirs('/etc') - - self.add_patch('cloudinit.config.cc_mounts.FSTAB_PATH', - 'mock_fstab_path', - self.fstab_path, - autospec=False) + self.fstab_path = os.path.join(self.new_root, "etc/fstab") + self._makedirs("/etc") - self.add_patch('cloudinit.config.cc_mounts._is_block_device', - 'mock_is_block_device', - return_value=True) + self.add_patch( + "cloudinit.config.cc_mounts.FSTAB_PATH", + "mock_fstab_path", + self.fstab_path, + autospec=False, + ) - self.add_patch('cloudinit.config.cc_mounts.subp.subp', - 'm_subp_subp') + self.add_patch( + "cloudinit.config.cc_mounts._is_block_device", + "mock_is_block_device", + return_value=True, + ) - self.add_patch('cloudinit.config.cc_mounts.util.mounts', - 'mock_util_mounts', - return_value={ - '/dev/sda1': {'fstype': 'ext4', - 'mountpoint': '/', - 'opts': 'rw,relatime,discard' - }}) + self.add_patch("cloudinit.config.cc_mounts.subp.subp", "m_subp_subp") + + self.add_patch( + "cloudinit.config.cc_mounts.util.mounts", + "mock_util_mounts", + return_value={ + "/dev/sda1": { + "fstype": "ext4", + "mountpoint": "/", + "opts": "rw,relatime,discard", + } + }, + ) self.mock_cloud = mock.Mock() self.mock_log = mock.Mock() self.mock_cloud.device_name_to_device = self.device_name_to_device def _makedirs(self, directory): - directory = os.path.join(self.new_root, directory.lstrip('/')) + directory = os.path.join(self.new_root, directory.lstrip("/")) if not os.path.exists(directory): os.makedirs(directory) def device_name_to_device(self, path): - if path == 'swap': + if path == "swap": return self.swap_path else: dev = None @@ -304,127 +365,126 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase): return dev def test_no_fstab(self): - """ Handle images which do not include an fstab. """ + """Handle images which do not include an fstab.""" self.assertFalse(os.path.exists(cc_mounts.FSTAB_PATH)) fstab_expected_content = ( - '%s\tnone\tswap\tsw,comment=cloudconfig\t' - '0\t0\n' % (self.swap_path,) + "%s\tnone\tswap\tsw,comment=cloudconfig\t0\t0\n" + % (self.swap_path,) ) cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) - with open(cc_mounts.FSTAB_PATH, 'r') as fd: + with open(cc_mounts.FSTAB_PATH, "r") as fd: fstab_new_content = fd.read() self.assertEqual(fstab_expected_content, fstab_new_content) def test_swap_integrity(self): - '''Ensure that the swap file is correctly created and can + """Ensure that the swap file is correctly created and can swapon successfully. Fixing the corner case of: - kernel: swapon: swapfile has holes''' + kernel: swapon: swapfile has holes""" - fstab = '/swap.img swap swap defaults 0 0\n' + fstab = "/swap.img swap swap defaults 0 0\n" - with open(cc_mounts.FSTAB_PATH, 'w') as fd: + with open(cc_mounts.FSTAB_PATH, "w") as fd: fd.write(fstab) - cc = {'swap': ['filename: /swap.img', 'size: 512', 'maxsize: 512']} + cc = {"swap": ["filename: /swap.img", "size: 512", "maxsize: 512"]} cc_mounts.handle(None, cc, self.mock_cloud, self.mock_log, []) def test_fstab_no_swap_device(self): - '''Ensure that cloud-init adds a discovered swap partition - to /etc/fstab.''' + """Ensure that cloud-init adds a discovered swap partition + to /etc/fstab.""" - fstab_original_content = '' + fstab_original_content = "" fstab_expected_content = ( - '%s\tnone\tswap\tsw,comment=cloudconfig\t' - '0\t0\n' % (self.swap_path,) + "%s\tnone\tswap\tsw,comment=cloudconfig\t0\t0\n" + % (self.swap_path,) ) - with open(cc_mounts.FSTAB_PATH, 'w') as fd: + with open(cc_mounts.FSTAB_PATH, "w") as fd: fd.write(fstab_original_content) cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) - with open(cc_mounts.FSTAB_PATH, 'r') as fd: + with open(cc_mounts.FSTAB_PATH, "r") as fd: fstab_new_content = fd.read() self.assertEqual(fstab_expected_content, fstab_new_content) def test_fstab_same_swap_device_already_configured(self): - '''Ensure that cloud-init will not add a swap device if the same - device already exists in /etc/fstab.''' + """Ensure that cloud-init will not add a swap device if the same + device already exists in /etc/fstab.""" - fstab_original_content = '%s swap swap defaults 0 0\n' % ( - self.swap_path,) + fstab_original_content = "%s swap swap defaults 0 0\n" % ( + self.swap_path, + ) fstab_expected_content = fstab_original_content - with open(cc_mounts.FSTAB_PATH, 'w') as fd: + with open(cc_mounts.FSTAB_PATH, "w") as fd: fd.write(fstab_original_content) cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) - with open(cc_mounts.FSTAB_PATH, 'r') as fd: + with open(cc_mounts.FSTAB_PATH, "r") as fd: fstab_new_content = fd.read() self.assertEqual(fstab_expected_content, fstab_new_content) def test_fstab_alternate_swap_device_already_configured(self): - '''Ensure that cloud-init will add a discovered swap device to + """Ensure that cloud-init will add a discovered swap device to /etc/fstab even when there exists a swap definition on another - device.''' + device.""" - fstab_original_content = '/dev/sdc1 swap swap defaults 0 0\n' + fstab_original_content = "/dev/sdc1 swap swap defaults 0 0\n" fstab_expected_content = ( - fstab_original_content + - '%s\tnone\tswap\tsw,comment=cloudconfig\t' - '0\t0\n' % (self.swap_path,) + fstab_original_content + + "%s\tnone\tswap\tsw,comment=cloudconfig\t0\t0\n" + % (self.swap_path,) ) - with open(cc_mounts.FSTAB_PATH, 'w') as fd: + with open(cc_mounts.FSTAB_PATH, "w") as fd: fd.write(fstab_original_content) cc_mounts.handle(None, {}, self.mock_cloud, self.mock_log, []) - with open(cc_mounts.FSTAB_PATH, 'r') as fd: + with open(cc_mounts.FSTAB_PATH, "r") as fd: fstab_new_content = fd.read() self.assertEqual(fstab_expected_content, fstab_new_content) def test_no_change_fstab_sets_needs_mount_all(self): - '''verify unchanged fstab entries are mounted if not call mount -a''' + """verify unchanged fstab entries are mounted if not call mount -a""" fstab_original_content = ( - 'LABEL=cloudimg-rootfs / ext4 defaults 0 0\n' - 'LABEL=UEFI /boot/efi vfat defaults 0 0\n' - '/dev/vdb /mnt auto defaults,noexec,comment=cloudconfig 0 2\n' + "LABEL=cloudimg-rootfs / ext4 defaults 0 0\n" + "LABEL=UEFI /boot/efi vfat defaults 0 0\n" + "/dev/vdb /mnt auto defaults,noexec,comment=cloudconfig 0 2\n" ) fstab_expected_content = fstab_original_content - cc = { - 'mounts': [ - ['/dev/vdb', '/mnt', 'auto', 'defaults,noexec'] - ] - } - with open(cc_mounts.FSTAB_PATH, 'w') as fd: + cc = {"mounts": [["/dev/vdb", "/mnt", "auto", "defaults,noexec"]]} + with open(cc_mounts.FSTAB_PATH, "w") as fd: fd.write(fstab_original_content) - with open(cc_mounts.FSTAB_PATH, 'r') as fd: + with open(cc_mounts.FSTAB_PATH, "r") as fd: fstab_new_content = fd.read() self.assertEqual(fstab_expected_content, fstab_new_content) cc_mounts.handle(None, cc, self.mock_cloud, self.mock_log, []) - self.m_subp_subp.assert_has_calls([ - mock.call(['mount', '-a']), - mock.call(['systemctl', 'daemon-reload'])]) + self.m_subp_subp.assert_has_calls( + [ + mock.call(["mount", "-a"]), + mock.call(["systemctl", "daemon-reload"]), + ] + ) class TestCreateSwapfile: - - @pytest.mark.parametrize('fstype', ('xfs', 'btrfs', 'ext4', 'other')) - @mock.patch(M_PATH + 'util.get_mount_info') - @mock.patch(M_PATH + 'subp.subp') + @pytest.mark.parametrize("fstype", ("xfs", "btrfs", "ext4", "other")) + @mock.patch(M_PATH + "util.get_mount_info") + @mock.patch(M_PATH + "subp.subp") def test_happy_path(self, m_subp, m_get_mount_info, fstype, tmpdir): swap_file = tmpdir.join("swap-file") fname = str(swap_file) # Some of the calls to subp.subp should create the swap file; this # roughly approximates that - m_subp.side_effect = lambda *args, **kwargs: swap_file.write('') + m_subp.side_effect = lambda *args, **kwargs: swap_file.write("") m_get_mount_info.return_value = (mock.ANY, fstype) - create_swapfile(fname, '') - assert mock.call(['mkswap', fname]) in m_subp.call_args_list + create_swapfile(fname, "") + assert mock.call(["mkswap", fname]) in m_subp.call_args_list @mock.patch(M_PATH + "util.get_mount_info") @mock.patch(M_PATH + "subp.subp") @@ -458,4 +518,5 @@ class TestCreateSwapfile: msg = "fallocate swap creation failed, will attempt with dd" assert msg in caplog.text + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_ntp.py b/tests/unittests/config/test_cc_ntp.py index 3426533a..7da82cee 100644 --- a/tests/unittests/config/test_cc_ntp.py +++ b/tests/unittests/config/test_cc_ntp.py @@ -5,14 +5,16 @@ import shutil from functools import partial from os.path import dirname -from cloudinit import (helpers, util) +from cloudinit import helpers, util from cloudinit.config import cc_ntp from tests.unittests.helpers import ( - CiTestCase, FilesystemMockingTestCase, mock, skipUnlessJsonSchema) - + CiTestCase, + FilesystemMockingTestCase, + mock, + skipUnlessJsonSchema, +) from tests.unittests.util import get_cloud - NTP_TEMPLATE = """\ ## template: jinja servers {{servers}} @@ -35,18 +37,18 @@ class TestNtp(FilesystemMockingTestCase): def setUp(self): super(TestNtp, self).setUp() self.new_root = self.tmp_dir() - self.add_patch('cloudinit.util.system_is_snappy', 'm_snappy') + self.add_patch("cloudinit.util.system_is_snappy", "m_snappy") self.m_snappy.return_value = False self.new_root = self.reRoot() self._get_cloud = partial( - get_cloud, - paths=helpers.Paths({'templates_dir': self.new_root}) + get_cloud, paths=helpers.Paths({"templates_dir": self.new_root}) ) def _get_template_path(self, template_name, distro, basepath=None): # ntp.conf.{distro} -> ntp.conf.debian.tmpl - template_fn = '{0}.tmpl'.format( - template_name.replace('{distro}', distro)) + template_fn = "{0}.tmpl".format( + template_name.replace("{distro}", distro) + ) if not basepath: basepath = self.new_root path = os.path.join(basepath, template_fn) @@ -55,25 +57,25 @@ class TestNtp(FilesystemMockingTestCase): def _generate_template(self, template=None): if not template: template = NTP_TEMPLATE - confpath = os.path.join(self.new_root, 'client.conf') - template_fn = os.path.join(self.new_root, 'client.conf.tmpl') + confpath = os.path.join(self.new_root, "client.conf") + template_fn = os.path.join(self.new_root, "client.conf.tmpl") util.write_file(template_fn, content=template) return (confpath, template_fn) def _mock_ntp_client_config(self, client=None, distro=None): if not client: - client = 'ntp' + client = "ntp" if not distro: - distro = 'ubuntu' + distro = "ubuntu" dcfg = cc_ntp.distro_ntp_client_configs(distro) - if client == 'systemd-timesyncd': + if client == "systemd-timesyncd": template = TIMESYNCD_TEMPLATE else: template = NTP_TEMPLATE (confpath, _template_fn) = self._generate_template(template=template) ntpconfig = copy.deepcopy(dcfg[client]) - ntpconfig['confpath'] = confpath - ntpconfig['template_name'] = os.path.basename(confpath) + ntpconfig["confpath"] = confpath + ntpconfig["template_name"] = os.path.basename(confpath) return ntpconfig @mock.patch("cloudinit.config.cc_ntp.subp") @@ -81,19 +83,21 @@ class TestNtp(FilesystemMockingTestCase): """ntp_install_client runs install_func when check_exe is absent.""" mock_subp.which.return_value = None # check_exe not found. install_func = mock.MagicMock() - cc_ntp.install_ntp_client(install_func, - packages=['ntpx'], check_exe='ntpdx') - mock_subp.which.assert_called_with('ntpdx') - install_func.assert_called_once_with(['ntpx']) + cc_ntp.install_ntp_client( + install_func, packages=["ntpx"], check_exe="ntpdx" + ) + mock_subp.which.assert_called_with("ntpdx") + install_func.assert_called_once_with(["ntpx"]) @mock.patch("cloudinit.config.cc_ntp.subp") def test_ntp_install_not_needed(self, mock_subp): """ntp_install_client doesn't install when check_exe is found.""" - client = 'chrony' + client = "chrony" mock_subp.which.return_value = [client] # check_exe found. install_func = mock.MagicMock() - cc_ntp.install_ntp_client(install_func, packages=[client], - check_exe=client) + cc_ntp.install_ntp_client( + install_func, packages=[client], check_exe=client + ) install_func.assert_not_called() @mock.patch("cloudinit.config.cc_ntp.subp") @@ -101,8 +105,9 @@ class TestNtp(FilesystemMockingTestCase): """ntp_install_client runs install_func with empty list""" mock_subp.which.return_value = None # check_exe not found install_func = mock.MagicMock() - cc_ntp.install_ntp_client(install_func, packages=[], - check_exe='timesyncd') + cc_ntp.install_ntp_client( + install_func, packages=[], check_exe="timesyncd" + ) install_func.assert_called_once_with([]) def test_ntp_rename_ntp_conf(self): @@ -124,18 +129,22 @@ class TestNtp(FilesystemMockingTestCase): def test_write_ntp_config_template_uses_ntp_conf_distro_no_servers(self): """write_ntp_config_template reads from $client.conf.distro.tmpl""" servers = [] - pools = ['10.0.0.1', '10.0.0.2'] + pools = ["10.0.0.1", "10.0.0.2"] (confpath, template_fn) = self._generate_template() - mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR' + mock_path = "cloudinit.config.cc_ntp.temp_utils._TMPDIR" with mock.patch(mock_path, self.new_root): - cc_ntp.write_ntp_config_template('ubuntu', - servers=servers, pools=pools, - path=confpath, - template_fn=template_fn, - template=None) + cc_ntp.write_ntp_config_template( + "ubuntu", + servers=servers, + pools=pools, + path=confpath, + template_fn=template_fn, + template=None, + ) self.assertEqual( "servers []\npools ['10.0.0.1', '10.0.0.2']\n", - util.load_file(confpath)) + util.load_file(confpath), + ) def test_write_ntp_config_template_defaults_pools_w_empty_lists(self): """write_ntp_config_template defaults pools servers upon empty config. @@ -143,20 +152,23 @@ class TestNtp(FilesystemMockingTestCase): When both pools and servers are empty, default NR_POOL_SERVERS get configured. """ - distro = 'ubuntu' + distro = "ubuntu" pools = cc_ntp.generate_server_names(distro) servers = [] (confpath, template_fn) = self._generate_template() - mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR' + mock_path = "cloudinit.config.cc_ntp.temp_utils._TMPDIR" with mock.patch(mock_path, self.new_root): - cc_ntp.write_ntp_config_template(distro, - servers=servers, pools=pools, - path=confpath, - template_fn=template_fn, - template=None) + cc_ntp.write_ntp_config_template( + distro, + servers=servers, + pools=pools, + path=confpath, + template_fn=template_fn, + template=None, + ) self.assertEqual( - "servers []\npools {0}\n".format(pools), - util.load_file(confpath)) + "servers []\npools {0}\n".format(pools), util.load_file(confpath) + ) def test_defaults_pools_empty_lists_sles(self): """write_ntp_config_template defaults opensuse pools upon empty config. @@ -164,39 +176,50 @@ class TestNtp(FilesystemMockingTestCase): When both pools and servers are empty, default NR_POOL_SERVERS get configured. """ - distro = 'sles' + distro = "sles" default_pools = cc_ntp.generate_server_names(distro) (confpath, template_fn) = self._generate_template() - cc_ntp.write_ntp_config_template(distro, - servers=[], pools=[], - path=confpath, - template_fn=template_fn, - template=None) + cc_ntp.write_ntp_config_template( + distro, + servers=[], + pools=[], + path=confpath, + template_fn=template_fn, + template=None, + ) for pool in default_pools: - self.assertIn('opensuse', pool) + self.assertIn("opensuse", pool) self.assertEqual( "servers []\npools {0}\n".format(default_pools), - util.load_file(confpath)) + util.load_file(confpath), + ) self.assertIn( "Adding distro default ntp pool servers: {0}".format( - ",".join(default_pools)), - self.logs.getvalue()) + ",".join(default_pools) + ), + self.logs.getvalue(), + ) def test_timesyncd_template(self): """Test timesycnd template is correct""" - pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org'] - servers = ['192.168.23.3', '192.168.23.4'] + pools = ["0.mycompany.pool.ntp.org", "3.mycompany.pool.ntp.org"] + servers = ["192.168.23.3", "192.168.23.4"] (confpath, template_fn) = self._generate_template( - template=TIMESYNCD_TEMPLATE) - cc_ntp.write_ntp_config_template('ubuntu', - servers=servers, pools=pools, - path=confpath, - template_fn=template_fn, - template=None) + template=TIMESYNCD_TEMPLATE + ) + cc_ntp.write_ntp_config_template( + "ubuntu", + servers=servers, + pools=pools, + path=confpath, + template_fn=template_fn, + template=None, + ) self.assertEqual( "[Time]\nNTP=%s %s \n" % (" ".join(servers), " ".join(pools)), - util.load_file(confpath)) + util.load_file(confpath), + ) def test_distro_ntp_client_configs(self): """Test we have updated ntp client configs on different distros""" @@ -213,55 +236,62 @@ class TestNtp(FilesystemMockingTestCase): result = cc_ntp.distro_ntp_client_configs(distro) for client in delta[distro].keys(): for key in delta[distro][client].keys(): - self.assertEqual(delta[distro][client][key], - result[client][key]) + self.assertEqual( + delta[distro][client][key], result[client][key] + ) def _get_expected_pools(self, pools, distro, client): - if client in ['ntp', 'chrony']: - if client == 'ntp' and distro == 'alpine': + if client in ["ntp", "chrony"]: + if client == "ntp" and distro == "alpine": # NTP for Alpine Linux is Busybox's ntp which does not # support 'pool' lines in its configuration file. expected_pools = [] else: expected_pools = [ - 'pool {0} iburst'.format(pool) for pool in pools] - elif client == 'systemd-timesyncd': + "pool {0} iburst".format(pool) for pool in pools + ] + elif client == "systemd-timesyncd": expected_pools = " ".join(pools) return expected_pools def _get_expected_servers(self, servers, distro, client): - if client in ['ntp', 'chrony']: - if client == 'ntp' and distro == 'alpine': + if client in ["ntp", "chrony"]: + if client == "ntp" and distro == "alpine": # NTP for Alpine Linux is Busybox's ntp which only supports # 'server' lines without iburst option. expected_servers = [ - 'server {0}'.format(srv) for srv in servers] + "server {0}".format(srv) for srv in servers + ] else: expected_servers = [ - 'server {0} iburst'.format(srv) for srv in servers] - elif client == 'systemd-timesyncd': + "server {0} iburst".format(srv) for srv in servers + ] + elif client == "systemd-timesyncd": expected_servers = " ".join(servers) return expected_servers def test_ntp_handler_real_distro_ntp_templates(self): """Test ntp handler renders the shipped distro ntp client templates.""" - pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org'] - servers = ['192.168.23.3', '192.168.23.4'] - for client in ['ntp', 'systemd-timesyncd', 'chrony']: + pools = ["0.mycompany.pool.ntp.org", "3.mycompany.pool.ntp.org"] + servers = ["192.168.23.3", "192.168.23.4"] + for client in ["ntp", "systemd-timesyncd", "chrony"]: for distro in cc_ntp.distros: distro_cfg = cc_ntp.distro_ntp_client_configs(distro) ntpclient = distro_cfg[client] - confpath = ( - os.path.join(self.new_root, ntpclient.get('confpath')[1:])) - template = ntpclient.get('template_name') + confpath = os.path.join( + self.new_root, ntpclient.get("confpath")[1:] + ) + template = ntpclient.get("template_name") # find sourcetree template file root_dir = ( - dirname(dirname(os.path.realpath(util.__file__))) + - '/templates') - source_fn = self._get_template_path(template, distro, - basepath=root_dir) + dirname(dirname(os.path.realpath(util.__file__))) + + "/templates" + ) + source_fn = self._get_template_path( + template, distro, basepath=root_dir + ) template_fn = self._get_template_path(template, distro) # don't fail if cloud-init doesn't have a template for # a distro,client pair @@ -269,64 +299,77 @@ class TestNtp(FilesystemMockingTestCase): continue # Create a copy in our tmp_dir shutil.copy(source_fn, template_fn) - cc_ntp.write_ntp_config_template(distro, servers=servers, - pools=pools, path=confpath, - template_fn=template_fn) + cc_ntp.write_ntp_config_template( + distro, + servers=servers, + pools=pools, + path=confpath, + template_fn=template_fn, + ) content = util.load_file(confpath) - if client in ['ntp', 'chrony']: + if client in ["ntp", "chrony"]: content_lines = content.splitlines() - expected_servers = self._get_expected_servers(servers, - distro, - client) - print('distro=%s client=%s' % (distro, client)) + expected_servers = self._get_expected_servers( + servers, distro, client + ) + print("distro=%s client=%s" % (distro, client)) for sline in expected_servers: - self.assertIn(sline, content_lines, - ('failed to render {0} conf' - ' for distro:{1}'.format(client, - distro))) - expected_pools = self._get_expected_pools(pools, distro, - client) + self.assertIn( + sline, + content_lines, + "failed to render {0} conf for distro:{1}".format( + client, distro + ), + ) + expected_pools = self._get_expected_pools( + pools, distro, client + ) if expected_pools != []: for pline in expected_pools: - self.assertIn(pline, content_lines, - ('failed to render {0} conf' - ' for distro:{1}'.format(client, - distro))) - elif client == 'systemd-timesyncd': - expected_servers = self._get_expected_servers(servers, - distro, - client) - expected_pools = self._get_expected_pools(pools, - distro, - client) + self.assertIn( + pline, + content_lines, + "failed to render {0} conf" + " for distro:{1}".format(client, distro), + ) + elif client == "systemd-timesyncd": + expected_servers = self._get_expected_servers( + servers, distro, client + ) + expected_pools = self._get_expected_pools( + pools, distro, client + ) expected_content = ( - "# cloud-init generated file\n" + - "# See timesyncd.conf(5) for details.\n\n" + - "[Time]\nNTP=%s %s \n" % (expected_servers, - expected_pools)) + "# cloud-init generated file\n" + + "# See timesyncd.conf(5) for details.\n\n" + + "[Time]\nNTP=%s %s \n" + % (expected_servers, expected_pools) + ) self.assertEqual(expected_content, content) def test_no_ntpcfg_does_nothing(self): """When no ntp section is defined handler logs a warning and noops.""" - cc_ntp.handle('cc_ntp', {}, None, None, []) + cc_ntp.handle("cc_ntp", {}, None, None, []) self.assertEqual( - 'DEBUG: Skipping module named cc_ntp, ' - 'not present or disabled by cfg\n', - self.logs.getvalue()) + "DEBUG: Skipping module named cc_ntp, " + "not present or disabled by cfg\n", + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') - def test_ntp_handler_schema_validation_allows_empty_ntp_config(self, - m_select): + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") + def test_ntp_handler_schema_validation_allows_empty_ntp_config( + self, m_select + ): """Ntp schema validation allows for an empty ntp: configuration.""" - valid_empty_configs = [{'ntp': {}}, {'ntp': None}] + valid_empty_configs = [{"ntp": {}}, {"ntp": None}] for valid_empty_config in valid_empty_configs: for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) ntpconfig = self._mock_ntp_client_config(distro=distro) - confpath = ntpconfig['confpath'] + confpath = ntpconfig["confpath"] m_select.return_value = ntpconfig - cc_ntp.handle('cc_ntp', valid_empty_config, mycloud, None, []) - if distro == 'alpine': + cc_ntp.handle("cc_ntp", valid_empty_config, mycloud, None, []) + if distro == "alpine": # _mock_ntp_client_config call above did not specify a # client value and so it defaults to "ntp" which on # Alpine Linux only supports servers and not pools. @@ -334,217 +377,240 @@ class TestNtp(FilesystemMockingTestCase): servers = cc_ntp.generate_server_names(mycloud.distro.name) self.assertEqual( "servers {0}\npools []\n".format(servers), - util.load_file(confpath)) + util.load_file(confpath), + ) else: pools = cc_ntp.generate_server_names(mycloud.distro.name) self.assertEqual( "servers []\npools {0}\n".format(pools), - util.load_file(confpath)) - self.assertNotIn('Invalid config:', self.logs.getvalue()) + util.load_file(confpath), + ) + self.assertNotIn("Invalid config:", self.logs.getvalue()) @skipUnlessJsonSchema() - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') - def test_ntp_handler_schema_validation_warns_non_string_item_type(self, - m_sel): + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") + def test_ntp_handler_schema_validation_warns_non_string_item_type( + self, m_sel + ): """Ntp schema validation warns of non-strings in pools or servers. Schema validation is not strict, so ntp config is still be rendered. """ - invalid_config = {'ntp': {'pools': [123], 'servers': ['valid', None]}} + invalid_config = {"ntp": {"pools": [123], "servers": ["valid", None]}} for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) ntpconfig = self._mock_ntp_client_config(distro=distro) - confpath = ntpconfig['confpath'] + confpath = ntpconfig["confpath"] m_sel.return_value = ntpconfig - cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, []) + cc_ntp.handle("cc_ntp", invalid_config, mycloud, None, []) self.assertIn( "Invalid config:\nntp.pools.0: 123 is not of type 'string'\n" "ntp.servers.1: None is not of type 'string'", - self.logs.getvalue()) - self.assertEqual("servers ['valid', None]\npools [123]\n", - util.load_file(confpath)) + self.logs.getvalue(), + ) + self.assertEqual( + "servers ['valid', None]\npools [123]\n", + util.load_file(confpath), + ) @skipUnlessJsonSchema() - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') - def test_ntp_handler_schema_validation_warns_of_non_array_type(self, - m_select): + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") + def test_ntp_handler_schema_validation_warns_of_non_array_type( + self, m_select + ): """Ntp schema validation warns of non-array pools or servers types. Schema validation is not strict, so ntp config is still be rendered. """ - invalid_config = {'ntp': {'pools': 123, 'servers': 'non-array'}} + invalid_config = {"ntp": {"pools": 123, "servers": "non-array"}} for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) ntpconfig = self._mock_ntp_client_config(distro=distro) - confpath = ntpconfig['confpath'] + confpath = ntpconfig["confpath"] m_select.return_value = ntpconfig - cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, []) + cc_ntp.handle("cc_ntp", invalid_config, mycloud, None, []) self.assertIn( "Invalid config:\nntp.pools: 123 is not of type 'array'\n" "ntp.servers: 'non-array' is not of type 'array'", - self.logs.getvalue()) - self.assertEqual("servers non-array\npools 123\n", - util.load_file(confpath)) + self.logs.getvalue(), + ) + self.assertEqual( + "servers non-array\npools 123\n", util.load_file(confpath) + ) @skipUnlessJsonSchema() - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') - def test_ntp_handler_schema_validation_warns_invalid_key_present(self, - m_select): + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") + def test_ntp_handler_schema_validation_warns_invalid_key_present( + self, m_select + ): """Ntp schema validation warns of invalid keys present in ntp config. Schema validation is not strict, so ntp config is still be rendered. """ invalid_config = { - 'ntp': {'invalidkey': 1, 'pools': ['0.mycompany.pool.ntp.org']}} + "ntp": {"invalidkey": 1, "pools": ["0.mycompany.pool.ntp.org"]} + } for distro in cc_ntp.distros: - if distro != 'alpine': + if distro != "alpine": mycloud = self._get_cloud(distro) ntpconfig = self._mock_ntp_client_config(distro=distro) - confpath = ntpconfig['confpath'] + confpath = ntpconfig["confpath"] m_select.return_value = ntpconfig - cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, []) + cc_ntp.handle("cc_ntp", invalid_config, mycloud, None, []) self.assertIn( "Invalid config:\nntp: Additional properties are not " "allowed ('invalidkey' was unexpected)", - self.logs.getvalue()) + self.logs.getvalue(), + ) self.assertEqual( "servers []\npools ['0.mycompany.pool.ntp.org']\n", - util.load_file(confpath)) + util.load_file(confpath), + ) @skipUnlessJsonSchema() - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") def test_ntp_handler_schema_validation_warns_of_duplicates(self, m_select): """Ntp schema validation warns of duplicates in servers or pools. Schema validation is not strict, so ntp config is still be rendered. """ invalid_config = { - 'ntp': {'pools': ['0.mypool.org', '0.mypool.org'], - 'servers': ['10.0.0.1', '10.0.0.1']}} + "ntp": { + "pools": ["0.mypool.org", "0.mypool.org"], + "servers": ["10.0.0.1", "10.0.0.1"], + } + } for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) ntpconfig = self._mock_ntp_client_config(distro=distro) - confpath = ntpconfig['confpath'] + confpath = ntpconfig["confpath"] m_select.return_value = ntpconfig - cc_ntp.handle('cc_ntp', invalid_config, mycloud, None, []) + cc_ntp.handle("cc_ntp", invalid_config, mycloud, None, []) self.assertIn( "Invalid config:\nntp.pools: ['0.mypool.org', '0.mypool.org']" " has non-unique elements\nntp.servers: " "['10.0.0.1', '10.0.0.1'] has non-unique elements", - self.logs.getvalue()) + self.logs.getvalue(), + ) self.assertEqual( "servers ['10.0.0.1', '10.0.0.1']\n" "pools ['0.mypool.org', '0.mypool.org']\n", - util.load_file(confpath)) + util.load_file(confpath), + ) - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") def test_ntp_handler_timesyncd(self, m_select): """Test ntp handler configures timesyncd""" - servers = ['192.168.2.1', '192.168.2.2'] - pools = ['0.mypool.org'] - cfg = {'ntp': {'servers': servers, 'pools': pools}} - client = 'systemd-timesyncd' + servers = ["192.168.2.1", "192.168.2.2"] + pools = ["0.mypool.org"] + cfg = {"ntp": {"servers": servers, "pools": pools}} + client = "systemd-timesyncd" for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) - ntpconfig = self._mock_ntp_client_config(distro=distro, - client=client) - confpath = ntpconfig['confpath'] + ntpconfig = self._mock_ntp_client_config( + distro=distro, client=client + ) + confpath = ntpconfig["confpath"] m_select.return_value = ntpconfig - cc_ntp.handle('cc_ntp', cfg, mycloud, None, []) + cc_ntp.handle("cc_ntp", cfg, mycloud, None, []) self.assertEqual( "[Time]\nNTP=192.168.2.1 192.168.2.2 0.mypool.org \n", - util.load_file(confpath)) + util.load_file(confpath), + ) - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") def test_ntp_handler_enabled_false(self, m_select): - """Test ntp handler does not run if enabled: false """ - cfg = {'ntp': {'enabled': False}} + """Test ntp handler does not run if enabled: false""" + cfg = {"ntp": {"enabled": False}} for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) - cc_ntp.handle('notimportant', cfg, mycloud, None, None) + cc_ntp.handle("notimportant", cfg, mycloud, None, None) self.assertEqual(0, m_select.call_count) @mock.patch("cloudinit.distros.subp") @mock.patch("cloudinit.config.cc_ntp.subp") - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") @mock.patch("cloudinit.distros.Distro.uses_systemd") def test_ntp_the_whole_package(self, m_sysd, m_select, m_subp, m_dsubp): - """Test enabled config renders template, and restarts service """ - cfg = {'ntp': {'enabled': True}} + """Test enabled config renders template, and restarts service""" + cfg = {"ntp": {"enabled": True}} for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) ntpconfig = self._mock_ntp_client_config(distro=distro) - confpath = ntpconfig['confpath'] - service_name = ntpconfig['service_name'] + confpath = ntpconfig["confpath"] + service_name = ntpconfig["service_name"] m_select.return_value = ntpconfig hosts = cc_ntp.generate_server_names(mycloud.distro.name) uses_systemd = True - expected_service_call = ['systemctl', 'reload-or-restart', - service_name] + expected_service_call = [ + "systemctl", + "reload-or-restart", + service_name, + ] expected_content = "servers []\npools {0}\n".format(hosts) - if distro == 'alpine': + if distro == "alpine": uses_systemd = False - expected_service_call = ['rc-service', service_name, 'restart'] + expected_service_call = ["rc-service", service_name, "restart"] # _mock_ntp_client_config call above did not specify a client # value and so it defaults to "ntp" which on Alpine Linux only # supports servers and not pools. expected_content = "servers {0}\npools []\n".format(hosts) m_sysd.return_value = uses_systemd - with mock.patch('cloudinit.config.cc_ntp.util') as m_util: + with mock.patch("cloudinit.config.cc_ntp.util") as m_util: # allow use of util.mergemanydict m_util.mergemanydict.side_effect = util.mergemanydict # default client is present m_subp.which.return_value = True # use the config 'enabled' value m_util.is_false.return_value = util.is_false( - cfg['ntp']['enabled']) - cc_ntp.handle('notimportant', cfg, mycloud, None, None) + cfg["ntp"]["enabled"] + ) + cc_ntp.handle("notimportant", cfg, mycloud, None, None) m_dsubp.subp.assert_called_with( - expected_service_call, capture=True) + expected_service_call, capture=True + ) self.assertEqual(expected_content, util.load_file(confpath)) - @mock.patch('cloudinit.util.system_info') + @mock.patch("cloudinit.util.system_info") def test_opensuse_picks_chrony(self, m_sysinfo): """Test opensuse picks chrony or ntp on certain distro versions""" # < 15.0 => ntp - m_sysinfo.return_value = { - 'dist': ('openSUSE', '13.2', 'Harlequin') - } - mycloud = self._get_cloud('opensuse') + m_sysinfo.return_value = {"dist": ("openSUSE", "13.2", "Harlequin")} + mycloud = self._get_cloud("opensuse") expected_client = mycloud.distro.preferred_ntp_clients[0] - self.assertEqual('ntp', expected_client) + self.assertEqual("ntp", expected_client) # >= 15.0 and not openSUSE => chrony m_sysinfo.return_value = { - 'dist': ('SLES', '15.0', 'SUSE Linux Enterprise Server 15') + "dist": ("SLES", "15.0", "SUSE Linux Enterprise Server 15") } - mycloud = self._get_cloud('sles') + mycloud = self._get_cloud("sles") expected_client = mycloud.distro.preferred_ntp_clients[0] - self.assertEqual('chrony', expected_client) + self.assertEqual("chrony", expected_client) # >= 15.0 and openSUSE and ver != 42 => chrony m_sysinfo.return_value = { - 'dist': ('openSUSE Tumbleweed', '20180326', 'timbleweed') + "dist": ("openSUSE Tumbleweed", "20180326", "timbleweed") } - mycloud = self._get_cloud('opensuse') + mycloud = self._get_cloud("opensuse") expected_client = mycloud.distro.preferred_ntp_clients[0] - self.assertEqual('chrony', expected_client) + self.assertEqual("chrony", expected_client) - @mock.patch('cloudinit.util.system_info') + @mock.patch("cloudinit.util.system_info") def test_ubuntu_xenial_picks_ntp(self, m_sysinfo): """Test Ubuntu picks ntp on xenial release""" - m_sysinfo.return_value = {'dist': ('Ubuntu', '16.04', 'xenial')} - mycloud = self._get_cloud('ubuntu') + m_sysinfo.return_value = {"dist": ("Ubuntu", "16.04", "xenial")} + mycloud = self._get_cloud("ubuntu") expected_client = mycloud.distro.preferred_ntp_clients[0] - self.assertEqual('ntp', expected_client) + self.assertEqual("ntp", expected_client) - @mock.patch('cloudinit.config.cc_ntp.subp.which') + @mock.patch("cloudinit.config.cc_ntp.subp.which") def test_snappy_system_picks_timesyncd(self, m_which): """Test snappy systems prefer installed clients""" @@ -552,26 +618,27 @@ class TestNtp(FilesystemMockingTestCase): self.m_snappy.return_value = True # ubuntu core systems will have timesyncd installed - m_which.side_effect = iter([None, '/lib/systemd/systemd-timesyncd', - None, None, None]) - distro = 'ubuntu' + m_which.side_effect = iter( + [None, "/lib/systemd/systemd-timesyncd", None, None, None] + ) + distro = "ubuntu" mycloud = self._get_cloud(distro) distro_configs = cc_ntp.distro_ntp_client_configs(distro) - expected_client = 'systemd-timesyncd' + expected_client = "systemd-timesyncd" expected_cfg = distro_configs[expected_client] expected_calls = [] # we only get to timesyncd for client in mycloud.distro.preferred_ntp_clients[0:2]: cfg = distro_configs[client] - expected_calls.append(mock.call(cfg['check_exe'])) + expected_calls.append(mock.call(cfg["check_exe"])) result = cc_ntp.select_ntp_client(None, mycloud.distro) m_which.assert_has_calls(expected_calls) self.assertEqual(sorted(expected_cfg), sorted(cfg)) self.assertEqual(sorted(expected_cfg), sorted(result)) - @mock.patch('cloudinit.config.cc_ntp.subp.which') + @mock.patch("cloudinit.config.cc_ntp.subp.which") def test_ntp_distro_searches_all_preferred_clients(self, m_which): - """Test select_ntp_client search all distro perferred clients """ + """Test select_ntp_client search all distro perferred clients""" # nothing is installed m_which.return_value = None for distro in cc_ntp.distros: @@ -582,12 +649,12 @@ class TestNtp(FilesystemMockingTestCase): expected_calls = [] for client in mycloud.distro.preferred_ntp_clients: cfg = distro_configs[client] - expected_calls.append(mock.call(cfg['check_exe'])) + expected_calls.append(mock.call(cfg["check_exe"])) cc_ntp.select_ntp_client({}, mycloud.distro) m_which.assert_has_calls(expected_calls) self.assertEqual(sorted(expected_cfg), sorted(cfg)) - @mock.patch('cloudinit.config.cc_ntp.subp.which') + @mock.patch("cloudinit.config.cc_ntp.subp.which") def test_user_cfg_ntp_client_auto_uses_distro_clients(self, m_which): """Test user_cfg.ntp_client='auto' defaults to distro search""" # nothing is installed @@ -600,34 +667,36 @@ class TestNtp(FilesystemMockingTestCase): expected_calls = [] for client in mycloud.distro.preferred_ntp_clients: cfg = distro_configs[client] - expected_calls.append(mock.call(cfg['check_exe'])) - cc_ntp.select_ntp_client('auto', mycloud.distro) + expected_calls.append(mock.call(cfg["check_exe"])) + cc_ntp.select_ntp_client("auto", mycloud.distro) m_which.assert_has_calls(expected_calls) self.assertEqual(sorted(expected_cfg), sorted(cfg)) - @mock.patch('cloudinit.config.cc_ntp.write_ntp_config_template') - @mock.patch('cloudinit.cloud.Cloud.get_template_filename') - @mock.patch('cloudinit.config.cc_ntp.subp.which') - def test_ntp_custom_client_overrides_installed_clients(self, m_which, - m_tmpfn, m_write): - """Test user client is installed despite other clients present """ - client = 'ntpdate' - cfg = {'ntp': {'ntp_client': client}} + @mock.patch("cloudinit.config.cc_ntp.write_ntp_config_template") + @mock.patch("cloudinit.cloud.Cloud.get_template_filename") + @mock.patch("cloudinit.config.cc_ntp.subp.which") + def test_ntp_custom_client_overrides_installed_clients( + self, m_which, m_tmpfn, m_write + ): + """Test user client is installed despite other clients present""" + client = "ntpdate" + cfg = {"ntp": {"ntp_client": client}} for distro in cc_ntp.distros: # client is not installed m_which.side_effect = iter([None]) mycloud = self._get_cloud(distro) - with mock.patch.object(mycloud.distro, - 'install_packages') as m_install: - cc_ntp.handle('notimportant', cfg, mycloud, None, None) + with mock.patch.object( + mycloud.distro, "install_packages" + ) as m_install: + cc_ntp.handle("notimportant", cfg, mycloud, None, None) m_install.assert_called_with([client]) m_which.assert_called_with(client) - @mock.patch('cloudinit.config.cc_ntp.subp.which') + @mock.patch("cloudinit.config.cc_ntp.subp.which") def test_ntp_system_config_overrides_distro_builtin_clients(self, m_which): """Test distro system_config overrides builtin preferred ntp clients""" - system_client = 'chrony' - sys_cfg = {'ntp_client': system_client} + system_client = "chrony" + sys_cfg = {"ntp_client": system_client} # no clients installed m_which.return_value = None for distro in cc_ntp.distros: @@ -638,12 +707,12 @@ class TestNtp(FilesystemMockingTestCase): self.assertEqual(sorted(expected_cfg), sorted(result)) m_which.assert_has_calls([]) - @mock.patch('cloudinit.config.cc_ntp.subp.which') + @mock.patch("cloudinit.config.cc_ntp.subp.which") def test_ntp_user_config_overrides_system_cfg(self, m_which): """Test user-data overrides system_config ntp_client""" - system_client = 'chrony' - sys_cfg = {'ntp_client': system_client} - user_client = 'systemd-timesyncd' + system_client = "chrony" + sys_cfg = {"ntp_client": system_client} + user_client = "systemd-timesyncd" # no clients installed m_which.return_value = None for distro in cc_ntp.distros: @@ -654,112 +723,145 @@ class TestNtp(FilesystemMockingTestCase): self.assertEqual(sorted(expected_cfg), sorted(result)) m_which.assert_has_calls([]) - @mock.patch('cloudinit.config.cc_ntp.install_ntp_client') + @mock.patch("cloudinit.config.cc_ntp.install_ntp_client") def test_ntp_user_provided_config_with_template(self, m_install): - custom = r'\n#MyCustomTemplate' + custom = r"\n#MyCustomTemplate" user_template = NTP_TEMPLATE + custom - confpath = os.path.join(self.new_root, 'etc/myntp/myntp.conf') + confpath = os.path.join(self.new_root, "etc/myntp/myntp.conf") cfg = { - 'ntp': { - 'pools': ['mypool.org'], - 'ntp_client': 'myntpd', - 'config': { - 'check_exe': 'myntpd', - 'confpath': confpath, - 'packages': ['myntp'], - 'service_name': 'myntp', - 'template': user_template, - } + "ntp": { + "pools": ["mypool.org"], + "ntp_client": "myntpd", + "config": { + "check_exe": "myntpd", + "confpath": confpath, + "packages": ["myntp"], + "service_name": "myntp", + "template": user_template, + }, } } for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) - mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR' + mock_path = "cloudinit.config.cc_ntp.temp_utils._TMPDIR" with mock.patch(mock_path, self.new_root): - cc_ntp.handle('notimportant', cfg, mycloud, None, None) + cc_ntp.handle("notimportant", cfg, mycloud, None, None) self.assertEqual( "servers []\npools ['mypool.org']\n%s" % custom, - util.load_file(confpath)) - - @mock.patch('cloudinit.config.cc_ntp.supplemental_schema_validation') - @mock.patch('cloudinit.config.cc_ntp.install_ntp_client') - @mock.patch('cloudinit.config.cc_ntp.select_ntp_client') - def test_ntp_user_provided_config_template_only(self, m_select, m_install, - m_schema): + util.load_file(confpath), + ) + + @mock.patch("cloudinit.config.cc_ntp.supplemental_schema_validation") + @mock.patch("cloudinit.config.cc_ntp.install_ntp_client") + @mock.patch("cloudinit.config.cc_ntp.select_ntp_client") + def test_ntp_user_provided_config_template_only( + self, m_select, m_install, m_schema + ): """Test custom template for default client""" - custom = r'\n#MyCustomTemplate' + custom = r"\n#MyCustomTemplate" user_template = NTP_TEMPLATE + custom - client = 'chrony' + client = "chrony" cfg = { - 'pools': ['mypool.org'], - 'ntp_client': client, - 'config': { - 'template': user_template, - } + "pools": ["mypool.org"], + "ntp_client": client, + "config": { + "template": user_template, + }, } expected_merged_cfg = { - 'check_exe': 'chronyd', - 'confpath': '{tmpdir}/client.conf'.format(tmpdir=self.new_root), - 'template_name': 'client.conf', 'template': user_template, - 'service_name': 'chrony', 'packages': ['chrony']} + "check_exe": "chronyd", + "confpath": "{tmpdir}/client.conf".format(tmpdir=self.new_root), + "template_name": "client.conf", + "template": user_template, + "service_name": "chrony", + "packages": ["chrony"], + } for distro in cc_ntp.distros: mycloud = self._get_cloud(distro) - ntpconfig = self._mock_ntp_client_config(client=client, - distro=distro) - confpath = ntpconfig['confpath'] + ntpconfig = self._mock_ntp_client_config( + client=client, distro=distro + ) + confpath = ntpconfig["confpath"] m_select.return_value = ntpconfig - mock_path = 'cloudinit.config.cc_ntp.temp_utils._TMPDIR' + mock_path = "cloudinit.config.cc_ntp.temp_utils._TMPDIR" with mock.patch(mock_path, self.new_root): - cc_ntp.handle('notimportant', - {'ntp': cfg}, mycloud, None, None) + cc_ntp.handle( + "notimportant", {"ntp": cfg}, mycloud, None, None + ) self.assertEqual( "servers []\npools ['mypool.org']\n%s" % custom, - util.load_file(confpath)) + util.load_file(confpath), + ) m_schema.assert_called_with(expected_merged_cfg) class TestSupplementalSchemaValidation(CiTestCase): - def test_error_on_missing_keys(self): """ValueError raised reporting any missing required ntp:config keys""" cfg = {} - match = (r'Invalid ntp configuration:\\nMissing required ntp:config' - ' keys: check_exe, confpath, packages, service_name') + match = ( + r"Invalid ntp configuration:\\nMissing required ntp:config" + " keys: check_exe, confpath, packages, service_name" + ) with self.assertRaisesRegex(ValueError, match): cc_ntp.supplemental_schema_validation(cfg) def test_error_requiring_either_template_or_template_name(self): """ValueError raised if both template not template_name are None.""" - cfg = {'confpath': 'someconf', 'check_exe': '', 'service_name': '', - 'template': None, 'template_name': None, 'packages': []} - match = (r'Invalid ntp configuration:\\nEither ntp:config:template' - ' or ntp:config:template_name values are required') + cfg = { + "confpath": "someconf", + "check_exe": "", + "service_name": "", + "template": None, + "template_name": None, + "packages": [], + } + match = ( + r"Invalid ntp configuration:\\nEither ntp:config:template" + " or ntp:config:template_name values are required" + ) with self.assertRaisesRegex(ValueError, match): cc_ntp.supplemental_schema_validation(cfg) def test_error_on_non_list_values(self): """ValueError raised when packages is not of type list.""" - cfg = {'confpath': 'someconf', 'check_exe': '', 'service_name': '', - 'template': 'asdf', 'template_name': None, 'packages': 'NOPE'} - match = (r'Invalid ntp configuration:\\nExpected a list of required' - ' package names for ntp:config:packages. Found \\(NOPE\\)') + cfg = { + "confpath": "someconf", + "check_exe": "", + "service_name": "", + "template": "asdf", + "template_name": None, + "packages": "NOPE", + } + match = ( + r"Invalid ntp configuration:\\nExpected a list of required" + " package names for ntp:config:packages. Found \\(NOPE\\)" + ) with self.assertRaisesRegex(ValueError, match): cc_ntp.supplemental_schema_validation(cfg) def test_error_on_non_string_values(self): """ValueError raised for any values expected as string type.""" - cfg = {'confpath': 1, 'check_exe': 2, 'service_name': 3, - 'template': 4, 'template_name': 5, 'packages': []} + cfg = { + "confpath": 1, + "check_exe": 2, + "service_name": 3, + "template": 4, + "template_name": 5, + "packages": [], + } errors = [ - 'Expected a config file path ntp:config:confpath. Found (1)', - 'Expected a string type for ntp:config:check_exe. Found (2)', - 'Expected a string type for ntp:config:service_name. Found (3)', - 'Expected a string type for ntp:config:template. Found (4)', - 'Expected a string type for ntp:config:template_name. Found (5)'] + "Expected a config file path ntp:config:confpath. Found (1)", + "Expected a string type for ntp:config:check_exe. Found (2)", + "Expected a string type for ntp:config:service_name. Found (3)", + "Expected a string type for ntp:config:template. Found (4)", + "Expected a string type for ntp:config:template_name. Found (5)", + ] with self.assertRaises(ValueError) as context_mgr: cc_ntp.supplemental_schema_validation(cfg) error_msg = str(context_mgr.exception) for error in errors: self.assertIn(error, error_msg) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_power_state_change.py b/tests/unittests/config/test_cc_power_state_change.py index e699f424..47eb0d58 100644 --- a/tests/unittests/config/test_cc_power_state_change.py +++ b/tests/unittests/config/test_cc_power_state_change.py @@ -2,11 +2,8 @@ import sys +from cloudinit import distros, helpers from cloudinit.config import cc_power_state_change as psc - -from cloudinit import distros -from cloudinit import helpers - from tests.unittests import helpers as t_help from tests.unittests.helpers import mock @@ -14,9 +11,9 @@ from tests.unittests.helpers import mock class TestLoadPowerState(t_help.TestCase): def setUp(self): super(TestLoadPowerState, self).setUp() - cls = distros.fetch('ubuntu') + cls = distros.fetch("ubuntu") paths = helpers.Paths({}) - self.dist = cls('ubuntu', {}, paths) + self.dist = cls("ubuntu", {}, paths) def test_no_config(self): # completely empty config should mean do nothing @@ -25,85 +22,86 @@ class TestLoadPowerState(t_help.TestCase): def test_irrelevant_config(self): # no power_state field in config should return None for cmd - (cmd, _timeout, _condition) = psc.load_power_state({'foo': 'bar'}, - self.dist) + (cmd, _timeout, _condition) = psc.load_power_state( + {"foo": "bar"}, self.dist + ) self.assertIsNone(cmd) def test_invalid_mode(self): - cfg = {'power_state': {'mode': 'gibberish'}} + cfg = {"power_state": {"mode": "gibberish"}} self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist) - cfg = {'power_state': {'mode': ''}} + cfg = {"power_state": {"mode": ""}} self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist) def test_empty_mode(self): - cfg = {'power_state': {'message': 'goodbye'}} + cfg = {"power_state": {"message": "goodbye"}} self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist) def test_valid_modes(self): - cfg = {'power_state': {}} - for mode in ('halt', 'poweroff', 'reboot'): - cfg['power_state']['mode'] = mode + cfg = {"power_state": {}} + for mode in ("halt", "poweroff", "reboot"): + cfg["power_state"]["mode"] = mode check_lps_ret(psc.load_power_state(cfg, self.dist), mode=mode) def test_invalid_delay(self): - cfg = {'power_state': {'mode': 'poweroff', 'delay': 'goodbye'}} + cfg = {"power_state": {"mode": "poweroff", "delay": "goodbye"}} self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist) def test_valid_delay(self): - cfg = {'power_state': {'mode': 'poweroff', 'delay': ''}} + cfg = {"power_state": {"mode": "poweroff", "delay": ""}} for delay in ("now", "+1", "+30"): - cfg['power_state']['delay'] = delay + cfg["power_state"]["delay"] = delay check_lps_ret(psc.load_power_state(cfg, self.dist)) def test_message_present(self): - cfg = {'power_state': {'mode': 'poweroff', 'message': 'GOODBYE'}} + cfg = {"power_state": {"mode": "poweroff", "message": "GOODBYE"}} ret = psc.load_power_state(cfg, self.dist) check_lps_ret(psc.load_power_state(cfg, self.dist)) - self.assertIn(cfg['power_state']['message'], ret[0]) + self.assertIn(cfg["power_state"]["message"], ret[0]) def test_no_message(self): # if message is not present, then no argument should be passed for it - cfg = {'power_state': {'mode': 'poweroff'}} + cfg = {"power_state": {"mode": "poweroff"}} (cmd, _timeout, _condition) = psc.load_power_state(cfg, self.dist) self.assertNotIn("", cmd) check_lps_ret(psc.load_power_state(cfg, self.dist)) self.assertTrue(len(cmd) == 3) def test_condition_null_raises(self): - cfg = {'power_state': {'mode': 'poweroff', 'condition': None}} + cfg = {"power_state": {"mode": "poweroff", "condition": None}} self.assertRaises(TypeError, psc.load_power_state, cfg, self.dist) def test_condition_default_is_true(self): - cfg = {'power_state': {'mode': 'poweroff'}} + cfg = {"power_state": {"mode": "poweroff"}} _cmd, _timeout, cond = psc.load_power_state(cfg, self.dist) self.assertEqual(cond, True) def test_freebsd_poweroff_uses_lowercase_p(self): - cls = distros.fetch('freebsd') + cls = distros.fetch("freebsd") paths = helpers.Paths({}) - freebsd = cls('freebsd', {}, paths) - cfg = {'power_state': {'mode': 'poweroff'}} + freebsd = cls("freebsd", {}, paths) + cfg = {"power_state": {"mode": "poweroff"}} ret = psc.load_power_state(cfg, freebsd) - self.assertIn('-p', ret[0]) + self.assertIn("-p", ret[0]) def test_alpine_delay(self): # alpine takes delay in seconds. - cls = distros.fetch('alpine') + cls = distros.fetch("alpine") paths = helpers.Paths({}) - alpine = cls('alpine', {}, paths) - cfg = {'power_state': {'mode': 'poweroff', 'delay': ''}} - for delay, value in (('now', 0), ("+1", 60), ("+30", 1800)): - cfg['power_state']['delay'] = delay + alpine = cls("alpine", {}, paths) + cfg = {"power_state": {"mode": "poweroff", "delay": ""}} + for delay, value in (("now", 0), ("+1", 60), ("+30", 1800)): + cfg["power_state"]["delay"] = delay ret = psc.load_power_state(cfg, alpine) - self.assertEqual('-d', ret[0][1]) + self.assertEqual("-d", ret[0][1]) self.assertEqual(str(value), ret[0][2]) class TestCheckCondition(t_help.TestCase): def cmd_with_exit(self, rc): - return([sys.executable, '-c', 'import sys; sys.exit(%s)' % rc]) + return [sys.executable, "-c", "import sys; sys.exit(%s)" % rc] def test_true_is_true(self): self.assertEqual(psc.check_condition(True), True) @@ -120,7 +118,8 @@ class TestCheckCondition(t_help.TestCase): def test_cmd_exit_nonzero_warns(self): mocklog = mock.Mock() self.assertEqual( - psc.check_condition(self.cmd_with_exit(2), mocklog), False) + psc.check_condition(self.cmd_with_exit(2), mocklog), False + ) self.assertEqual(mocklog.warning.call_count, 1) @@ -133,14 +132,14 @@ def check_lps_ret(psc_return, mode=None): timeout = psc_return[1] condition = psc_return[2] - if 'shutdown' not in psc_return[0][0]: + if "shutdown" not in psc_return[0][0]: errs.append("string 'shutdown' not in cmd") if condition is None: errs.append("condition was not returned") if mode is not None: - opt = {'halt': '-H', 'poweroff': '-P', 'reboot': '-r'}[mode] + opt = {"halt": "-H", "poweroff": "-P", "reboot": "-r"}[mode] if opt not in psc_return[0]: errs.append("opt '%s' not in cmd: %s" % (opt, cmd)) @@ -154,6 +153,7 @@ def check_lps_ret(psc_return, mode=None): if len(errs): lines = ["Errors in result: %s" % str(psc_return)] + errs - raise Exception('\n'.join(lines)) + raise Exception("\n".join(lines)) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_puppet.py b/tests/unittests/config/test_cc_puppet.py index 1f67dc4c..2c4481da 100644 --- a/tests/unittests/config/test_cc_puppet.py +++ b/tests/unittests/config/test_cc_puppet.py @@ -2,58 +2,71 @@ import logging import textwrap -from cloudinit.config import cc_puppet from cloudinit import util +from cloudinit.config import cc_puppet from tests.unittests.helpers import CiTestCase, HttprettyTestCase, mock - from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) -@mock.patch('cloudinit.config.cc_puppet.subp.subp') -@mock.patch('cloudinit.config.cc_puppet.os') +@mock.patch("cloudinit.config.cc_puppet.subp.subp") +@mock.patch("cloudinit.config.cc_puppet.os") class TestAutostartPuppet(CiTestCase): - def test_wb_autostart_puppet_updates_puppet_default(self, m_os, m_subp): """Update /etc/default/puppet to autostart if it exists.""" def _fake_exists(path): - return path == '/etc/default/puppet' + return path == "/etc/default/puppet" m_os.path.exists.side_effect = _fake_exists cc_puppet._autostart_puppet(LOG) self.assertEqual( - [mock.call(['sed', '-i', '-e', 's/^START=.*/START=yes/', - '/etc/default/puppet'], capture=False)], - m_subp.call_args_list) + [ + mock.call( + [ + "sed", + "-i", + "-e", + "s/^START=.*/START=yes/", + "/etc/default/puppet", + ], + capture=False, + ) + ], + m_subp.call_args_list, + ) def test_wb_autostart_pupppet_enables_puppet_systemctl(self, m_os, m_subp): """If systemctl is present, enable puppet via systemctl.""" def _fake_exists(path): - return path == '/bin/systemctl' + return path == "/bin/systemctl" m_os.path.exists.side_effect = _fake_exists cc_puppet._autostart_puppet(LOG) - expected_calls = [mock.call( - ['/bin/systemctl', 'enable', 'puppet.service'], capture=False)] + expected_calls = [ + mock.call( + ["/bin/systemctl", "enable", "puppet.service"], capture=False + ) + ] self.assertEqual(expected_calls, m_subp.call_args_list) def test_wb_autostart_pupppet_enables_puppet_chkconfig(self, m_os, m_subp): """If chkconfig is present, enable puppet via checkcfg.""" def _fake_exists(path): - return path == '/sbin/chkconfig' + return path == "/sbin/chkconfig" m_os.path.exists.side_effect = _fake_exists cc_puppet._autostart_puppet(LOG) - expected_calls = [mock.call( - ['/sbin/chkconfig', 'puppet', 'on'], capture=False)] + expected_calls = [ + mock.call(["/sbin/chkconfig", "puppet", "on"], capture=False) + ] self.assertEqual(expected_calls, m_subp.call_args_list) -@mock.patch('cloudinit.config.cc_puppet._autostart_puppet') +@mock.patch("cloudinit.config.cc_puppet._autostart_puppet") class TestPuppetHandle(CiTestCase): with_logs = True @@ -61,145 +74,164 @@ class TestPuppetHandle(CiTestCase): def setUp(self): super(TestPuppetHandle, self).setUp() self.new_root = self.tmp_dir() - self.conf = self.tmp_path('puppet.conf') - self.csr_attributes_path = self.tmp_path( - 'csr_attributes.yaml') + self.conf = self.tmp_path("puppet.conf") + self.csr_attributes_path = self.tmp_path("csr_attributes.yaml") self.cloud = get_cloud() def test_skips_missing_puppet_key_in_cloudconfig(self, m_auto): """Cloud-config containing no 'puppet' key is skipped.""" cfg = {} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) - self.assertIn( - "no 'puppet' configuration found", self.logs.getvalue()) + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) + self.assertIn("no 'puppet' configuration found", self.logs.getvalue()) self.assertEqual(0, m_auto.call_count) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_config_starts_puppet_service(self, m_subp, m_auto): """Cloud-config 'puppet' configuration starts puppet.""" - cfg = {'puppet': {'install': False}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {"install": False}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual(1, m_auto.call_count) self.assertIn( - [mock.call(['service', 'puppet', 'start'], capture=False)], - m_subp.call_args_list) + [mock.call(["service", "puppet", "start"], capture=False)], + m_subp.call_args_list, + ) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_empty_puppet_config_installs_puppet(self, m_subp, m_auto): """Cloud-config empty 'puppet' configuration installs latest puppet.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual( - [mock.call(('puppet', None))], - self.cloud.distro.install_packages.call_args_list) + [mock.call(("puppet", None))], + self.cloud.distro.install_packages.call_args_list, + ) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_config_installs_puppet_on_true(self, m_subp, _): """Cloud-config with 'puppet' key installs when 'install' is True.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {'install': True}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {"install": True}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual( - [mock.call(('puppet', None))], - self.cloud.distro.install_packages.call_args_list) + [mock.call(("puppet", None))], + self.cloud.distro.install_packages.call_args_list, + ) - @mock.patch('cloudinit.config.cc_puppet.install_puppet_aio', autospec=True) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.install_puppet_aio", autospec=True) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_config_installs_puppet_aio(self, m_subp, m_aio, _): """Cloud-config with 'puppet' key installs when 'install_type' is 'aio'.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {'install': True, 'install_type': 'aio'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) - m_aio.assert_called_with( - cc_puppet.AIO_INSTALL_URL, - None, None, True) - - @mock.patch('cloudinit.config.cc_puppet.install_puppet_aio', autospec=True) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_config_installs_puppet_aio_with_version(self, - m_subp, m_aio, _): + cfg = {"puppet": {"install": True, "install_type": "aio"}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) + m_aio.assert_called_with(cc_puppet.AIO_INSTALL_URL, None, None, True) + + @mock.patch("cloudinit.config.cc_puppet.install_puppet_aio", autospec=True) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_config_installs_puppet_aio_with_version( + self, m_subp, m_aio, _ + ): """Cloud-config with 'puppet' key installs when 'install_type' is 'aio' and 'version' is specified.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {'install': True, - 'version': '6.24.0', 'install_type': 'aio'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = { + "puppet": { + "install": True, + "version": "6.24.0", + "install_type": "aio", + } + } + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) m_aio.assert_called_with( - cc_puppet.AIO_INSTALL_URL, - '6.24.0', None, True) - - @mock.patch('cloudinit.config.cc_puppet.install_puppet_aio', autospec=True) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_config_installs_puppet_aio_with_collection(self, - m_subp, - m_aio, _): + cc_puppet.AIO_INSTALL_URL, "6.24.0", None, True + ) + + @mock.patch("cloudinit.config.cc_puppet.install_puppet_aio", autospec=True) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_config_installs_puppet_aio_with_collection( + self, m_subp, m_aio, _ + ): """Cloud-config with 'puppet' key installs when 'install_type' is 'aio' and 'collection' is specified.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {'install': True, - 'collection': 'puppet6', 'install_type': 'aio'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = { + "puppet": { + "install": True, + "collection": "puppet6", + "install_type": "aio", + } + } + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) m_aio.assert_called_with( - cc_puppet.AIO_INSTALL_URL, - None, 'puppet6', True) - - @mock.patch('cloudinit.config.cc_puppet.install_puppet_aio', autospec=True) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_config_installs_puppet_aio_with_custom_url(self, - m_subp, - m_aio, _): + cc_puppet.AIO_INSTALL_URL, None, "puppet6", True + ) + + @mock.patch("cloudinit.config.cc_puppet.install_puppet_aio", autospec=True) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_config_installs_puppet_aio_with_custom_url( + self, m_subp, m_aio, _ + ): """Cloud-config with 'puppet' key installs when 'install_type' is 'aio' and 'aio_install_url' is specified.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': - {'install': True, - 'aio_install_url': 'http://test.url/path/to/script.sh', - 'install_type': 'aio'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = { + "puppet": { + "install": True, + "aio_install_url": "http://test.url/path/to/script.sh", + "install_type": "aio", + } + } + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) m_aio.assert_called_with( - 'http://test.url/path/to/script.sh', None, None, True) - - @mock.patch('cloudinit.config.cc_puppet.install_puppet_aio', autospec=True) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_config_installs_puppet_aio_without_cleanup(self, - m_subp, - m_aio, _): + "http://test.url/path/to/script.sh", None, None, True + ) + + @mock.patch("cloudinit.config.cc_puppet.install_puppet_aio", autospec=True) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_config_installs_puppet_aio_without_cleanup( + self, m_subp, m_aio, _ + ): """Cloud-config with 'puppet' key installs when 'install_type' is 'aio' and no cleanup.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {'install': True, - 'cleanup': False, 'install_type': 'aio'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) - m_aio.assert_called_with( - cc_puppet.AIO_INSTALL_URL, - None, None, False) + cfg = { + "puppet": { + "install": True, + "cleanup": False, + "install_type": "aio", + } + } + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) + m_aio.assert_called_with(cc_puppet.AIO_INSTALL_URL, None, None, False) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_config_installs_puppet_version(self, m_subp, _): """Cloud-config 'puppet' configuration can specify a version.""" self.cloud.distro = mock.MagicMock() - cfg = {'puppet': {'version': '3.8'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {"version": "3.8"}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual( - [mock.call(('puppet', '3.8'))], - self.cloud.distro.install_packages.call_args_list) - - @mock.patch('cloudinit.config.cc_puppet.get_config_value') - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_config_updates_puppet_conf(self, - m_subp, m_default, m_auto): + [mock.call(("puppet", "3.8"))], + self.cloud.distro.install_packages.call_args_list, + ) + + @mock.patch("cloudinit.config.cc_puppet.get_config_value") + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_config_updates_puppet_conf( + self, m_subp, m_default, m_auto + ): """When 'conf' is provided update values in PUPPET_CONF_PATH.""" def _fake_get_config_value(puppet_bin, setting): @@ -208,22 +240,24 @@ class TestPuppetHandle(CiTestCase): m_default.side_effect = _fake_get_config_value cfg = { - 'puppet': { - 'conf': {'agent': {'server': 'puppetserver.example.org'}}}} - util.write_file( - self.conf, '[agent]\nserver = origpuppet\nother = 3') + "puppet": { + "conf": {"agent": {"server": "puppetserver.example.org"}} + } + } + util.write_file(self.conf, "[agent]\nserver = origpuppet\nother = 3") self.cloud.distro = mock.MagicMock() - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) content = util.load_file(self.conf) - expected = '[agent]\nserver = puppetserver.example.org\nother = 3\n\n' + expected = "[agent]\nserver = puppetserver.example.org\nother = 3\n\n" self.assertEqual(expected, content) - @mock.patch('cloudinit.config.cc_puppet.get_config_value') - @mock.patch('cloudinit.config.cc_puppet.subp.subp') - def test_puppet_writes_csr_attributes_file(self, - m_subp, m_default, m_auto): + @mock.patch("cloudinit.config.cc_puppet.get_config_value") + @mock.patch("cloudinit.config.cc_puppet.subp.subp") + def test_puppet_writes_csr_attributes_file( + self, m_subp, m_default, m_auto + ): """When csr_attributes is provided - creates file in PUPPET_CSR_ATTRIBUTES_PATH.""" + creates file in PUPPET_CSR_ATTRIBUTES_PATH.""" def _fake_get_config_value(puppet_bin, setting): return self.csr_attributes_path @@ -232,105 +266,131 @@ class TestPuppetHandle(CiTestCase): self.cloud.distro = mock.MagicMock() cfg = { - 'puppet': { - 'csr_attributes': { - 'custom_attributes': { - '1.2.840.113549.1.9.7': - '342thbjkt82094y0uthhor289jnqthpc2290' + "puppet": { + "csr_attributes": { + "custom_attributes": { + "1.2.840.113549.1.9.7": ( + "342thbjkt82094y0uthhor289jnqthpc2290" + ) + }, + "extension_requests": { + "pp_uuid": "ED803750-E3C7-44F5-BB08-41A04433FE2E", + "pp_image_name": "my_ami_image", + "pp_preshared_key": ( + "342thbjkt82094y0uthhor289jnqthpc2290" + ), }, - 'extension_requests': { - 'pp_uuid': 'ED803750-E3C7-44F5-BB08-41A04433FE2E', - 'pp_image_name': 'my_ami_image', - 'pp_preshared_key': - '342thbjkt82094y0uthhor289jnqthpc2290' - } } } } - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) content = util.load_file(self.csr_attributes_path) - expected = textwrap.dedent("""\ + expected = textwrap.dedent( + """\ custom_attributes: 1.2.840.113549.1.9.7: 342thbjkt82094y0uthhor289jnqthpc2290 extension_requests: pp_image_name: my_ami_image pp_preshared_key: 342thbjkt82094y0uthhor289jnqthpc2290 pp_uuid: ED803750-E3C7-44F5-BB08-41A04433FE2E - """) + """ + ) self.assertEqual(expected, content) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_runs_puppet_if_requested(self, m_subp, m_auto): """Run puppet with default args if 'exec' is set to True.""" - cfg = {'puppet': {'exec': True}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {"exec": True}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual(1, m_auto.call_count) self.assertIn( - [mock.call(['puppet', 'agent', '--test'], capture=False)], - m_subp.call_args_list) + [mock.call(["puppet", "agent", "--test"], capture=False)], + m_subp.call_args_list, + ) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_starts_puppetd(self, m_subp, m_auto): """Run puppet with default args if 'exec' is set to True.""" - cfg = {'puppet': {}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual(1, m_auto.call_count) self.assertIn( - [mock.call(['service', 'puppet', 'start'], capture=False)], - m_subp.call_args_list) + [mock.call(["service", "puppet", "start"], capture=False)], + m_subp.call_args_list, + ) - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) def test_puppet_skips_puppetd(self, m_subp, m_auto): """Run puppet with default args if 'exec' is set to True.""" - cfg = {'puppet': {'start_service': False}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = {"puppet": {"start_service": False}} + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual(0, m_auto.call_count) self.assertNotIn( - [mock.call(['service', 'puppet', 'start'], capture=False)], - m_subp.call_args_list) - - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_runs_puppet_with_args_list_if_requested(self, - m_subp, m_auto): + [mock.call(["service", "puppet", "start"], capture=False)], + m_subp.call_args_list, + ) + + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_runs_puppet_with_args_list_if_requested( + self, m_subp, m_auto + ): """Run puppet with 'exec_args' list if 'exec' is set to True.""" - cfg = {'puppet': {'exec': True, 'exec_args': [ - '--onetime', '--detailed-exitcodes']}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = { + "puppet": { + "exec": True, + "exec_args": ["--onetime", "--detailed-exitcodes"], + } + } + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual(1, m_auto.call_count) self.assertIn( - [mock.call( - ['puppet', 'agent', '--onetime', '--detailed-exitcodes'], - capture=False)], - m_subp.call_args_list) - - @mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=("", "")) - def test_puppet_runs_puppet_with_args_string_if_requested(self, - m_subp, m_auto): + [ + mock.call( + ["puppet", "agent", "--onetime", "--detailed-exitcodes"], + capture=False, + ) + ], + m_subp.call_args_list, + ) + + @mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=("", "")) + def test_puppet_runs_puppet_with_args_string_if_requested( + self, m_subp, m_auto + ): """Run puppet with 'exec_args' string if 'exec' is set to True.""" - cfg = {'puppet': {'exec': True, - 'exec_args': '--onetime --detailed-exitcodes'}} - cc_puppet.handle('notimportant', cfg, self.cloud, LOG, None) + cfg = { + "puppet": { + "exec": True, + "exec_args": "--onetime --detailed-exitcodes", + } + } + cc_puppet.handle("notimportant", cfg, self.cloud, LOG, None) self.assertEqual(1, m_auto.call_count) self.assertIn( - [mock.call( - ['puppet', 'agent', '--onetime', '--detailed-exitcodes'], - capture=False)], - m_subp.call_args_list) + [ + mock.call( + ["puppet", "agent", "--onetime", "--detailed-exitcodes"], + capture=False, + ) + ], + m_subp.call_args_list, + ) URL_MOCK = mock.Mock() URL_MOCK.contents = b'#!/bin/bash\necho "Hi Mom"' -@mock.patch('cloudinit.config.cc_puppet.subp.subp', return_value=(None, None)) +@mock.patch("cloudinit.config.cc_puppet.subp.subp", return_value=(None, None)) @mock.patch( - 'cloudinit.config.cc_puppet.url_helper.readurl', - return_value=URL_MOCK, autospec=True, + "cloudinit.config.cc_puppet.url_helper.readurl", + return_value=URL_MOCK, + autospec=True, ) class TestInstallPuppetAio(HttprettyTestCase): def test_install_with_default_arguments(self, m_readurl, m_subp): @@ -338,43 +398,53 @@ class TestInstallPuppetAio(HttprettyTestCase): cc_puppet.install_puppet_aio() self.assertEqual( - [mock.call([mock.ANY, '--cleanup'], capture=False)], - m_subp.call_args_list) + [mock.call([mock.ANY, "--cleanup"], capture=False)], + m_subp.call_args_list, + ) def test_install_with_custom_url(self, m_readurl, m_subp): """Install AIO from custom URL""" - cc_puppet.install_puppet_aio('http://custom.url/path/to/script.sh') + cc_puppet.install_puppet_aio("http://custom.url/path/to/script.sh") m_readurl.assert_called_with( - url='http://custom.url/path/to/script.sh', - retries=5) + url="http://custom.url/path/to/script.sh", retries=5 + ) self.assertEqual( - [mock.call([mock.ANY, '--cleanup'], capture=False)], - m_subp.call_args_list) + [mock.call([mock.ANY, "--cleanup"], capture=False)], + m_subp.call_args_list, + ) def test_install_with_version(self, m_readurl, m_subp): """Install AIO with specific version""" - cc_puppet.install_puppet_aio(cc_puppet.AIO_INSTALL_URL, '7.6.0') + cc_puppet.install_puppet_aio(cc_puppet.AIO_INSTALL_URL, "7.6.0") self.assertEqual( - [mock.call([mock.ANY, '-v', '7.6.0', '--cleanup'], capture=False)], - m_subp.call_args_list) + [mock.call([mock.ANY, "-v", "7.6.0", "--cleanup"], capture=False)], + m_subp.call_args_list, + ) def test_install_with_collection(self, m_readurl, m_subp): """Install AIO with specific collection""" cc_puppet.install_puppet_aio( - cc_puppet.AIO_INSTALL_URL, None, 'puppet6-nightly') + cc_puppet.AIO_INSTALL_URL, None, "puppet6-nightly" + ) self.assertEqual( - [mock.call([mock.ANY, '-c', 'puppet6-nightly', '--cleanup'], - capture=False)], - m_subp.call_args_list) + [ + mock.call( + [mock.ANY, "-c", "puppet6-nightly", "--cleanup"], + capture=False, + ) + ], + m_subp.call_args_list, + ) def test_install_with_no_cleanup(self, m_readurl, m_subp): """Install AIO with no cleanup""" cc_puppet.install_puppet_aio( - cc_puppet.AIO_INSTALL_URL, None, None, False) + cc_puppet.AIO_INSTALL_URL, None, None, False + ) self.assertEqual( - [mock.call([mock.ANY], capture=False)], - m_subp.call_args_list) + [mock.call([mock.ANY], capture=False)], m_subp.call_args_list + ) diff --git a/tests/unittests/config/test_cc_refresh_rmc_and_interface.py b/tests/unittests/config/test_cc_refresh_rmc_and_interface.py index 522de23d..e038f814 100644 --- a/tests/unittests/config/test_cc_refresh_rmc_and_interface.py +++ b/tests/unittests/config/test_cc_refresh_rmc_and_interface.py @@ -1,51 +1,83 @@ -from cloudinit.config import cc_refresh_rmc_and_interface as ccrmci +import logging +from textwrap import dedent from cloudinit import util - +from cloudinit.config import cc_refresh_rmc_and_interface as ccrmci from tests.unittests import helpers as t_help from tests.unittests.helpers import mock -from textwrap import dedent -import logging - LOG = logging.getLogger(__name__) MPATH = "cloudinit.config.cc_refresh_rmc_and_interface" NET_INFO = { - 'lo': {'ipv4': [{'ip': '127.0.0.1', - 'bcast': '', 'mask': '255.0.0.0', - 'scope': 'host'}], - 'ipv6': [{'ip': '::1/128', - 'scope6': 'host'}], 'hwaddr': '', - 'up': 'True'}, - 'env2': {'ipv4': [{'ip': '8.0.0.19', - 'bcast': '8.0.0.255', 'mask': '255.255.255.0', - 'scope': 'global'}], - 'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8220/64', - 'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:20', - 'up': 'True'}, - 'env3': {'ipv4': [{'ip': '90.0.0.14', - 'bcast': '90.0.0.255', 'mask': '255.255.255.0', - 'scope': 'global'}], - 'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8221/64', - 'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:21', - 'up': 'True'}, - 'env4': {'ipv4': [{'ip': '9.114.23.7', - 'bcast': '9.114.23.255', 'mask': '255.255.255.0', - 'scope': 'global'}], - 'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8222/64', - 'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:22', - 'up': 'True'}, - 'env5': {'ipv4': [], - 'ipv6': [{'ip': 'fe80::9c26:c3ff:fea4:62c8/64', - 'scope6': 'link'}], 'hwaddr': '42:20:86:df:fa:4c', - 'up': 'True'}} + "lo": { + "ipv4": [ + { + "ip": "127.0.0.1", + "bcast": "", + "mask": "255.0.0.0", + "scope": "host", + } + ], + "ipv6": [{"ip": "::1/128", "scope6": "host"}], + "hwaddr": "", + "up": "True", + }, + "env2": { + "ipv4": [ + { + "ip": "8.0.0.19", + "bcast": "8.0.0.255", + "mask": "255.255.255.0", + "scope": "global", + } + ], + "ipv6": [{"ip": "fe80::f896:c2ff:fe81:8220/64", "scope6": "link"}], + "hwaddr": "fa:96:c2:81:82:20", + "up": "True", + }, + "env3": { + "ipv4": [ + { + "ip": "90.0.0.14", + "bcast": "90.0.0.255", + "mask": "255.255.255.0", + "scope": "global", + } + ], + "ipv6": [{"ip": "fe80::f896:c2ff:fe81:8221/64", "scope6": "link"}], + "hwaddr": "fa:96:c2:81:82:21", + "up": "True", + }, + "env4": { + "ipv4": [ + { + "ip": "9.114.23.7", + "bcast": "9.114.23.255", + "mask": "255.255.255.0", + "scope": "global", + } + ], + "ipv6": [{"ip": "fe80::f896:c2ff:fe81:8222/64", "scope6": "link"}], + "hwaddr": "fa:96:c2:81:82:22", + "up": "True", + }, + "env5": { + "ipv4": [], + "ipv6": [{"ip": "fe80::9c26:c3ff:fea4:62c8/64", "scope6": "link"}], + "hwaddr": "42:20:86:df:fa:4c", + "up": "True", + }, +} class TestRsctNodeFile(t_help.CiTestCase): def test_disable_ipv6_interface(self): """test parsing of iface files.""" fname = self.tmp_path("iface-eth5") - util.write_file(fname, dedent("""\ + util.write_file( + fname, + dedent( + """\ BOOTPROTO=static DEVICE=eth5 HWADDR=42:20:86:df:fa:4c @@ -57,10 +89,14 @@ class TestRsctNodeFile(t_help.CiTestCase): STARTMODE=auto TYPE=Ethernet USERCTL=no - """)) + """ + ), + ) ccrmci.disable_ipv6(fname) - self.assertEqual(dedent("""\ + self.assertEqual( + dedent( + """\ BOOTPROTO=static DEVICE=eth5 HWADDR=42:20:86:df:fa:4c @@ -69,41 +105,53 @@ class TestRsctNodeFile(t_help.CiTestCase): TYPE=Ethernet USERCTL=no NM_CONTROLLED=no - """), util.load_file(fname)) + """ + ), + util.load_file(fname), + ) - @mock.patch(MPATH + '.refresh_rmc') - @mock.patch(MPATH + '.restart_network_manager') - @mock.patch(MPATH + '.disable_ipv6') - @mock.patch(MPATH + '.refresh_ipv6') - @mock.patch(MPATH + '.netinfo.netdev_info') - @mock.patch(MPATH + '.subp.which') - def test_handle(self, m_refresh_rmc, - m_netdev_info, m_refresh_ipv6, m_disable_ipv6, - m_restart_nm, m_which): + @mock.patch(MPATH + ".refresh_rmc") + @mock.patch(MPATH + ".restart_network_manager") + @mock.patch(MPATH + ".disable_ipv6") + @mock.patch(MPATH + ".refresh_ipv6") + @mock.patch(MPATH + ".netinfo.netdev_info") + @mock.patch(MPATH + ".subp.which") + def test_handle( + self, + m_refresh_rmc, + m_netdev_info, + m_refresh_ipv6, + m_disable_ipv6, + m_restart_nm, + m_which, + ): """Basic test of handle.""" m_netdev_info.return_value = NET_INFO - m_which.return_value = '/opt/rsct/bin/rmcctrl' - ccrmci.handle( - "refresh_rmc_and_interface", None, None, None, None) + m_which.return_value = "/opt/rsct/bin/rmcctrl" + ccrmci.handle("refresh_rmc_and_interface", None, None, None, None) self.assertEqual(1, m_netdev_info.call_count) - m_refresh_ipv6.assert_called_with('env5') + m_refresh_ipv6.assert_called_with("env5") m_disable_ipv6.assert_called_with( - '/etc/sysconfig/network-scripts/ifcfg-env5') + "/etc/sysconfig/network-scripts/ifcfg-env5" + ) self.assertEqual(1, m_restart_nm.call_count) self.assertEqual(1, m_refresh_rmc.call_count) - @mock.patch(MPATH + '.netinfo.netdev_info') + @mock.patch(MPATH + ".netinfo.netdev_info") def test_find_ipv6(self, m_netdev_info): """find_ipv6_ifaces parses netdev_info returning those with ipv6""" m_netdev_info.return_value = NET_INFO found = ccrmci.find_ipv6_ifaces() - self.assertEqual(['env5'], found) + self.assertEqual(["env5"], found) - @mock.patch(MPATH + '.subp.subp') + @mock.patch(MPATH + ".subp.subp") def test_refresh_ipv6(self, m_subp): """refresh_ipv6 should ip down and up the interface.""" iface = "myeth0" ccrmci.refresh_ipv6(iface) - m_subp.assert_has_calls([ - mock.call(['ip', 'link', 'set', iface, 'down']), - mock.call(['ip', 'link', 'set', iface, 'up'])]) + m_subp.assert_has_calls( + [ + mock.call(["ip", "link", "set", iface, "down"]), + mock.call(["ip", "link", "set", iface, "up"]), + ] + ) diff --git a/tests/unittests/config/test_cc_resizefs.py b/tests/unittests/config/test_cc_resizefs.py index 1f9e24da..228f1e45 100644 --- a/tests/unittests/config/test_cc_resizefs.py +++ b/tests/unittests/config/test_cc_resizefs.py @@ -1,16 +1,26 @@ # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit.config.cc_resizefs import ( - can_skip_resize, handle, maybe_get_writable_device_path, _resize_btrfs, - _resize_zfs, _resize_xfs, _resize_ext, _resize_ufs) - -from collections import namedtuple import logging +from collections import namedtuple +from cloudinit.config.cc_resizefs import ( + _resize_btrfs, + _resize_ext, + _resize_ufs, + _resize_xfs, + _resize_zfs, + can_skip_resize, + handle, + maybe_get_writable_device_path, +) from cloudinit.subp import ProcessExecutionError from tests.unittests.helpers import ( - CiTestCase, mock, skipUnlessJsonSchema, util, wrap_and_call) - + CiTestCase, + mock, + skipUnlessJsonSchema, + util, + wrap_and_call, +) LOG = logging.getLogger(__name__) @@ -22,32 +32,34 @@ class TestResizefs(CiTestCase): super(TestResizefs, self).setUp() self.name = "resizefs" - @mock.patch('cloudinit.subp.subp') + @mock.patch("cloudinit.subp.subp") def test_skip_ufs_resize(self, m_subp): fs_type = "ufs" resize_what = "/" devpth = "/dev/da0p2" - err = ("growfs: requested size 2.0GB is not larger than the " - "current filesystem size 2.0GB\n") + err = ( + "growfs: requested size 2.0GB is not larger than the " + "current filesystem size 2.0GB\n" + ) exception = ProcessExecutionError(stderr=err, exit_code=1) m_subp.side_effect = exception res = can_skip_resize(fs_type, resize_what, devpth) self.assertTrue(res) - @mock.patch('cloudinit.subp.subp') + @mock.patch("cloudinit.subp.subp") def test_cannot_skip_ufs_resize(self, m_subp): fs_type = "ufs" resize_what = "/" devpth = "/dev/da0p2" m_subp.return_value = ( - ("stdout: super-block backups (for fsck_ffs -b #) at:\n\n"), - ("growfs: no room to allocate last cylinder group; " - "leaving 364KB unused\n") + "stdout: super-block backups (for fsck_ffs -b #) at:\n\n", + "growfs: no room to allocate last cylinder group; " + "leaving 364KB unused\n", ) res = can_skip_resize(fs_type, resize_what, devpth) self.assertFalse(res) - @mock.patch('cloudinit.subp.subp') + @mock.patch("cloudinit.subp.subp") def test_cannot_skip_ufs_growfs_exception(self, m_subp): fs_type = "ufs" resize_what = "/" @@ -59,15 +71,16 @@ class TestResizefs(CiTestCase): can_skip_resize(fs_type, resize_what, devpth) def test_can_skip_resize_ext(self): - self.assertFalse(can_skip_resize('ext', '/', '/dev/sda1')) + self.assertFalse(can_skip_resize("ext", "/", "/dev/sda1")) def test_handle_noops_on_disabled(self): """The handle function logs when the configuration disables resize.""" - cfg = {'resize_rootfs': False} - handle('cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) + cfg = {"resize_rootfs": False} + handle("cc_resizefs", cfg, _cloud=None, log=LOG, args=[]) self.assertIn( - 'DEBUG: Skipping module named cc_resizefs, resizing disabled\n', - self.logs.getvalue()) + "DEBUG: Skipping module named cc_resizefs, resizing disabled\n", + self.logs.getvalue(), + ) @skipUnlessJsonSchema() def test_handle_schema_validation_logs_invalid_resize_rootfs_value(self): @@ -75,164 +88,189 @@ class TestResizefs(CiTestCase): Invalid values for resize_rootfs result in disabling the module. """ - cfg = {'resize_rootfs': 'junk'} - handle('cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) + cfg = {"resize_rootfs": "junk"} + handle("cc_resizefs", cfg, _cloud=None, log=LOG, args=[]) logs = self.logs.getvalue() self.assertIn( "WARNING: Invalid config:\nresize_rootfs: 'junk' is not one of" " [True, False, 'noblock']", - logs) + logs, + ) self.assertIn( - 'DEBUG: Skipping module named cc_resizefs, resizing disabled\n', - logs) + "DEBUG: Skipping module named cc_resizefs, resizing disabled\n", + logs, + ) - @mock.patch('cloudinit.config.cc_resizefs.util.get_mount_info') + @mock.patch("cloudinit.config.cc_resizefs.util.get_mount_info") def test_handle_warns_on_unknown_mount_info(self, m_get_mount_info): """handle warns when get_mount_info sees unknown filesystem for /.""" m_get_mount_info.return_value = None - cfg = {'resize_rootfs': True} - handle('cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) + cfg = {"resize_rootfs": True} + handle("cc_resizefs", cfg, _cloud=None, log=LOG, args=[]) logs = self.logs.getvalue() self.assertNotIn("WARNING: Invalid config:\nresize_rootfs:", logs) self.assertIn( - 'WARNING: Could not determine filesystem type of /\n', - logs) + "WARNING: Could not determine filesystem type of /\n", logs + ) self.assertEqual( - [mock.call('/', LOG)], - m_get_mount_info.call_args_list) + [mock.call("/", LOG)], m_get_mount_info.call_args_list + ) def test_handle_warns_on_undiscoverable_root_path_in_commandline(self): """handle noops when the root path is not found on the commandline.""" - cfg = {'resize_rootfs': True} - exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists' + cfg = {"resize_rootfs": True} + exists_mock_path = "cloudinit.config.cc_resizefs.os.path.exists" def fake_mount_info(path, log): - self.assertEqual('/', path) + self.assertEqual("/", path) self.assertEqual(LOG, log) - return ('/dev/root', 'ext4', '/') + return ("/dev/root", "ext4", "/") with mock.patch(exists_mock_path) as m_exists: m_exists.return_value = False wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': False}, - 'get_mount_info': {'side_effect': fake_mount_info}, - 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}}, - handle, 'cc_resizefs', cfg, _cloud=None, log=LOG, - args=[]) + "cloudinit.config.cc_resizefs.util", + { + "is_container": {"return_value": False}, + "get_mount_info": {"side_effect": fake_mount_info}, + "get_cmdline": {"return_value": "BOOT_IMAGE=/vmlinuz.efi"}, + }, + handle, + "cc_resizefs", + cfg, + _cloud=None, + log=LOG, + args=[], + ) logs = self.logs.getvalue() self.assertIn("WARNING: Unable to find device '/dev/root'", logs) def test_resize_zfs_cmd_return(self): - zpool = 'zroot' - devpth = 'gpt/system' - self.assertEqual(('zpool', 'online', '-e', zpool, devpth), - _resize_zfs(zpool, devpth)) + zpool = "zroot" + devpth = "gpt/system" + self.assertEqual( + ("zpool", "online", "-e", zpool, devpth), + _resize_zfs(zpool, devpth), + ) def test_resize_xfs_cmd_return(self): - mount_point = '/mnt/test' - devpth = '/dev/sda1' - self.assertEqual(('xfs_growfs', mount_point), - _resize_xfs(mount_point, devpth)) + mount_point = "/mnt/test" + devpth = "/dev/sda1" + self.assertEqual( + ("xfs_growfs", mount_point), _resize_xfs(mount_point, devpth) + ) def test_resize_ext_cmd_return(self): - mount_point = '/' - devpth = '/dev/sdb1' - self.assertEqual(('resize2fs', devpth), - _resize_ext(mount_point, devpth)) + mount_point = "/" + devpth = "/dev/sdb1" + self.assertEqual( + ("resize2fs", devpth), _resize_ext(mount_point, devpth) + ) def test_resize_ufs_cmd_return(self): - mount_point = '/' - devpth = '/dev/sda2' - self.assertEqual(('growfs', '-y', mount_point), - _resize_ufs(mount_point, devpth)) - - @mock.patch('cloudinit.util.is_container', return_value=False) - @mock.patch('cloudinit.util.parse_mount') - @mock.patch('cloudinit.util.get_device_info_from_zpool') - @mock.patch('cloudinit.util.get_mount_info') - def test_handle_zfs_root(self, mount_info, zpool_info, parse_mount, - is_container): - devpth = 'vmzroot/ROOT/freebsd' - disk = 'gpt/system' - fs_type = 'zfs' - mount_point = '/' + mount_point = "/" + devpth = "/dev/sda2" + self.assertEqual( + ("growfs", "-y", mount_point), _resize_ufs(mount_point, devpth) + ) + + @mock.patch("cloudinit.util.is_container", return_value=False) + @mock.patch("cloudinit.util.parse_mount") + @mock.patch("cloudinit.util.get_device_info_from_zpool") + @mock.patch("cloudinit.util.get_mount_info") + def test_handle_zfs_root( + self, mount_info, zpool_info, parse_mount, is_container + ): + devpth = "vmzroot/ROOT/freebsd" + disk = "gpt/system" + fs_type = "zfs" + mount_point = "/" mount_info.return_value = (devpth, fs_type, mount_point) zpool_info.return_value = disk parse_mount.return_value = (devpth, fs_type, mount_point) - cfg = {'resize_rootfs': True} + cfg = {"resize_rootfs": True} - with mock.patch('cloudinit.config.cc_resizefs.do_resize') as dresize: - handle('cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) + with mock.patch("cloudinit.config.cc_resizefs.do_resize") as dresize: + handle("cc_resizefs", cfg, _cloud=None, log=LOG, args=[]) ret = dresize.call_args[0][0] - self.assertEqual(('zpool', 'online', '-e', 'vmzroot', disk), ret) + self.assertEqual(("zpool", "online", "-e", "vmzroot", disk), ret) - @mock.patch('cloudinit.util.is_container', return_value=False) - @mock.patch('cloudinit.util.get_mount_info') - @mock.patch('cloudinit.util.get_device_info_from_zpool') - @mock.patch('cloudinit.util.parse_mount') - def test_handle_modern_zfsroot(self, mount_info, zpool_info, parse_mount, - is_container): - devpth = 'zroot/ROOT/default' - disk = 'da0p3' - fs_type = 'zfs' - mount_point = '/' + @mock.patch("cloudinit.util.is_container", return_value=False) + @mock.patch("cloudinit.util.get_mount_info") + @mock.patch("cloudinit.util.get_device_info_from_zpool") + @mock.patch("cloudinit.util.parse_mount") + def test_handle_modern_zfsroot( + self, mount_info, zpool_info, parse_mount, is_container + ): + devpth = "zroot/ROOT/default" + disk = "da0p3" + fs_type = "zfs" + mount_point = "/" mount_info.return_value = (devpth, fs_type, mount_point) zpool_info.return_value = disk parse_mount.return_value = (devpth, fs_type, mount_point) - cfg = {'resize_rootfs': True} + cfg = {"resize_rootfs": True} def fake_stat(devpath): if devpath == disk: raise OSError("not here") FakeStat = namedtuple( - 'FakeStat', ['st_mode', 'st_size', 'st_mtime']) # minimal stat + "FakeStat", ["st_mode", "st_size", "st_mtime"] + ) # minimal stat return FakeStat(25008, 0, 1) # fake char block device - with mock.patch('cloudinit.config.cc_resizefs.do_resize') as dresize: - with mock.patch('cloudinit.config.cc_resizefs.os.stat') as m_stat: + with mock.patch("cloudinit.config.cc_resizefs.do_resize") as dresize: + with mock.patch("cloudinit.config.cc_resizefs.os.stat") as m_stat: m_stat.side_effect = fake_stat - handle('cc_resizefs', cfg, _cloud=None, log=LOG, args=[]) + handle("cc_resizefs", cfg, _cloud=None, log=LOG, args=[]) - self.assertEqual(('zpool', 'online', '-e', 'zroot', '/dev/' + disk), - dresize.call_args[0][0]) + self.assertEqual( + ("zpool", "online", "-e", "zroot", "/dev/" + disk), + dresize.call_args[0][0], + ) class TestRootDevFromCmdline(CiTestCase): - def test_rootdev_from_cmdline_with_no_root(self): """Return None from rootdev_from_cmdline when root is not present.""" invalid_cases = [ - 'BOOT_IMAGE=/adsf asdfa werasef root adf', 'BOOT_IMAGE=/adsf', ''] + "BOOT_IMAGE=/adsf asdfa werasef root adf", + "BOOT_IMAGE=/adsf", + "", + ] for case in invalid_cases: self.assertIsNone(util.rootdev_from_cmdline(case)) def test_rootdev_from_cmdline_with_root_startswith_dev(self): """Return the cmdline root when the path starts with /dev.""" self.assertEqual( - '/dev/this', util.rootdev_from_cmdline('asdf root=/dev/this')) + "/dev/this", util.rootdev_from_cmdline("asdf root=/dev/this") + ) def test_rootdev_from_cmdline_with_root_without_dev_prefix(self): """Add /dev prefix to cmdline root when the path lacks the prefix.""" self.assertEqual( - '/dev/this', util.rootdev_from_cmdline('asdf root=this')) + "/dev/this", util.rootdev_from_cmdline("asdf root=this") + ) def test_rootdev_from_cmdline_with_root_with_label(self): """When cmdline root contains a LABEL, our root is disk/by-label.""" self.assertEqual( - '/dev/disk/by-label/unique', - util.rootdev_from_cmdline('asdf root=LABEL=unique')) + "/dev/disk/by-label/unique", + util.rootdev_from_cmdline("asdf root=LABEL=unique"), + ) def test_rootdev_from_cmdline_with_root_with_uuid(self): """When cmdline root contains a UUID, our root is disk/by-uuid.""" self.assertEqual( - '/dev/disk/by-uuid/adsfdsaf-adsf', - util.rootdev_from_cmdline('asdf root=UUID=adsfdsaf-adsf')) + "/dev/disk/by-uuid/adsfdsaf-adsf", + util.rootdev_from_cmdline("asdf root=UUID=adsfdsaf-adsf"), + ) class TestMaybeGetDevicePathAsWritableBlock(CiTestCase): @@ -241,158 +279,210 @@ class TestMaybeGetDevicePathAsWritableBlock(CiTestCase): def test_maybe_get_writable_device_path_none_on_overlayroot(self): """When devpath is overlayroot (on MAAS), is_dev_writable is False.""" - info = 'does not matter' + info = "does not matter" devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': False}}, - maybe_get_writable_device_path, 'overlayroot', info, LOG) + "cloudinit.config.cc_resizefs.util", + {"is_container": {"return_value": False}}, + maybe_get_writable_device_path, + "overlayroot", + info, + LOG, + ) self.assertIsNone(devpath) self.assertIn( "Not attempting to resize devpath 'overlayroot'", - self.logs.getvalue()) + self.logs.getvalue(), + ) def test_maybe_get_writable_device_path_warns_missing_cmdline_root(self): """When root does not exist isn't in the cmdline, log warning.""" - info = 'does not matter' + info = "does not matter" def fake_mount_info(path, log): - self.assertEqual('/', path) + self.assertEqual("/", path) self.assertEqual(LOG, log) - return ('/dev/root', 'ext4', '/') + return ("/dev/root", "ext4", "/") - exists_mock_path = 'cloudinit.config.cc_resizefs.os.path.exists' + exists_mock_path = "cloudinit.config.cc_resizefs.os.path.exists" with mock.patch(exists_mock_path) as m_exists: m_exists.return_value = False devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': False}, - 'get_mount_info': {'side_effect': fake_mount_info}, - 'get_cmdline': {'return_value': 'BOOT_IMAGE=/vmlinuz.efi'}}, - maybe_get_writable_device_path, '/dev/root', info, LOG) + "cloudinit.config.cc_resizefs.util", + { + "is_container": {"return_value": False}, + "get_mount_info": {"side_effect": fake_mount_info}, + "get_cmdline": {"return_value": "BOOT_IMAGE=/vmlinuz.efi"}, + }, + maybe_get_writable_device_path, + "/dev/root", + info, + LOG, + ) self.assertIsNone(devpath) logs = self.logs.getvalue() self.assertIn("WARNING: Unable to find device '/dev/root'", logs) def test_maybe_get_writable_device_path_does_not_exist(self): """When devpath does not exist, a warning is logged.""" - info = 'dev=/dev/I/dont/exist mnt_point=/ path=/dev/none' + info = "dev=/dev/I/dont/exist mnt_point=/ path=/dev/none" devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': False}}, - maybe_get_writable_device_path, '/dev/I/dont/exist', info, LOG) + "cloudinit.config.cc_resizefs.util", + {"is_container": {"return_value": False}}, + maybe_get_writable_device_path, + "/dev/I/dont/exist", + info, + LOG, + ) self.assertIsNone(devpath) self.assertIn( "WARNING: Device '/dev/I/dont/exist' did not exist." - ' cannot resize: %s' % info, - self.logs.getvalue()) + " cannot resize: %s" % info, + self.logs.getvalue(), + ) def test_maybe_get_writable_device_path_does_not_exist_in_container(self): """When devpath does not exist in a container, log a debug message.""" - info = 'dev=/dev/I/dont/exist mnt_point=/ path=/dev/none' + info = "dev=/dev/I/dont/exist mnt_point=/ path=/dev/none" devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': True}}, - maybe_get_writable_device_path, '/dev/I/dont/exist', info, LOG) + "cloudinit.config.cc_resizefs.util", + {"is_container": {"return_value": True}}, + maybe_get_writable_device_path, + "/dev/I/dont/exist", + info, + LOG, + ) self.assertIsNone(devpath) self.assertIn( "DEBUG: Device '/dev/I/dont/exist' did not exist in container." - ' cannot resize: %s' % info, - self.logs.getvalue()) + " cannot resize: %s" % info, + self.logs.getvalue(), + ) def test_maybe_get_writable_device_path_raises_oserror(self): """When unexpected OSError is raises by os.stat it is reraised.""" - info = 'dev=/dev/I/dont/exist mnt_point=/ path=/dev/none' + info = "dev=/dev/I/dont/exist mnt_point=/ path=/dev/none" with self.assertRaises(OSError) as context_manager: wrap_and_call( - 'cloudinit.config.cc_resizefs', - {'util.is_container': {'return_value': True}, - 'os.stat': {'side_effect': OSError('Something unexpected')}}, - maybe_get_writable_device_path, '/dev/I/dont/exist', info, LOG) + "cloudinit.config.cc_resizefs", + { + "util.is_container": {"return_value": True}, + "os.stat": { + "side_effect": OSError("Something unexpected") + }, + }, + maybe_get_writable_device_path, + "/dev/I/dont/exist", + info, + LOG, + ) self.assertEqual( - 'Something unexpected', str(context_manager.exception)) + "Something unexpected", str(context_manager.exception) + ) def test_maybe_get_writable_device_path_non_block(self): """When device is not a block device, emit warning return False.""" - fake_devpath = self.tmp_path('dev/readwrite') - util.write_file(fake_devpath, '', mode=0o600) # read-write - info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath) + fake_devpath = self.tmp_path("dev/readwrite") + util.write_file(fake_devpath, "", mode=0o600) # read-write + info = "dev=/dev/root mnt_point=/ path={0}".format(fake_devpath) devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': False}}, - maybe_get_writable_device_path, fake_devpath, info, LOG) + "cloudinit.config.cc_resizefs.util", + {"is_container": {"return_value": False}}, + maybe_get_writable_device_path, + fake_devpath, + info, + LOG, + ) self.assertIsNone(devpath) self.assertIn( "WARNING: device '{0}' not a block device. cannot resize".format( - fake_devpath), - self.logs.getvalue()) + fake_devpath + ), + self.logs.getvalue(), + ) def test_maybe_get_writable_device_path_non_block_on_container(self): """When device is non-block device in container, emit debug log.""" - fake_devpath = self.tmp_path('dev/readwrite') - util.write_file(fake_devpath, '', mode=0o600) # read-write - info = 'dev=/dev/root mnt_point=/ path={0}'.format(fake_devpath) + fake_devpath = self.tmp_path("dev/readwrite") + util.write_file(fake_devpath, "", mode=0o600) # read-write + info = "dev=/dev/root mnt_point=/ path={0}".format(fake_devpath) devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs.util', - {'is_container': {'return_value': True}}, - maybe_get_writable_device_path, fake_devpath, info, LOG) + "cloudinit.config.cc_resizefs.util", + {"is_container": {"return_value": True}}, + maybe_get_writable_device_path, + fake_devpath, + info, + LOG, + ) self.assertIsNone(devpath) self.assertIn( "DEBUG: device '{0}' not a block device in container." - ' cannot resize'.format(fake_devpath), - self.logs.getvalue()) + " cannot resize".format(fake_devpath), + self.logs.getvalue(), + ) def test_maybe_get_writable_device_path_returns_cmdline_root(self): """When root device is UUID in kernel commandline, update devpath.""" # XXX Long-term we want to use FilesystemMocking test to avoid # touching os.stat. FakeStat = namedtuple( - 'FakeStat', ['st_mode', 'st_size', 'st_mtime']) # minimal def. - info = 'dev=/dev/root mnt_point=/ path=/does/not/matter' + "FakeStat", ["st_mode", "st_size", "st_mtime"] + ) # minimal def. + info = "dev=/dev/root mnt_point=/ path=/does/not/matter" devpath = wrap_and_call( - 'cloudinit.config.cc_resizefs', - {'util.get_cmdline': {'return_value': 'asdf root=UUID=my-uuid'}, - 'util.is_container': False, - 'os.path.exists': False, # /dev/root doesn't exist - 'os.stat': { - 'return_value': FakeStat(25008, 0, 1)} # char block device - }, - maybe_get_writable_device_path, '/dev/root', info, LOG) - self.assertEqual('/dev/disk/by-uuid/my-uuid', devpath) + "cloudinit.config.cc_resizefs", + { + "util.get_cmdline": {"return_value": "asdf root=UUID=my-uuid"}, + "util.is_container": False, + "os.path.exists": False, # /dev/root doesn't exist + "os.stat": { + "return_value": FakeStat(25008, 0, 1) + }, # char block device + }, + maybe_get_writable_device_path, + "/dev/root", + info, + LOG, + ) + self.assertEqual("/dev/disk/by-uuid/my-uuid", devpath) self.assertIn( "DEBUG: Converted /dev/root to '/dev/disk/by-uuid/my-uuid'" " per kernel cmdline", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.util.mount_is_read_write') - @mock.patch('cloudinit.config.cc_resizefs.os.path.isdir') + @mock.patch("cloudinit.util.mount_is_read_write") + @mock.patch("cloudinit.config.cc_resizefs.os.path.isdir") def test_resize_btrfs_mount_is_ro(self, m_is_dir, m_is_rw): """Do not resize / directly if it is read-only. (LP: #1734787).""" m_is_rw.return_value = False m_is_dir.return_value = True self.assertEqual( - ('btrfs', 'filesystem', 'resize', 'max', '//.snapshots'), - _resize_btrfs("/", "/dev/sda1")) + ("btrfs", "filesystem", "resize", "max", "//.snapshots"), + _resize_btrfs("/", "/dev/sda1"), + ) - @mock.patch('cloudinit.util.mount_is_read_write') - @mock.patch('cloudinit.config.cc_resizefs.os.path.isdir') + @mock.patch("cloudinit.util.mount_is_read_write") + @mock.patch("cloudinit.config.cc_resizefs.os.path.isdir") def test_resize_btrfs_mount_is_rw(self, m_is_dir, m_is_rw): """Do not resize / directly if it is read-only. (LP: #1734787).""" m_is_rw.return_value = True m_is_dir.return_value = True self.assertEqual( - ('btrfs', 'filesystem', 'resize', 'max', '/'), - _resize_btrfs("/", "/dev/sda1")) + ("btrfs", "filesystem", "resize", "max", "/"), + _resize_btrfs("/", "/dev/sda1"), + ) - @mock.patch('cloudinit.util.is_container', return_value=True) - @mock.patch('cloudinit.util.is_FreeBSD') - def test_maybe_get_writable_device_path_zfs_freebsd(self, freebsd, - m_is_container): + @mock.patch("cloudinit.util.is_container", return_value=True) + @mock.patch("cloudinit.util.is_FreeBSD") + def test_maybe_get_writable_device_path_zfs_freebsd( + self, freebsd, m_is_container + ): freebsd.return_value = True - info = 'dev=gpt/system mnt_point=/ path=/' - devpth = maybe_get_writable_device_path('gpt/system', info, LOG) - self.assertEqual('gpt/system', devpth) + info = "dev=gpt/system mnt_point=/ path=/" + devpth = maybe_get_writable_device_path("gpt/system", info, LOG) + self.assertEqual("gpt/system", devpth) # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_resolv_conf.py b/tests/unittests/config/test_cc_resolv_conf.py index ab2de17a..8896a4e8 100644 --- a/tests/unittests/config/test_cc_resolv_conf.py +++ b/tests/unittests/config/test_cc_resolv_conf.py @@ -4,19 +4,16 @@ import logging import os import shutil import tempfile -import pytest from copy import deepcopy from unittest import mock -from cloudinit import cloud -from cloudinit import distros -from cloudinit import helpers -from cloudinit import util +import pytest -from tests.unittests import helpers as t_help -from tests.unittests.util import MockDistro +from cloudinit import cloud, distros, helpers, util from cloudinit.config import cc_resolv_conf from cloudinit.config.cc_resolv_conf import generate_resolv_conf +from tests.unittests import helpers as t_help +from tests.unittests.util import MockDistro LOG = logging.getLogger(__name__) EXPECTED_HEADER = """\ @@ -29,17 +26,17 @@ EXPECTED_HEADER = """\ class TestResolvConf(t_help.FilesystemMockingTestCase): with_logs = True - cfg = {'manage_resolv_conf': True, 'resolv_conf': {}} + cfg = {"manage_resolv_conf": True, "resolv_conf": {}} def setUp(self): super(TestResolvConf, self).setUp() self.tmp = tempfile.mkdtemp() - util.ensure_dir(os.path.join(self.tmp, 'data')) + util.ensure_dir(os.path.join(self.tmp, "data")) self.addCleanup(shutil.rmtree, self.tmp) def _fetch_distro(self, kind, conf=None): cls = distros.fetch(kind) - paths = helpers.Paths({'cloud_dir': self.tmp}) + paths = helpers.Paths({"cloud_dir": self.tmp}) conf = {} if conf is None else conf return cls(kind, conf, paths) @@ -47,67 +44,73 @@ class TestResolvConf(t_help.FilesystemMockingTestCase): if not cc: ds = None distro = self._fetch_distro(distro_name, conf) - paths = helpers.Paths({'cloud_dir': self.tmp}) + paths = helpers.Paths({"cloud_dir": self.tmp}) cc = cloud.Cloud(ds, paths, {}, distro, None) - cc_resolv_conf.handle('cc_resolv_conf', conf, cc, LOG, []) + cc_resolv_conf.handle("cc_resolv_conf", conf, cc, LOG, []) @mock.patch("cloudinit.config.cc_resolv_conf.templater.render_to_file") def test_resolv_conf_systemd_resolved(self, m_render_to_file): - self.call_resolv_conf_handler('photon', self.cfg) + self.call_resolv_conf_handler("photon", self.cfg) assert [ - mock.call(mock.ANY, '/etc/systemd/resolved.conf', mock.ANY) + mock.call(mock.ANY, "/etc/systemd/resolved.conf", mock.ANY) ] == m_render_to_file.call_args_list @mock.patch("cloudinit.config.cc_resolv_conf.templater.render_to_file") def test_resolv_conf_no_param(self, m_render_to_file): tmp = deepcopy(self.cfg) self.logs.truncate(0) - tmp.pop('resolv_conf') - self.call_resolv_conf_handler('photon', tmp) + tmp.pop("resolv_conf") + self.call_resolv_conf_handler("photon", tmp) - self.assertIn('manage_resolv_conf True but no parameters provided', - self.logs.getvalue()) + self.assertIn( + "manage_resolv_conf True but no parameters provided", + self.logs.getvalue(), + ) assert [ - mock.call(mock.ANY, '/etc/systemd/resolved.conf', mock.ANY) + mock.call(mock.ANY, "/etc/systemd/resolved.conf", mock.ANY) ] not in m_render_to_file.call_args_list @mock.patch("cloudinit.config.cc_resolv_conf.templater.render_to_file") def test_resolv_conf_manage_resolv_conf_false(self, m_render_to_file): tmp = deepcopy(self.cfg) self.logs.truncate(0) - tmp['manage_resolv_conf'] = False - self.call_resolv_conf_handler('photon', tmp) - self.assertIn("'manage_resolv_conf' present but set to False", - self.logs.getvalue()) + tmp["manage_resolv_conf"] = False + self.call_resolv_conf_handler("photon", tmp) + self.assertIn( + "'manage_resolv_conf' present but set to False", + self.logs.getvalue(), + ) assert [ - mock.call(mock.ANY, '/etc/systemd/resolved.conf', mock.ANY) + mock.call(mock.ANY, "/etc/systemd/resolved.conf", mock.ANY) ] not in m_render_to_file.call_args_list @mock.patch("cloudinit.config.cc_resolv_conf.templater.render_to_file") def test_resolv_conf_etc_resolv_conf(self, m_render_to_file): - self.call_resolv_conf_handler('rhel', self.cfg) + self.call_resolv_conf_handler("rhel", self.cfg) assert [ - mock.call(mock.ANY, '/etc/resolv.conf', mock.ANY) + mock.call(mock.ANY, "/etc/resolv.conf", mock.ANY) ] == m_render_to_file.call_args_list @mock.patch("cloudinit.config.cc_resolv_conf.templater.render_to_file") def test_resolv_conf_invalid_resolve_conf_fn(self, m_render_to_file): ds = None - distro = self._fetch_distro('rhel', self.cfg) - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("rhel", self.cfg) + paths = helpers.Paths({"cloud_dir": self.tmp}) cc = cloud.Cloud(ds, paths, {}, distro, None) - cc.distro.resolve_conf_fn = 'bla' + cc.distro.resolve_conf_fn = "bla" self.logs.truncate(0) - self.call_resolv_conf_handler('rhel', self.cfg, cc) + self.call_resolv_conf_handler("rhel", self.cfg, cc) - self.assertIn('No template found, not rendering resolve configs', - self.logs.getvalue()) + self.assertIn( + "No template found, not rendering resolve configs", + self.logs.getvalue(), + ) assert [ - mock.call(mock.ANY, '/etc/resolv.conf', mock.ANY) + mock.call(mock.ANY, "/etc/resolv.conf", mock.ANY) ] not in m_render_to_file.call_args_list @@ -119,9 +122,9 @@ class TestGenerateResolvConf: @mock.patch("cloudinit.config.cc_resolv_conf.templater.render_to_file") def test_dist_resolv_conf_fn(self, m_render_to_file): self.dist.resolve_conf_fn = "/tmp/resolv-test.conf" - generate_resolv_conf(self.tmpl_fn, - mock.MagicMock(), - self.dist.resolve_conf_fn) + generate_resolv_conf( + self.tmpl_fn, mock.MagicMock(), self.dist.resolve_conf_fn + ) assert [ mock.call(mock.ANY, self.dist.resolve_conf_fn, mock.ANY) @@ -190,4 +193,5 @@ class TestGenerateResolvConf: mock.call(mock.ANY, expected_content, mode=mock.ANY) ] == m_write_file.call_args_list + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_rh_subscription.py b/tests/unittests/config/test_cc_rh_subscription.py index bd7ebc98..fcc7db34 100644 --- a/tests/unittests/config/test_cc_rh_subscription.py +++ b/tests/unittests/config/test_cc_rh_subscription.py @@ -5,13 +5,12 @@ import copy import logging -from cloudinit.config import cc_rh_subscription from cloudinit import subp - +from cloudinit.config import cc_rh_subscription from tests.unittests.helpers import CiTestCase, mock SUBMGR = cc_rh_subscription.SubscriptionManager -SUB_MAN_CLI = 'cloudinit.config.cc_rh_subscription._sub_man_cli' +SUB_MAN_CLI = "cloudinit.config.cc_rh_subscription._sub_man_cli" @mock.patch(SUB_MAN_CLI) @@ -26,87 +25,115 @@ class GoodTests(CiTestCase): self.args = [] self.handle = cc_rh_subscription.handle - self.config = {'rh_subscription': - {'username': 'scooby@do.com', - 'password': 'scooby-snacks' - }} - self.config_full = {'rh_subscription': - {'username': 'scooby@do.com', - 'password': 'scooby-snacks', - 'auto-attach': True, - 'service-level': 'self-support', - 'add-pool': ['pool1', 'pool2', 'pool3'], - 'enable-repo': ['repo1', 'repo2', 'repo3'], - 'disable-repo': ['repo4', 'repo5'] - }} + self.config = { + "rh_subscription": { + "username": "scooby@do.com", + "password": "scooby-snacks", + } + } + self.config_full = { + "rh_subscription": { + "username": "scooby@do.com", + "password": "scooby-snacks", + "auto-attach": True, + "service-level": "self-support", + "add-pool": ["pool1", "pool2", "pool3"], + "enable-repo": ["repo1", "repo2", "repo3"], + "disable-repo": ["repo4", "repo5"], + } + } def test_already_registered(self, m_sman_cli): - ''' + """ Emulates a system that is already registered. Ensure it gets a non-ProcessExecution error from is_registered() - ''' - self.handle(self.name, self.config, self.cloud_init, - self.log, self.args) + """ + self.handle( + self.name, self.config, self.cloud_init, self.log, self.args + ) self.assertEqual(m_sman_cli.call_count, 1) - self.assertIn('System is already registered', self.logs.getvalue()) + self.assertIn("System is already registered", self.logs.getvalue()) def test_simple_registration(self, m_sman_cli): - ''' + """ Simple registration with username and password - ''' - reg = "The system has been registered with ID:" \ - " 12345678-abde-abcde-1234-1234567890abc" - m_sman_cli.side_effect = [subp.ProcessExecutionError, (reg, 'bar')] - self.handle(self.name, self.config, self.cloud_init, - self.log, self.args) - self.assertIn(mock.call(['identity']), m_sman_cli.call_args_list) - self.assertIn(mock.call(['register', '--username=scooby@do.com', - '--password=scooby-snacks'], - logstring_val=True), - m_sman_cli.call_args_list) - self.assertIn('rh_subscription plugin completed successfully', - self.logs.getvalue()) + """ + reg = ( + "The system has been registered with ID:" + " 12345678-abde-abcde-1234-1234567890abc" + ) + m_sman_cli.side_effect = [subp.ProcessExecutionError, (reg, "bar")] + self.handle( + self.name, self.config, self.cloud_init, self.log, self.args + ) + self.assertIn(mock.call(["identity"]), m_sman_cli.call_args_list) + self.assertIn( + mock.call( + [ + "register", + "--username=scooby@do.com", + "--password=scooby-snacks", + ], + logstring_val=True, + ), + m_sman_cli.call_args_list, + ) + self.assertIn( + "rh_subscription plugin completed successfully", + self.logs.getvalue(), + ) self.assertEqual(m_sman_cli.call_count, 2) @mock.patch.object(cc_rh_subscription.SubscriptionManager, "_getRepos") def test_update_repos_disable_with_none(self, m_get_repos, m_sman_cli): cfg = copy.deepcopy(self.config) - m_get_repos.return_value = ([], ['repo1']) - cfg['rh_subscription'].update( - {'enable-repo': ['repo1'], 'disable-repo': None}) + m_get_repos.return_value = ([], ["repo1"]) + cfg["rh_subscription"].update( + {"enable-repo": ["repo1"], "disable-repo": None} + ) mysm = cc_rh_subscription.SubscriptionManager(cfg) self.assertEqual(True, mysm.update_repos()) m_get_repos.assert_called_with() - self.assertEqual(m_sman_cli.call_args_list, - [mock.call(['repos', '--enable=repo1'])]) + self.assertEqual( + m_sman_cli.call_args_list, [mock.call(["repos", "--enable=repo1"])] + ) def test_full_registration(self, m_sman_cli): - ''' + """ Registration with auto-attach, service-level, adding pools, and enabling and disabling yum repos - ''' + """ call_lists = [] - call_lists.append(['attach', '--pool=pool1', '--pool=pool3']) - call_lists.append(['repos', '--disable=repo5', '--enable=repo2', - '--enable=repo3']) - call_lists.append(['attach', '--auto', '--servicelevel=self-support']) - reg = "The system has been registered with ID:" \ - " 12345678-abde-abcde-1234-1234567890abc" + call_lists.append(["attach", "--pool=pool1", "--pool=pool3"]) + call_lists.append( + ["repos", "--disable=repo5", "--enable=repo2", "--enable=repo3"] + ) + call_lists.append(["attach", "--auto", "--servicelevel=self-support"]) + reg = ( + "The system has been registered with ID:" + " 12345678-abde-abcde-1234-1234567890abc" + ) m_sman_cli.side_effect = [ subp.ProcessExecutionError, - (reg, 'bar'), - ('Service level set to: self-support', ''), - ('pool1\npool3\n', ''), ('pool2\n', ''), ('', ''), - ('Repo ID: repo1\nRepo ID: repo5\n', ''), - ('Repo ID: repo2\nRepo ID: repo3\nRepo ID: repo4', ''), - ('', '')] - self.handle(self.name, self.config_full, self.cloud_init, - self.log, self.args) + (reg, "bar"), + ("Service level set to: self-support", ""), + ("pool1\npool3\n", ""), + ("pool2\n", ""), + ("", ""), + ("Repo ID: repo1\nRepo ID: repo5\n", ""), + ("Repo ID: repo2\nRepo ID: repo3\nRepo ID: repo4", ""), + ("", ""), + ] + self.handle( + self.name, self.config_full, self.cloud_init, self.log, self.args + ) self.assertEqual(m_sman_cli.call_count, 9) for call in call_lists: self.assertIn(mock.call(call), m_sman_cli.call_args_list) - self.assertIn("rh_subscription plugin completed successfully", - self.logs.getvalue()) + self.assertIn( + "rh_subscription plugin completed successfully", + self.logs.getvalue(), + ) @mock.patch(SUB_MAN_CLI) @@ -117,38 +144,48 @@ class TestBadInput(CiTestCase): log = logging.getLogger("bad_tests") args = [] SM = cc_rh_subscription.SubscriptionManager - reg = "The system has been registered with ID:" \ - " 12345678-abde-abcde-1234-1234567890abc" - - config_no_password = {'rh_subscription': - {'username': 'scooby@do.com' - }} - - config_no_key = {'rh_subscription': - {'activation-key': '1234abcde', - }} - - config_service = {'rh_subscription': - {'username': 'scooby@do.com', - 'password': 'scooby-snacks', - 'service-level': 'self-support' - }} - - config_badpool = {'rh_subscription': - {'username': 'scooby@do.com', - 'password': 'scooby-snacks', - 'add-pool': 'not_a_list' - }} - config_badrepo = {'rh_subscription': - {'username': 'scooby@do.com', - 'password': 'scooby-snacks', - 'enable-repo': 'not_a_list' - }} - config_badkey = {'rh_subscription': - {'activation-key': 'abcdef1234', - 'fookey': 'bar', - 'org': '123', - }} + reg = ( + "The system has been registered with ID:" + " 12345678-abde-abcde-1234-1234567890abc" + ) + + config_no_password = {"rh_subscription": {"username": "scooby@do.com"}} + + config_no_key = { + "rh_subscription": { + "activation-key": "1234abcde", + } + } + + config_service = { + "rh_subscription": { + "username": "scooby@do.com", + "password": "scooby-snacks", + "service-level": "self-support", + } + } + + config_badpool = { + "rh_subscription": { + "username": "scooby@do.com", + "password": "scooby-snacks", + "add-pool": "not_a_list", + } + } + config_badrepo = { + "rh_subscription": { + "username": "scooby@do.com", + "password": "scooby-snacks", + "enable-repo": "not_a_list", + } + } + config_badkey = { + "rh_subscription": { + "activation-key": "abcdef1234", + "fookey": "bar", + "org": "123", + } + } def setUp(self): super(TestBadInput, self).setUp() @@ -160,75 +197,124 @@ class TestBadInput(CiTestCase): self.assertEqual([], missing, "Missing expected warnings.") def test_no_password(self, m_sman_cli): - '''Attempt to register without the password key/value.''' - m_sman_cli.side_effect = [subp.ProcessExecutionError, - (self.reg, 'bar')] - self.handle(self.name, self.config_no_password, self.cloud_init, - self.log, self.args) + """Attempt to register without the password key/value.""" + m_sman_cli.side_effect = [ + subp.ProcessExecutionError, + (self.reg, "bar"), + ] + self.handle( + self.name, + self.config_no_password, + self.cloud_init, + self.log, + self.args, + ) self.assertEqual(m_sman_cli.call_count, 0) def test_no_org(self, m_sman_cli): - '''Attempt to register without the org key/value.''' + """Attempt to register without the org key/value.""" m_sman_cli.side_effect = [subp.ProcessExecutionError] - self.handle(self.name, self.config_no_key, self.cloud_init, - self.log, self.args) - m_sman_cli.assert_called_with(['identity']) + self.handle( + self.name, self.config_no_key, self.cloud_init, self.log, self.args + ) + m_sman_cli.assert_called_with(["identity"]) self.assertEqual(m_sman_cli.call_count, 1) - self.assert_logged_warnings(( - 'Unable to register system due to incomplete information.', - 'Use either activationkey and org *or* userid and password', - 'Registration failed or did not run completely', - 'rh_subscription plugin did not complete successfully')) + self.assert_logged_warnings( + ( + "Unable to register system due to incomplete information.", + "Use either activationkey and org *or* userid and password", + "Registration failed or did not run completely", + "rh_subscription plugin did not complete successfully", + ) + ) def test_service_level_without_auto(self, m_sman_cli): - '''Attempt to register using service-level without auto-attach key.''' - m_sman_cli.side_effect = [subp.ProcessExecutionError, - (self.reg, 'bar')] - self.handle(self.name, self.config_service, self.cloud_init, - self.log, self.args) + """Attempt to register using service-level without auto-attach key.""" + m_sman_cli.side_effect = [ + subp.ProcessExecutionError, + (self.reg, "bar"), + ] + self.handle( + self.name, + self.config_service, + self.cloud_init, + self.log, + self.args, + ) self.assertEqual(m_sman_cli.call_count, 1) - self.assert_logged_warnings(( - 'The service-level key must be used in conjunction with ', - 'rh_subscription plugin did not complete successfully')) + self.assert_logged_warnings( + ( + "The service-level key must be used in conjunction with ", + "rh_subscription plugin did not complete successfully", + ) + ) def test_pool_not_a_list(self, m_sman_cli): - ''' + """ Register with pools that are not in the format of a list - ''' - m_sman_cli.side_effect = [subp.ProcessExecutionError, - (self.reg, 'bar')] - self.handle(self.name, self.config_badpool, self.cloud_init, - self.log, self.args) + """ + m_sman_cli.side_effect = [ + subp.ProcessExecutionError, + (self.reg, "bar"), + ] + self.handle( + self.name, + self.config_badpool, + self.cloud_init, + self.log, + self.args, + ) self.assertEqual(m_sman_cli.call_count, 2) - self.assert_logged_warnings(( - 'Pools must in the format of a list', - 'rh_subscription plugin did not complete successfully')) + self.assert_logged_warnings( + ( + "Pools must in the format of a list", + "rh_subscription plugin did not complete successfully", + ) + ) def test_repo_not_a_list(self, m_sman_cli): - ''' + """ Register with repos that are not in the format of a list - ''' - m_sman_cli.side_effect = [subp.ProcessExecutionError, - (self.reg, 'bar')] - self.handle(self.name, self.config_badrepo, self.cloud_init, - self.log, self.args) + """ + m_sman_cli.side_effect = [ + subp.ProcessExecutionError, + (self.reg, "bar"), + ] + self.handle( + self.name, + self.config_badrepo, + self.cloud_init, + self.log, + self.args, + ) self.assertEqual(m_sman_cli.call_count, 2) - self.assert_logged_warnings(( - 'Repo IDs must in the format of a list.', - 'Unable to add or remove repos', - 'rh_subscription plugin did not complete successfully')) + self.assert_logged_warnings( + ( + "Repo IDs must in the format of a list.", + "Unable to add or remove repos", + "rh_subscription plugin did not complete successfully", + ) + ) def test_bad_key_value(self, m_sman_cli): - ''' + """ Attempt to register with a key that we don't know - ''' - m_sman_cli.side_effect = [subp.ProcessExecutionError, - (self.reg, 'bar')] - self.handle(self.name, self.config_badkey, self.cloud_init, - self.log, self.args) + """ + m_sman_cli.side_effect = [ + subp.ProcessExecutionError, + (self.reg, "bar"), + ] + self.handle( + self.name, self.config_badkey, self.cloud_init, self.log, self.args + ) self.assertEqual(m_sman_cli.call_count, 1) - self.assert_logged_warnings(( - 'fookey is not a valid key for rh_subscription. Valid keys are:', - 'rh_subscription plugin did not complete successfully')) + self.assert_logged_warnings( + ( + "fookey is not a valid key for rh_subscription. Valid keys" + " are:", + "rh_subscription plugin did not complete successfully", + ) + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_rsyslog.py b/tests/unittests/config/test_cc_rsyslog.py index bc147dac..e5d06ca2 100644 --- a/tests/unittests/config/test_cc_rsyslog.py +++ b/tests/unittests/config/test_cc_rsyslog.py @@ -4,11 +4,16 @@ import os import shutil import tempfile -from cloudinit.config.cc_rsyslog import ( - apply_rsyslog_changes, DEF_DIR, DEF_FILENAME, DEF_RELOAD, load_config, - parse_remotes_line, remotes_to_rsyslog_cfg) from cloudinit import util - +from cloudinit.config.cc_rsyslog import ( + DEF_DIR, + DEF_FILENAME, + DEF_RELOAD, + apply_rsyslog_changes, + load_config, + parse_remotes_line, + remotes_to_rsyslog_cfg, +) from tests.unittests import helpers as t_help @@ -16,43 +21,46 @@ class TestLoadConfig(t_help.TestCase): def setUp(self): super(TestLoadConfig, self).setUp() self.basecfg = { - 'config_filename': DEF_FILENAME, - 'config_dir': DEF_DIR, - 'service_reload_command': DEF_RELOAD, - 'configs': [], - 'remotes': {}, + "config_filename": DEF_FILENAME, + "config_dir": DEF_DIR, + "service_reload_command": DEF_RELOAD, + "configs": [], + "remotes": {}, } def test_legacy_full(self): - found = load_config({ - 'rsyslog': ['*.* @192.168.1.1'], - 'rsyslog_dir': "mydir", - 'rsyslog_filename': "myfilename"}) - self.basecfg.update({ - 'configs': ['*.* @192.168.1.1'], - 'config_dir': "mydir", - 'config_filename': 'myfilename', - 'service_reload_command': 'auto'} + found = load_config( + { + "rsyslog": ["*.* @192.168.1.1"], + "rsyslog_dir": "mydir", + "rsyslog_filename": "myfilename", + } + ) + self.basecfg.update( + { + "configs": ["*.* @192.168.1.1"], + "config_dir": "mydir", + "config_filename": "myfilename", + "service_reload_command": "auto", + } ) self.assertEqual(found, self.basecfg) def test_legacy_defaults(self): - found = load_config({ - 'rsyslog': ['*.* @192.168.1.1']}) - self.basecfg.update({ - 'configs': ['*.* @192.168.1.1']}) + found = load_config({"rsyslog": ["*.* @192.168.1.1"]}) + self.basecfg.update({"configs": ["*.* @192.168.1.1"]}) self.assertEqual(found, self.basecfg) def test_new_defaults(self): self.assertEqual(load_config({}), self.basecfg) def test_new_configs(self): - cfgs = ['*.* myhost', '*.* my2host'] - self.basecfg.update({'configs': cfgs}) + cfgs = ["*.* myhost", "*.* my2host"] + self.basecfg.update({"configs": cfgs}) self.assertEqual( - load_config({'rsyslog': {'configs': cfgs}}), - self.basecfg) + load_config({"rsyslog": {"configs": cfgs}}), self.basecfg + ) class TestApplyChanges(t_help.TestCase): @@ -63,27 +71,29 @@ class TestApplyChanges(t_help.TestCase): def test_simple(self): cfgline = "*.* foohost" changed = apply_rsyslog_changes( - configs=[cfgline], def_fname="foo.cfg", cfg_dir=self.tmp) + configs=[cfgline], def_fname="foo.cfg", cfg_dir=self.tmp + ) fname = os.path.join(self.tmp, "foo.cfg") self.assertEqual([fname], changed) - self.assertEqual( - util.load_file(fname), cfgline + "\n") + self.assertEqual(util.load_file(fname), cfgline + "\n") def test_multiple_files(self): configs = [ - '*.* foohost', - {'content': 'abc', 'filename': 'my.cfg'}, - {'content': 'filefoo-content', - 'filename': os.path.join(self.tmp, 'mydir/mycfg')}, + "*.* foohost", + {"content": "abc", "filename": "my.cfg"}, + { + "content": "filefoo-content", + "filename": os.path.join(self.tmp, "mydir/mycfg"), + }, ] changed = apply_rsyslog_changes( - configs=configs, def_fname="default.cfg", cfg_dir=self.tmp) + configs=configs, def_fname="default.cfg", cfg_dir=self.tmp + ) expected = [ - (os.path.join(self.tmp, "default.cfg"), - "*.* foohost\n"), + (os.path.join(self.tmp, "default.cfg"), "*.* foohost\n"), (os.path.join(self.tmp, "my.cfg"), "abc\n"), (os.path.join(self.tmp, "mydir/mycfg"), "filefoo-content\n"), ] @@ -91,30 +101,37 @@ class TestApplyChanges(t_help.TestCase): actual = [] for fname, _content in expected: util.load_file(fname) - actual.append((fname, util.load_file(fname),)) + actual.append( + ( + fname, + util.load_file(fname), + ) + ) self.assertEqual(expected, actual) def test_repeat_def(self): - configs = ['*.* foohost', "*.warn otherhost"] + configs = ["*.* foohost", "*.warn otherhost"] changed = apply_rsyslog_changes( - configs=configs, def_fname="default.cfg", cfg_dir=self.tmp) + configs=configs, def_fname="default.cfg", cfg_dir=self.tmp + ) fname = os.path.join(self.tmp, "default.cfg") self.assertEqual([fname], changed) - expected_content = '\n'.join([c for c in configs]) + '\n' + expected_content = "\n".join([c for c in configs]) + "\n" found_content = util.load_file(fname) self.assertEqual(expected_content, found_content) def test_multiline_content(self): - configs = ['line1', 'line2\nline3\n'] + configs = ["line1", "line2\nline3\n"] apply_rsyslog_changes( - configs=configs, def_fname="default.cfg", cfg_dir=self.tmp) + configs=configs, def_fname="default.cfg", cfg_dir=self.tmp + ) fname = os.path.join(self.tmp, "default.cfg") - expected_content = '\n'.join([c for c in configs]) + expected_content = "\n".join([c for c in configs]) found_content = util.load_file(fname) self.assertEqual(expected_content, found_content) @@ -152,7 +169,7 @@ class TestRemotesToSyslog(t_help.TestCase): # str rendered line must appear in remotes_to_ryslog_cfg return mycfg = "*.* myhost" myline = str(parse_remotes_line(mycfg, name="myname")) - r = remotes_to_rsyslog_cfg({'myname': mycfg}) + r = remotes_to_rsyslog_cfg({"myname": mycfg}) lines = r.splitlines() self.assertEqual(1, len(lines)) self.assertTrue(myline in r.splitlines()) @@ -161,7 +178,8 @@ class TestRemotesToSyslog(t_help.TestCase): header = "#foo head" footer = "#foo foot" r = remotes_to_rsyslog_cfg( - {'myname': "*.* myhost"}, header=header, footer=footer) + {"myname": "*.* myhost"}, header=header, footer=footer + ) lines = r.splitlines() self.assertTrue(header, lines[0]) self.assertTrue(footer, lines[-1]) @@ -170,9 +188,11 @@ class TestRemotesToSyslog(t_help.TestCase): mycfg = "*.* myhost" myline = str(parse_remotes_line(mycfg, name="myname")) r = remotes_to_rsyslog_cfg( - {'myname': mycfg, 'removed': None, 'removed2': ""}) + {"myname": mycfg, "removed": None, "removed2": ""} + ) lines = r.splitlines() self.assertEqual(1, len(lines)) self.assertTrue(myline in r.splitlines()) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_runcmd.py b/tests/unittests/config/test_cc_runcmd.py index 01de6af0..34b3fb77 100644 --- a/tests/unittests/config/test_cc_runcmd.py +++ b/tests/unittests/config/test_cc_runcmd.py @@ -4,12 +4,14 @@ import os import stat from unittest.mock import patch +from cloudinit import helpers, subp, util from cloudinit.config.cc_runcmd import handle, schema -from cloudinit import (helpers, subp, util) from tests.unittests.helpers import ( - CiTestCase, FilesystemMockingTestCase, SchemaTestCaseMixin, - skipUnlessJsonSchema) - + CiTestCase, + FilesystemMockingTestCase, + SchemaTestCaseMixin, + skipUnlessJsonSchema, +) from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -24,38 +26,40 @@ class TestRuncmd(FilesystemMockingTestCase): self.subp = subp.subp self.new_root = self.tmp_dir() self.patchUtils(self.new_root) - self.paths = helpers.Paths({'scripts': self.new_root}) + self.paths = helpers.Paths({"scripts": self.new_root}) def test_handler_skip_if_no_runcmd(self): """When the provided config doesn't contain runcmd, skip it.""" cfg = {} mycloud = get_cloud(paths=self.paths) - handle('notimportant', cfg, mycloud, LOG, None) + handle("notimportant", cfg, mycloud, LOG, None) self.assertIn( "Skipping module named notimportant, no 'runcmd' key", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @patch('cloudinit.util.shellify') + @patch("cloudinit.util.shellify") def test_runcmd_shellify_fails(self, cls): """When shellify fails throw exception""" cls.side_effect = TypeError("patched shellify") - valid_config = {'runcmd': ['echo 42']} + valid_config = {"runcmd": ["echo 42"]} cc = get_cloud(paths=self.paths) with self.assertRaises(TypeError) as cm: - with self.allow_subp(['/bin/sh']): - handle('cc_runcmd', valid_config, cc, LOG, None) + with self.allow_subp(["/bin/sh"]): + handle("cc_runcmd", valid_config, cc, LOG, None) self.assertIn("Failed to shellify", str(cm.exception)) def test_handler_invalid_command_set(self): """Commands which can't be converted to shell will raise errors.""" - invalid_config = {'runcmd': 1} + invalid_config = {"runcmd": 1} cc = get_cloud(paths=self.paths) with self.assertRaises(TypeError) as cm: - handle('cc_runcmd', invalid_config, cc, LOG, []) + handle("cc_runcmd", invalid_config, cc, LOG, []) self.assertIn( - 'Failed to shellify 1 into file' - ' /var/lib/cloud/instances/iid-datasource-none/scripts/runcmd', - str(cm.exception)) + "Failed to shellify 1 into file" + " /var/lib/cloud/instances/iid-datasource-none/scripts/runcmd", + str(cm.exception), + ) @skipUnlessJsonSchema() def test_handler_schema_validation_warns_non_array_type(self): @@ -64,14 +68,15 @@ class TestRuncmd(FilesystemMockingTestCase): Schema validation is not strict, so runcmd attempts to shellify the invalid content. """ - invalid_config = {'runcmd': 1} + invalid_config = {"runcmd": 1} cc = get_cloud(paths=self.paths) with self.assertRaises(TypeError) as cm: - handle('cc_runcmd', invalid_config, cc, LOG, []) + handle("cc_runcmd", invalid_config, cc, LOG, []) self.assertIn( - 'Invalid config:\nruncmd: 1 is not of type \'array\'', - self.logs.getvalue()) - self.assertIn('Failed to shellify', str(cm.exception)) + "Invalid config:\nruncmd: 1 is not of type 'array'", + self.logs.getvalue(), + ) + self.assertIn("Failed to shellify", str(cm.exception)) @skipUnlessJsonSchema() def test_handler_schema_validation_warns_non_array_item_type(self): @@ -81,28 +86,29 @@ class TestRuncmd(FilesystemMockingTestCase): invalid content. """ invalid_config = { - 'runcmd': ['ls /', 20, ['wget', 'http://stuff/blah'], {'a': 'n'}]} + "runcmd": ["ls /", 20, ["wget", "http://stuff/blah"], {"a": "n"}] + } cc = get_cloud(paths=self.paths) with self.assertRaises(TypeError) as cm: - handle('cc_runcmd', invalid_config, cc, LOG, []) + handle("cc_runcmd", invalid_config, cc, LOG, []) expected_warnings = [ - 'runcmd.1: 20 is not valid under any of the given schemas', - 'runcmd.3: {\'a\': \'n\'} is not valid under any of the given' - ' schema' + "runcmd.1: 20 is not valid under any of the given schemas", + "runcmd.3: {'a': 'n'} is not valid under any of the given schema", ] logs = self.logs.getvalue() for warning in expected_warnings: self.assertIn(warning, logs) - self.assertIn('Failed to shellify', str(cm.exception)) + self.assertIn("Failed to shellify", str(cm.exception)) def test_handler_write_valid_runcmd_schema_to_file(self): """Valid runcmd schema is written to a runcmd shell script.""" - valid_config = {'runcmd': [['ls', '/']]} + valid_config = {"runcmd": [["ls", "/"]]} cc = get_cloud(paths=self.paths) - handle('cc_runcmd', valid_config, cc, LOG, []) + handle("cc_runcmd", valid_config, cc, LOG, []) runcmd_file = os.path.join( self.new_root, - 'var/lib/cloud/instances/iid-datasource-none/scripts/runcmd') + "var/lib/cloud/instances/iid-datasource-none/scripts/runcmd", + ) self.assertEqual("#!/bin/sh\n'ls' '/'\n", util.load_file(runcmd_file)) file_stat = os.stat(runcmd_file) self.assertEqual(0o700, stat.S_IMODE(file_stat.st_mode)) @@ -118,12 +124,14 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): """Duplicated commands array/array entries are allowed.""" self.assertSchemaValid( [["echo", "bye"], ["echo", "bye"]], - "command entries can be duplicate.") + "command entries can be duplicate.", + ) def test_duplicates_are_fine_array_string(self): """Duplicated commands array/string entries are allowed.""" self.assertSchemaValid( - ["echo bye", "echo bye"], - "command entries can be duplicate.") + ["echo bye", "echo bye"], "command entries can be duplicate." + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_seed_random.py b/tests/unittests/config/test_cc_seed_random.py index cfd67dce..8b2fdcdd 100644 --- a/tests/unittests/config/test_cc_seed_random.py +++ b/tests/unittests/config/test_cc_seed_random.py @@ -12,11 +12,9 @@ import logging import tempfile from io import BytesIO -from cloudinit import subp -from cloudinit import util +from cloudinit import subp, util from cloudinit.config import cc_seed_random from tests.unittests import helpers as t_help - from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -29,8 +27,8 @@ class TestRandomSeed(t_help.TestCase): self.unapply = [] # by default 'which' has nothing in its path - self.apply_patches([(subp, 'which', self._which)]) - self.apply_patches([(subp, 'subp', self._subp)]) + self.apply_patches([(subp, "which", self._which)]) + self.apply_patches([(subp, "subp", self._subp)]) self.subp_called = [] self.whichdata = {} @@ -47,149 +45,166 @@ class TestRandomSeed(t_help.TestCase): def _subp(self, *args, **kwargs): # supports subp calling with cmd as args or kwargs - if 'args' not in kwargs: - kwargs['args'] = args[0] + if "args" not in kwargs: + kwargs["args"] = args[0] self.subp_called.append(kwargs) return def _compress(self, text): contents = BytesIO() - gz_fh = gzip.GzipFile(mode='wb', fileobj=contents) + gz_fh = gzip.GzipFile(mode="wb", fileobj=contents) gz_fh.write(text) gz_fh.close() return contents.getvalue() def test_append_random(self): cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': 'tiny-tim-was-here', + "random_seed": { + "file": self._seed_file, + "data": "tiny-tim-was-here", } } - cc_seed_random.handle('test', cfg, get_cloud('ubuntu'), LOG, []) + cc_seed_random.handle("test", cfg, get_cloud("ubuntu"), LOG, []) contents = util.load_file(self._seed_file) self.assertEqual("tiny-tim-was-here", contents) def test_append_random_unknown_encoding(self): data = self._compress(b"tiny-toe") cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': data, - 'encoding': 'special_encoding', + "random_seed": { + "file": self._seed_file, + "data": data, + "encoding": "special_encoding", } } - self.assertRaises(IOError, cc_seed_random.handle, 'test', cfg, - get_cloud('ubuntu'), LOG, []) + self.assertRaises( + IOError, + cc_seed_random.handle, + "test", + cfg, + get_cloud("ubuntu"), + LOG, + [], + ) def test_append_random_gzip(self): data = self._compress(b"tiny-toe") cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': data, - 'encoding': 'gzip', + "random_seed": { + "file": self._seed_file, + "data": data, + "encoding": "gzip", } } - cc_seed_random.handle('test', cfg, get_cloud('ubuntu'), LOG, []) + cc_seed_random.handle("test", cfg, get_cloud("ubuntu"), LOG, []) contents = util.load_file(self._seed_file) self.assertEqual("tiny-toe", contents) def test_append_random_gz(self): data = self._compress(b"big-toe") cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': data, - 'encoding': 'gz', + "random_seed": { + "file": self._seed_file, + "data": data, + "encoding": "gz", } } - cc_seed_random.handle('test', cfg, get_cloud('ubuntu'), LOG, []) + cc_seed_random.handle("test", cfg, get_cloud("ubuntu"), LOG, []) contents = util.load_file(self._seed_file) self.assertEqual("big-toe", contents) def test_append_random_base64(self): - data = util.b64e('bubbles') + data = util.b64e("bubbles") cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': data, - 'encoding': 'base64', + "random_seed": { + "file": self._seed_file, + "data": data, + "encoding": "base64", } } - cc_seed_random.handle('test', cfg, get_cloud('ubuntu'), LOG, []) + cc_seed_random.handle("test", cfg, get_cloud("ubuntu"), LOG, []) contents = util.load_file(self._seed_file) self.assertEqual("bubbles", contents) def test_append_random_b64(self): - data = util.b64e('kit-kat') + data = util.b64e("kit-kat") cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': data, - 'encoding': 'b64', + "random_seed": { + "file": self._seed_file, + "data": data, + "encoding": "b64", } } - cc_seed_random.handle('test', cfg, get_cloud('ubuntu'), LOG, []) + cc_seed_random.handle("test", cfg, get_cloud("ubuntu"), LOG, []) contents = util.load_file(self._seed_file) self.assertEqual("kit-kat", contents) def test_append_random_metadata(self): cfg = { - 'random_seed': { - 'file': self._seed_file, - 'data': 'tiny-tim-was-here', + "random_seed": { + "file": self._seed_file, + "data": "tiny-tim-was-here", } } - c = get_cloud('ubuntu', metadata={'random_seed': '-so-was-josh'}) - cc_seed_random.handle('test', cfg, c, LOG, []) + c = get_cloud("ubuntu", metadata={"random_seed": "-so-was-josh"}) + cc_seed_random.handle("test", cfg, c, LOG, []) contents = util.load_file(self._seed_file) - self.assertEqual('tiny-tim-was-here-so-was-josh', contents) + self.assertEqual("tiny-tim-was-here-so-was-josh", contents) def test_seed_command_provided_and_available(self): - c = get_cloud('ubuntu') - self.whichdata = {'pollinate': '/usr/bin/pollinate'} - cfg = {'random_seed': {'command': ['pollinate', '-q']}} - cc_seed_random.handle('test', cfg, c, LOG, []) + c = get_cloud("ubuntu") + self.whichdata = {"pollinate": "/usr/bin/pollinate"} + cfg = {"random_seed": {"command": ["pollinate", "-q"]}} + cc_seed_random.handle("test", cfg, c, LOG, []) - subp_args = [f['args'] for f in self.subp_called] - self.assertIn(['pollinate', '-q'], subp_args) + subp_args = [f["args"] for f in self.subp_called] + self.assertIn(["pollinate", "-q"], subp_args) def test_seed_command_not_provided(self): - c = get_cloud('ubuntu') + c = get_cloud("ubuntu") self.whichdata = {} - cc_seed_random.handle('test', {}, c, LOG, []) + cc_seed_random.handle("test", {}, c, LOG, []) # subp should not have been called as which would say not available self.assertFalse(self.subp_called) def test_unavailable_seed_command_and_required_raises_error(self): - c = get_cloud('ubuntu') + c = get_cloud("ubuntu") self.whichdata = {} - cfg = {'random_seed': {'command': ['THIS_NO_COMMAND'], - 'command_required': True}} - self.assertRaises(ValueError, cc_seed_random.handle, - 'test', cfg, c, LOG, []) + cfg = { + "random_seed": { + "command": ["THIS_NO_COMMAND"], + "command_required": True, + } + } + self.assertRaises( + ValueError, cc_seed_random.handle, "test", cfg, c, LOG, [] + ) def test_seed_command_and_required(self): - c = get_cloud('ubuntu') - self.whichdata = {'foo': 'foo'} - cfg = {'random_seed': {'command_required': True, 'command': ['foo']}} - cc_seed_random.handle('test', cfg, c, LOG, []) + c = get_cloud("ubuntu") + self.whichdata = {"foo": "foo"} + cfg = {"random_seed": {"command_required": True, "command": ["foo"]}} + cc_seed_random.handle("test", cfg, c, LOG, []) - self.assertIn(['foo'], [f['args'] for f in self.subp_called]) + self.assertIn(["foo"], [f["args"] for f in self.subp_called]) def test_file_in_environment_for_command(self): - c = get_cloud('ubuntu') - self.whichdata = {'foo': 'foo'} - cfg = {'random_seed': {'command_required': True, 'command': ['foo'], - 'file': self._seed_file}} - cc_seed_random.handle('test', cfg, c, LOG, []) + c = get_cloud("ubuntu") + self.whichdata = {"foo": "foo"} + cfg = { + "random_seed": { + "command_required": True, + "command": ["foo"], + "file": self._seed_file, + } + } + cc_seed_random.handle("test", cfg, c, LOG, []) # this just instists that the first time subp was called, # RANDOM_SEED_FILE was in the environment set up correctly - subp_env = [f['env'] for f in self.subp_called] - self.assertEqual(subp_env[0].get('RANDOM_SEED_FILE'), self._seed_file) + subp_env = [f["env"] for f in self.subp_called] + self.assertEqual(subp_env[0].get("RANDOM_SEED_FILE"), self._seed_file) def apply_patches(patches): @@ -202,4 +217,5 @@ def apply_patches(patches): ret.append((ref, name, orig)) return ret + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_set_hostname.py b/tests/unittests/config/test_cc_set_hostname.py index b9a783a7..fd994c4e 100644 --- a/tests/unittests/config/test_cc_set_hostname.py +++ b/tests/unittests/config/test_cc_set_hostname.py @@ -1,15 +1,5 @@ # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit.config import cc_set_hostname - -from cloudinit import cloud -from cloudinit import distros -from cloudinit import helpers -from cloudinit import util - -from tests.unittests import helpers as t_help - -from configobj import ConfigObj import logging import os import shutil @@ -17,6 +7,12 @@ import tempfile from io import BytesIO from unittest import mock +from configobj import ConfigObj + +from cloudinit import cloud, distros, helpers, util +from cloudinit.config import cc_set_hostname +from tests.unittests import helpers as t_help + LOG = logging.getLogger(__name__) @@ -27,181 +23,186 @@ class TestHostname(t_help.FilesystemMockingTestCase): def setUp(self): super(TestHostname, self).setUp() self.tmp = tempfile.mkdtemp() - util.ensure_dir(os.path.join(self.tmp, 'data')) + util.ensure_dir(os.path.join(self.tmp, "data")) self.addCleanup(shutil.rmtree, self.tmp) def _fetch_distro(self, kind, conf=None): cls = distros.fetch(kind) - paths = helpers.Paths({'cloud_dir': self.tmp}) + paths = helpers.Paths({"cloud_dir": self.tmp}) conf = {} if conf is None else conf return cls(kind, conf, paths) def test_debian_write_hostname_prefer_fqdn(self): cfg = { - 'hostname': 'blah', - 'prefer_fqdn_over_hostname': True, - 'fqdn': 'blah.yahoo.com', + "hostname": "blah", + "prefer_fqdn_over_hostname": True, + "fqdn": "blah.yahoo.com", } - distro = self._fetch_distro('debian', cfg) - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("debian", cfg) + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_set_hostname.handle('cc_set_hostname', - cfg, cc, LOG, []) + cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) contents = util.load_file("/etc/hostname") - self.assertEqual('blah.yahoo.com', contents.strip()) + self.assertEqual("blah.yahoo.com", contents.strip()) - @mock.patch('cloudinit.distros.Distro.uses_systemd', return_value=False) + @mock.patch("cloudinit.distros.Distro.uses_systemd", return_value=False) def test_rhel_write_hostname_prefer_hostname(self, m_uses_systemd): cfg = { - 'hostname': 'blah', - 'prefer_fqdn_over_hostname': False, - 'fqdn': 'blah.yahoo.com', + "hostname": "blah", + "prefer_fqdn_over_hostname": False, + "fqdn": "blah.yahoo.com", } - distro = self._fetch_distro('rhel', cfg) - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("rhel", cfg) + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_set_hostname.handle('cc_set_hostname', - cfg, cc, LOG, []) + cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) contents = util.load_file("/etc/sysconfig/network", decode=False) n_cfg = ConfigObj(BytesIO(contents)) - self.assertEqual( - {'HOSTNAME': 'blah'}, - dict(n_cfg)) + self.assertEqual({"HOSTNAME": "blah"}, dict(n_cfg)) - @mock.patch('cloudinit.distros.Distro.uses_systemd', return_value=False) + @mock.patch("cloudinit.distros.Distro.uses_systemd", return_value=False) def test_write_hostname_rhel(self, m_uses_systemd): - cfg = { - 'hostname': 'blah', - 'fqdn': 'blah.blah.blah.yahoo.com' - } - distro = self._fetch_distro('rhel') - paths = helpers.Paths({'cloud_dir': self.tmp}) + cfg = {"hostname": "blah", "fqdn": "blah.blah.blah.yahoo.com"} + distro = self._fetch_distro("rhel") + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_set_hostname.handle('cc_set_hostname', - cfg, cc, LOG, []) + cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) contents = util.load_file("/etc/sysconfig/network", decode=False) n_cfg = ConfigObj(BytesIO(contents)) - self.assertEqual( - {'HOSTNAME': 'blah.blah.blah.yahoo.com'}, - dict(n_cfg)) + self.assertEqual({"HOSTNAME": "blah.blah.blah.yahoo.com"}, dict(n_cfg)) def test_write_hostname_debian(self): cfg = { - 'hostname': 'blah', - 'fqdn': 'blah.blah.blah.yahoo.com', + "hostname": "blah", + "fqdn": "blah.blah.blah.yahoo.com", } - distro = self._fetch_distro('debian') - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("debian") + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_set_hostname.handle('cc_set_hostname', - cfg, cc, LOG, []) + cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) contents = util.load_file("/etc/hostname") - self.assertEqual('blah', contents.strip()) + self.assertEqual("blah", contents.strip()) - @mock.patch('cloudinit.distros.Distro.uses_systemd', return_value=False) + @mock.patch("cloudinit.distros.Distro.uses_systemd", return_value=False) def test_write_hostname_sles(self, m_uses_systemd): cfg = { - 'hostname': 'blah.blah.blah.suse.com', + "hostname": "blah.blah.blah.suse.com", } - distro = self._fetch_distro('sles') - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("sles") + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_set_hostname.handle('cc_set_hostname', cfg, cc, LOG, []) + cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) contents = util.load_file(distro.hostname_conf_fn) - self.assertEqual('blah', contents.strip()) + self.assertEqual("blah", contents.strip()) - @mock.patch('cloudinit.distros.photon.subp.subp') + @mock.patch("cloudinit.distros.photon.subp.subp") def test_photon_hostname(self, m_subp): cfg1 = { - 'hostname': 'photon', - 'prefer_fqdn_over_hostname': True, - 'fqdn': 'test1.vmware.com', + "hostname": "photon", + "prefer_fqdn_over_hostname": True, + "fqdn": "test1.vmware.com", } cfg2 = { - 'hostname': 'photon', - 'prefer_fqdn_over_hostname': False, - 'fqdn': 'test2.vmware.com', + "hostname": "photon", + "prefer_fqdn_over_hostname": False, + "fqdn": "test2.vmware.com", } ds = None m_subp.return_value = (None, None) - distro = self._fetch_distro('photon', cfg1) - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("photon", cfg1) + paths = helpers.Paths({"cloud_dir": self.tmp}) cc = cloud.Cloud(ds, paths, {}, distro, None) for c in [cfg1, cfg2]: - cc_set_hostname.handle('cc_set_hostname', c, cc, LOG, []) + cc_set_hostname.handle("cc_set_hostname", c, cc, LOG, []) print("\n", m_subp.call_args_list) - if c['prefer_fqdn_over_hostname']: + if c["prefer_fqdn_over_hostname"]: assert [ - mock.call(['hostnamectl', 'set-hostname', c['fqdn']], - capture=True) + mock.call( + ["hostnamectl", "set-hostname", c["fqdn"]], + capture=True, + ) ] in m_subp.call_args_list assert [ - mock.call(['hostnamectl', 'set-hostname', c['hostname']], - capture=True) + mock.call( + ["hostnamectl", "set-hostname", c["hostname"]], + capture=True, + ) ] not in m_subp.call_args_list else: assert [ - mock.call(['hostnamectl', 'set-hostname', c['hostname']], - capture=True) + mock.call( + ["hostnamectl", "set-hostname", c["hostname"]], + capture=True, + ) ] in m_subp.call_args_list assert [ - mock.call(['hostnamectl', 'set-hostname', c['fqdn']], - capture=True) + mock.call( + ["hostnamectl", "set-hostname", c["fqdn"]], + capture=True, + ) ] not in m_subp.call_args_list def test_multiple_calls_skips_unchanged_hostname(self): """Only new hostname or fqdn values will generate a hostname call.""" - distro = self._fetch_distro('debian') - paths = helpers.Paths({'cloud_dir': self.tmp}) + distro = self._fetch_distro("debian") + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) cc_set_hostname.handle( - 'cc_set_hostname', {'hostname': 'hostname1.me.com'}, cc, LOG, []) + "cc_set_hostname", {"hostname": "hostname1.me.com"}, cc, LOG, [] + ) contents = util.load_file("/etc/hostname") - self.assertEqual('hostname1', contents.strip()) + self.assertEqual("hostname1", contents.strip()) cc_set_hostname.handle( - 'cc_set_hostname', {'hostname': 'hostname1.me.com'}, cc, LOG, []) + "cc_set_hostname", {"hostname": "hostname1.me.com"}, cc, LOG, [] + ) self.assertIn( - 'DEBUG: No hostname changes. Skipping set-hostname\n', - self.logs.getvalue()) + "DEBUG: No hostname changes. Skipping set-hostname\n", + self.logs.getvalue(), + ) cc_set_hostname.handle( - 'cc_set_hostname', {'hostname': 'hostname2.me.com'}, cc, LOG, []) + "cc_set_hostname", {"hostname": "hostname2.me.com"}, cc, LOG, [] + ) contents = util.load_file("/etc/hostname") - self.assertEqual('hostname2', contents.strip()) + self.assertEqual("hostname2", contents.strip()) self.assertIn( - 'Non-persistently setting the system hostname to hostname2', - self.logs.getvalue()) + "Non-persistently setting the system hostname to hostname2", + self.logs.getvalue(), + ) def test_error_on_distro_set_hostname_errors(self): """Raise SetHostnameError on exceptions from distro.set_hostname.""" - distro = self._fetch_distro('debian') + distro = self._fetch_distro("debian") def set_hostname_error(hostname, fqdn): raise Exception("OOPS on: %s" % fqdn) distro.set_hostname = set_hostname_error - paths = helpers.Paths({'cloud_dir': self.tmp}) + paths = helpers.Paths({"cloud_dir": self.tmp}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) with self.assertRaises(cc_set_hostname.SetHostnameError) as ctx_mgr: cc_set_hostname.handle( - 'somename', {'hostname': 'hostname1.me.com'}, cc, LOG, []) + "somename", {"hostname": "hostname1.me.com"}, cc, LOG, [] + ) self.assertEqual( - 'Failed to set the hostname to hostname1.me.com (hostname1):' - ' OOPS on: hostname1.me.com', - str(ctx_mgr.exception)) + "Failed to set the hostname to hostname1.me.com (hostname1):" + " OOPS on: hostname1.me.com", + str(ctx_mgr.exception), + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index 9bcd0439..bc81214b 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -2,9 +2,9 @@ from unittest import mock +from cloudinit import util from cloudinit.config import cc_set_passwords as setpass from tests.unittests.helpers import CiTestCase -from cloudinit import util MODPATH = "cloudinit.config.cc_set_passwords." @@ -16,27 +16,29 @@ class TestHandleSshPwauth(CiTestCase): @mock.patch("cloudinit.distros.subp.subp") def test_unknown_value_logs_warning(self, m_subp): - cloud = self.tmp_cloud(distro='ubuntu') + cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth("floo", cloud.distro) - self.assertIn("Unrecognized value: ssh_pwauth=floo", - self.logs.getvalue()) + self.assertIn( + "Unrecognized value: ssh_pwauth=floo", self.logs.getvalue() + ) m_subp.assert_not_called() @mock.patch(MODPATH + "update_ssh_config", return_value=True) @mock.patch("cloudinit.distros.subp.subp") def test_systemctl_as_service_cmd(self, m_subp, m_update_ssh_config): """If systemctl in service cmd: systemctl restart name.""" - cloud = self.tmp_cloud(distro='ubuntu') - cloud.distro.init_cmd = ['systemctl'] + cloud = self.tmp_cloud(distro="ubuntu") + cloud.distro.init_cmd = ["systemctl"] setpass.handle_ssh_pwauth(True, cloud.distro) m_subp.assert_called_with( - ["systemctl", "restart", "ssh"], capture=True) + ["systemctl", "restart", "ssh"], capture=True + ) @mock.patch(MODPATH + "update_ssh_config", return_value=False) @mock.patch("cloudinit.distros.subp.subp") def test_not_restarted_if_not_updated(self, m_subp, m_update_ssh_config): """If config is not updated, then no system restart should be done.""" - cloud = self.tmp_cloud(distro='ubuntu') + cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth(True, cloud.distro) m_subp.assert_not_called() self.assertIn("No need to restart SSH", self.logs.getvalue()) @@ -45,7 +47,7 @@ class TestHandleSshPwauth(CiTestCase): @mock.patch("cloudinit.distros.subp.subp") def test_unchanged_does_nothing(self, m_subp, m_update_ssh_config): """If 'unchanged', then no updates to config and no restart.""" - cloud = self.tmp_cloud(distro='ubuntu') + cloud = self.tmp_cloud(distro="ubuntu") setpass.handle_ssh_pwauth("unchanged", cloud.distro) m_update_ssh_config.assert_not_called() m_subp.assert_not_called() @@ -53,7 +55,7 @@ class TestHandleSshPwauth(CiTestCase): @mock.patch("cloudinit.distros.subp.subp") def test_valid_change_values(self, m_subp): """If value is a valid changen value, then update should be called.""" - cloud = self.tmp_cloud(distro='ubuntu') + cloud = self.tmp_cloud(distro="ubuntu") upname = MODPATH + "update_ssh_config" optname = "PasswordAuthentication" for value in util.FALSE_STRINGS + util.TRUE_STRINGS: @@ -71,52 +73,65 @@ class TestSetPasswordsHandle(CiTestCase): def test_handle_on_empty_config(self, *args): """handle logs that no password has changed when config is empty.""" - cloud = self.tmp_cloud(distro='ubuntu') + cloud = self.tmp_cloud(distro="ubuntu") setpass.handle( - 'IGNORED', cfg={}, cloud=cloud, log=self.logger, args=[]) + "IGNORED", cfg={}, cloud=cloud, log=self.logger, args=[] + ) self.assertEqual( "DEBUG: Leaving SSH config 'PasswordAuthentication' unchanged. " - 'ssh_pwauth=None\n', - self.logs.getvalue()) + "ssh_pwauth=None\n", + self.logs.getvalue(), + ) def test_handle_on_chpasswd_list_parses_common_hashes(self): """handle parses command password hashes.""" - cloud = self.tmp_cloud(distro='ubuntu') + cloud = self.tmp_cloud(distro="ubuntu") valid_hashed_pwds = [ - 'root:$2y$10$8BQjxjVByHA/Ee.O1bCXtO8S7Y5WojbXWqnqYpUW.BrPx/' - 'Dlew1Va', - 'ubuntu:$6$5hOurLPO$naywm3Ce0UlmZg9gG2Fl9acWCVEoakMMC7dR52q' - 'SDexZbrN9z8yHxhUM2b.sxpguSwOlbOQSW/HpXazGGx3oo1'] - cfg = {'chpasswd': {'list': valid_hashed_pwds}} - with mock.patch.object(setpass, 'chpasswd') as chpasswd: + "root:$2y$10$8BQjxjVByHA/Ee.O1bCXtO8S7Y5WojbXWqnqYpUW.BrPx/" + "Dlew1Va", + "ubuntu:$6$5hOurLPO$naywm3Ce0UlmZg9gG2Fl9acWCVEoakMMC7dR52q" + "SDexZbrN9z8yHxhUM2b.sxpguSwOlbOQSW/HpXazGGx3oo1", + ] + cfg = {"chpasswd": {"list": valid_hashed_pwds}} + with mock.patch.object(setpass, "chpasswd") as chpasswd: setpass.handle( - 'IGNORED', cfg=cfg, cloud=cloud, log=self.logger, args=[]) + "IGNORED", cfg=cfg, cloud=cloud, log=self.logger, args=[] + ) self.assertIn( - 'DEBUG: Handling input for chpasswd as list.', - self.logs.getvalue()) + "DEBUG: Handling input for chpasswd as list.", self.logs.getvalue() + ) self.assertIn( "DEBUG: Setting hashed password for ['root', 'ubuntu']", - self.logs.getvalue()) - valid = '\n'.join(valid_hashed_pwds) + '\n' + self.logs.getvalue(), + ) + valid = "\n".join(valid_hashed_pwds) + "\n" called = chpasswd.call_args[0][1] self.assertEqual(valid, called) @mock.patch(MODPATH + "util.is_BSD") @mock.patch(MODPATH + "subp.subp") def test_bsd_calls_custom_pw_cmds_to_set_and_expire_passwords( - self, m_subp, m_is_bsd): + self, m_subp, m_is_bsd + ): """BSD don't use chpasswd""" m_is_bsd.return_value = True - cloud = self.tmp_cloud(distro='freebsd') - valid_pwds = ['ubuntu:passw0rd'] - cfg = {'chpasswd': {'list': valid_pwds}} + cloud = self.tmp_cloud(distro="freebsd") + valid_pwds = ["ubuntu:passw0rd"] + cfg = {"chpasswd": {"list": valid_pwds}} setpass.handle( - 'IGNORED', cfg=cfg, cloud=cloud, log=self.logger, args=[]) - self.assertEqual([ - mock.call(['pw', 'usermod', 'ubuntu', '-h', '0'], data='passw0rd', - logstring="chpasswd for ubuntu"), - mock.call(['pw', 'usermod', 'ubuntu', '-p', '01-Jan-1970'])], - m_subp.call_args_list) + "IGNORED", cfg=cfg, cloud=cloud, log=self.logger, args=[] + ) + self.assertEqual( + [ + mock.call( + ["pw", "usermod", "ubuntu", "-h", "0"], + data="passw0rd", + logstring="chpasswd for ubuntu", + ), + mock.call(["pw", "usermod", "ubuntu", "-p", "01-Jan-1970"]), + ], + m_subp.call_args_list, + ) @mock.patch(MODPATH + "util.multi_log") @mock.patch(MODPATH + "subp.subp") @@ -124,29 +139,29 @@ class TestSetPasswordsHandle(CiTestCase): self, m_subp, m_multi_log ): """handle parses command set random passwords.""" - cloud = self.tmp_cloud(distro='ubuntu') - valid_random_pwds = [ - 'root:R', - 'ubuntu:RANDOM'] - cfg = {'chpasswd': {'expire': 'false', 'list': valid_random_pwds}} - with mock.patch.object(setpass, 'chpasswd') as chpasswd: + cloud = self.tmp_cloud(distro="ubuntu") + valid_random_pwds = ["root:R", "ubuntu:RANDOM"] + cfg = {"chpasswd": {"expire": "false", "list": valid_random_pwds}} + with mock.patch.object(setpass, "chpasswd") as chpasswd: setpass.handle( - 'IGNORED', cfg=cfg, cloud=cloud, log=self.logger, args=[]) + "IGNORED", cfg=cfg, cloud=cloud, log=self.logger, args=[] + ) self.assertIn( - 'DEBUG: Handling input for chpasswd as list.', - self.logs.getvalue()) + "DEBUG: Handling input for chpasswd as list.", self.logs.getvalue() + ) self.assertEqual(1, chpasswd.call_count) passwords, _ = chpasswd.call_args user_pass = { user: password - for user, password - in (line.split(":") for line in passwords[1].splitlines()) + for user, password in ( + line.split(":") for line in passwords[1].splitlines() + ) } self.assertEqual(1, m_multi_log.call_count) self.assertEqual( mock.call(mock.ANY, stderr=False, fallback_to_stdout=False), - m_multi_log.call_args + m_multi_log.call_args, ) self.assertEqual(set(["root", "ubuntu"]), set(user_pass.keys())) diff --git a/tests/unittests/config/test_cc_snap.py b/tests/unittests/config/test_cc_snap.py index e8113eca..f7e66ad2 100644 --- a/tests/unittests/config/test_cc_snap.py +++ b/tests/unittests/config/test_cc_snap.py @@ -3,14 +3,23 @@ import re from io import StringIO +from cloudinit import util from cloudinit.config.cc_snap import ( - ASSERTIONS_FILE, add_assertions, handle, maybe_install_squashfuse, - run_commands, schema) + ASSERTIONS_FILE, + add_assertions, + handle, + maybe_install_squashfuse, + run_commands, + schema, +) from cloudinit.config.schema import validate_cloudconfig_schema -from cloudinit import util from tests.unittests.helpers import ( - CiTestCase, SchemaTestCaseMixin, mock, wrap_and_call, skipUnlessJsonSchema) - + CiTestCase, + SchemaTestCaseMixin, + mock, + skipUnlessJsonSchema, + wrap_and_call, +) SYSTEM_USER_ASSERTION = """\ type: system-user @@ -92,11 +101,11 @@ class TestAddAssertions(CiTestCase): super(TestAddAssertions, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('cloudinit.config.cc_snap.subp.subp') + @mock.patch("cloudinit.config.cc_snap.subp.subp") def test_add_assertions_on_empty_list(self, m_subp): """When provided with an empty list, add_assertions does nothing.""" add_assertions([]) - self.assertEqual('', self.logs.getvalue()) + self.assertEqual("", self.logs.getvalue()) m_subp.assert_not_called() def test_add_assertions_on_non_list_or_dict(self): @@ -105,58 +114,72 @@ class TestAddAssertions(CiTestCase): add_assertions(assertions="I'm Not Valid") self.assertEqual( "assertion parameter was not a list or dict: I'm Not Valid", - str(context_manager.exception)) + str(context_manager.exception), + ) - @mock.patch('cloudinit.config.cc_snap.subp.subp') + @mock.patch("cloudinit.config.cc_snap.subp.subp") def test_add_assertions_adds_assertions_as_list(self, m_subp): """When provided with a list, add_assertions adds all assertions.""" self.assertEqual( - ASSERTIONS_FILE, '/var/lib/cloud/instance/snapd.assertions') - assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) + ASSERTIONS_FILE, "/var/lib/cloud/instance/snapd.assertions" + ) + assert_file = self.tmp_path("snapd.assertions", dir=self.tmp) assertions = [SYSTEM_USER_ASSERTION, ACCOUNT_ASSERTION] wrap_and_call( - 'cloudinit.config.cc_snap', - {'ASSERTIONS_FILE': {'new': assert_file}}, - add_assertions, assertions) + "cloudinit.config.cc_snap", + {"ASSERTIONS_FILE": {"new": assert_file}}, + add_assertions, + assertions, + ) self.assertIn( - 'Importing user-provided snap assertions', self.logs.getvalue()) - self.assertIn( - 'sertions', self.logs.getvalue()) + "Importing user-provided snap assertions", self.logs.getvalue() + ) + self.assertIn("sertions", self.logs.getvalue()) self.assertEqual( - [mock.call(['snap', 'ack', assert_file], capture=True)], - m_subp.call_args_list) - compare_file = self.tmp_path('comparison', dir=self.tmp) - util.write_file(compare_file, '\n'.join(assertions).encode('utf-8')) + [mock.call(["snap", "ack", assert_file], capture=True)], + m_subp.call_args_list, + ) + compare_file = self.tmp_path("comparison", dir=self.tmp) + util.write_file(compare_file, "\n".join(assertions).encode("utf-8")) self.assertEqual( - util.load_file(compare_file), util.load_file(assert_file)) + util.load_file(compare_file), util.load_file(assert_file) + ) - @mock.patch('cloudinit.config.cc_snap.subp.subp') + @mock.patch("cloudinit.config.cc_snap.subp.subp") def test_add_assertions_adds_assertions_as_dict(self, m_subp): """When provided with a dict, add_assertions adds all assertions.""" self.assertEqual( - ASSERTIONS_FILE, '/var/lib/cloud/instance/snapd.assertions') - assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) - assertions = {'00': SYSTEM_USER_ASSERTION, '01': ACCOUNT_ASSERTION} + ASSERTIONS_FILE, "/var/lib/cloud/instance/snapd.assertions" + ) + assert_file = self.tmp_path("snapd.assertions", dir=self.tmp) + assertions = {"00": SYSTEM_USER_ASSERTION, "01": ACCOUNT_ASSERTION} wrap_and_call( - 'cloudinit.config.cc_snap', - {'ASSERTIONS_FILE': {'new': assert_file}}, - add_assertions, assertions) + "cloudinit.config.cc_snap", + {"ASSERTIONS_FILE": {"new": assert_file}}, + add_assertions, + assertions, + ) self.assertIn( - 'Importing user-provided snap assertions', self.logs.getvalue()) + "Importing user-provided snap assertions", self.logs.getvalue() + ) self.assertIn( "DEBUG: Snap acking: ['type: system-user', 'authority-id: Lqv", - self.logs.getvalue()) + self.logs.getvalue(), + ) self.assertIn( "DEBUG: Snap acking: ['type: account-key', 'authority-id: canonic", - self.logs.getvalue()) + self.logs.getvalue(), + ) self.assertEqual( - [mock.call(['snap', 'ack', assert_file], capture=True)], - m_subp.call_args_list) - compare_file = self.tmp_path('comparison', dir=self.tmp) - combined = '\n'.join(assertions.values()) - util.write_file(compare_file, combined.encode('utf-8')) + [mock.call(["snap", "ack", assert_file], capture=True)], + m_subp.call_args_list, + ) + compare_file = self.tmp_path("comparison", dir=self.tmp) + combined = "\n".join(assertions.values()) + util.write_file(compare_file, combined.encode("utf-8")) self.assertEqual( - util.load_file(compare_file), util.load_file(assert_file)) + util.load_file(compare_file), util.load_file(assert_file) + ) class TestRunCommands(CiTestCase): @@ -168,11 +191,11 @@ class TestRunCommands(CiTestCase): super(TestRunCommands, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('cloudinit.config.cc_snap.subp.subp') + @mock.patch("cloudinit.config.cc_snap.subp.subp") def test_run_commands_on_empty_list(self, m_subp): """When provided with an empty list, run_commands does nothing.""" run_commands([]) - self.assertEqual('', self.logs.getvalue()) + self.assertEqual("", self.logs.getvalue()) m_subp.assert_not_called() def test_run_commands_on_non_list_or_dict(self): @@ -181,68 +204,74 @@ class TestRunCommands(CiTestCase): run_commands(commands="I'm Not Valid") self.assertEqual( "commands parameter was not a list or dict: I'm Not Valid", - str(context_manager.exception)) + str(context_manager.exception), + ) def test_run_command_logs_commands_and_exit_codes_to_stderr(self): """All exit codes are logged to stderr.""" - outfile = self.tmp_path('output.log', dir=self.tmp) + outfile = self.tmp_path("output.log", dir=self.tmp) cmd1 = 'echo "HI" >> %s' % outfile - cmd2 = 'bogus command' + cmd2 = "bogus command" cmd3 = 'echo "MOM" >> %s' % outfile commands = [cmd1, cmd2, cmd3] - mock_path = 'cloudinit.config.cc_snap.sys.stderr' + mock_path = "cloudinit.config.cc_snap.sys.stderr" with mock.patch(mock_path, new_callable=StringIO) as m_stderr: with self.assertRaises(RuntimeError) as context_manager: run_commands(commands=commands) self.assertIsNotNone( - re.search(r'bogus: (command )?not found', - str(context_manager.exception)), - msg='Expected bogus command not found') - expected_stderr_log = '\n'.join([ - 'Begin run command: {cmd}'.format(cmd=cmd1), - 'End run command: exit(0)', - 'Begin run command: {cmd}'.format(cmd=cmd2), - 'ERROR: End run command: exit(127)', - 'Begin run command: {cmd}'.format(cmd=cmd3), - 'End run command: exit(0)\n']) + re.search( + r"bogus: (command )?not found", str(context_manager.exception) + ), + msg="Expected bogus command not found", + ) + expected_stderr_log = "\n".join( + [ + "Begin run command: {cmd}".format(cmd=cmd1), + "End run command: exit(0)", + "Begin run command: {cmd}".format(cmd=cmd2), + "ERROR: End run command: exit(127)", + "Begin run command: {cmd}".format(cmd=cmd3), + "End run command: exit(0)\n", + ] + ) self.assertEqual(expected_stderr_log, m_stderr.getvalue()) def test_run_command_as_lists(self): """When commands are specified as a list, run them in order.""" - outfile = self.tmp_path('output.log', dir=self.tmp) + outfile = self.tmp_path("output.log", dir=self.tmp) cmd1 = 'echo "HI" >> %s' % outfile cmd2 = 'echo "MOM" >> %s' % outfile commands = [cmd1, cmd2] - mock_path = 'cloudinit.config.cc_snap.sys.stderr' + mock_path = "cloudinit.config.cc_snap.sys.stderr" with mock.patch(mock_path, new_callable=StringIO): run_commands(commands=commands) self.assertIn( - 'DEBUG: Running user-provided snap commands', - self.logs.getvalue()) - self.assertEqual('HI\nMOM\n', util.load_file(outfile)) + "DEBUG: Running user-provided snap commands", self.logs.getvalue() + ) + self.assertEqual("HI\nMOM\n", util.load_file(outfile)) self.assertIn( - 'WARNING: Non-snap commands in snap config:', self.logs.getvalue()) + "WARNING: Non-snap commands in snap config:", self.logs.getvalue() + ) def test_run_command_dict_sorted_as_command_script(self): """When commands are a dict, sort them and run.""" - outfile = self.tmp_path('output.log', dir=self.tmp) + outfile = self.tmp_path("output.log", dir=self.tmp) cmd1 = 'echo "HI" >> %s' % outfile cmd2 = 'echo "MOM" >> %s' % outfile - commands = {'02': cmd1, '01': cmd2} - mock_path = 'cloudinit.config.cc_snap.sys.stderr' + commands = {"02": cmd1, "01": cmd2} + mock_path = "cloudinit.config.cc_snap.sys.stderr" with mock.patch(mock_path, new_callable=StringIO): run_commands(commands=commands) - expected_messages = [ - 'DEBUG: Running user-provided snap commands'] + expected_messages = ["DEBUG: Running user-provided snap commands"] for message in expected_messages: self.assertIn(message, self.logs.getvalue()) - self.assertEqual('MOM\nHI\n', util.load_file(outfile)) + self.assertEqual("MOM\nHI\n", util.load_file(outfile)) @skipUnlessJsonSchema() @@ -253,70 +282,72 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): def test_schema_warns_on_snap_not_as_dict(self): """If the snap configuration is not a dict, emit a warning.""" - validate_cloudconfig_schema({'snap': 'wrong type'}, schema) + validate_cloudconfig_schema({"snap": "wrong type"}, schema) self.assertEqual( "WARNING: Invalid config:\nsnap: 'wrong type' is not of type" " 'object'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_schema_disallows_unknown_keys(self, _): """Unknown keys in the snap configuration emit warnings.""" validate_cloudconfig_schema( - {'snap': {'commands': ['ls'], 'invalid-key': ''}}, schema) + {"snap": {"commands": ["ls"], "invalid-key": ""}}, schema + ) self.assertIn( - 'WARNING: Invalid config:\nsnap: Additional properties are not' + "WARNING: Invalid config:\nsnap: Additional properties are not" " allowed ('invalid-key' was unexpected)", - self.logs.getvalue()) + self.logs.getvalue(), + ) def test_warn_schema_requires_either_commands_or_assertions(self): """Warn when snap configuration lacks both commands and assertions.""" - validate_cloudconfig_schema( - {'snap': {}}, schema) + validate_cloudconfig_schema({"snap": {}}, schema) self.assertIn( - 'WARNING: Invalid config:\nsnap: {} does not have enough' - ' properties', - self.logs.getvalue()) + "WARNING: Invalid config:\nsnap: {} does not have enough" + " properties", + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_warn_schema_commands_is_not_list_or_dict(self, _): """Warn when snap:commands config is not a list or dict.""" - validate_cloudconfig_schema( - {'snap': {'commands': 'broken'}}, schema) + validate_cloudconfig_schema({"snap": {"commands": "broken"}}, schema) self.assertEqual( "WARNING: Invalid config:\nsnap.commands: 'broken' is not of type" " 'object', 'array'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_warn_schema_when_commands_is_empty(self, _): """Emit warnings when snap:commands is an empty list or dict.""" - validate_cloudconfig_schema( - {'snap': {'commands': []}}, schema) - validate_cloudconfig_schema( - {'snap': {'commands': {}}}, schema) + validate_cloudconfig_schema({"snap": {"commands": []}}, schema) + validate_cloudconfig_schema({"snap": {"commands": {}}}, schema) self.assertEqual( "WARNING: Invalid config:\nsnap.commands: [] is too short\n" "WARNING: Invalid config:\nsnap.commands: {} does not have enough" " properties\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_schema_when_commands_are_list_or_dict(self, _): """No warnings when snap:commands are either a list or dict.""" + validate_cloudconfig_schema({"snap": {"commands": ["valid"]}}, schema) validate_cloudconfig_schema( - {'snap': {'commands': ['valid']}}, schema) - validate_cloudconfig_schema( - {'snap': {'commands': {'01': 'also valid'}}}, schema) - self.assertEqual('', self.logs.getvalue()) + {"snap": {"commands": {"01": "also valid"}}}, schema + ) + self.assertEqual("", self.logs.getvalue()) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_schema_when_commands_values_are_invalid_type(self, _): """Warnings when snap:commands values are invalid type (e.g. int)""" + validate_cloudconfig_schema({"snap": {"commands": [123]}}, schema) validate_cloudconfig_schema( - {'snap': {'commands': [123]}}, schema) - validate_cloudconfig_schema( - {'snap': {'commands': {'01': 123}}}, schema) + {"snap": {"commands": {"01": 123}}}, schema + ) self.assertEqual( "WARNING: Invalid config:\n" "snap.commands.0: 123 is not valid under any of the given" @@ -324,15 +355,18 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): "WARNING: Invalid config:\n" "snap.commands.01: 123 is not valid under any of the given" " schemas\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_schema_when_commands_list_values_are_invalid_type(self, _): """Warnings when snap:commands list values are wrong type (e.g. int)""" validate_cloudconfig_schema( - {'snap': {'commands': [["snap", "install", 123]]}}, schema) + {"snap": {"commands": [["snap", "install", 123]]}}, schema + ) validate_cloudconfig_schema( - {'snap': {'commands': {'01': ["snap", "install", 123]}}}, schema) + {"snap": {"commands": {"01": ["snap", "install", 123]}}}, schema + ) self.assertEqual( "WARNING: Invalid config:\n" "snap.commands.0: ['snap', 'install', 123] is not valid under any" @@ -340,77 +374,84 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): "WARNING: Invalid config:\n" "snap.commands.0: ['snap', 'install', 123] is not valid under any" " of the given schemas\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.run_commands') + @mock.patch("cloudinit.config.cc_snap.run_commands") def test_schema_when_assertions_values_are_invalid_type(self, _): """Warnings when snap:assertions values are invalid type (e.g. int)""" + validate_cloudconfig_schema({"snap": {"assertions": [123]}}, schema) validate_cloudconfig_schema( - {'snap': {'assertions': [123]}}, schema) - validate_cloudconfig_schema( - {'snap': {'assertions': {'01': 123}}}, schema) + {"snap": {"assertions": {"01": 123}}}, schema + ) self.assertEqual( "WARNING: Invalid config:\n" "snap.assertions.0: 123 is not of type 'string'\n" "WARNING: Invalid config:\n" "snap.assertions.01: 123 is not of type 'string'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.add_assertions') + @mock.patch("cloudinit.config.cc_snap.add_assertions") def test_warn_schema_assertions_is_not_list_or_dict(self, _): """Warn when snap:assertions config is not a list or dict.""" - validate_cloudconfig_schema( - {'snap': {'assertions': 'broken'}}, schema) + validate_cloudconfig_schema({"snap": {"assertions": "broken"}}, schema) self.assertEqual( "WARNING: Invalid config:\nsnap.assertions: 'broken' is not of" " type 'object', 'array'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.add_assertions') + @mock.patch("cloudinit.config.cc_snap.add_assertions") def test_warn_schema_when_assertions_is_empty(self, _): """Emit warnings when snap:assertions is an empty list or dict.""" - validate_cloudconfig_schema( - {'snap': {'assertions': []}}, schema) - validate_cloudconfig_schema( - {'snap': {'assertions': {}}}, schema) + validate_cloudconfig_schema({"snap": {"assertions": []}}, schema) + validate_cloudconfig_schema({"snap": {"assertions": {}}}, schema) self.assertEqual( "WARNING: Invalid config:\nsnap.assertions: [] is too short\n" "WARNING: Invalid config:\nsnap.assertions: {} does not have" " enough properties\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('cloudinit.config.cc_snap.add_assertions') + @mock.patch("cloudinit.config.cc_snap.add_assertions") def test_schema_when_assertions_are_list_or_dict(self, _): """No warnings when snap:assertions are a list or dict.""" validate_cloudconfig_schema( - {'snap': {'assertions': ['valid']}}, schema) + {"snap": {"assertions": ["valid"]}}, schema + ) validate_cloudconfig_schema( - {'snap': {'assertions': {'01': 'also valid'}}}, schema) - self.assertEqual('', self.logs.getvalue()) + {"snap": {"assertions": {"01": "also valid"}}}, schema + ) + self.assertEqual("", self.logs.getvalue()) def test_duplicates_are_fine_array_array(self): """Duplicated commands array/array entries are allowed.""" self.assertSchemaValid( - {'commands': [["echo", "bye"], ["echo", "bye"]]}, - "command entries can be duplicate.") + {"commands": [["echo", "bye"], ["echo", "bye"]]}, + "command entries can be duplicate.", + ) def test_duplicates_are_fine_array_string(self): """Duplicated commands array/string entries are allowed.""" self.assertSchemaValid( - {'commands': ["echo bye", "echo bye"]}, - "command entries can be duplicate.") + {"commands": ["echo bye", "echo bye"]}, + "command entries can be duplicate.", + ) def test_duplicates_are_fine_dict_array(self): """Duplicated commands dict/array entries are allowed.""" self.assertSchemaValid( - {'commands': {'00': ["echo", "bye"], '01': ["echo", "bye"]}}, - "command entries can be duplicate.") + {"commands": {"00": ["echo", "bye"], "01": ["echo", "bye"]}}, + "command entries can be duplicate.", + ) def test_duplicates_are_fine_dict_string(self): """Duplicated commands dict/string entries are allowed.""" self.assertSchemaValid( - {'commands': {'00': "echo bye", '01': "echo bye"}}, - "command entries can be duplicate.") + {"commands": {"00": "echo bye", "01": "echo bye"}}, + "command entries can be duplicate.", + ) class TestHandle(CiTestCase): @@ -421,92 +462,122 @@ class TestHandle(CiTestCase): super(TestHandle, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('cloudinit.config.cc_snap.run_commands') - @mock.patch('cloudinit.config.cc_snap.add_assertions') - @mock.patch('cloudinit.config.cc_snap.validate_cloudconfig_schema') + @mock.patch("cloudinit.config.cc_snap.run_commands") + @mock.patch("cloudinit.config.cc_snap.add_assertions") + @mock.patch("cloudinit.config.cc_snap.validate_cloudconfig_schema") def test_handle_no_config(self, m_schema, m_add, m_run): """When no snap-related configuration is provided, nothing happens.""" cfg = {} - handle('snap', cfg=cfg, cloud=None, log=self.logger, args=None) + handle("snap", cfg=cfg, cloud=None, log=self.logger, args=None) self.assertIn( "DEBUG: Skipping module named snap, no 'snap' key in config", - self.logs.getvalue()) + self.logs.getvalue(), + ) m_schema.assert_not_called() m_add.assert_not_called() m_run.assert_not_called() - @mock.patch('cloudinit.config.cc_snap.run_commands') - @mock.patch('cloudinit.config.cc_snap.add_assertions') - @mock.patch('cloudinit.config.cc_snap.maybe_install_squashfuse') - def test_handle_skips_squashfuse_when_unconfigured(self, m_squash, m_add, - m_run): + @mock.patch("cloudinit.config.cc_snap.run_commands") + @mock.patch("cloudinit.config.cc_snap.add_assertions") + @mock.patch("cloudinit.config.cc_snap.maybe_install_squashfuse") + def test_handle_skips_squashfuse_when_unconfigured( + self, m_squash, m_add, m_run + ): """When squashfuse_in_container is unset, don't attempt to install.""" handle( - 'snap', cfg={'snap': {}}, cloud=None, log=self.logger, args=None) + "snap", cfg={"snap": {}}, cloud=None, log=self.logger, args=None + ) handle( - 'snap', cfg={'snap': {'squashfuse_in_container': None}}, - cloud=None, log=self.logger, args=None) + "snap", + cfg={"snap": {"squashfuse_in_container": None}}, + cloud=None, + log=self.logger, + args=None, + ) handle( - 'snap', cfg={'snap': {'squashfuse_in_container': False}}, - cloud=None, log=self.logger, args=None) + "snap", + cfg={"snap": {"squashfuse_in_container": False}}, + cloud=None, + log=self.logger, + args=None, + ) self.assertEqual([], m_squash.call_args_list) # No calls # snap configuration missing assertions and commands will default to [] self.assertIn(mock.call([]), m_add.call_args_list) self.assertIn(mock.call([]), m_run.call_args_list) - @mock.patch('cloudinit.config.cc_snap.maybe_install_squashfuse') + @mock.patch("cloudinit.config.cc_snap.maybe_install_squashfuse") def test_handle_tries_to_install_squashfuse(self, m_squash): """If squashfuse_in_container is True, try installing squashfuse.""" - cfg = {'snap': {'squashfuse_in_container': True}} + cfg = {"snap": {"squashfuse_in_container": True}} mycloud = FakeCloud(None) - handle('snap', cfg=cfg, cloud=mycloud, log=self.logger, args=None) - self.assertEqual( - [mock.call(mycloud)], m_squash.call_args_list) + handle("snap", cfg=cfg, cloud=mycloud, log=self.logger, args=None) + self.assertEqual([mock.call(mycloud)], m_squash.call_args_list) def test_handle_runs_commands_provided(self): """If commands are specified as a list, run them.""" - outfile = self.tmp_path('output.log', dir=self.tmp) + outfile = self.tmp_path("output.log", dir=self.tmp) cfg = { - 'snap': {'commands': ['echo "HI" >> %s' % outfile, - 'echo "MOM" >> %s' % outfile]}} - mock_path = 'cloudinit.config.cc_snap.sys.stderr' + "snap": { + "commands": [ + 'echo "HI" >> %s' % outfile, + 'echo "MOM" >> %s' % outfile, + ] + } + } + mock_path = "cloudinit.config.cc_snap.sys.stderr" with self.allow_subp([CiTestCase.SUBP_SHELL_TRUE]): with mock.patch(mock_path, new_callable=StringIO): - handle('snap', cfg=cfg, cloud=None, log=self.logger, args=None) + handle("snap", cfg=cfg, cloud=None, log=self.logger, args=None) - self.assertEqual('HI\nMOM\n', util.load_file(outfile)) + self.assertEqual("HI\nMOM\n", util.load_file(outfile)) - @mock.patch('cloudinit.config.cc_snap.subp.subp') + @mock.patch("cloudinit.config.cc_snap.subp.subp") def test_handle_adds_assertions(self, m_subp): """Any configured snap assertions are provided to add_assertions.""" - assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) - compare_file = self.tmp_path('comparison', dir=self.tmp) + assert_file = self.tmp_path("snapd.assertions", dir=self.tmp) + compare_file = self.tmp_path("comparison", dir=self.tmp) cfg = { - 'snap': {'assertions': [SYSTEM_USER_ASSERTION, ACCOUNT_ASSERTION]}} + "snap": {"assertions": [SYSTEM_USER_ASSERTION, ACCOUNT_ASSERTION]} + } wrap_and_call( - 'cloudinit.config.cc_snap', - {'ASSERTIONS_FILE': {'new': assert_file}}, - handle, 'snap', cfg=cfg, cloud=None, log=self.logger, args=None) - content = '\n'.join(cfg['snap']['assertions']) - util.write_file(compare_file, content.encode('utf-8')) + "cloudinit.config.cc_snap", + {"ASSERTIONS_FILE": {"new": assert_file}}, + handle, + "snap", + cfg=cfg, + cloud=None, + log=self.logger, + args=None, + ) + content = "\n".join(cfg["snap"]["assertions"]) + util.write_file(compare_file, content.encode("utf-8")) self.assertEqual( - util.load_file(compare_file), util.load_file(assert_file)) + util.load_file(compare_file), util.load_file(assert_file) + ) - @mock.patch('cloudinit.config.cc_snap.subp.subp') + @mock.patch("cloudinit.config.cc_snap.subp.subp") @skipUnlessJsonSchema() def test_handle_validates_schema(self, m_subp): """Any provided configuration is runs validate_cloudconfig_schema.""" - assert_file = self.tmp_path('snapd.assertions', dir=self.tmp) - cfg = {'snap': {'invalid': ''}} # Generates schema warning + assert_file = self.tmp_path("snapd.assertions", dir=self.tmp) + cfg = {"snap": {"invalid": ""}} # Generates schema warning wrap_and_call( - 'cloudinit.config.cc_snap', - {'ASSERTIONS_FILE': {'new': assert_file}}, - handle, 'snap', cfg=cfg, cloud=None, log=self.logger, args=None) + "cloudinit.config.cc_snap", + {"ASSERTIONS_FILE": {"new": assert_file}}, + handle, + "snap", + cfg=cfg, + cloud=None, + log=self.logger, + args=None, + ) self.assertEqual( "WARNING: Invalid config:\nsnap: Additional properties are not" " allowed ('invalid' was unexpected)\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) class TestMaybeInstallSquashFuse(CiTestCase): @@ -517,48 +588,52 @@ class TestMaybeInstallSquashFuse(CiTestCase): super(TestMaybeInstallSquashFuse, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('cloudinit.config.cc_snap.util.is_container') + @mock.patch("cloudinit.config.cc_snap.util.is_container") def test_maybe_install_squashfuse_skips_non_containers(self, m_container): """maybe_install_squashfuse does nothing when not on a container.""" m_container.return_value = False maybe_install_squashfuse(cloud=FakeCloud(None)) self.assertEqual([mock.call()], m_container.call_args_list) - self.assertEqual('', self.logs.getvalue()) + self.assertEqual("", self.logs.getvalue()) - @mock.patch('cloudinit.config.cc_snap.util.is_container') + @mock.patch("cloudinit.config.cc_snap.util.is_container") def test_maybe_install_squashfuse_raises_install_errors(self, m_container): """maybe_install_squashfuse logs and raises package install errors.""" m_container.return_value = True distro = mock.MagicMock() distro.update_package_sources.side_effect = RuntimeError( - 'Some apt error') + "Some apt error" + ) with self.assertRaises(RuntimeError) as context_manager: maybe_install_squashfuse(cloud=FakeCloud(distro)) - self.assertEqual('Some apt error', str(context_manager.exception)) - self.assertIn('Package update failed\nTraceback', self.logs.getvalue()) + self.assertEqual("Some apt error", str(context_manager.exception)) + self.assertIn("Package update failed\nTraceback", self.logs.getvalue()) - @mock.patch('cloudinit.config.cc_snap.util.is_container') + @mock.patch("cloudinit.config.cc_snap.util.is_container") def test_maybe_install_squashfuse_raises_update_errors(self, m_container): """maybe_install_squashfuse logs and raises package update errors.""" m_container.return_value = True distro = mock.MagicMock() distro.update_package_sources.side_effect = RuntimeError( - 'Some apt error') + "Some apt error" + ) with self.assertRaises(RuntimeError) as context_manager: maybe_install_squashfuse(cloud=FakeCloud(distro)) - self.assertEqual('Some apt error', str(context_manager.exception)) - self.assertIn('Package update failed\nTraceback', self.logs.getvalue()) + self.assertEqual("Some apt error", str(context_manager.exception)) + self.assertIn("Package update failed\nTraceback", self.logs.getvalue()) - @mock.patch('cloudinit.config.cc_snap.util.is_container') + @mock.patch("cloudinit.config.cc_snap.util.is_container") def test_maybe_install_squashfuse_happy_path(self, m_container): """maybe_install_squashfuse logs and raises package install errors.""" m_container.return_value = True distro = mock.MagicMock() # No errors raised maybe_install_squashfuse(cloud=FakeCloud(distro)) self.assertEqual( - [mock.call()], distro.update_package_sources.call_args_list) + [mock.call()], distro.update_package_sources.call_args_list + ) self.assertEqual( - [mock.call(['squashfuse'])], - distro.install_packages.call_args_list) + [mock.call(["squashfuse"])], distro.install_packages.call_args_list + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_spacewalk.py b/tests/unittests/config/test_cc_spacewalk.py index 96efccf0..e1f42968 100644 --- a/tests/unittests/config/test_cc_spacewalk.py +++ b/tests/unittests/config/test_cc_spacewalk.py @@ -1,21 +1,20 @@ # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit.config import cc_spacewalk -from cloudinit import subp - -from tests.unittests import helpers - import logging from unittest import mock +from cloudinit import subp +from cloudinit.config import cc_spacewalk +from tests.unittests import helpers + LOG = logging.getLogger(__name__) class TestSpacewalk(helpers.TestCase): space_cfg = { - 'spacewalk': { - 'server': 'localhost', - 'profile_name': 'test', + "spacewalk": { + "server": "localhost", + "profile_name": "test", } } @@ -31,12 +30,19 @@ class TestSpacewalk(helpers.TestCase): @mock.patch("cloudinit.config.cc_spacewalk.subp.subp") def test_do_register(self, mock_subp): - cc_spacewalk.do_register(**self.space_cfg['spacewalk']) - mock_subp.assert_called_with([ - 'rhnreg_ks', - '--serverUrl', 'https://localhost/XMLRPC', - '--profilename', 'test', - '--sslCACert', cc_spacewalk.def_ca_cert_path, - ], capture=False) + cc_spacewalk.do_register(**self.space_cfg["spacewalk"]) + mock_subp.assert_called_with( + [ + "rhnreg_ks", + "--serverUrl", + "https://localhost/XMLRPC", + "--profilename", + "test", + "--sslCACert", + cc_spacewalk.def_ca_cert_path, + ], + capture=False, + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_ssh.py b/tests/unittests/config/test_cc_ssh.py index ba179bbf..d66cc4cb 100644 --- a/tests/unittests/config/test_cc_ssh.py +++ b/tests/unittests/config/test_cc_ssh.py @@ -1,17 +1,18 @@ # This file is part of cloud-init. See LICENSE file for license information. +import logging import os.path -from cloudinit.config import cc_ssh from cloudinit import ssh_util +from cloudinit.config import cc_ssh from tests.unittests.helpers import CiTestCase, mock -import logging LOG = logging.getLogger(__name__) MODPATH = "cloudinit.config.cc_ssh." -KEY_NAMES_NO_DSA = [name for name in cc_ssh.GENERATE_KEY_NAMES - if name not in 'dsa'] +KEY_NAMES_NO_DSA = [ + name for name in cc_ssh.GENERATE_KEY_NAMES if name not in "dsa" +] @mock.patch(MODPATH + "ssh_util.setup_user_keys") @@ -20,39 +21,45 @@ class TestHandleSsh(CiTestCase): def _publish_hostkey_test_setup(self): self.test_hostkeys = { - 'dsa': ('ssh-dss', 'AAAAB3NzaC1kc3MAAACB'), - 'ecdsa': ('ecdsa-sha2-nistp256', 'AAAAE2VjZ'), - 'ed25519': ('ssh-ed25519', 'AAAAC3NzaC1lZDI'), - 'rsa': ('ssh-rsa', 'AAAAB3NzaC1yc2EAAA'), + "dsa": ("ssh-dss", "AAAAB3NzaC1kc3MAAACB"), + "ecdsa": ("ecdsa-sha2-nistp256", "AAAAE2VjZ"), + "ed25519": ("ssh-ed25519", "AAAAC3NzaC1lZDI"), + "rsa": ("ssh-rsa", "AAAAB3NzaC1yc2EAAA"), } self.test_hostkey_files = [] hostkey_tmpdir = self.tmp_dir() for key_type in cc_ssh.GENERATE_KEY_NAMES: key_data = self.test_hostkeys[key_type] - filename = 'ssh_host_%s_key.pub' % key_type + filename = "ssh_host_%s_key.pub" % key_type filepath = os.path.join(hostkey_tmpdir, filename) self.test_hostkey_files.append(filepath) - with open(filepath, 'w') as f: - f.write(' '.join(key_data)) + with open(filepath, "w") as f: + f.write(" ".join(key_data)) - cc_ssh.KEY_FILE_TPL = os.path.join(hostkey_tmpdir, 'ssh_host_%s_key') + cc_ssh.KEY_FILE_TPL = os.path.join(hostkey_tmpdir, "ssh_host_%s_key") def test_apply_credentials_with_user(self, m_setup_keys): """Apply keys for the given user and root.""" keys = ["key1"] user = "clouduser" cc_ssh.apply_credentials(keys, user, False, ssh_util.DISABLE_USER_OPTS) - self.assertEqual([mock.call(set(keys), user), - mock.call(set(keys), "root", options="")], - m_setup_keys.call_args_list) + self.assertEqual( + [ + mock.call(set(keys), user), + mock.call(set(keys), "root", options=""), + ], + m_setup_keys.call_args_list, + ) def test_apply_credentials_with_no_user(self, m_setup_keys): """Apply keys for root only.""" keys = ["key1"] user = None cc_ssh.apply_credentials(keys, user, False, ssh_util.DISABLE_USER_OPTS) - self.assertEqual([mock.call(set(keys), "root", options="")], - m_setup_keys.call_args_list) + self.assertEqual( + [mock.call(set(keys), "root", options="")], + m_setup_keys.call_args_list, + ) def test_apply_credentials_with_user_disable_root(self, m_setup_keys): """Apply keys for the given user and disable root ssh.""" @@ -62,9 +69,13 @@ class TestHandleSsh(CiTestCase): cc_ssh.apply_credentials(keys, user, True, options) options = options.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") - self.assertEqual([mock.call(set(keys), user), - mock.call(set(keys), "root", options=options)], - m_setup_keys.call_args_list) + self.assertEqual( + [ + mock.call(set(keys), user), + mock.call(set(keys), "root", options=options), + ], + m_setup_keys.call_args_list, + ) def test_apply_credentials_with_no_user_disable_root(self, m_setup_keys): """Apply keys no user and disable root ssh.""" @@ -74,14 +85,15 @@ class TestHandleSsh(CiTestCase): cc_ssh.apply_credentials(keys, user, True, options) options = options.replace("$USER", "NONE") options = options.replace("$DISABLE_USER", "root") - self.assertEqual([mock.call(set(keys), "root", options=options)], - m_setup_keys.call_args_list) + self.assertEqual( + [mock.call(set(keys), "root", options=options)], + m_setup_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") - def test_handle_no_cfg(self, m_path_exists, m_nug, - m_glob, m_setup_keys): + def test_handle_no_cfg(self, m_path_exists, m_nug, m_glob, m_setup_keys): """Test handle with no config ignores generating existing keyfiles.""" cfg = {} keys = ["key1"] @@ -90,28 +102,33 @@ class TestHandleSsh(CiTestCase): m_path_exists.return_value = True m_nug.return_value = ([], {}) cc_ssh.PUBLISH_HOST_KEYS = False - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", "NONE") options = options.replace("$DISABLE_USER", "root") - m_glob.assert_called_once_with('/etc/ssh/ssh_host_*key*') + m_glob.assert_called_once_with("/etc/ssh/ssh_host_*key*") self.assertIn( - [mock.call('/etc/ssh/ssh_host_rsa_key'), - mock.call('/etc/ssh/ssh_host_dsa_key'), - mock.call('/etc/ssh/ssh_host_ecdsa_key'), - mock.call('/etc/ssh/ssh_host_ed25519_key')], - m_path_exists.call_args_list) - self.assertEqual([mock.call(set(keys), "root", options=options)], - m_setup_keys.call_args_list) + [ + mock.call("/etc/ssh/ssh_host_rsa_key"), + mock.call("/etc/ssh/ssh_host_dsa_key"), + mock.call("/etc/ssh/ssh_host_ecdsa_key"), + mock.call("/etc/ssh/ssh_host_ed25519_key"), + ], + m_path_exists.call_args_list, + ) + self.assertEqual( + [mock.call(set(keys), "root", options=options)], + m_setup_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") - def test_dont_allow_public_ssh_keys(self, m_path_exists, m_nug, - m_glob, m_setup_keys): + def test_dont_allow_public_ssh_keys( + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test allow_public_ssh_keys=False ignores ssh public keys from - platform. + platform. """ cfg = {"allow_public_ssh_keys": False} keys = ["key1"] @@ -120,21 +137,25 @@ class TestHandleSsh(CiTestCase): # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") - self.assertEqual([mock.call(set(), user), - mock.call(set(), "root", options=options)], - m_setup_keys.call_args_list) + self.assertEqual( + [ + mock.call(set(), user), + mock.call(set(), "root", options=options), + ], + m_setup_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") - def test_handle_no_cfg_and_default_root(self, m_path_exists, m_nug, - m_glob, m_setup_keys): + def test_handle_no_cfg_and_default_root( + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with no config and a default distro user.""" cfg = {} keys = ["key1"] @@ -143,21 +164,25 @@ class TestHandleSsh(CiTestCase): # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") - self.assertEqual([mock.call(set(keys), user), - mock.call(set(keys), "root", options=options)], - m_setup_keys.call_args_list) + self.assertEqual( + [ + mock.call(set(keys), user), + mock.call(set(keys), "root", options=options), + ], + m_setup_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") - def test_handle_cfg_with_explicit_disable_root(self, m_path_exists, m_nug, - m_glob, m_setup_keys): + def test_handle_cfg_with_explicit_disable_root( + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with explicit disable_root and a default distro user.""" # This test is identical to test_handle_no_cfg_and_default_root, # except this uses an explicit cfg value @@ -168,21 +193,25 @@ class TestHandleSsh(CiTestCase): # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cc_ssh.handle("name", cfg, cloud, LOG, None) options = ssh_util.DISABLE_USER_OPTS.replace("$USER", user) options = options.replace("$DISABLE_USER", "root") - self.assertEqual([mock.call(set(keys), user), - mock.call(set(keys), "root", options=options)], - m_setup_keys.call_args_list) + self.assertEqual( + [ + mock.call(set(keys), user), + mock.call(set(keys), "root", options=options), + ], + m_setup_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") - def test_handle_cfg_without_disable_root(self, m_path_exists, m_nug, - m_glob, m_setup_keys): + def test_handle_cfg_without_disable_root( + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with disable_root == False.""" # When disable_root == False, the ssh redirect for root is skipped cfg = {"disable_root": False} @@ -192,96 +221,111 @@ class TestHandleSsh(CiTestCase): # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.get_public_ssh_keys = mock.Mock(return_value=keys) cc_ssh.handle("name", cfg, cloud, LOG, None) - self.assertEqual([mock.call(set(keys), user), - mock.call(set(keys), "root", options="")], - m_setup_keys.call_args_list) + self.assertEqual( + [ + mock.call(set(keys), user), + mock.call(set(keys), "root", options=""), + ], + m_setup_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") def test_handle_publish_hostkeys_default( - self, m_path_exists, m_nug, m_glob, m_setup_keys): + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "clouduser" # Return no matching keys for first glob, test keys for second. - m_glob.side_effect = iter([ - [], - self.test_hostkey_files, - ]) + m_glob.side_effect = iter( + [ + [], + self.test_hostkey_files, + ] + ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() cfg = {} - expected_call = [self.test_hostkeys[key_type] for key_type - in KEY_NAMES_NO_DSA] + expected_call = [ + self.test_hostkeys[key_type] for key_type in KEY_NAMES_NO_DSA + ] cc_ssh.handle("name", cfg, cloud, LOG, None) - self.assertEqual([mock.call(expected_call)], - cloud.datasource.publish_host_keys.call_args_list) + self.assertEqual( + [mock.call(expected_call)], + cloud.datasource.publish_host_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") def test_handle_publish_hostkeys_config_enable( - self, m_path_exists, m_nug, m_glob, m_setup_keys): + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = False keys = ["key1"] user = "clouduser" # Return no matching keys for first glob, test keys for second. - m_glob.side_effect = iter([ - [], - self.test_hostkey_files, - ]) + m_glob.side_effect = iter( + [ + [], + self.test_hostkey_files, + ] + ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() - cfg = {'ssh_publish_hostkeys': {'enabled': True}} - expected_call = [self.test_hostkeys[key_type] for key_type - in KEY_NAMES_NO_DSA] + cfg = {"ssh_publish_hostkeys": {"enabled": True}} + expected_call = [ + self.test_hostkeys[key_type] for key_type in KEY_NAMES_NO_DSA + ] cc_ssh.handle("name", cfg, cloud, LOG, None) - self.assertEqual([mock.call(expected_call)], - cloud.datasource.publish_host_keys.call_args_list) + self.assertEqual( + [mock.call(expected_call)], + cloud.datasource.publish_host_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") def test_handle_publish_hostkeys_config_disable( - self, m_path_exists, m_nug, m_glob, m_setup_keys): + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "clouduser" # Return no matching keys for first glob, test keys for second. - m_glob.side_effect = iter([ - [], - self.test_hostkey_files, - ]) + m_glob.side_effect = iter( + [ + [], + self.test_hostkey_files, + ] + ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() - cfg = {'ssh_publish_hostkeys': {'enabled': False}} + cfg = {"ssh_publish_hostkeys": {"enabled": False}} cc_ssh.handle("name", cfg, cloud, LOG, None) self.assertFalse(cloud.datasource.publish_host_keys.call_args_list) cloud.datasource.publish_host_keys.assert_not_called() @@ -290,61 +334,75 @@ class TestHandleSsh(CiTestCase): @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") def test_handle_publish_hostkeys_config_blacklist( - self, m_path_exists, m_nug, m_glob, m_setup_keys): + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "clouduser" # Return no matching keys for first glob, test keys for second. - m_glob.side_effect = iter([ - [], - self.test_hostkey_files, - ]) + m_glob.side_effect = iter( + [ + [], + self.test_hostkey_files, + ] + ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() - cfg = {'ssh_publish_hostkeys': {'enabled': True, - 'blacklist': ['dsa', 'rsa']}} - expected_call = [self.test_hostkeys[key_type] for key_type - in ['ecdsa', 'ed25519']] + cfg = { + "ssh_publish_hostkeys": { + "enabled": True, + "blacklist": ["dsa", "rsa"], + } + } + expected_call = [ + self.test_hostkeys[key_type] for key_type in ["ecdsa", "ed25519"] + ] cc_ssh.handle("name", cfg, cloud, LOG, None) - self.assertEqual([mock.call(expected_call)], - cloud.datasource.publish_host_keys.call_args_list) + self.assertEqual( + [mock.call(expected_call)], + cloud.datasource.publish_host_keys.call_args_list, + ) @mock.patch(MODPATH + "glob.glob") @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "os.path.exists") def test_handle_publish_hostkeys_empty_blacklist( - self, m_path_exists, m_nug, m_glob, m_setup_keys): + self, m_path_exists, m_nug, m_glob, m_setup_keys + ): """Test handle with various configs for ssh_publish_hostkeys.""" self._publish_hostkey_test_setup() cc_ssh.PUBLISH_HOST_KEYS = True keys = ["key1"] user = "clouduser" # Return no matching keys for first glob, test keys for second. - m_glob.side_effect = iter([ - [], - self.test_hostkey_files, - ]) + m_glob.side_effect = iter( + [ + [], + self.test_hostkey_files, + ] + ) # Mock os.path.exits to True to short-circuit the key writing logic m_path_exists.return_value = True m_nug.return_value = ({user: {"default": user}}, {}) - cloud = self.tmp_cloud( - distro='ubuntu', metadata={'public-keys': keys}) + cloud = self.tmp_cloud(distro="ubuntu", metadata={"public-keys": keys}) cloud.datasource.publish_host_keys = mock.Mock() - cfg = {'ssh_publish_hostkeys': {'enabled': True, - 'blacklist': []}} - expected_call = [self.test_hostkeys[key_type] for key_type - in cc_ssh.GENERATE_KEY_NAMES] + cfg = {"ssh_publish_hostkeys": {"enabled": True, "blacklist": []}} + expected_call = [ + self.test_hostkeys[key_type] + for key_type in cc_ssh.GENERATE_KEY_NAMES + ] cc_ssh.handle("name", cfg, cloud, LOG, None) - self.assertEqual([mock.call(expected_call)], - cloud.datasource.publish_host_keys.call_args_list) + self.assertEqual( + [mock.call(expected_call)], + cloud.datasource.publish_host_keys.call_args_list, + ) @mock.patch(MODPATH + "ug_util.normalize_users_groups") @mock.patch(MODPATH + "util.write_file") @@ -369,36 +427,40 @@ class TestHandleSsh(CiTestCase): cfg["ssh_keys"][public_name] = public_value cfg["ssh_keys"][cert_name] = cert_value - expected_calls.extend([ - mock.call( - '/etc/ssh/ssh_host_{}_key'.format(key_type), - private_value, - 384 - ), - mock.call( - '/etc/ssh/ssh_host_{}_key.pub'.format(key_type), - public_value, - 384 - ), - mock.call( - '/etc/ssh/ssh_host_{}_key-cert.pub'.format(key_type), - cert_value, - 384 - ), - mock.call( - '/etc/ssh/sshd_config', - ('HostCertificate /etc/ssh/ssh_host_{}_key-cert.pub' - '\n'.format(key_type)), - preserve_mode=True - ) - ]) + expected_calls.extend( + [ + mock.call( + "/etc/ssh/ssh_host_{}_key".format(key_type), + private_value, + 384, + ), + mock.call( + "/etc/ssh/ssh_host_{}_key.pub".format(key_type), + public_value, + 384, + ), + mock.call( + "/etc/ssh/ssh_host_{}_key-cert.pub".format(key_type), + cert_value, + 384, + ), + mock.call( + "/etc/ssh/sshd_config", + "HostCertificate /etc/ssh/ssh_host_{}_key-cert.pub" + "\n".format(key_type), + preserve_mode=True, + ), + ] + ) # Run the handler. m_nug.return_value = ([], {}) - with mock.patch(MODPATH + 'ssh_util.parse_ssh_config', - return_value=[]): - cc_ssh.handle("name", cfg, self.tmp_cloud(distro='ubuntu'), - LOG, None) + with mock.patch( + MODPATH + "ssh_util.parse_ssh_config", return_value=[] + ): + cc_ssh.handle( + "name", cfg, self.tmp_cloud(distro="ubuntu"), LOG, None + ) # Check that all expected output has been done. for call_ in expected_calls: diff --git a/tests/unittests/config/test_cc_timezone.py b/tests/unittests/config/test_cc_timezone.py index fb6aab5f..f76397b7 100644 --- a/tests/unittests/config/test_cc_timezone.py +++ b/tests/unittests/config/test_cc_timezone.py @@ -4,19 +4,16 @@ # # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit.config import cc_timezone - -from cloudinit import util - - import logging import shutil import tempfile -from configobj import ConfigObj from io import BytesIO -from tests.unittests import helpers as t_help +from configobj import ConfigObj +from cloudinit import util +from cloudinit.config import cc_timezone +from tests.unittests import helpers as t_help from tests.unittests.util import get_cloud LOG = logging.getLogger(__name__) @@ -33,22 +30,24 @@ class TestTimezone(t_help.FilesystemMockingTestCase): def test_set_timezone_sles(self): cfg = { - 'timezone': 'Tatooine/Bestine', + "timezone": "Tatooine/Bestine", } - cc = get_cloud('sles') + cc = get_cloud("sles") # Create a dummy timezone file - dummy_contents = '0123456789abcdefgh' - util.write_file('/usr/share/zoneinfo/%s' % cfg['timezone'], - dummy_contents) + dummy_contents = "0123456789abcdefgh" + util.write_file( + "/usr/share/zoneinfo/%s" % cfg["timezone"], dummy_contents + ) - cc_timezone.handle('cc_timezone', cfg, cc, LOG, []) + cc_timezone.handle("cc_timezone", cfg, cc, LOG, []) - contents = util.load_file('/etc/sysconfig/clock', decode=False) + contents = util.load_file("/etc/sysconfig/clock", decode=False) n_cfg = ConfigObj(BytesIO(contents)) - self.assertEqual({'TIMEZONE': cfg['timezone']}, dict(n_cfg)) + self.assertEqual({"TIMEZONE": cfg["timezone"]}, dict(n_cfg)) - contents = util.load_file('/etc/localtime') + contents = util.load_file("/etc/localtime") self.assertEqual(dummy_contents, contents.strip()) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_ubuntu_advantage.py b/tests/unittests/config/test_cc_ubuntu_advantage.py index 8d0c9665..d7519a1b 100644 --- a/tests/unittests/config/test_cc_ubuntu_advantage.py +++ b/tests/unittests/config/test_cc_ubuntu_advantage.py @@ -1,15 +1,22 @@ # This file is part of cloud-init. See LICENSE file for license information. +from cloudinit import subp from cloudinit.config.cc_ubuntu_advantage import ( - configure_ua, handle, maybe_install_ua_tools, schema) + configure_ua, + handle, + maybe_install_ua_tools, + schema, +) from cloudinit.config.schema import validate_cloudconfig_schema -from cloudinit import subp from tests.unittests.helpers import ( - CiTestCase, mock, SchemaTestCaseMixin, skipUnlessJsonSchema) - + CiTestCase, + SchemaTestCaseMixin, + mock, + skipUnlessJsonSchema, +) # Module path used in mocks -MPATH = 'cloudinit.config.cc_ubuntu_advantage' +MPATH = "cloudinit.config.cc_ubuntu_advantage" class FakeCloud(object): @@ -26,111 +33,131 @@ class TestConfigureUA(CiTestCase): super(TestConfigureUA, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_error(self, m_subp): """Errors from ua attach command are raised.""" m_subp.side_effect = subp.ProcessExecutionError( - 'Invalid token SomeToken') + "Invalid token SomeToken" + ) with self.assertRaises(RuntimeError) as context_manager: - configure_ua(token='SomeToken') + configure_ua(token="SomeToken") self.assertEqual( - 'Failure attaching Ubuntu Advantage:\nUnexpected error while' - ' running command.\nCommand: -\nExit code: -\nReason: -\n' - 'Stdout: Invalid token SomeToken\nStderr: -', - str(context_manager.exception)) + "Failure attaching Ubuntu Advantage:\nUnexpected error while" + " running command.\nCommand: -\nExit code: -\nReason: -\n" + "Stdout: Invalid token SomeToken\nStderr: -", + str(context_manager.exception), + ) - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_with_token(self, m_subp): """When token is provided, attach the machine to ua using the token.""" - configure_ua(token='SomeToken') - m_subp.assert_called_once_with(['ua', 'attach', 'SomeToken']) + configure_ua(token="SomeToken") + m_subp.assert_called_once_with(["ua", "attach", "SomeToken"]) self.assertEqual( - 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', - self.logs.getvalue()) + "DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n", + self.logs.getvalue(), + ) - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_on_service_error(self, m_subp): """all services should be enabled and then any failures raised""" def fake_subp(cmd, capture=None): - fail_cmds = [['ua', 'enable', svc] for svc in ['esm', 'cc']] + fail_cmds = [["ua", "enable", svc] for svc in ["esm", "cc"]] if cmd in fail_cmds and capture: svc = cmd[-1] raise subp.ProcessExecutionError( - 'Invalid {} credentials'.format(svc.upper())) + "Invalid {} credentials".format(svc.upper()) + ) m_subp.side_effect = fake_subp with self.assertRaises(RuntimeError) as context_manager: - configure_ua(token='SomeToken', enable=['esm', 'cc', 'fips']) + configure_ua(token="SomeToken", enable=["esm", "cc", "fips"]) self.assertEqual( m_subp.call_args_list, - [mock.call(['ua', 'attach', 'SomeToken']), - mock.call(['ua', 'enable', 'esm'], capture=True), - mock.call(['ua', 'enable', 'cc'], capture=True), - mock.call(['ua', 'enable', 'fips'], capture=True)]) + [ + mock.call(["ua", "attach", "SomeToken"]), + mock.call(["ua", "enable", "esm"], capture=True), + mock.call(["ua", "enable", "cc"], capture=True), + mock.call(["ua", "enable", "fips"], capture=True), + ], + ) self.assertIn( 'WARNING: Failure enabling "esm":\nUnexpected error' - ' while running command.\nCommand: -\nExit code: -\nReason: -\n' - 'Stdout: Invalid ESM credentials\nStderr: -\n', - self.logs.getvalue()) + " while running command.\nCommand: -\nExit code: -\nReason: -\n" + "Stdout: Invalid ESM credentials\nStderr: -\n", + self.logs.getvalue(), + ) self.assertIn( 'WARNING: Failure enabling "cc":\nUnexpected error' - ' while running command.\nCommand: -\nExit code: -\nReason: -\n' - 'Stdout: Invalid CC credentials\nStderr: -\n', - self.logs.getvalue()) + " while running command.\nCommand: -\nExit code: -\nReason: -\n" + "Stdout: Invalid CC credentials\nStderr: -\n", + self.logs.getvalue(), + ) self.assertEqual( 'Failure enabling Ubuntu Advantage service(s): "esm", "cc"', - str(context_manager.exception)) + str(context_manager.exception), + ) - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_with_empty_services(self, m_subp): """When services is an empty list, do not auto-enable attach.""" - configure_ua(token='SomeToken', enable=[]) - m_subp.assert_called_once_with(['ua', 'attach', 'SomeToken']) + configure_ua(token="SomeToken", enable=[]) + m_subp.assert_called_once_with(["ua", "attach", "SomeToken"]) self.assertEqual( - 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', - self.logs.getvalue()) + "DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n", + self.logs.getvalue(), + ) - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_with_specific_services(self, m_subp): """When services a list, only enable specific services.""" - configure_ua(token='SomeToken', enable=['fips']) + configure_ua(token="SomeToken", enable=["fips"]) self.assertEqual( m_subp.call_args_list, - [mock.call(['ua', 'attach', 'SomeToken']), - mock.call(['ua', 'enable', 'fips'], capture=True)]) + [ + mock.call(["ua", "attach", "SomeToken"]), + mock.call(["ua", "enable", "fips"], capture=True), + ], + ) self.assertEqual( - 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', - self.logs.getvalue()) + "DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n", + self.logs.getvalue(), + ) - @mock.patch('%s.maybe_install_ua_tools' % MPATH, mock.MagicMock()) - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH, mock.MagicMock()) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_with_string_services(self, m_subp): """When services a string, treat as singleton list and warn""" - configure_ua(token='SomeToken', enable='fips') + configure_ua(token="SomeToken", enable="fips") self.assertEqual( m_subp.call_args_list, - [mock.call(['ua', 'attach', 'SomeToken']), - mock.call(['ua', 'enable', 'fips'], capture=True)]) + [ + mock.call(["ua", "attach", "SomeToken"]), + mock.call(["ua", "enable", "fips"], capture=True), + ], + ) self.assertEqual( - 'WARNING: ubuntu_advantage: enable should be a list, not a' - ' string; treating as a single enable\n' - 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', - self.logs.getvalue()) + "WARNING: ubuntu_advantage: enable should be a list, not a" + " string; treating as a single enable\n" + "DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n", + self.logs.getvalue(), + ) - @mock.patch('%s.subp.subp' % MPATH) + @mock.patch("%s.subp.subp" % MPATH) def test_configure_ua_attach_with_weird_services(self, m_subp): """When services not string or list, warn but still attach""" - configure_ua(token='SomeToken', enable={'deffo': 'wont work'}) + configure_ua(token="SomeToken", enable={"deffo": "wont work"}) self.assertEqual( - m_subp.call_args_list, - [mock.call(['ua', 'attach', 'SomeToken'])]) + m_subp.call_args_list, [mock.call(["ua", "attach", "SomeToken"])] + ) self.assertEqual( - 'WARNING: ubuntu_advantage: enable should be a list, not a' - ' dict; skipping enabling services\n' - 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', - self.logs.getvalue()) + "WARNING: ubuntu_advantage: enable should be a list, not a" + " dict; skipping enabling services\n" + "DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n", + self.logs.getvalue(), + ) @skipUnlessJsonSchema() @@ -139,49 +166,57 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): with_logs = True schema = schema - @mock.patch('%s.maybe_install_ua_tools' % MPATH) - @mock.patch('%s.configure_ua' % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH) + @mock.patch("%s.configure_ua" % MPATH) def test_schema_warns_on_ubuntu_advantage_not_dict(self, _cfg, _): """If ubuntu_advantage configuration is not a dict, emit a warning.""" - validate_cloudconfig_schema({'ubuntu_advantage': 'wrong type'}, schema) + validate_cloudconfig_schema({"ubuntu_advantage": "wrong type"}, schema) self.assertEqual( "WARNING: Invalid config:\nubuntu_advantage: 'wrong type' is not" " of type 'object'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('%s.maybe_install_ua_tools' % MPATH) - @mock.patch('%s.configure_ua' % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH) + @mock.patch("%s.configure_ua" % MPATH) def test_schema_disallows_unknown_keys(self, _cfg, _): """Unknown keys in ubuntu_advantage configuration emit warnings.""" validate_cloudconfig_schema( - {'ubuntu_advantage': {'token': 'winner', 'invalid-key': ''}}, - schema) + {"ubuntu_advantage": {"token": "winner", "invalid-key": ""}}, + schema, + ) self.assertIn( - 'WARNING: Invalid config:\nubuntu_advantage: Additional properties' + "WARNING: Invalid config:\nubuntu_advantage: Additional properties" " are not allowed ('invalid-key' was unexpected)", - self.logs.getvalue()) + self.logs.getvalue(), + ) - @mock.patch('%s.maybe_install_ua_tools' % MPATH) - @mock.patch('%s.configure_ua' % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH) + @mock.patch("%s.configure_ua" % MPATH) def test_warn_schema_requires_token(self, _cfg, _): """Warn if ubuntu_advantage configuration lacks token.""" validate_cloudconfig_schema( - {'ubuntu_advantage': {'enable': ['esm']}}, schema) + {"ubuntu_advantage": {"enable": ["esm"]}}, schema + ) self.assertEqual( "WARNING: Invalid config:\nubuntu_advantage:" - " 'token' is a required property\n", self.logs.getvalue()) + " 'token' is a required property\n", + self.logs.getvalue(), + ) - @mock.patch('%s.maybe_install_ua_tools' % MPATH) - @mock.patch('%s.configure_ua' % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH) + @mock.patch("%s.configure_ua" % MPATH) def test_warn_schema_services_is_not_list_or_dict(self, _cfg, _): """Warn when ubuntu_advantage:enable config is not a list.""" validate_cloudconfig_schema( - {'ubuntu_advantage': {'enable': 'needslist'}}, schema) + {"ubuntu_advantage": {"enable": "needslist"}}, schema + ) self.assertEqual( "WARNING: Invalid config:\nubuntu_advantage: 'token' is a" " required property\nubuntu_advantage.enable: 'needslist'" " is not of type 'array'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) class TestHandle(CiTestCase): @@ -192,89 +227,93 @@ class TestHandle(CiTestCase): super(TestHandle, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('%s.validate_cloudconfig_schema' % MPATH) + @mock.patch("%s.validate_cloudconfig_schema" % MPATH) def test_handle_no_config(self, m_schema): """When no ua-related configuration is provided, nothing happens.""" cfg = {} - handle('ua-test', cfg=cfg, cloud=None, log=self.logger, args=None) + handle("ua-test", cfg=cfg, cloud=None, log=self.logger, args=None) self.assertIn( "DEBUG: Skipping module named ua-test, no 'ubuntu_advantage'" - ' configuration found', - self.logs.getvalue()) + " configuration found", + self.logs.getvalue(), + ) m_schema.assert_not_called() - @mock.patch('%s.configure_ua' % MPATH) - @mock.patch('%s.maybe_install_ua_tools' % MPATH) + @mock.patch("%s.configure_ua" % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH) def test_handle_tries_to_install_ubuntu_advantage_tools( - self, m_install, m_cfg): + self, m_install, m_cfg + ): """If ubuntu_advantage is provided, try installing ua-tools package.""" - cfg = {'ubuntu_advantage': {'token': 'valid'}} + cfg = {"ubuntu_advantage": {"token": "valid"}} mycloud = FakeCloud(None) - handle('nomatter', cfg=cfg, cloud=mycloud, log=self.logger, args=None) + handle("nomatter", cfg=cfg, cloud=mycloud, log=self.logger, args=None) m_install.assert_called_once_with(mycloud) - @mock.patch('%s.configure_ua' % MPATH) - @mock.patch('%s.maybe_install_ua_tools' % MPATH) + @mock.patch("%s.configure_ua" % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH) def test_handle_passes_credentials_and_services_to_configure_ua( - self, m_install, m_configure_ua): + self, m_install, m_configure_ua + ): """All ubuntu_advantage config keys are passed to configure_ua.""" - cfg = {'ubuntu_advantage': {'token': 'token', 'enable': ['esm']}} - handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) - m_configure_ua.assert_called_once_with( - token='token', enable=['esm']) + cfg = {"ubuntu_advantage": {"token": "token", "enable": ["esm"]}} + handle("nomatter", cfg=cfg, cloud=None, log=self.logger, args=None) + m_configure_ua.assert_called_once_with(token="token", enable=["esm"]) - @mock.patch('%s.maybe_install_ua_tools' % MPATH, mock.MagicMock()) - @mock.patch('%s.configure_ua' % MPATH) + @mock.patch("%s.maybe_install_ua_tools" % MPATH, mock.MagicMock()) + @mock.patch("%s.configure_ua" % MPATH) def test_handle_warns_on_deprecated_ubuntu_advantage_key_w_config( - self, m_configure_ua): + self, m_configure_ua + ): """Warning when ubuntu-advantage key is present with new config""" - cfg = {'ubuntu-advantage': {'token': 'token', 'enable': ['esm']}} - handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) + cfg = {"ubuntu-advantage": {"token": "token", "enable": ["esm"]}} + handle("nomatter", cfg=cfg, cloud=None, log=self.logger, args=None) self.assertEqual( 'WARNING: Deprecated configuration key "ubuntu-advantage"' ' provided. Expected underscore delimited "ubuntu_advantage";' - ' will attempt to continue.', - self.logs.getvalue().splitlines()[0]) - m_configure_ua.assert_called_once_with( - token='token', enable=['esm']) + " will attempt to continue.", + self.logs.getvalue().splitlines()[0], + ) + m_configure_ua.assert_called_once_with(token="token", enable=["esm"]) def test_handle_error_on_deprecated_commands_key_dashed(self): """Error when commands is present in ubuntu-advantage key.""" - cfg = {'ubuntu-advantage': {'commands': 'nogo'}} + cfg = {"ubuntu-advantage": {"commands": "nogo"}} with self.assertRaises(RuntimeError) as context_manager: - handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) + handle("nomatter", cfg=cfg, cloud=None, log=self.logger, args=None) self.assertEqual( 'Deprecated configuration "ubuntu-advantage: commands" provided.' ' Expected "token"', - str(context_manager.exception)) + str(context_manager.exception), + ) def test_handle_error_on_deprecated_commands_key_underscored(self): """Error when commands is present in ubuntu_advantage key.""" - cfg = {'ubuntu_advantage': {'commands': 'nogo'}} + cfg = {"ubuntu_advantage": {"commands": "nogo"}} with self.assertRaises(RuntimeError) as context_manager: - handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) + handle("nomatter", cfg=cfg, cloud=None, log=self.logger, args=None) self.assertEqual( 'Deprecated configuration "ubuntu-advantage: commands" provided.' ' Expected "token"', - str(context_manager.exception)) + str(context_manager.exception), + ) - @mock.patch('%s.maybe_install_ua_tools' % MPATH, mock.MagicMock()) - @mock.patch('%s.configure_ua' % MPATH) - def test_handle_prefers_new_style_config( - self, m_configure_ua): + @mock.patch("%s.maybe_install_ua_tools" % MPATH, mock.MagicMock()) + @mock.patch("%s.configure_ua" % MPATH) + def test_handle_prefers_new_style_config(self, m_configure_ua): """ubuntu_advantage should be preferred over ubuntu-advantage""" cfg = { - 'ubuntu-advantage': {'token': 'nope', 'enable': ['wrong']}, - 'ubuntu_advantage': {'token': 'token', 'enable': ['esm']}, + "ubuntu-advantage": {"token": "nope", "enable": ["wrong"]}, + "ubuntu_advantage": {"token": "token", "enable": ["esm"]}, } - handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) + handle("nomatter", cfg=cfg, cloud=None, log=self.logger, args=None) self.assertEqual( 'WARNING: Deprecated configuration key "ubuntu-advantage"' ' provided. Expected underscore delimited "ubuntu_advantage";' - ' will attempt to continue.', - self.logs.getvalue().splitlines()[0]) - m_configure_ua.assert_called_once_with( - token='token', enable=['esm']) + " will attempt to continue.", + self.logs.getvalue().splitlines()[0], + ) + m_configure_ua.assert_called_once_with(token="token", enable=["esm"]) class TestMaybeInstallUATools(CiTestCase): @@ -285,42 +324,46 @@ class TestMaybeInstallUATools(CiTestCase): super(TestMaybeInstallUATools, self).setUp() self.tmp = self.tmp_dir() - @mock.patch('%s.subp.which' % MPATH) + @mock.patch("%s.subp.which" % MPATH) def test_maybe_install_ua_tools_noop_when_ua_tools_present(self, m_which): """Do nothing if ubuntu-advantage-tools already exists.""" - m_which.return_value = '/usr/bin/ua' # already installed + m_which.return_value = "/usr/bin/ua" # already installed distro = mock.MagicMock() distro.update_package_sources.side_effect = RuntimeError( - 'Some apt error') + "Some apt error" + ) maybe_install_ua_tools(cloud=FakeCloud(distro)) # No RuntimeError - @mock.patch('%s.subp.which' % MPATH) + @mock.patch("%s.subp.which" % MPATH) def test_maybe_install_ua_tools_raises_update_errors(self, m_which): """maybe_install_ua_tools logs and raises apt update errors.""" m_which.return_value = None distro = mock.MagicMock() distro.update_package_sources.side_effect = RuntimeError( - 'Some apt error') + "Some apt error" + ) with self.assertRaises(RuntimeError) as context_manager: maybe_install_ua_tools(cloud=FakeCloud(distro)) - self.assertEqual('Some apt error', str(context_manager.exception)) - self.assertIn('Package update failed\nTraceback', self.logs.getvalue()) + self.assertEqual("Some apt error", str(context_manager.exception)) + self.assertIn("Package update failed\nTraceback", self.logs.getvalue()) - @mock.patch('%s.subp.which' % MPATH) + @mock.patch("%s.subp.which" % MPATH) def test_maybe_install_ua_raises_install_errors(self, m_which): """maybe_install_ua_tools logs and raises package install errors.""" m_which.return_value = None distro = mock.MagicMock() distro.update_package_sources.return_value = None distro.install_packages.side_effect = RuntimeError( - 'Some install error') + "Some install error" + ) with self.assertRaises(RuntimeError) as context_manager: maybe_install_ua_tools(cloud=FakeCloud(distro)) - self.assertEqual('Some install error', str(context_manager.exception)) + self.assertEqual("Some install error", str(context_manager.exception)) self.assertIn( - 'Failed to install ubuntu-advantage-tools\n', self.logs.getvalue()) + "Failed to install ubuntu-advantage-tools\n", self.logs.getvalue() + ) - @mock.patch('%s.subp.which' % MPATH) + @mock.patch("%s.subp.which" % MPATH) def test_maybe_install_ua_tools_happy_path(self, m_which): """maybe_install_ua_tools installs ubuntu-advantage-tools.""" m_which.return_value = None @@ -328,6 +371,8 @@ class TestMaybeInstallUATools(CiTestCase): maybe_install_ua_tools(cloud=FakeCloud(distro)) distro.update_package_sources.assert_called_once_with() distro.install_packages.assert_called_once_with( - ['ubuntu-advantage-tools']) + ["ubuntu-advantage-tools"] + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_ubuntu_drivers.py b/tests/unittests/config/test_cc_ubuntu_drivers.py index d341fbfd..4987492d 100644 --- a/tests/unittests/config/test_cc_ubuntu_drivers.py +++ b/tests/unittests/config/test_cc_ubuntu_drivers.py @@ -3,17 +3,20 @@ import copy import os -from tests.unittests.helpers import CiTestCase, skipUnlessJsonSchema, mock -from cloudinit.config.schema import ( - SchemaValidationError, validate_cloudconfig_schema) from cloudinit.config import cc_ubuntu_drivers as drivers +from cloudinit.config.schema import ( + SchemaValidationError, + validate_cloudconfig_schema, +) from cloudinit.subp import ProcessExecutionError +from tests.unittests.helpers import CiTestCase, mock, skipUnlessJsonSchema MPATH = "cloudinit.config.cc_ubuntu_drivers." M_TMP_PATH = MPATH + "temp_utils.mkdtemp" OLD_UBUNTU_DRIVERS_ERROR_STDERR = ( "ubuntu-drivers: error: argument <command>: invalid choice: 'install' " - "(choose from 'list', 'autoinstall', 'devices', 'debug')\n") + "(choose from 'list', 'autoinstall', 'devices', 'debug')\n" +) # The tests in this module call helper methods which are decorated with @@ -23,8 +26,8 @@ OLD_UBUNTU_DRIVERS_ERROR_STDERR = ( # disable it for the entire module: # pylint: disable=no-value-for-parameter -class AnyTempScriptAndDebconfFile(object): +class AnyTempScriptAndDebconfFile(object): def __init__(self, tmp_dir, debconf_file): self.tmp_dir = tmp_dir self.debconf_file = debconf_file @@ -33,60 +36,68 @@ class AnyTempScriptAndDebconfFile(object): if not len(cmd) == 2: return False script, debconf_file = cmd - if bool(script.startswith(self.tmp_dir) and script.endswith('.sh')): + if bool(script.startswith(self.tmp_dir) and script.endswith(".sh")): return debconf_file == self.debconf_file return False class TestUbuntuDrivers(CiTestCase): - cfg_accepted = {'drivers': {'nvidia': {'license-accepted': True}}} - install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'] + cfg_accepted = {"drivers": {"nvidia": {"license-accepted": True}}} + install_gpgpu = ["ubuntu-drivers", "install", "--gpgpu", "nvidia"] with_logs = True @skipUnlessJsonSchema() def test_schema_requires_boolean_for_license_accepted(self): with self.assertRaisesRegex( - SchemaValidationError, ".*license-accepted.*TRUE.*boolean"): + SchemaValidationError, ".*license-accepted.*TRUE.*boolean" + ): validate_cloudconfig_schema( - {'drivers': {'nvidia': {'license-accepted': "TRUE"}}}, - schema=drivers.schema, strict=True) + {"drivers": {"nvidia": {"license-accepted": "TRUE"}}}, + schema=drivers.schema, + strict=True, + ) @mock.patch(M_TMP_PATH) - @mock.patch(MPATH + "subp.subp", return_value=('', '')) + @mock.patch(MPATH + "subp.subp", return_value=("", "")) @mock.patch(MPATH + "subp.which", return_value=False) - def _assert_happy_path_taken( - self, config, m_which, m_subp, m_tmp): + def _assert_happy_path_taken(self, config, m_which, m_subp, m_tmp): """Positive path test through handle. Package should be installed.""" tdir = self.tmp_dir() - debconf_file = os.path.join(tdir, 'nvidia.template') + debconf_file = os.path.join(tdir, "nvidia.template") m_tmp.return_value = tdir myCloud = mock.MagicMock() - drivers.handle('ubuntu_drivers', config, myCloud, None, None) - self.assertEqual([mock.call(['ubuntu-drivers-common'])], - myCloud.distro.install_packages.call_args_list) + drivers.handle("ubuntu_drivers", config, myCloud, None, None) + self.assertEqual( + [mock.call(["ubuntu-drivers-common"])], + myCloud.distro.install_packages.call_args_list, + ) self.assertEqual( - [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), - mock.call(self.install_gpgpu)], - m_subp.call_args_list) + [ + mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), + mock.call(self.install_gpgpu), + ], + m_subp.call_args_list, + ) def test_handle_does_package_install(self): self._assert_happy_path_taken(self.cfg_accepted) def test_trueish_strings_are_considered_approval(self): - for true_value in ['yes', 'true', 'on', '1']: + for true_value in ["yes", "true", "on", "1"]: new_config = copy.deepcopy(self.cfg_accepted) - new_config['drivers']['nvidia']['license-accepted'] = true_value + new_config["drivers"]["nvidia"]["license-accepted"] = true_value self._assert_happy_path_taken(new_config) @mock.patch(M_TMP_PATH) @mock.patch(MPATH + "subp.subp") @mock.patch(MPATH + "subp.which", return_value=False) def test_handle_raises_error_if_no_drivers_found( - self, m_which, m_subp, m_tmp): + self, m_which, m_subp, m_tmp + ): """If ubuntu-drivers doesn't install any drivers, raise an error.""" tdir = self.tmp_dir() - debconf_file = os.path.join(tdir, 'nvidia.template') + debconf_file = os.path.join(tdir, "nvidia.template") m_tmp.return_value = tdir myCloud = mock.MagicMock() @@ -94,84 +105,103 @@ class TestUbuntuDrivers(CiTestCase): if cmd[0].startswith(tdir): return raise ProcessExecutionError( - stdout='No drivers found for installation.\n', exit_code=1) + stdout="No drivers found for installation.\n", exit_code=1 + ) + m_subp.side_effect = fake_subp with self.assertRaises(Exception): drivers.handle( - 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None) - self.assertEqual([mock.call(['ubuntu-drivers-common'])], - myCloud.distro.install_packages.call_args_list) + "ubuntu_drivers", self.cfg_accepted, myCloud, None, None + ) self.assertEqual( - [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), - mock.call(self.install_gpgpu)], - m_subp.call_args_list) - self.assertIn('ubuntu-drivers found no drivers for installation', - self.logs.getvalue()) - - @mock.patch(MPATH + "subp.subp", return_value=('', '')) + [mock.call(["ubuntu-drivers-common"])], + myCloud.distro.install_packages.call_args_list, + ) + self.assertEqual( + [ + mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), + mock.call(self.install_gpgpu), + ], + m_subp.call_args_list, + ) + self.assertIn( + "ubuntu-drivers found no drivers for installation", + self.logs.getvalue(), + ) + + @mock.patch(MPATH + "subp.subp", return_value=("", "")) @mock.patch(MPATH + "subp.which", return_value=False) def _assert_inert_with_config(self, config, m_which, m_subp): """Helper to reduce repetition when testing negative cases""" myCloud = mock.MagicMock() - drivers.handle('ubuntu_drivers', config, myCloud, None, None) + drivers.handle("ubuntu_drivers", config, myCloud, None, None) self.assertEqual(0, myCloud.distro.install_packages.call_count) self.assertEqual(0, m_subp.call_count) def test_handle_inert_if_license_not_accepted(self): """Ensure we don't do anything if the license is rejected.""" self._assert_inert_with_config( - {'drivers': {'nvidia': {'license-accepted': False}}}) + {"drivers": {"nvidia": {"license-accepted": False}}} + ) def test_handle_inert_if_garbage_in_license_field(self): """Ensure we don't do anything if unknown text is in license field.""" self._assert_inert_with_config( - {'drivers': {'nvidia': {'license-accepted': 'garbage'}}}) + {"drivers": {"nvidia": {"license-accepted": "garbage"}}} + ) def test_handle_inert_if_no_license_key(self): """Ensure we don't do anything if no license key.""" - self._assert_inert_with_config({'drivers': {'nvidia': {}}}) + self._assert_inert_with_config({"drivers": {"nvidia": {}}}) def test_handle_inert_if_no_nvidia_key(self): """Ensure we don't do anything if other license accepted.""" self._assert_inert_with_config( - {'drivers': {'acme': {'license-accepted': True}}}) + {"drivers": {"acme": {"license-accepted": True}}} + ) def test_handle_inert_if_string_given(self): """Ensure we don't do anything if string refusal given.""" - for false_value in ['no', 'false', 'off', '0']: + for false_value in ["no", "false", "off", "0"]: self._assert_inert_with_config( - {'drivers': {'nvidia': {'license-accepted': false_value}}}) + {"drivers": {"nvidia": {"license-accepted": false_value}}} + ) @mock.patch(MPATH + "install_drivers") def test_handle_no_drivers_does_nothing(self, m_install_drivers): """If no 'drivers' key in the config, nothing should be done.""" myCloud = mock.MagicMock() myLog = mock.MagicMock() - drivers.handle('ubuntu_drivers', {'foo': 'bzr'}, myCloud, myLog, None) - self.assertIn('Skipping module named', - myLog.debug.call_args_list[0][0][0]) + drivers.handle("ubuntu_drivers", {"foo": "bzr"}, myCloud, myLog, None) + self.assertIn( + "Skipping module named", myLog.debug.call_args_list[0][0][0] + ) self.assertEqual(0, m_install_drivers.call_count) @mock.patch(M_TMP_PATH) - @mock.patch(MPATH + "subp.subp", return_value=('', '')) + @mock.patch(MPATH + "subp.subp", return_value=("", "")) @mock.patch(MPATH + "subp.which", return_value=True) def test_install_drivers_no_install_if_present( - self, m_which, m_subp, m_tmp): + self, m_which, m_subp, m_tmp + ): """If 'ubuntu-drivers' is present, no package install should occur.""" tdir = self.tmp_dir() - debconf_file = os.path.join(tdir, 'nvidia.template') + debconf_file = os.path.join(tdir, "nvidia.template") m_tmp.return_value = tdir pkg_install = mock.MagicMock() - drivers.install_drivers(self.cfg_accepted['drivers'], - pkg_install_func=pkg_install) + drivers.install_drivers( + self.cfg_accepted["drivers"], pkg_install_func=pkg_install + ) self.assertEqual(0, pkg_install.call_count) - self.assertEqual([mock.call('ubuntu-drivers')], - m_which.call_args_list) + self.assertEqual([mock.call("ubuntu-drivers")], m_which.call_args_list) self.assertEqual( - [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), - mock.call(self.install_gpgpu)], - m_subp.call_args_list) + [ + mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), + mock.call(self.install_gpgpu), + ], + m_subp.call_args_list, + ) def test_install_drivers_rejects_invalid_config(self): """install_drivers should raise TypeError if not given a config dict""" @@ -184,10 +214,11 @@ class TestUbuntuDrivers(CiTestCase): @mock.patch(MPATH + "subp.subp") @mock.patch(MPATH + "subp.which", return_value=False) def test_install_drivers_handles_old_ubuntu_drivers_gracefully( - self, m_which, m_subp, m_tmp): + self, m_which, m_subp, m_tmp + ): """Older ubuntu-drivers versions should emit message and raise error""" tdir = self.tmp_dir() - debconf_file = os.path.join(tdir, 'nvidia.template') + debconf_file = os.path.join(tdir, "nvidia.template") m_tmp.return_value = tdir myCloud = mock.MagicMock() @@ -195,50 +226,68 @@ class TestUbuntuDrivers(CiTestCase): if cmd[0].startswith(tdir): return raise ProcessExecutionError( - stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2) + stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2 + ) + m_subp.side_effect = fake_subp with self.assertRaises(Exception): drivers.handle( - 'ubuntu_drivers', self.cfg_accepted, myCloud, None, None) - self.assertEqual([mock.call(['ubuntu-drivers-common'])], - myCloud.distro.install_packages.call_args_list) + "ubuntu_drivers", self.cfg_accepted, myCloud, None, None + ) self.assertEqual( - [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), - mock.call(self.install_gpgpu)], - m_subp.call_args_list) - self.assertIn('WARNING: the available version of ubuntu-drivers is' - ' too old to perform requested driver installation', - self.logs.getvalue()) + [mock.call(["ubuntu-drivers-common"])], + myCloud.distro.install_packages.call_args_list, + ) + self.assertEqual( + [ + mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), + mock.call(self.install_gpgpu), + ], + m_subp.call_args_list, + ) + self.assertIn( + "WARNING: the available version of ubuntu-drivers is" + " too old to perform requested driver installation", + self.logs.getvalue(), + ) # Sub-class TestUbuntuDrivers to run the same test cases, but with a version class TestUbuntuDriversWithVersion(TestUbuntuDrivers): cfg_accepted = { - 'drivers': {'nvidia': {'license-accepted': True, 'version': '123'}}} - install_gpgpu = ['ubuntu-drivers', 'install', '--gpgpu', 'nvidia:123'] + "drivers": {"nvidia": {"license-accepted": True, "version": "123"}} + } + install_gpgpu = ["ubuntu-drivers", "install", "--gpgpu", "nvidia:123"] @mock.patch(M_TMP_PATH) - @mock.patch(MPATH + "subp.subp", return_value=('', '')) + @mock.patch(MPATH + "subp.subp", return_value=("", "")) @mock.patch(MPATH + "subp.which", return_value=False) def test_version_none_uses_latest(self, m_which, m_subp, m_tmp): tdir = self.tmp_dir() - debconf_file = os.path.join(tdir, 'nvidia.template') + debconf_file = os.path.join(tdir, "nvidia.template") m_tmp.return_value = tdir myCloud = mock.MagicMock() version_none_cfg = { - 'drivers': {'nvidia': {'license-accepted': True, 'version': None}}} - drivers.handle( - 'ubuntu_drivers', version_none_cfg, myCloud, None, None) + "drivers": {"nvidia": {"license-accepted": True, "version": None}} + } + drivers.handle("ubuntu_drivers", version_none_cfg, myCloud, None, None) self.assertEqual( - [mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), - mock.call(['ubuntu-drivers', 'install', '--gpgpu', 'nvidia'])], - m_subp.call_args_list) + [ + mock.call(AnyTempScriptAndDebconfFile(tdir, debconf_file)), + mock.call(["ubuntu-drivers", "install", "--gpgpu", "nvidia"]), + ], + m_subp.call_args_list, + ) def test_specifying_a_version_doesnt_override_license_acceptance(self): - self._assert_inert_with_config({ - 'drivers': {'nvidia': {'license-accepted': False, - 'version': '123'}} - }) + self._assert_inert_with_config( + { + "drivers": { + "nvidia": {"license-accepted": False, "version": "123"} + } + } + ) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_update_etc_hosts.py b/tests/unittests/config/test_cc_update_etc_hosts.py index 35ad6413..2bbc16f4 100644 --- a/tests/unittests/config/test_cc_update_etc_hosts.py +++ b/tests/unittests/config/test_cc_update_etc_hosts.py @@ -1,18 +1,13 @@ # This file is part of cloud-init. See LICENSE file for license information. -from cloudinit.config import cc_update_etc_hosts - -from cloudinit import cloud -from cloudinit import distros -from cloudinit import helpers -from cloudinit import util - -from tests.unittests import helpers as t_help - import logging import os import shutil +from cloudinit import cloud, distros, helpers, util +from cloudinit.config import cc_update_etc_hosts +from tests.unittests import helpers as t_help + LOG = logging.getLogger(__name__) @@ -28,46 +23,46 @@ class TestHostsFile(t_help.FilesystemMockingTestCase): def test_write_etc_hosts_suse_localhost(self): cfg = { - 'manage_etc_hosts': 'localhost', - 'hostname': 'cloud-init.test.us' + "manage_etc_hosts": "localhost", + "hostname": "cloud-init.test.us", } - os.makedirs('%s/etc/' % self.tmp) - hosts_content = '192.168.1.1 blah.blah.us blah\n' - fout = open('%s/etc/hosts' % self.tmp, 'w') + os.makedirs("%s/etc/" % self.tmp) + hosts_content = "192.168.1.1 blah.blah.us blah\n" + fout = open("%s/etc/hosts" % self.tmp, "w") fout.write(hosts_content) fout.close() - distro = self._fetch_distro('sles') - distro.hosts_fn = '%s/etc/hosts' % self.tmp + distro = self._fetch_distro("sles") + distro.hosts_fn = "%s/etc/hosts" % self.tmp paths = helpers.Paths({}) ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_update_etc_hosts.handle('test', cfg, cc, LOG, []) - contents = util.load_file('%s/etc/hosts' % self.tmp) - if '127.0.1.1\tcloud-init.test.us\tcloud-init' not in contents: - self.assertIsNone('No entry for 127.0.1.1 in etc/hosts') - if '192.168.1.1\tblah.blah.us\tblah' not in contents: - self.assertIsNone('Default etc/hosts content modified') + cc_update_etc_hosts.handle("test", cfg, cc, LOG, []) + contents = util.load_file("%s/etc/hosts" % self.tmp) + if "127.0.1.1\tcloud-init.test.us\tcloud-init" not in contents: + self.assertIsNone("No entry for 127.0.1.1 in etc/hosts") + if "192.168.1.1\tblah.blah.us\tblah" not in contents: + self.assertIsNone("Default etc/hosts content modified") @t_help.skipUnlessJinja() def test_write_etc_hosts_suse_template(self): cfg = { - 'manage_etc_hosts': 'template', - 'hostname': 'cloud-init.test.us' + "manage_etc_hosts": "template", + "hostname": "cloud-init.test.us", } shutil.copytree( - t_help.cloud_init_project_dir('templates'), - '%s/etc/cloud/templates' % self.tmp, + t_help.cloud_init_project_dir("templates"), + "%s/etc/cloud/templates" % self.tmp, ) - distro = self._fetch_distro('sles') + distro = self._fetch_distro("sles") paths = helpers.Paths({}) - paths.template_tpl = '%s' % self.tmp + '/etc/cloud/templates/%s.tmpl' + paths.template_tpl = "%s" % self.tmp + "/etc/cloud/templates/%s.tmpl" ds = None cc = cloud.Cloud(ds, paths, {}, distro, None) self.patchUtils(self.tmp) - cc_update_etc_hosts.handle('test', cfg, cc, LOG, []) - contents = util.load_file('%s/etc/hosts' % self.tmp) - if '127.0.1.1 cloud-init.test.us cloud-init' not in contents: - self.assertIsNone('No entry for 127.0.1.1 in etc/hosts') - if '::1 cloud-init.test.us cloud-init' not in contents: - self.assertIsNone('No entry for 127.0.0.1 in etc/hosts') + cc_update_etc_hosts.handle("test", cfg, cc, LOG, []) + contents = util.load_file("%s/etc/hosts" % self.tmp) + if "127.0.1.1 cloud-init.test.us cloud-init" not in contents: + self.assertIsNone("No entry for 127.0.1.1 in etc/hosts") + if "::1 cloud-init.test.us cloud-init" not in contents: + self.assertIsNone("No entry for 127.0.0.1 in etc/hosts") diff --git a/tests/unittests/config/test_cc_users_groups.py b/tests/unittests/config/test_cc_users_groups.py index 4ef844cb..0bd3c980 100644 --- a/tests/unittests/config/test_cc_users_groups.py +++ b/tests/unittests/config/test_cc_users_groups.py @@ -7,8 +7,8 @@ from tests.unittests.helpers import CiTestCase, mock MODPATH = "cloudinit.config.cc_users_groups" -@mock.patch('cloudinit.distros.ubuntu.Distro.create_group') -@mock.patch('cloudinit.distros.ubuntu.Distro.create_user') +@mock.patch("cloudinit.distros.ubuntu.Distro.create_group") +@mock.patch("cloudinit.distros.ubuntu.Distro.create_user") class TestHandleUsersGroups(CiTestCase): """Test cc_users_groups handling of config.""" @@ -18,58 +18,90 @@ class TestHandleUsersGroups(CiTestCase): """Test handle with no config will not create users or groups.""" cfg = {} # merged cloud-config # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'ubuntu', 'lock_passwd': True, - 'groups': ['lxd', 'sudo'], - 'shell': '/bin/bash'}} + sys_cfg = { + "default_user": { + "name": "ubuntu", + "lock_passwd": True, + "groups": ["lxd", "sudo"], + "shell": "/bin/bash", + } + } metadata = {} cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) m_user.assert_not_called() m_group.assert_not_called() def test_handle_users_in_cfg_calls_create_users(self, m_user, m_group): """When users in config, create users with distro.create_user.""" - cfg = {'users': ['default', {'name': 'me2'}]} # merged cloud-config + cfg = {"users": ["default", {"name": "me2"}]} # merged cloud-config # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'ubuntu', 'lock_passwd': True, - 'groups': ['lxd', 'sudo'], - 'shell': '/bin/bash'}} + sys_cfg = { + "default_user": { + "name": "ubuntu", + "lock_passwd": True, + "groups": ["lxd", "sudo"], + "shell": "/bin/bash", + } + } metadata = {} cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) self.assertCountEqual( m_user.call_args_list, - [mock.call('ubuntu', groups='lxd,sudo', lock_passwd=True, - shell='/bin/bash'), - mock.call('me2', default=False)]) + [ + mock.call( + "ubuntu", + groups="lxd,sudo", + lock_passwd=True, + shell="/bin/bash", + ), + mock.call("me2", default=False), + ], + ) m_group.assert_not_called() - @mock.patch('cloudinit.distros.freebsd.Distro.create_group') - @mock.patch('cloudinit.distros.freebsd.Distro.create_user') + @mock.patch("cloudinit.distros.freebsd.Distro.create_group") + @mock.patch("cloudinit.distros.freebsd.Distro.create_user") def test_handle_users_in_cfg_calls_create_users_on_bsd( - self, - m_fbsd_user, - m_fbsd_group, - m_linux_user, - m_linux_group, + self, + m_fbsd_user, + m_fbsd_group, + m_linux_user, + m_linux_group, ): """When users in config, create users with freebsd.create_user.""" - cfg = {'users': ['default', {'name': 'me2'}]} # merged cloud-config + cfg = {"users": ["default", {"name": "me2"}]} # merged cloud-config # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'freebsd', 'lock_passwd': True, - 'groups': ['wheel'], - 'shell': '/bin/tcsh'}} + sys_cfg = { + "default_user": { + "name": "freebsd", + "lock_passwd": True, + "groups": ["wheel"], + "shell": "/bin/tcsh", + } + } metadata = {} cloud = self.tmp_cloud( - distro='freebsd', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) + distro="freebsd", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) self.assertCountEqual( m_fbsd_user.call_args_list, - [mock.call('freebsd', groups='wheel', lock_passwd=True, - shell='/bin/tcsh'), - mock.call('me2', default=False)]) + [ + mock.call( + "freebsd", + groups="wheel", + lock_passwd=True, + shell="/bin/tcsh", + ), + mock.call("me2", default=False), + ], + ) m_fbsd_group.assert_not_called() m_linux_group.assert_not_called() m_linux_user.assert_not_called() @@ -77,96 +109,160 @@ class TestHandleUsersGroups(CiTestCase): def test_users_with_ssh_redirect_user_passes_keys(self, m_user, m_group): """When ssh_redirect_user is True pass default user and cloud keys.""" cfg = { - 'users': ['default', {'name': 'me2', 'ssh_redirect_user': True}]} + "users": ["default", {"name": "me2", "ssh_redirect_user": True}] + } # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'ubuntu', 'lock_passwd': True, - 'groups': ['lxd', 'sudo'], - 'shell': '/bin/bash'}} - metadata = {'public-keys': ['key1']} + sys_cfg = { + "default_user": { + "name": "ubuntu", + "lock_passwd": True, + "groups": ["lxd", "sudo"], + "shell": "/bin/bash", + } + } + metadata = {"public-keys": ["key1"]} cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) self.assertCountEqual( m_user.call_args_list, - [mock.call('ubuntu', groups='lxd,sudo', lock_passwd=True, - shell='/bin/bash'), - mock.call('me2', cloud_public_ssh_keys=['key1'], default=False, - ssh_redirect_user='ubuntu')]) + [ + mock.call( + "ubuntu", + groups="lxd,sudo", + lock_passwd=True, + shell="/bin/bash", + ), + mock.call( + "me2", + cloud_public_ssh_keys=["key1"], + default=False, + ssh_redirect_user="ubuntu", + ), + ], + ) m_group.assert_not_called() def test_users_with_ssh_redirect_user_default_str(self, m_user, m_group): """When ssh_redirect_user is 'default' pass default username.""" cfg = { - 'users': ['default', {'name': 'me2', - 'ssh_redirect_user': 'default'}]} + "users": [ + "default", + {"name": "me2", "ssh_redirect_user": "default"}, + ] + } # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'ubuntu', 'lock_passwd': True, - 'groups': ['lxd', 'sudo'], - 'shell': '/bin/bash'}} - metadata = {'public-keys': ['key1']} + sys_cfg = { + "default_user": { + "name": "ubuntu", + "lock_passwd": True, + "groups": ["lxd", "sudo"], + "shell": "/bin/bash", + } + } + metadata = {"public-keys": ["key1"]} cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) self.assertCountEqual( m_user.call_args_list, - [mock.call('ubuntu', groups='lxd,sudo', lock_passwd=True, - shell='/bin/bash'), - mock.call('me2', cloud_public_ssh_keys=['key1'], default=False, - ssh_redirect_user='ubuntu')]) + [ + mock.call( + "ubuntu", + groups="lxd,sudo", + lock_passwd=True, + shell="/bin/bash", + ), + mock.call( + "me2", + cloud_public_ssh_keys=["key1"], + default=False, + ssh_redirect_user="ubuntu", + ), + ], + ) m_group.assert_not_called() def test_users_with_ssh_redirect_user_non_default(self, m_user, m_group): """Warn when ssh_redirect_user is not 'default'.""" cfg = { - 'users': ['default', {'name': 'me2', - 'ssh_redirect_user': 'snowflake'}]} + "users": [ + "default", + {"name": "me2", "ssh_redirect_user": "snowflake"}, + ] + } # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'ubuntu', 'lock_passwd': True, - 'groups': ['lxd', 'sudo'], - 'shell': '/bin/bash'}} - metadata = {'public-keys': ['key1']} + sys_cfg = { + "default_user": { + "name": "ubuntu", + "lock_passwd": True, + "groups": ["lxd", "sudo"], + "shell": "/bin/bash", + } + } + metadata = {"public-keys": ["key1"]} cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) with self.assertRaises(ValueError) as context_manager: - cc_users_groups.handle('modulename', cfg, cloud, None, None) + cc_users_groups.handle("modulename", cfg, cloud, None, None) m_group.assert_not_called() self.assertEqual( - 'Not creating user me2. Invalid value of ssh_redirect_user:' - ' snowflake. Expected values: true, default or false.', - str(context_manager.exception)) + "Not creating user me2. Invalid value of ssh_redirect_user:" + " snowflake. Expected values: true, default or false.", + str(context_manager.exception), + ) def test_users_with_ssh_redirect_user_default_false(self, m_user, m_group): """When unspecified ssh_redirect_user is false and not set up.""" - cfg = {'users': ['default', {'name': 'me2'}]} + cfg = {"users": ["default", {"name": "me2"}]} # System config defines a default user for the distro. - sys_cfg = {'default_user': {'name': 'ubuntu', 'lock_passwd': True, - 'groups': ['lxd', 'sudo'], - 'shell': '/bin/bash'}} - metadata = {'public-keys': ['key1']} + sys_cfg = { + "default_user": { + "name": "ubuntu", + "lock_passwd": True, + "groups": ["lxd", "sudo"], + "shell": "/bin/bash", + } + } + metadata = {"public-keys": ["key1"]} cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) self.assertCountEqual( m_user.call_args_list, - [mock.call('ubuntu', groups='lxd,sudo', lock_passwd=True, - shell='/bin/bash'), - mock.call('me2', default=False)]) + [ + mock.call( + "ubuntu", + groups="lxd,sudo", + lock_passwd=True, + shell="/bin/bash", + ), + mock.call("me2", default=False), + ], + ) m_group.assert_not_called() def test_users_ssh_redirect_user_and_no_default(self, m_user, m_group): """Warn when ssh_redirect_user is True and no default user present.""" cfg = { - 'users': ['default', {'name': 'me2', 'ssh_redirect_user': True}]} + "users": ["default", {"name": "me2", "ssh_redirect_user": True}] + } # System config defines *no* default user for the distro. sys_cfg = {} metadata = {} # no public-keys defined cloud = self.tmp_cloud( - distro='ubuntu', sys_cfg=sys_cfg, metadata=metadata) - cc_users_groups.handle('modulename', cfg, cloud, None, None) - m_user.assert_called_once_with('me2', default=False) + distro="ubuntu", sys_cfg=sys_cfg, metadata=metadata + ) + cc_users_groups.handle("modulename", cfg, cloud, None, None) + m_user.assert_called_once_with("me2", default=False) m_group.assert_not_called() self.assertEqual( - 'WARNING: Ignoring ssh_redirect_user: True for me2. No' - ' default_user defined. Perhaps missing' - ' cloud configuration users: [default, ..].\n', - self.logs.getvalue()) + "WARNING: Ignoring ssh_redirect_user: True for me2. No" + " default_user defined. Perhaps missing" + " cloud configuration users: [default, ..].\n", + self.logs.getvalue(), + ) diff --git a/tests/unittests/config/test_cc_write_files.py b/tests/unittests/config/test_cc_write_files.py index 99248f74..7eea99d3 100644 --- a/tests/unittests/config/test_cc_write_files.py +++ b/tests/unittests/config/test_cc_write_files.py @@ -7,13 +7,15 @@ import io import shutil import tempfile -from cloudinit.config.cc_write_files import ( - handle, decode_perms, write_files) from cloudinit import log as logging from cloudinit import util - +from cloudinit.config.cc_write_files import decode_perms, handle, write_files from tests.unittests.helpers import ( - CiTestCase, FilesystemMockingTestCase, mock, skipUnlessJsonSchema) + CiTestCase, + FilesystemMockingTestCase, + mock, + skipUnlessJsonSchema, +) LOG = logging.getLogger(__name__) @@ -35,73 +37,89 @@ write_files: """ YAML_CONTENT_EXPECTED = { - '/usr/bin/hello': "#!/bin/sh\necho hello world\n", - '/wark': "foobar\n", - '/tmp/message': "hi mom line 1\nhi mom line 2\n", + "/usr/bin/hello": "#!/bin/sh\necho hello world\n", + "/wark": "foobar\n", + "/tmp/message": "hi mom line 1\nhi mom line 2\n", } VALID_SCHEMA = { - 'write_files': [ - {'append': False, 'content': 'a', 'encoding': 'gzip', 'owner': 'jeff', - 'path': '/some', 'permissions': '0777'} + "write_files": [ + { + "append": False, + "content": "a", + "encoding": "gzip", + "owner": "jeff", + "path": "/some", + "permissions": "0777", + } ] } INVALID_SCHEMA = { # Dropped required path key - 'write_files': [ - {'append': False, 'content': 'a', 'encoding': 'gzip', 'owner': 'jeff', - 'permissions': '0777'} + "write_files": [ + { + "append": False, + "content": "a", + "encoding": "gzip", + "owner": "jeff", + "permissions": "0777", + } ] } @skipUnlessJsonSchema() -@mock.patch('cloudinit.config.cc_write_files.write_files') +@mock.patch("cloudinit.config.cc_write_files.write_files") class TestWriteFilesSchema(CiTestCase): with_logs = True def test_schema_validation_warns_missing_path(self, m_write_files): """The only required file item property is 'path'.""" - cc = self.tmp_cloud('ubuntu') - valid_config = {'write_files': [{'path': '/some/path'}]} - handle('cc_write_file', valid_config, cc, LOG, []) - self.assertNotIn('Invalid config:', self.logs.getvalue()) - handle('cc_write_file', INVALID_SCHEMA, cc, LOG, []) - self.assertIn('Invalid config:', self.logs.getvalue()) + cc = self.tmp_cloud("ubuntu") + valid_config = {"write_files": [{"path": "/some/path"}]} + handle("cc_write_file", valid_config, cc, LOG, []) + self.assertNotIn("Invalid config:", self.logs.getvalue()) + handle("cc_write_file", INVALID_SCHEMA, cc, LOG, []) + self.assertIn("Invalid config:", self.logs.getvalue()) self.assertIn("'path' is a required property", self.logs.getvalue()) def test_schema_validation_warns_non_string_type_for_files( - self, m_write_files): + self, m_write_files + ): """Schema validation warns of non-string values for each file item.""" - cc = self.tmp_cloud('ubuntu') - for key in VALID_SCHEMA['write_files'][0].keys(): - if key == 'append': - key_type = 'boolean' + cc = self.tmp_cloud("ubuntu") + for key in VALID_SCHEMA["write_files"][0].keys(): + if key == "append": + key_type = "boolean" else: - key_type = 'string' + key_type = "string" invalid_config = copy.deepcopy(VALID_SCHEMA) - invalid_config['write_files'][0][key] = 1 - handle('cc_write_file', invalid_config, cc, LOG, []) + invalid_config["write_files"][0][key] = 1 + handle("cc_write_file", invalid_config, cc, LOG, []) self.assertIn( - mock.call('cc_write_file', invalid_config['write_files']), - m_write_files.call_args_list) + mock.call("cc_write_file", invalid_config["write_files"]), + m_write_files.call_args_list, + ) self.assertIn( - 'write_files.0.%s: 1 is not of type \'%s\'' % (key, key_type), - self.logs.getvalue()) - self.assertIn('Invalid config:', self.logs.getvalue()) + "write_files.0.%s: 1 is not of type '%s'" % (key, key_type), + self.logs.getvalue(), + ) + self.assertIn("Invalid config:", self.logs.getvalue()) def test_schema_validation_warns_on_additional_undefined_propertes( - self, m_write_files): + self, m_write_files + ): """Schema validation warns on additional undefined file properties.""" - cc = self.tmp_cloud('ubuntu') + cc = self.tmp_cloud("ubuntu") invalid_config = copy.deepcopy(VALID_SCHEMA) - invalid_config['write_files'][0]['bogus'] = 'value' - handle('cc_write_file', invalid_config, cc, LOG, []) + invalid_config["write_files"][0]["bogus"] = "value" + handle("cc_write_file", invalid_config, cc, LOG, []) self.assertIn( "Invalid config:\nwrite_files.0: Additional properties" " are not allowed ('bogus' was unexpected)", - self.logs.getvalue()) + self.logs.getvalue(), + ) class TestWriteFiles(FilesystemMockingTestCase): @@ -116,20 +134,20 @@ class TestWriteFiles(FilesystemMockingTestCase): @skipUnlessJsonSchema() def test_handler_schema_validation_warns_non_array_type(self): """Schema validation warns of non-array value.""" - invalid_config = {'write_files': 1} - cc = self.tmp_cloud('ubuntu') + invalid_config = {"write_files": 1} + cc = self.tmp_cloud("ubuntu") with self.assertRaises(TypeError): - handle('cc_write_file', invalid_config, cc, LOG, []) + handle("cc_write_file", invalid_config, cc, LOG, []) self.assertIn( - 'Invalid config:\nwrite_files: 1 is not of type \'array\'', - self.logs.getvalue()) + "Invalid config:\nwrite_files: 1 is not of type 'array'", + self.logs.getvalue(), + ) def test_simple(self): self.patchUtils(self.tmp) expected = "hello world\n" filename = "/tmp/my.file" - write_files( - "test_simple", [{"content": expected, "path": filename}]) + write_files("test_simple", [{"content": expected, "path": filename}]) self.assertEqual(util.load_file(filename), expected) def test_append(self): @@ -141,13 +159,14 @@ class TestWriteFiles(FilesystemMockingTestCase): util.write_file(filename, existing) write_files( "test_append", - [{"content": added, "path": filename, "append": "true"}]) + [{"content": added, "path": filename, "append": "true"}], + ) self.assertEqual(util.load_file(filename), expected) def test_yaml_binary(self): self.patchUtils(self.tmp) data = util.load_yaml(YAML_TEXT) - write_files("testname", data['write_files']) + write_files("testname", data["write_files"]) for path, content in YAML_CONTENT_EXPECTED.items(): self.assertEqual(util.load_file(path), content) @@ -158,13 +177,13 @@ class TestWriteFiles(FilesystemMockingTestCase): # for 'gz', 'gzip', 'gz+base64' ... data = b"foobzr" utf8_valid = b"foobzr" - utf8_invalid = b'ab\xaadef' + utf8_invalid = b"ab\xaadef" files = [] expected = [] - gz_aliases = ('gz', 'gzip') - gz_b64_aliases = ('gz+base64', 'gzip+base64', 'gz+b64', 'gzip+b64') - b64_aliases = ('base64', 'b64') + gz_aliases = ("gz", "gzip") + gz_b64_aliases = ("gz+base64", "gzip+base64", "gz+b64", "gzip+b64") + b64_aliases = ("base64", "b64") datum = (("utf8", utf8_valid), ("no-utf8", utf8_invalid)) for name, data in datum: @@ -173,11 +192,13 @@ class TestWriteFiles(FilesystemMockingTestCase): b64 = (base64.b64encode(data), b64_aliases) for content, aliases in (gz, gz_b64, b64): for enc in aliases: - cur = {'content': content, - 'path': '/tmp/file-%s-%s' % (name, enc), - 'encoding': enc} + cur = { + "content": content, + "path": "/tmp/file-%s-%s" % (name, enc), + "encoding": enc, + } files.append(cur) - expected.append((cur['path'], data)) + expected.append((cur["path"], data)) write_files("test_decoding", files) @@ -185,20 +206,17 @@ class TestWriteFiles(FilesystemMockingTestCase): self.assertEqual(util.load_file(path, decode=False), content) # make sure we actually wrote *some* files. - flen_expected = ( - len(gz_aliases + gz_b64_aliases + b64_aliases) * len(datum)) + flen_expected = len(gz_aliases + gz_b64_aliases + b64_aliases) * len( + datum + ) self.assertEqual(len(expected), flen_expected) def test_deferred(self): self.patchUtils(self.tmp) - file_path = '/tmp/deferred.file' - config = { - 'write_files': [ - {'path': file_path, 'defer': True} - ] - } - cc = self.tmp_cloud('ubuntu') - handle('cc_write_file', config, cc, LOG, []) + file_path = "/tmp/deferred.file" + config = {"write_files": [{"path": file_path, "defer": True}]} + cc = self.tmp_cloud("ubuntu") + handle("cc_write_file", config, cc, LOG, []) with self.assertRaises(FileNotFoundError): util.load_file(file_path) diff --git a/tests/unittests/config/test_cc_write_files_deferred.py b/tests/unittests/config/test_cc_write_files_deferred.py index d33d250a..3faac1bf 100644 --- a/tests/unittests/config/test_cc_write_files_deferred.py +++ b/tests/unittests/config/test_cc_write_files_deferred.py @@ -1,48 +1,54 @@ # This file is part of cloud-init. See LICENSE file for license information. -import tempfile import shutil +import tempfile -from cloudinit.config.cc_write_files_deferred import (handle) -from .test_cc_write_files import (VALID_SCHEMA) from cloudinit import log as logging from cloudinit import util - +from cloudinit.config.cc_write_files_deferred import handle from tests.unittests.helpers import ( - CiTestCase, FilesystemMockingTestCase, mock, skipUnlessJsonSchema) + CiTestCase, + FilesystemMockingTestCase, + mock, + skipUnlessJsonSchema, +) + +from .test_cc_write_files import VALID_SCHEMA LOG = logging.getLogger(__name__) @skipUnlessJsonSchema() -@mock.patch('cloudinit.config.cc_write_files_deferred.write_files') +@mock.patch("cloudinit.config.cc_write_files_deferred.write_files") class TestWriteFilesDeferredSchema(CiTestCase): with_logs = True - def test_schema_validation_warns_invalid_value(self, - m_write_files_deferred): + def test_schema_validation_warns_invalid_value( + self, m_write_files_deferred + ): """If 'defer' is defined, it must be of type 'bool'.""" valid_config = { - 'write_files': [ - {**VALID_SCHEMA.get('write_files')[0], 'defer': True} + "write_files": [ + {**VALID_SCHEMA.get("write_files")[0], "defer": True} ] } invalid_config = { - 'write_files': [ - {**VALID_SCHEMA.get('write_files')[0], 'defer': str('no')} + "write_files": [ + {**VALID_SCHEMA.get("write_files")[0], "defer": str("no")} ] } - cc = self.tmp_cloud('ubuntu') - handle('cc_write_files_deferred', valid_config, cc, LOG, []) - self.assertNotIn('Invalid config:', self.logs.getvalue()) - handle('cc_write_files_deferred', invalid_config, cc, LOG, []) - self.assertIn('Invalid config:', self.logs.getvalue()) - self.assertIn("defer: 'no' is not of type 'boolean'", - self.logs.getvalue()) + cc = self.tmp_cloud("ubuntu") + handle("cc_write_files_deferred", valid_config, cc, LOG, []) + self.assertNotIn("Invalid config:", self.logs.getvalue()) + handle("cc_write_files_deferred", invalid_config, cc, LOG, []) + self.assertIn("Invalid config:", self.logs.getvalue()) + self.assertIn( + "defer: 'no' is not of type 'boolean'", self.logs.getvalue() + ) class TestWriteFilesDeferred(FilesystemMockingTestCase): @@ -58,20 +64,20 @@ class TestWriteFilesDeferred(FilesystemMockingTestCase): self.patchUtils(self.tmp) expected = "hello world\n" config = { - 'write_files': [ + "write_files": [ { - 'path': '/tmp/deferred.file', - 'defer': True, - 'content': expected + "path": "/tmp/deferred.file", + "defer": True, + "content": expected, }, - {'path': '/tmp/not_deferred.file'} + {"path": "/tmp/not_deferred.file"}, ] } - cc = self.tmp_cloud('ubuntu') - handle('cc_write_files_deferred', config, cc, LOG, []) - self.assertEqual(util.load_file('/tmp/deferred.file'), expected) + cc = self.tmp_cloud("ubuntu") + handle("cc_write_files_deferred", config, cc, LOG, []) + self.assertEqual(util.load_file("/tmp/deferred.file"), expected) with self.assertRaises(FileNotFoundError): - util.load_file('/tmp/not_deferred.file') + util.load_file("/tmp/not_deferred.file") # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_yum_add_repo.py b/tests/unittests/config/test_cc_yum_add_repo.py index 2f11b96a..550b0af2 100644 --- a/tests/unittests/config/test_cc_yum_add_repo.py +++ b/tests/unittests/config/test_cc_yum_add_repo.py @@ -20,92 +20,101 @@ class TestConfig(helpers.FilesystemMockingTestCase): def test_bad_config(self): cfg = { - 'yum_repos': { - 'epel-testing': { - 'name': 'Extra Packages for Enterprise Linux 5 - Testing', + "yum_repos": { + "epel-testing": { + "name": "Extra Packages for Enterprise Linux 5 - Testing", # Missing this should cause the repo not to be written # 'baseurl': 'http://blah.org/pub/epel/testing/5/$barch', - 'enabled': False, - 'gpgcheck': True, - 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL', - 'failovermethod': 'priority', + "enabled": False, + "gpgcheck": True, + "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL", + "failovermethod": "priority", }, }, } self.patchUtils(self.tmp) - cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, []) - self.assertRaises(IOError, util.load_file, - "/etc/yum.repos.d/epel_testing.repo") + cc_yum_add_repo.handle("yum_add_repo", cfg, None, LOG, []) + self.assertRaises( + IOError, util.load_file, "/etc/yum.repos.d/epel_testing.repo" + ) def test_write_config(self): cfg = { - 'yum_repos': { - 'epel-testing': { - 'name': 'Extra Packages for Enterprise Linux 5 - Testing', - 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch', - 'enabled': False, - 'gpgcheck': True, - 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL', - 'failovermethod': 'priority', + "yum_repos": { + "epel-testing": { + "name": "Extra Packages for Enterprise Linux 5 - Testing", + "baseurl": "http://blah.org/pub/epel/testing/5/$basearch", + "enabled": False, + "gpgcheck": True, + "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL", + "failovermethod": "priority", }, }, } self.patchUtils(self.tmp) - cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, []) + cc_yum_add_repo.handle("yum_add_repo", cfg, None, LOG, []) contents = util.load_file("/etc/yum.repos.d/epel_testing.repo") parser = configparser.ConfigParser() parser.read_string(contents) expected = { - 'epel_testing': { - 'name': 'Extra Packages for Enterprise Linux 5 - Testing', - 'failovermethod': 'priority', - 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL', - 'enabled': '0', - 'baseurl': 'http://blah.org/pub/epel/testing/5/$basearch', - 'gpgcheck': '1', + "epel_testing": { + "name": "Extra Packages for Enterprise Linux 5 - Testing", + "failovermethod": "priority", + "gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL", + "enabled": "0", + "baseurl": "http://blah.org/pub/epel/testing/5/$basearch", + "gpgcheck": "1", } } for section in expected: - self.assertTrue(parser.has_section(section), - "Contains section {0}".format(section)) + self.assertTrue( + parser.has_section(section), + "Contains section {0}".format(section), + ) for k, v in expected[section].items(): self.assertEqual(parser.get(section, k), v) def test_write_config_array(self): cfg = { - 'yum_repos': { - 'puppetlabs-products': { - 'name': 'Puppet Labs Products El 6 - $basearch', - 'baseurl': - 'http://yum.puppetlabs.com/el/6/products/$basearch', - 'gpgkey': [ - 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs', - 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppet', + "yum_repos": { + "puppetlabs-products": { + "name": "Puppet Labs Products El 6 - $basearch", + "baseurl": ( + "http://yum.puppetlabs.com/el/6/products/$basearch" + ), + "gpgkey": [ + "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs", + "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppet", ], - 'enabled': True, - 'gpgcheck': True, + "enabled": True, + "gpgcheck": True, } } } self.patchUtils(self.tmp) - cc_yum_add_repo.handle('yum_add_repo', cfg, None, LOG, []) + cc_yum_add_repo.handle("yum_add_repo", cfg, None, LOG, []) contents = util.load_file("/etc/yum.repos.d/puppetlabs_products.repo") parser = configparser.ConfigParser() parser.read_string(contents) expected = { - 'puppetlabs_products': { - 'name': 'Puppet Labs Products El 6 - $basearch', - 'baseurl': 'http://yum.puppetlabs.com/el/6/products/$basearch', - 'gpgkey': 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs\n' - 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppet', - 'enabled': '1', - 'gpgcheck': '1', + "puppetlabs_products": { + "name": "Puppet Labs Products El 6 - $basearch", + "baseurl": "http://yum.puppetlabs.com/el/6/products/$basearch", + "gpgkey": ( + "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs\n" + "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppet" + ), + "enabled": "1", + "gpgcheck": "1", } } for section in expected: - self.assertTrue(parser.has_section(section), - "Contains section {0}".format(section)) + self.assertTrue( + parser.has_section(section), + "Contains section {0}".format(section), + ) for k, v in expected[section].items(): self.assertEqual(parser.get(section, k), v) + # vi: ts=4 expandtab diff --git a/tests/unittests/config/test_cc_zypper_add_repo.py b/tests/unittests/config/test_cc_zypper_add_repo.py index 4af04bee..4304fee1 100644 --- a/tests/unittests/config/test_cc_zypper_add_repo.py +++ b/tests/unittests/config/test_cc_zypper_add_repo.py @@ -17,31 +17,28 @@ class TestConfig(helpers.FilesystemMockingTestCase): def setUp(self): super(TestConfig, self).setUp() self.tmp = self.tmp_dir() - self.zypp_conf = 'etc/zypp/zypp.conf' + self.zypp_conf = "etc/zypp/zypp.conf" def test_bad_repo_config(self): """Config has no baseurl, no file should be written""" cfg = { - 'repos': [ - { - 'id': 'foo', - 'name': 'suse-test', - 'enabled': '1' - }, + "repos": [ + {"id": "foo", "name": "suse-test", "enabled": "1"}, ] } self.patchUtils(self.tmp) - cc_zypper_add_repo._write_repos(cfg['repos'], '/etc/zypp/repos.d') - self.assertRaises(IOError, util.load_file, - "/etc/zypp/repos.d/foo.repo") + cc_zypper_add_repo._write_repos(cfg["repos"], "/etc/zypp/repos.d") + self.assertRaises( + IOError, util.load_file, "/etc/zypp/repos.d/foo.repo" + ) def test_write_repos(self): """Verify valid repos get written""" cfg = self._get_base_config_repos() root_d = self.tmp_dir() - cc_zypper_add_repo._write_repos(cfg['zypper']['repos'], root_d) - repos = glob.glob('%s/*.repo' % root_d) - expected_repos = ['testing-foo.repo', 'testing-bar.repo'] + cc_zypper_add_repo._write_repos(cfg["zypper"]["repos"], root_d) + repos = glob.glob("%s/*.repo" % root_d) + expected_repos = ["testing-foo.repo", "testing-bar.repo"] if len(repos) != 2: assert 'Number of repos written is "%d" expected 2' % len(repos) for repo in repos: @@ -53,80 +50,77 @@ class TestConfig(helpers.FilesystemMockingTestCase): def test_write_repo(self): """Verify the content of a repo file""" cfg = { - 'repos': [ + "repos": [ { - 'baseurl': 'http://foo', - 'name': 'test-foo', - 'id': 'testing-foo' + "baseurl": "http://foo", + "name": "test-foo", + "id": "testing-foo", }, ] } root_d = self.tmp_dir() - cc_zypper_add_repo._write_repos(cfg['repos'], root_d) + cc_zypper_add_repo._write_repos(cfg["repos"], root_d) contents = util.load_file("%s/testing-foo.repo" % root_d) parser = configparser.ConfigParser() parser.read_string(contents) expected = { - 'testing-foo': { - 'name': 'test-foo', - 'baseurl': 'http://foo', - 'enabled': '1', - 'autorefresh': '1' + "testing-foo": { + "name": "test-foo", + "baseurl": "http://foo", + "enabled": "1", + "autorefresh": "1", } } for section in expected: - self.assertTrue(parser.has_section(section), - "Contains section {0}".format(section)) + self.assertTrue( + parser.has_section(section), + "Contains section {0}".format(section), + ) for k, v in expected[section].items(): self.assertEqual(parser.get(section, k), v) def test_config_write(self): """Write valid configuration data""" - cfg = { - 'config': { - 'download.deltarpm': 'False', - 'reposdir': 'foo' - } - } + cfg = {"config": {"download.deltarpm": "False", "reposdir": "foo"}} root_d = self.tmp_dir() - helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) + helpers.populate_dir(root_d, {self.zypp_conf: "# Zypp config\n"}) self.reRoot(root_d) - cc_zypper_add_repo._write_zypp_config(cfg['config']) + cc_zypper_add_repo._write_zypp_config(cfg["config"]) cfg_out = os.path.join(root_d, self.zypp_conf) contents = util.load_file(cfg_out) expected = [ - '# Zypp config', - '# Added via cloud.cfg', - 'download.deltarpm=False', - 'reposdir=foo' + "# Zypp config", + "# Added via cloud.cfg", + "download.deltarpm=False", + "reposdir=foo", ] - for item in contents.split('\n'): + for item in contents.split("\n"): if item not in expected: self.assertIsNone(item) - @mock.patch('cloudinit.log.logging') + @mock.patch("cloudinit.log.logging") def test_config_write_skip_configdir(self, mock_logging): """Write configuration but skip writing 'configdir' setting""" cfg = { - 'config': { - 'download.deltarpm': 'False', - 'reposdir': 'foo', - 'configdir': 'bar' + "config": { + "download.deltarpm": "False", + "reposdir": "foo", + "configdir": "bar", } } root_d = self.tmp_dir() - helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) + helpers.populate_dir(root_d, {self.zypp_conf: "# Zypp config\n"}) self.reRoot(root_d) - cc_zypper_add_repo._write_zypp_config(cfg['config']) + cc_zypper_add_repo._write_zypp_config(cfg["config"]) cfg_out = os.path.join(root_d, self.zypp_conf) contents = util.load_file(cfg_out) expected = [ - '# Zypp config', - '# Added via cloud.cfg', - 'download.deltarpm=False', - 'reposdir=foo' + "# Zypp config", + "# Added via cloud.cfg", + "download.deltarpm=False", + "reposdir=foo", ] - for item in contents.split('\n'): + for item in contents.split("\n"): if item not in expected: self.assertIsNone(item) # Not finding teh right path for mocking :( @@ -134,55 +128,53 @@ class TestConfig(helpers.FilesystemMockingTestCase): def test_empty_config_section_no_new_data(self): """When the config section is empty no new data should be written to - zypp.conf""" + zypp.conf""" cfg = self._get_base_config_repos() - cfg['zypper']['config'] = None + cfg["zypper"]["config"] = None root_d = self.tmp_dir() - helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) + helpers.populate_dir(root_d, {self.zypp_conf: "# No data"}) self.reRoot(root_d) - cc_zypper_add_repo._write_zypp_config(cfg.get('config', {})) + cc_zypper_add_repo._write_zypp_config(cfg.get("config", {})) cfg_out = os.path.join(root_d, self.zypp_conf) contents = util.load_file(cfg_out) - self.assertEqual(contents, '# No data') + self.assertEqual(contents, "# No data") def test_empty_config_value_no_new_data(self): """When the config section is not empty but there are no values - no new data should be written to zypp.conf""" + no new data should be written to zypp.conf""" cfg = self._get_base_config_repos() - cfg['zypper']['config'] = { - 'download.deltarpm': None - } + cfg["zypper"]["config"] = {"download.deltarpm": None} root_d = self.tmp_dir() - helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) + helpers.populate_dir(root_d, {self.zypp_conf: "# No data"}) self.reRoot(root_d) - cc_zypper_add_repo._write_zypp_config(cfg.get('config', {})) + cc_zypper_add_repo._write_zypp_config(cfg.get("config", {})) cfg_out = os.path.join(root_d, self.zypp_conf) contents = util.load_file(cfg_out) - self.assertEqual(contents, '# No data') + self.assertEqual(contents, "# No data") def test_handler_full_setup(self): """Test that the handler ends up calling the renderers""" cfg = self._get_base_config_repos() - cfg['zypper']['config'] = { - 'download.deltarpm': 'False', + cfg["zypper"]["config"] = { + "download.deltarpm": "False", } root_d = self.tmp_dir() - os.makedirs('%s/etc/zypp/repos.d' % root_d) - helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) + os.makedirs("%s/etc/zypp/repos.d" % root_d) + helpers.populate_dir(root_d, {self.zypp_conf: "# Zypp config\n"}) self.reRoot(root_d) - cc_zypper_add_repo.handle('zypper_add_repo', cfg, None, LOG, []) + cc_zypper_add_repo.handle("zypper_add_repo", cfg, None, LOG, []) cfg_out = os.path.join(root_d, self.zypp_conf) contents = util.load_file(cfg_out) expected = [ - '# Zypp config', - '# Added via cloud.cfg', - 'download.deltarpm=False', + "# Zypp config", + "# Added via cloud.cfg", + "download.deltarpm=False", ] - for item in contents.split('\n'): + for item in contents.split("\n"): if item not in expected: self.assertIsNone(item) - repos = glob.glob('%s/etc/zypp/repos.d/*.repo' % root_d) - expected_repos = ['testing-foo.repo', 'testing-bar.repo'] + repos = glob.glob("%s/etc/zypp/repos.d/*.repo" % root_d) + expected_repos = ["testing-foo.repo", "testing-bar.repo"] if len(repos) != 2: assert 'Number of repos written is "%d" expected 2' % len(repos) for repo in repos: @@ -192,39 +184,39 @@ class TestConfig(helpers.FilesystemMockingTestCase): def test_no_config_section_no_new_data(self): """When there is no config section no new data should be written to - zypp.conf""" + zypp.conf""" cfg = self._get_base_config_repos() root_d = self.tmp_dir() - helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) + helpers.populate_dir(root_d, {self.zypp_conf: "# No data"}) self.reRoot(root_d) - cc_zypper_add_repo._write_zypp_config(cfg.get('config', {})) + cc_zypper_add_repo._write_zypp_config(cfg.get("config", {})) cfg_out = os.path.join(root_d, self.zypp_conf) contents = util.load_file(cfg_out) - self.assertEqual(contents, '# No data') + self.assertEqual(contents, "# No data") def test_no_repo_data(self): """When there is no repo data nothing should happen""" root_d = self.tmp_dir() self.reRoot(root_d) cc_zypper_add_repo._write_repos(None, root_d) - content = glob.glob('%s/*' % root_d) + content = glob.glob("%s/*" % root_d) self.assertEqual(len(content), 0) def _get_base_config_repos(self): """Basic valid repo configuration""" cfg = { - 'zypper': { - 'repos': [ + "zypper": { + "repos": [ { - 'baseurl': 'http://foo', - 'name': 'test-foo', - 'id': 'testing-foo' + "baseurl": "http://foo", + "name": "test-foo", + "id": "testing-foo", }, { - 'baseurl': 'http://bar', - 'name': 'test-bar', - 'id': 'testing-bar' - } + "baseurl": "http://bar", + "name": "test-bar", + "id": "testing-bar", + }, ] } } diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py index 40803cae..fb5b891d 100644 --- a/tests/unittests/config/test_schema.py +++ b/tests/unittests/config/test_schema.py @@ -2,35 +2,36 @@ import importlib -import sys import inspect +import itertools import logging +import sys from copy import copy -import itertools -import pytest from pathlib import Path from textwrap import dedent + +import pytest from yaml import safe_load from cloudinit.config.schema import ( CLOUD_CONFIG_HEADER, + MetaSchema, SchemaValidationError, annotated_cloudconfig_file, + get_jsonschema_validator, get_meta_doc, get_schema, - get_jsonschema_validator, + main, validate_cloudconfig_file, validate_cloudconfig_metaschema, validate_cloudconfig_schema, - main, - MetaSchema, ) from cloudinit.util import write_file from tests.unittests.helpers import ( CiTestCase, + cloud_init_project_dir, mock, skipUnlessJsonSchema, - cloud_init_project_dir, ) @@ -78,26 +79,25 @@ def get_module_variable(var_name) -> dict: class GetSchemaTest(CiTestCase): - def test_get_schema_coalesces_known_schema(self): """Every cloudconfig module with schema is listed in allOf keyword.""" schema = get_schema() self.assertCountEqual( [ - 'cc_apk_configure', - 'cc_apt_configure', - 'cc_bootcmd', - 'cc_locale', - 'cc_ntp', - 'cc_resizefs', - 'cc_runcmd', - 'cc_snap', - 'cc_ubuntu_advantage', - 'cc_ubuntu_drivers', - 'cc_write_files', - 'cc_zypper_add_repo', - 'cc_chef', - 'cc_install_hotplug', + "cc_apk_configure", + "cc_apt_configure", + "cc_bootcmd", + "cc_locale", + "cc_ntp", + "cc_resizefs", + "cc_runcmd", + "cc_snap", + "cc_ubuntu_advantage", + "cc_ubuntu_drivers", + "cc_write_files", + "cc_zypper_add_repo", + "cc_chef", + "cc_install_hotplug", ], [meta["id"] for meta in get_metas().values() if meta is not None], ) @@ -113,15 +113,18 @@ class SchemaValidationErrorTest(CiTestCase): def test_schema_validation_error_expects_schema_errors(self): """SchemaValidationError is initialized from schema_errors.""" - errors = (('key.path', 'unexpected key "junk"'), - ('key2.path', '"-123" is not a valid "hostname" format')) + errors = ( + ("key.path", 'unexpected key "junk"'), + ("key2.path", '"-123" is not a valid "hostname" format'), + ) exception = SchemaValidationError(schema_errors=errors) self.assertIsInstance(exception, Exception) self.assertEqual(exception.schema_errors, errors) self.assertEqual( 'Cloud config schema errors: key.path: unexpected key "junk", ' 'key2.path: "-123" is not a valid "hostname" format', - str(exception)) + str(exception), + ) self.assertTrue(isinstance(exception, ValueError)) @@ -133,18 +136,19 @@ class ValidateCloudConfigSchemaTest(CiTestCase): @skipUnlessJsonSchema() def test_validateconfig_schema_non_strict_emits_warnings(self): """When strict is False validate_cloudconfig_schema emits warnings.""" - schema = {'properties': {'p1': {'type': 'string'}}} - validate_cloudconfig_schema({'p1': -1}, schema, strict=False) + schema = {"properties": {"p1": {"type": "string"}}} + validate_cloudconfig_schema({"p1": -1}, schema, strict=False) self.assertIn( "Invalid config:\np1: -1 is not of type 'string'\n", - self.logs.getvalue()) + self.logs.getvalue(), + ) @skipUnlessJsonSchema() def test_validateconfig_schema_emits_warning_on_missing_jsonschema(self): """Warning from validate_cloudconfig_schema when missing jsonschema.""" - schema = {'properties': {'p1': {'type': 'string'}}} - with mock.patch.dict('sys.modules', **{'jsonschema': ImportError()}): - validate_cloudconfig_schema({'p1': -1}, schema, strict=True) + schema = {"properties": {"p1": {"type": "string"}}} + with mock.patch.dict("sys.modules", **{"jsonschema": ImportError()}): + validate_cloudconfig_schema({"p1": -1}, schema, strict=True) self.assertIn( "Ignoring schema validation. jsonschema is not present", self.logs.getvalue(), @@ -153,28 +157,28 @@ class ValidateCloudConfigSchemaTest(CiTestCase): @skipUnlessJsonSchema() def test_validateconfig_schema_strict_raises_errors(self): """When strict is True validate_cloudconfig_schema raises errors.""" - schema = {'properties': {'p1': {'type': 'string'}}} + schema = {"properties": {"p1": {"type": "string"}}} with self.assertRaises(SchemaValidationError) as context_mgr: - validate_cloudconfig_schema({'p1': -1}, schema, strict=True) + validate_cloudconfig_schema({"p1": -1}, schema, strict=True) self.assertEqual( "Cloud config schema errors: p1: -1 is not of type 'string'", - str(context_mgr.exception)) + str(context_mgr.exception), + ) @skipUnlessJsonSchema() def test_validateconfig_schema_honors_formats(self): """With strict True, validate_cloudconfig_schema errors on format.""" - schema = { - 'properties': {'p1': {'type': 'string', 'format': 'email'}}} + schema = {"properties": {"p1": {"type": "string", "format": "email"}}} with self.assertRaises(SchemaValidationError) as context_mgr: - validate_cloudconfig_schema({'p1': '-1'}, schema, strict=True) + validate_cloudconfig_schema({"p1": "-1"}, schema, strict=True) self.assertEqual( "Cloud config schema errors: p1: '-1' is not a 'email'", - str(context_mgr.exception)) + str(context_mgr.exception), + ) @skipUnlessJsonSchema() def test_validateconfig_schema_honors_formats_strict_metaschema(self): - """With strict True and strict_metascheam True, ensure errors on format - """ + """With strict and strict_metaschema True, ensure errors on format""" schema = {"properties": {"p1": {"type": "string", "format": "email"}}} with self.assertRaises(SchemaValidationError) as context_mgr: validate_cloudconfig_schema( @@ -229,15 +233,15 @@ class ValidateCloudConfigFileTest(CiTestCase): def setUp(self): super(ValidateCloudConfigFileTest, self).setUp() - self.config_file = self.tmp_path('cloudcfg.yaml') + self.config_file = self.tmp_path("cloudcfg.yaml") def test_validateconfig_file_error_on_absent_file(self): """On absent config_path, validate_cloudconfig_file errors.""" with self.assertRaises(RuntimeError) as context_mgr: - validate_cloudconfig_file('/not/here', {}) + validate_cloudconfig_file("/not/here", {}) self.assertEqual( - 'Configfile /not/here does not exist', - str(context_mgr.exception)) + "Configfile /not/here does not exist", str(context_mgr.exception) + ) def test_validateconfig_file_error_on_invalid_header(self): """On invalid header, validate_cloudconfig_file errors. @@ -245,48 +249,54 @@ class ValidateCloudConfigFileTest(CiTestCase): A SchemaValidationError is raised when the file doesn't begin with CLOUD_CONFIG_HEADER. """ - write_file(self.config_file, '#junk') + write_file(self.config_file, "#junk") with self.assertRaises(SchemaValidationError) as context_mgr: validate_cloudconfig_file(self.config_file, {}) self.assertEqual( - 'Cloud config schema errors: format-l1.c1: File {0} needs to begin' + "Cloud config schema errors: format-l1.c1: File {0} needs to begin" ' with "{1}"'.format( - self.config_file, CLOUD_CONFIG_HEADER.decode()), - str(context_mgr.exception)) + self.config_file, CLOUD_CONFIG_HEADER.decode() + ), + str(context_mgr.exception), + ) def test_validateconfig_file_error_on_non_yaml_scanner_error(self): """On non-yaml scan issues, validate_cloudconfig_file errors.""" # Generate a scanner error by providing text on a single line with # improper indent. - write_file(self.config_file, '#cloud-config\nasdf:\nasdf') + write_file(self.config_file, "#cloud-config\nasdf:\nasdf") with self.assertRaises(SchemaValidationError) as context_mgr: validate_cloudconfig_file(self.config_file, {}) self.assertIn( - 'schema errors: format-l3.c1: File {0} is not valid yaml.'.format( - self.config_file), - str(context_mgr.exception)) + "schema errors: format-l3.c1: File {0} is not valid yaml.".format( + self.config_file + ), + str(context_mgr.exception), + ) def test_validateconfig_file_error_on_non_yaml_parser_error(self): """On non-yaml parser issues, validate_cloudconfig_file errors.""" - write_file(self.config_file, '#cloud-config\n{}}') + write_file(self.config_file, "#cloud-config\n{}}") with self.assertRaises(SchemaValidationError) as context_mgr: validate_cloudconfig_file(self.config_file, {}) self.assertIn( - 'schema errors: format-l2.c3: File {0} is not valid yaml.'.format( - self.config_file), - str(context_mgr.exception)) + "schema errors: format-l2.c3: File {0} is not valid yaml.".format( + self.config_file + ), + str(context_mgr.exception), + ) @skipUnlessJsonSchema() def test_validateconfig_file_sctrictly_validates_schema(self): """validate_cloudconfig_file raises errors on invalid schema.""" - schema = { - 'properties': {'p1': {'type': 'string', 'format': 'string'}}} - write_file(self.config_file, '#cloud-config\np1: -1') + schema = {"properties": {"p1": {"type": "string", "format": "string"}}} + write_file(self.config_file, "#cloud-config\np1: -1") with self.assertRaises(SchemaValidationError) as context_mgr: validate_cloudconfig_file(self.config_file, schema) self.assertEqual( "Cloud config schema errors: p1: -1 is not of type 'string'", - str(context_mgr.exception)) + str(context_mgr.exception), + ) class GetSchemaDocTest(CiTestCase): @@ -321,13 +331,21 @@ class GetSchemaDocTest(CiTestCase): """get_meta_doc returns restructured text for a cloudinit schema.""" full_schema = copy(self.required_schema) full_schema.update( - {'properties': { - 'prop1': {'type': 'array', 'description': 'prop-description', - 'items': {'type': 'integer'}}}}) + { + "properties": { + "prop1": { + "type": "array", + "description": "prop-description", + "items": {"type": "integer"}, + } + } + } + ) doc = get_meta_doc(self.meta, full_schema) self.assertEqual( - dedent(""" + dedent( + """ name ---- **Summary:** title @@ -349,7 +367,8 @@ class GetSchemaDocTest(CiTestCase): [don't, expand, "this"] # --- Example2 --- ex2: true - """), + """ + ), doc, ) @@ -388,12 +407,23 @@ class GetSchemaDocTest(CiTestCase): """get_meta_doc properly indented examples as a list of strings.""" full_schema = copy(self.required_schema) full_schema.update( - {'examples': ['ex1:\n [don\'t, expand, "this"]', 'ex2: true'], - 'properties': { - 'prop1': {'type': 'array', 'description': 'prop-description', - 'items': {'type': 'integer'}}}}) + { + "examples": [ + 'ex1:\n [don\'t, expand, "this"]', + "ex2: true", + ], + "properties": { + "prop1": { + "type": "array", + "description": "prop-description", + "items": {"type": "integer"}, + } + }, + } + ) self.assertIn( - dedent(""" + dedent( + """ **Config schema**: **prop1:** (array of integer) prop-description @@ -403,7 +433,8 @@ class GetSchemaDocTest(CiTestCase): [don't, expand, "this"] # --- Example2 --- ex2: true - """), + """ + ), get_meta_doc(self.meta, full_schema), ) @@ -424,13 +455,15 @@ class GetSchemaDocTest(CiTestCase): - option3 The default value is - option1""") + option1""" + ), } } } self.assertIn( - dedent(""" + dedent( + """ **Config schema**: **p1:** (string) This item has the following options: @@ -440,7 +473,8 @@ class GetSchemaDocTest(CiTestCase): The default value is option1 - """), + """ + ), get_meta_doc(self.meta, schema), ) @@ -475,7 +509,7 @@ class GetSchemaDocTest(CiTestCase): "type": "string", }, "prop_array": { - "label": 'array_label', + "label": "array_label", "type": "array", "items": { "type": "object", @@ -490,7 +524,7 @@ class GetSchemaDocTest(CiTestCase): "type": "string", "label": "label2", } - } + }, } meta_doc = get_meta_doc(self.meta, schema) assert "**label1:** (string)" in meta_doc @@ -507,20 +541,23 @@ class AnnotatedCloudconfigFileTest(CiTestCase): def test_annotated_cloudconfig_file_no_schema_errors(self): """With no schema_errors, print the original content.""" - content = b'ntp:\n pools: [ntp1.pools.com]\n' + content = b"ntp:\n pools: [ntp1.pools.com]\n" self.assertEqual( - content, - annotated_cloudconfig_file({}, content, schema_errors=[])) + content, annotated_cloudconfig_file({}, content, schema_errors=[]) + ) def test_annotated_cloudconfig_file_schema_annotates_and_adds_footer(self): """With schema_errors, error lines are annotated and a footer added.""" - content = dedent("""\ + content = dedent( + """\ #cloud-config # comment ntp: pools: [-99, 75] - """).encode() - expected = dedent("""\ + """ + ).encode() + expected = dedent( + """\ #cloud-config # comment ntp: # E1 @@ -531,38 +568,48 @@ class AnnotatedCloudconfigFileTest(CiTestCase): # E2: -99 is not a string # E3: 75 is not a string - """) + """ + ) parsed_config = safe_load(content[13:]) schema_errors = [ - ('ntp', 'Some type error'), ('ntp.pools.0', '-99 is not a string'), - ('ntp.pools.1', '75 is not a string')] + ("ntp", "Some type error"), + ("ntp.pools.0", "-99 is not a string"), + ("ntp.pools.1", "75 is not a string"), + ] self.assertEqual( expected, - annotated_cloudconfig_file(parsed_config, content, schema_errors)) + annotated_cloudconfig_file(parsed_config, content, schema_errors), + ) def test_annotated_cloudconfig_file_annotates_separate_line_items(self): """Errors are annotated for lists with items on separate lines.""" - content = dedent("""\ + content = dedent( + """\ #cloud-config # comment ntp: pools: - -99 - 75 - """).encode() - expected = dedent("""\ + """ + ).encode() + expected = dedent( + """\ ntp: pools: - -99 # E1 - 75 # E2 - """) + """ + ) parsed_config = safe_load(content[13:]) schema_errors = [ - ('ntp.pools.0', '-99 is not a string'), - ('ntp.pools.1', '75 is not a string')] + ("ntp.pools.0", "-99 is not a string"), + ("ntp.pools.1", "75 is not a string"), + ] self.assertIn( expected, - annotated_cloudconfig_file(parsed_config, content, schema_errors)) + annotated_cloudconfig_file(parsed_config, content, schema_errors), + ) class TestMain: @@ -575,94 +622,94 @@ class TestMain: def test_main_exclusive_args(self, params, capsys): """Main exits non-zero and error on required exclusive args.""" params = list(itertools.chain(*[a.split() for a in params])) - with mock.patch('sys.argv', ['mycmd'] + params): + with mock.patch("sys.argv", ["mycmd"] + params): with pytest.raises(SystemExit) as context_manager: main() assert 1 == context_manager.value.code _out, err = capsys.readouterr() expected = ( - 'Error:\n' - 'Expected one of --config-file, --system or --docs arguments\n' + "Error:\n" + "Expected one of --config-file, --system or --docs arguments\n" ) assert expected == err def test_main_missing_args(self, capsys): """Main exits non-zero and reports an error on missing parameters.""" - with mock.patch('sys.argv', ['mycmd']): + with mock.patch("sys.argv", ["mycmd"]): with pytest.raises(SystemExit) as context_manager: main() assert 1 == context_manager.value.code _out, err = capsys.readouterr() expected = ( - 'Error:\n' - 'Expected one of --config-file, --system or --docs arguments\n' + "Error:\n" + "Expected one of --config-file, --system or --docs arguments\n" ) assert expected == err def test_main_absent_config_file(self, capsys): """Main exits non-zero when config file is absent.""" - myargs = ['mycmd', '--annotate', '--config-file', 'NOT_A_FILE'] - with mock.patch('sys.argv', myargs): + myargs = ["mycmd", "--annotate", "--config-file", "NOT_A_FILE"] + with mock.patch("sys.argv", myargs): with pytest.raises(SystemExit) as context_manager: main() assert 1 == context_manager.value.code _out, err = capsys.readouterr() - assert 'Error:\nConfigfile NOT_A_FILE does not exist\n' == err + assert "Error:\nConfigfile NOT_A_FILE does not exist\n" == err def test_main_prints_docs(self, capsys): """When --docs parameter is provided, main generates documentation.""" - myargs = ['mycmd', '--docs', 'all'] - with mock.patch('sys.argv', myargs): - assert 0 == main(), 'Expected 0 exit code' + myargs = ["mycmd", "--docs", "all"] + with mock.patch("sys.argv", myargs): + assert 0 == main(), "Expected 0 exit code" out, _err = capsys.readouterr() - assert '\nNTP\n---\n' in out - assert '\nRuncmd\n------\n' in out + assert "\nNTP\n---\n" in out + assert "\nRuncmd\n------\n" in out def test_main_validates_config_file(self, tmpdir, capsys): """When --config-file parameter is provided, main validates schema.""" - myyaml = tmpdir.join('my.yaml') - myargs = ['mycmd', '--config-file', myyaml.strpath] - myyaml.write(b'#cloud-config\nntp:') # shortest ntp schema - with mock.patch('sys.argv', myargs): - assert 0 == main(), 'Expected 0 exit code' + myyaml = tmpdir.join("my.yaml") + myargs = ["mycmd", "--config-file", myyaml.strpath] + myyaml.write(b"#cloud-config\nntp:") # shortest ntp schema + with mock.patch("sys.argv", myargs): + assert 0 == main(), "Expected 0 exit code" out, _err = capsys.readouterr() - assert 'Valid cloud-config: {0}\n'.format(myyaml) == out + assert "Valid cloud-config: {0}\n".format(myyaml) == out - @mock.patch('cloudinit.config.schema.read_cfg_paths') - @mock.patch('cloudinit.config.schema.os.getuid', return_value=0) + @mock.patch("cloudinit.config.schema.read_cfg_paths") + @mock.patch("cloudinit.config.schema.os.getuid", return_value=0) def test_main_validates_system_userdata( self, m_getuid, m_read_cfg_paths, capsys, paths ): """When --system is provided, main validates system userdata.""" m_read_cfg_paths.return_value = paths ud_file = paths.get_ipath_cur("userdata_raw") - write_file(ud_file, b'#cloud-config\nntp:') - myargs = ['mycmd', '--system'] - with mock.patch('sys.argv', myargs): - assert 0 == main(), 'Expected 0 exit code' + write_file(ud_file, b"#cloud-config\nntp:") + myargs = ["mycmd", "--system"] + with mock.patch("sys.argv", myargs): + assert 0 == main(), "Expected 0 exit code" out, _err = capsys.readouterr() - assert 'Valid cloud-config: system userdata\n' == out + assert "Valid cloud-config: system userdata\n" == out - @mock.patch('cloudinit.config.schema.os.getuid', return_value=1000) + @mock.patch("cloudinit.config.schema.os.getuid", return_value=1000) def test_main_system_userdata_requires_root(self, m_getuid, capsys, paths): """Non-root user can't use --system param""" - myargs = ['mycmd', '--system'] - with mock.patch('sys.argv', myargs): + myargs = ["mycmd", "--system"] + with mock.patch("sys.argv", myargs): with pytest.raises(SystemExit) as context_manager: main() assert 1 == context_manager.value.code _out, err = capsys.readouterr() expected = ( - 'Error:\nUnable to read system userdata as non-root user. ' - 'Try using sudo\n' + "Error:\nUnable to read system userdata as non-root user. " + "Try using sudo\n" ) assert expected == err def _get_meta_doc_examples(): - examples_dir = Path(cloud_init_project_dir('doc/examples')) + examples_dir = Path(cloud_init_project_dir("doc/examples")) assert examples_dir.is_dir() return ( @@ -712,7 +759,7 @@ class TestStrictMetaschema: } with pytest.raises( SchemaValidationError, - match=(r"Additional properties are not allowed.*") + match=r"Additional properties are not allowed.*", ): validate_cloudconfig_metaschema(validator, schema) |