diff --git a/cloud-init.changes b/cloud-init.changes index 1f6e001..f0cc88c 100644 --- a/cloud-init.changes +++ b/cloud-init.changes @@ -30,9 +30,10 @@ Thu Sep 21 17:32:55 EDT 2017 - rjschwei@suse.com + Forward port cloud-init-sysconfig-netpathfix.patch + Remove cloud-init-net-sysconfig-lp1665441.patch, included upstream + Remove cloud-init-python26.patch, included upstream + + Remove skip-argparse-on-python3.patch + Add cloud-init-tests-set-exec.patch + Add cloud-init-final-no-apt.patch - + Remove skip-argparse-on-python3.patch + + Add zypp_add_repo_test.patch + doc: document GCE datasource. [Arnd Hannemann] + suse: updates to templates to support openSUSE and SLES. + [Robert Schweikert] (LP: #1718640) diff --git a/cloud-init.spec b/cloud-init.spec index 62fe931..39245d9 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -28,6 +28,7 @@ Source0: %{name}-%{version}.tar.gz Source1: rsyslog-cloud-init.cfg # FIXME zypp_add_repos.diff needs proposed for upstream merge Patch4: zypp_add_repos.diff +Patch5: zypp_add_repo_test.patch Patch10: cloud-init-no-user-lock-if-already-locked.patch Patch12: fix-default-systemd-unit-dir.patch # FIXME cloud-init-more-tasks.patch proposed for upstream merge @@ -188,6 +189,7 @@ Documentation and examples for cloud-init tools %prep %setup -q %patch4 -p0 +%patch5 -p0 %patch10 -p1 %patch12 %patch13 diff --git a/zypp_add_repo_test.patch b/zypp_add_repo_test.patch new file mode 100644 index 0000000..4aa4135 --- /dev/null +++ b/zypp_add_repo_test.patch @@ -0,0 +1,262 @@ +Index: tests/unittests/test_handler/test_handler_zypper_add_repo.py +=================================================================== +--- /dev/null ++++ tests/unittests/test_handler/test_handler_zypper_add_repo.py +@@ -0,0 +1,238 @@ ++ ++# This file is part of cloud-init. See LICENSE file for license information. ++ ++import glob ++import os ++ ++from cloudinit.config import cc_zypper_add_repo ++from cloudinit import util ++ ++from cloudinit.tests import helpers ++from cloudinit.tests.helpers import mock ++ ++try: ++ from configparser import ConfigParser ++except ImportError: ++ from ConfigParser import ConfigParser ++import logging ++from six import StringIO ++ ++LOG = logging.getLogger(__name__) ++ ++ ++class TestConfig(helpers.FilesystemMockingTestCase): ++ def setUp(self): ++ super(TestConfig, self).setUp() ++ self.tmp = self.tmp_dir() ++ 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' ++ }, ++ ] ++ } ++ 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") ++ ++ 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'] ++ if len(repos) != 2: ++ assert 'Number of repos written is "%d" expected 2' % len(repos) ++ for repo in repos: ++ repo_name = os.path.basename(repo) ++ if repo_name not in expected_repos: ++ assert 'Found repo with name "%s"; unexpected' % repo_name ++ # Validation that the content gets properly written is in another test ++ ++ def test_write_repo(self): ++ """Verify the content of a repo file""" ++ cfg = { ++ 'repos': [ ++ { ++ 'baseurl': 'http://foo', ++ 'name': 'test-foo', ++ 'id': 'testing-foo' ++ }, ++ ] ++ } ++ root_d = self.tmp_dir() ++ cc_zypper_add_repo._write_repos(cfg['repos'], root_d) ++ contents = util.load_file("%s/testing-foo.repo" % root_d) ++ parser = ConfigParser() ++ parser.readfp(StringIO(contents)) ++ expected = { ++ '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)) ++ 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' ++ } ++ } ++ root_d = self.tmp_dir() ++ helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) ++ self.reRoot(root_d) ++ 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' ++ ] ++ for item in contents.split('\n'): ++ if item not in expected: ++ self.assertIsNone(item) ++ ++ @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' ++ } ++ } ++ root_d = self.tmp_dir() ++ helpers.populate_dir(root_d, {self.zypp_conf: '# Zypp config\n'}) ++ self.reRoot(root_d) ++ 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' ++ ] ++ for item in contents.split('\n'): ++ if item not in expected: ++ self.assertIsNone(item) ++ # Not finding teh right path for mocking :( ++ # assert mock_logging.warning.called ++ ++ def test_empty_config_section_no_new_data(self): ++ """When the config section is empty no new data should be written to ++ zypp.conf""" ++ cfg = self._get_base_config_repos() ++ cfg['zypper']['config'] = None ++ root_d = self.tmp_dir() ++ helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) ++ self.reRoot(root_d) ++ 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') ++ ++ 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""" ++ cfg = self._get_base_config_repos() ++ cfg['zypper']['config'] = { ++ 'download.deltarpm': None ++ } ++ root_d = self.tmp_dir() ++ helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) ++ self.reRoot(root_d) ++ 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') ++ ++ 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', ++ } ++ 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'}) ++ self.reRoot(root_d) ++ 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', ++ ] ++ 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'] ++ if len(repos) != 2: ++ assert 'Number of repos written is "%d" expected 2' % len(repos) ++ for repo in repos: ++ repo_name = os.path.basename(repo) ++ if repo_name not in expected_repos: ++ assert 'Found repo with name "%s"; unexpected' % repo_name ++ ++ def test_no_config_section_no_new_data(self): ++ """When there is no config section no new data should be written to ++ zypp.conf""" ++ cfg = self._get_base_config_repos() ++ root_d = self.tmp_dir() ++ helpers.populate_dir(root_d, {self.zypp_conf: '# No data'}) ++ self.reRoot(root_d) ++ 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') ++ ++ 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) ++ self.assertEqual(len(content), 0) ++ ++ def _get_base_config_repos(self): ++ """Basic valid repo configuration""" ++ cfg = { ++ 'zypper': { ++ 'repos': [ ++ { ++ 'baseurl': 'http://foo', ++ 'name': 'test-foo', ++ 'id': 'testing-foo' ++ }, ++ { ++ 'baseurl': 'http://bar', ++ 'name': 'test-bar', ++ 'id': 'testing-bar' ++ } ++ ] ++ } ++ } ++ return cfg +Index: tests/unittests/test_handler/test_schema.py +=================================================================== +--- tests/unittests/test_handler/test_schema.py ++++ tests/unittests/test_handler/test_schema.py +@@ -27,7 +27,13 @@ class GetSchemaTest(CiTestCase): + """Every cloudconfig module with schema is listed in allOf keyword.""" + schema = get_schema() + self.assertItemsEqual( +- ['cc_bootcmd', 'cc_ntp', 'cc_resizefs', 'cc_runcmd'], ++ [ ++ 'cc_bootcmd', ++ 'cc_ntp', ++ 'cc_resizefs', ++ 'cc_runcmd', ++ 'cc_zypper_add_repo' ++ ], + [subschema['id'] for subschema in schema['allOf']]) + self.assertEqual('cloud-config-schema', schema['id']) + self.assertEqual(