From b169d2c1be4a26127b426352a2ea320cda3f5285 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Mon, 18 Jan 2021 21:28:52 -0600 Subject: [PATCH 01/17] openstack: read the dynamic metadata group vendor_data2.json This has been present in the openestack metadata since version 'Newton'. Bug: 1841104 --- cloudinit/cmd/tests/test_main.py | 3 +- cloudinit/helpers.py | 6 ++ cloudinit/settings.py | 1 + cloudinit/sources/DataSourceOpenStack.py | 8 +++ cloudinit/sources/__init__.py | 12 +++- cloudinit/sources/helpers/openstack.py | 5 ++ cloudinit/stages.py | 64 +++++++++++++++++++ tests/unittests/test_data.py | 33 ++++++++-- .../test_datasource/test_openstack.py | 32 +++++++++- 9 files changed, 155 insertions(+), 9 deletions(-) diff --git a/cloudinit/cmd/tests/test_main.py b/cloudinit/cmd/tests/test_main.py index 585b3b0eab4..78b27441c47 100644 --- a/cloudinit/cmd/tests/test_main.py +++ b/cloudinit/cmd/tests/test_main.py @@ -127,7 +127,8 @@ def set_hostname(name, cfg, cloud, log, args): 'syslog_fix_perms': [ 'syslog:adm', 'root:adm', 'root:wheel', 'root:root' ], - 'vendor_data': {'enabled': True, 'prefix': []}}) + 'vendor_data': {'enabled': True, 'prefix': []}, + 'vendor_data2': {'enabled': True, 'prefix': []}}) updated_cfg.pop('system_info') self.assertEqual(updated_cfg, cfg) diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py index 9752ad28319..4bef3eaa4f4 100644 --- a/cloudinit/helpers.py +++ b/cloudinit/helpers.py @@ -230,6 +230,9 @@ def _get_instance_configs(self): cc_paths = ['cloud_config'] if self._include_vendor: + # the order is important here: we want vendor2 (dynamic vendor data from OpenStack) + # to override vendor (static data from OpenStack) + cc_paths.append('vendor2_cloud_config') cc_paths.append('vendor_cloud_config') for cc_p in cc_paths: @@ -337,9 +340,12 @@ def __init__(self, path_cfgs, ds=None): "obj_pkl": "obj.pkl", "cloud_config": "cloud-config.txt", "vendor_cloud_config": "vendor-cloud-config.txt", + "vendor2_cloud_config": "vendor2-cloud-config.txt", "data": "data", "vendordata_raw": "vendor-data.txt", + "vendordata2_raw": "vendor-data2.txt", "vendordata": "vendor-data.txt.i", + "vendordata2": "vendor-data2.txt.i", "instance_id": ".instance-id", "manual_clean_marker": "manual-clean", "warnings": "warnings", diff --git a/cloudinit/settings.py b/cloudinit/settings.py index ca4ffa8e681..7516e17bcfa 100644 --- a/cloudinit/settings.py +++ b/cloudinit/settings.py @@ -56,6 +56,7 @@ 'network': {'renderers': None}, }, 'vendor_data': {'enabled': True, 'prefix': []}, + 'vendor_data2': {'enabled': True, 'prefix': []}, } # Valid frequencies of handlers/modules diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py index b3406c67ea8..619a171e014 100644 --- a/cloudinit/sources/DataSourceOpenStack.py +++ b/cloudinit/sources/DataSourceOpenStack.py @@ -167,6 +167,14 @@ def _get_data(self): LOG.warning("Invalid content in vendor-data: %s", e) self.vendordata_raw = None + vd2 = results.get('vendordata2') + self.vendordata2_pure = vd2 + try: + self.vendordata2_raw = sources.convert_vendordata(vd2) + except ValueError as e: + LOG.warning("Invalid content in vendor-data2: %s", e) + self.vendordata2_raw = None + return True def _crawl_metadata(self): diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 9dccc6870fc..572adff7f2f 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -187,7 +187,7 @@ class DataSource(metaclass=abc.ABCMeta): cached_attr_defaults = ( ('ec2_metadata', UNSET), ('network_json', UNSET), ('metadata', {}), ('userdata', None), ('userdata_raw', None), - ('vendordata', None), ('vendordata_raw', None)) + ('vendordata', None), ('vendordata_raw', None), ('vendordata2', None), ('vendordata2_raw', None)) _dirty_cache = False @@ -203,7 +203,9 @@ def __init__(self, sys_cfg, distro, paths, ud_proc=None): self.metadata = {} self.userdata_raw = None self.vendordata = None + self.vendordata2 = None self.vendordata_raw = None + self.vendordata2_raw = None self.ds_cfg = util.get_cfg_by_path( self.sys_cfg, ("datasource", self.dsname), {}) @@ -392,6 +394,11 @@ def get_vendordata(self): self.vendordata = self.ud_proc.process(self.get_vendordata_raw()) return self.vendordata + def get_vendordata2(self): + if self.vendordata2 is None: + self.vendordata2 = self.ud_proc.process(self.get_vendordata2_raw()) + return self.vendordata2 + @property def fallback_interface(self): """Determine the network interface used during local network config.""" @@ -494,6 +501,9 @@ def get_userdata_raw(self): def get_vendordata_raw(self): return self.vendordata_raw + def get_vendordata2_raw(self): + return self.vendordata2_raw + # the data sources' config_obj is a cloud-config formated # object that came to it from ways other than cloud-config # because cloud-config content would be handled elsewhere diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py index 3e6365f1d9a..4f566e64d05 100644 --- a/cloudinit/sources/helpers/openstack.py +++ b/cloudinit/sources/helpers/openstack.py @@ -247,6 +247,11 @@ def datafiles(version): False, load_json_anytype, ) + files['vendordata2'] = ( + self._path_join("openstack", version, 'vendor_data2.json'), + False, + load_json_anytype, + ) files['networkdata'] = ( self._path_join("openstack", version, 'network_data.json'), False, diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 0cce6e8061c..c2ac69c4932 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -362,6 +362,7 @@ def cloudify(self): def update(self): self._store_userdata() self._store_vendordata() + self._store_vendordata2() def setup_datasource(self): with events.ReportEventStack("setup-datasource", @@ -404,6 +405,18 @@ def _store_vendordata(self): util.write_file(self._get_ipath('vendordata'), str(processed_vd), 0o600) + def _store_vendordata2(self): + raw_vd = self.datasource.get_vendordata2_raw() + if raw_vd is None: + raw_vd = b'' + util.write_file(self._get_ipath('vendordata2_raw'), raw_vd, 0o600) + # processed vendor data is a Mime message, so write it as string. + processed_vd = str(self.datasource.get_vendordata2()) + if processed_vd is None: + processed_vd = '' + util.write_file(self._get_ipath('vendordata2'), str(processed_vd), + 0o600) + def _default_handlers(self, opts=None): if opts is None: opts = {} @@ -434,6 +447,11 @@ def _default_vendordata_handlers(self): opts={'script_path': 'vendor_scripts', 'cloud_config_path': 'vendor_cloud_config'}) + def _default_vendordata2_handlers(self): + return self._default_handlers( + opts={'script_path': 'vendor_scripts', + 'cloud_config_path': 'vendor2_cloud_config'}) + def _do_handlers(self, data_msg, c_handlers_list, frequency, excluded=None): """ @@ -557,6 +575,11 @@ def consume_data(self, frequency=PER_INSTANCE): parent=self.reporter): self._consume_vendordata(frequency) + with events.ReportEventStack("consume-vendor-data2", + "reading and applying vendor-data2", + parent=self.reporter): + self._consume_vendordata2(frequency) + # Perform post-consumption adjustments so that # modules that run during the init stage reflect # this consumed set. @@ -613,6 +636,47 @@ def _consume_vendordata(self, frequency=PER_INSTANCE): self._do_handlers(vendor_data_msg, c_handlers_list, frequency, excluded=no_handlers) + def _consume_vendordata2(self, frequency=PER_INSTANCE): + """ + Consume the vendordata2 and run the part handlers on it + """ + if not self.datasource.get_vendordata2_raw(): + LOG.debug("no vendordata2 from datasource") + return + + _cc_merger = helpers.ConfigMerger(paths=self._paths, + datasource=self.datasource, + additional_fns=[], + base_cfg=self.cfg, + include_vendor=False) + vdcfg = _cc_merger.cfg.get('vendor_data2', {}) + + if not isinstance(vdcfg, dict): + vdcfg = {'enabled': False} + LOG.warning("invalid 'vendor_data' setting. resetting to: %s", + vdcfg) + + enabled = vdcfg.get('enabled') + no_handlers = vdcfg.get('disabled_handlers', None) + + if not util.is_true(enabled): + LOG.debug("vendordata2 consumption is disabled.") + return + + LOG.debug("vendordata2 will be consumed. disabled_handlers=%s", + no_handlers) + + # Ensure vendordata source fetched before activation (just incase) + vendor2_data_msg = self.datasource.get_vendordata2() + + # This keeps track of all the active handlers, while excluding what the + # users doesn't want run, i.e. boot_hook, cloud_config, shell_script + c_handlers_list = self._default_vendordata2_handlers() + + # Run the handlers + self._do_handlers(vendor2_data_msg, c_handlers_list, frequency, + excluded=no_handlers) + def _consume_userdata(self, frequency=PER_INSTANCE): """ Consume the userdata and run the part handlers diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py index fb2b55e8e79..f4139838185 100644 --- a/tests/unittests/test_data.py +++ b/tests/unittests/test_data.py @@ -33,11 +33,12 @@ class FakeDataSource(sources.DataSource): - def __init__(self, userdata=None, vendordata=None): + def __init__(self, userdata=None, vendordata=None, vendordata2=None): sources.DataSource.__init__(self, {}, None, None) self.metadata = {'instance-id': INSTANCE_ID} self.userdata_raw = userdata self.vendordata_raw = vendordata + self.vendordata2_raw = vendordata2 def count_messages(root): @@ -105,13 +106,14 @@ def test_simple_jsonp(self): self.assertEqual('qux', cc['baz']) self.assertEqual('qux2', cc['bar']) - def test_simple_jsonp_vendor_and_user(self): + def test_simple_jsonp_vendor_and_vendor2_and_user(self): # test that user-data wins over vendor user_blob = ''' #cloud-config-jsonp [ { "op": "add", "path": "/baz", "value": "qux" }, - { "op": "add", "path": "/bar", "value": "qux2" } + { "op": "add", "path": "/bar", "value": "qux2" }, + { "op": "add", "path": "/foobar", "value": "qux3" } ] ''' vendor_blob = ''' @@ -119,12 +121,21 @@ def test_simple_jsonp_vendor_and_user(self): [ { "op": "add", "path": "/baz", "value": "quxA" }, { "op": "add", "path": "/bar", "value": "quxB" }, - { "op": "add", "path": "/foo", "value": "quxC" } + { "op": "add", "path": "/foo", "value": "quxC" }, + { "op": "add", "path": "/corge", "value": "quxEE" } +] +''' + vendor2_blob = ''' +#cloud-config-jsonp +[ + { "op": "add", "path": "/corge", "value": "quxD" }, + { "op": "add", "path": "/grault", "value": "quxFF" }, + { "op": "add", "path": "/foobar", "value": "quxGG" } ] ''' self.reRoot() initer = stages.Init() - initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob) + initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob, vendordata2=vendor2_blob) initer.read_cfg() initer.initialize() initer.fetch() @@ -138,9 +149,15 @@ def test_simple_jsonp_vendor_and_user(self): (_which_ran, _failures) = mods.run_section('cloud_init_modules') cfg = mods.cfg self.assertIn('vendor_data', cfg) + self.assertIn('vendor_data2', cfg) + # Confirm that vendordata2 overrides vendordata, and that + # userdata overrides both self.assertEqual('qux', cfg['baz']) self.assertEqual('qux2', cfg['bar']) + self.assertEqual('qux3', cfg['foobar']) self.assertEqual('quxC', cfg['foo']) + self.assertEqual('quxD', cfg['corge']) + self.assertEqual('quxFF', cfg['grault']) def test_simple_jsonp_no_vendor_consumed(self): # make sure that vendor data is not consumed @@ -293,6 +310,10 @@ def test_vendordata_script(self): vendor_blob = ''' #!/bin/bash echo "test" +''' + vendor2_blob = ''' +#!/bin/bash +echo "dynamic test" ''' user_blob = ''' @@ -303,7 +324,7 @@ def test_vendordata_script(self): ''' new_root = self.reRoot() initer = stages.Init() - initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob) + initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob, vendordata2=vendor2_blob) initer.read_cfg() initer.initialize() initer.fetch() diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py index 415755aa9eb..478f3503d90 100644 --- a/tests/unittests/test_datasource/test_openstack.py +++ b/tests/unittests/test_datasource/test_openstack.py @@ -40,6 +40,9 @@ VENDOR_DATA = { 'magic': '', } +VENDOR_DATA2 = { + 'static': {} +} OSTACK_META = { 'availability_zone': 'nova', 'files': [{'content_path': '/content/0000', 'path': '/etc/foo.cfg'}, @@ -60,6 +63,7 @@ {'links': [], 'networks': [], 'services': []}), 'openstack/latest/user_data': USER_DATA, 'openstack/latest/vendor_data.json': json.dumps(VENDOR_DATA), + 'openstack/latest/vendor_data2.json': json.dumps(VENDOR_DATA2), } EC2_FILES = { 'latest/user-data': USER_DATA, @@ -142,6 +146,7 @@ def test_successful(self): _register_uris(self.VERSION, EC2_FILES, EC2_META, OS_FILES) f = _read_metadata_service() self.assertEqual(VENDOR_DATA, f.get('vendordata')) + self.assertEqual(VENDOR_DATA2, f.get('vendordata2')) self.assertEqual(CONTENT_0, f['files']['/etc/foo.cfg']) self.assertEqual(CONTENT_1, f['files']['/etc/bar/bar.cfg']) self.assertEqual(2, len(f['files'])) @@ -163,6 +168,7 @@ def test_no_ec2(self): _register_uris(self.VERSION, {}, {}, OS_FILES) f = _read_metadata_service() self.assertEqual(VENDOR_DATA, f.get('vendordata')) + self.assertEqual(VENDOR_DATA2, f.get('vendordata2')) self.assertEqual(CONTENT_0, f['files']['/etc/foo.cfg']) self.assertEqual(CONTENT_1, f['files']['/etc/bar/bar.cfg']) self.assertEqual(USER_DATA, f.get('userdata')) @@ -195,6 +201,7 @@ def test_userdata_empty(self): _register_uris(self.VERSION, {}, {}, os_files) f = _read_metadata_service() self.assertEqual(VENDOR_DATA, f.get('vendordata')) + self.assertEqual(VENDOR_DATA2, f.get('vendordata2')) self.assertEqual(CONTENT_0, f['files']['/etc/foo.cfg']) self.assertEqual(CONTENT_1, f['files']['/etc/bar/bar.cfg']) self.assertFalse(f.get('userdata')) @@ -210,6 +217,17 @@ def test_vendordata_empty(self): self.assertEqual(CONTENT_1, f['files']['/etc/bar/bar.cfg']) self.assertFalse(f.get('vendordata')) + def test_vendordata2_empty(self): + os_files = copy.deepcopy(OS_FILES) + for k in list(os_files.keys()): + if k.endswith('vendor_data2.json'): + os_files.pop(k, None) + _register_uris(self.VERSION, {}, {}, os_files) + f = _read_metadata_service() + self.assertEqual(CONTENT_0, f['files']['/etc/foo.cfg']) + self.assertEqual(CONTENT_1, f['files']['/etc/bar/bar.cfg']) + self.assertFalse(f.get('vendordata2')) + def test_vendordata_invalid(self): os_files = copy.deepcopy(OS_FILES) for k in list(os_files.keys()): @@ -218,6 +236,14 @@ def test_vendordata_invalid(self): _register_uris(self.VERSION, {}, {}, os_files) self.assertRaises(BrokenMetadata, _read_metadata_service) + def test_vendordata2_invalid(self): + os_files = copy.deepcopy(OS_FILES) + for k in list(os_files.keys()): + if k.endswith('vendor_data2.json'): + os_files[k] = '{' # some invalid json + _register_uris(self.VERSION, {}, {}, os_files) + self.assertRaises(BrokenMetadata, _read_metadata_service) + def test_metadata_invalid(self): os_files = copy.deepcopy(OS_FILES) for k in list(os_files.keys()): @@ -246,6 +272,7 @@ def test_datasource(self, m_dhcp): self.assertEqual(USER_DATA, ds_os.userdata_raw) self.assertEqual(2, len(ds_os.files)) self.assertEqual(VENDOR_DATA, ds_os.vendordata_pure) + self.assertEqual(VENDOR_DATA2, ds_os.vendordata2_pure) self.assertIsNone(ds_os.vendordata_raw) m_dhcp.assert_not_called() @@ -278,6 +305,7 @@ def test_local_datasource(self, m_dhcp, m_net): self.assertEqual(USER_DATA, ds_os_local.userdata_raw) self.assertEqual(2, len(ds_os_local.files)) self.assertEqual(VENDOR_DATA, ds_os_local.vendordata_pure) + self.assertEqual(VENDOR_DATA2, ds_os_local.vendordata2_pure) self.assertIsNone(ds_os_local.vendordata_raw) m_dhcp.assert_called_with('eth9', None) @@ -401,7 +429,7 @@ def test_wb__crawl_metadata_does_not_persist(self): self.assertIsNone(ds_os.vendordata_raw) self.assertEqual( ['dsmode', 'ec2-metadata', 'files', 'metadata', 'networkdata', - 'userdata', 'vendordata', 'version'], + 'userdata', 'vendordata', 'vendordata2', 'version'], sorted(crawled_data.keys())) self.assertEqual('local', crawled_data['dsmode']) self.assertEqual(EC2_META, crawled_data['ec2-metadata']) @@ -415,6 +443,7 @@ def test_wb__crawl_metadata_does_not_persist(self): crawled_data['networkdata']) self.assertEqual(USER_DATA, crawled_data['userdata']) self.assertEqual(VENDOR_DATA, crawled_data['vendordata']) + self.assertEqual(VENDOR_DATA2, crawled_data['vendordata2']) self.assertEqual(2, crawled_data['version']) @@ -681,6 +710,7 @@ def test_read_v2_os_ocata(self): 'version': 2, 'metadata': expected_md, 'vendordata': vendor_data, + 'vendordata2': vendor_data2, 'networkdata': network_data, 'ec2-metadata': mock_read_ec2.return_value, 'files': {}, From 7a89399d3f16ca70791d34fb4eb1c8ca40789c06 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Tue, 19 Jan 2021 12:23:23 -0500 Subject: [PATCH 02/17] Revert "ssh_util: handle non-default AuthorizedKeysFile config (#586)" (#775) This reverts commit b0e73814db4027dba0b7dc0282e295b7f653325c. --- cloudinit/ssh_util.py | 6 +++--- tests/unittests/test_sshutil.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py index d5113996c81..c08042d6ff0 100644 --- a/cloudinit/ssh_util.py +++ b/cloudinit/ssh_util.py @@ -262,13 +262,13 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG): except (IOError, OSError): # Give up and use a default key filename - auth_key_fns.append(default_authorizedkeys_file) + auth_key_fns[0] = default_authorizedkeys_file util.logexc(LOG, "Failed extracting 'AuthorizedKeysFile' in SSH " "config from %r, using 'AuthorizedKeysFile' file " "%r instead", DEF_SSHD_CFG, auth_key_fns[0]) - # always store all the keys in the first file configured on sshd_config - return (auth_key_fns[0], parse_authorized_keys(auth_key_fns)) + # always store all the keys in the user's private file + return (default_authorizedkeys_file, parse_authorized_keys(auth_key_fns)) def setup_user_keys(keys, username, options=None): diff --git a/tests/unittests/test_sshutil.py b/tests/unittests/test_sshutil.py index 88a111e3752..fd1d1baca71 100644 --- a/tests/unittests/test_sshutil.py +++ b/tests/unittests/test_sshutil.py @@ -593,7 +593,7 @@ def test_multiple_authorizedkeys_file_order1(self, m_getpwnam): fpw.pw_name, sshd_config) content = ssh_util.update_authorized_keys(auth_key_entries, []) - self.assertEqual(authorized_keys, auth_key_fn) + self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn) self.assertTrue(VALID_CONTENT['rsa'] in content) self.assertTrue(VALID_CONTENT['dsa'] in content) @@ -610,7 +610,7 @@ def test_multiple_authorizedkeys_file_order2(self, m_getpwnam): sshd_config = self.tmp_path('sshd_config') util.write_file( sshd_config, - "AuthorizedKeysFile %s %s" % (user_keys, authorized_keys) + "AuthorizedKeysFile %s %s" % (authorized_keys, user_keys) ) (auth_key_fn, auth_key_entries) = ssh_util.extract_authorized_keys( @@ -618,7 +618,7 @@ def test_multiple_authorizedkeys_file_order2(self, m_getpwnam): ) content = ssh_util.update_authorized_keys(auth_key_entries, []) - self.assertEqual(user_keys, auth_key_fn) + self.assertEqual("%s/.ssh/authorized_keys" % fpw.pw_dir, auth_key_fn) self.assertTrue(VALID_CONTENT['rsa'] in content) self.assertTrue(VALID_CONTENT['dsa'] in content) From 8feacec78d71f30eb55d4ea3d49bb4b263d0892e Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Tue, 19 Jan 2021 12:40:16 -0500 Subject: [PATCH 03/17] integration_tests: log image serial if available (#772) Ubuntu cloud images ship /etc/cloud/build.info which includes a line with the build serial used to identify the image: serial: 20210108 This is valuable information when verifying Ubuntu issues (to confirm that testing is happening against the expected image), but is also useful when debugging test failures: manifests of all packages in (the base) images can be found at http://cloud-images.ubuntu.com/ --- tests/integration_tests/clouds.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration_tests/clouds.py b/tests/integration_tests/clouds.py index 72d770581cf..fc0a61d5be9 100644 --- a/tests/integration_tests/clouds.py +++ b/tests/integration_tests/clouds.py @@ -173,6 +173,9 @@ def launch(self, user_data=None, launch_kwargs=None, wait=True, 'cloud-init version: %s', instance.execute("cloud-init --version") ) + serial = instance.execute("grep serial /etc/cloud/build.info") + if serial: + log.info('image serial: %s', serial.split()[1]) return instance def get_instance(self, cloud_instance, settings=integration_settings): From ea45f7a216c9fdd480587bfa51d15552ac456477 Mon Sep 17 00:00:00 2001 From: Anton Chaporgin Date: Tue, 19 Jan 2021 20:57:25 +0300 Subject: [PATCH 04/17] Add antonyc to .github-cla-signers (#747) --- tools/.github-cla-signers | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers index ac95b4224ba..4cee82acab5 100644 --- a/tools/.github-cla-signers +++ b/tools/.github-cla-signers @@ -2,6 +2,7 @@ ader1990 ajmyyra AlexBaranowski Aman306 +antonyc aswinrajamannar beezly bipinbachhao From eee7b6959f039dbb060fd199974e6826dc7e7a33 Mon Sep 17 00:00:00 2001 From: Dan Kenigsberg Date: Wed, 20 Jan 2021 21:50:10 +0200 Subject: [PATCH 05/17] Use proper spelling for Red Hat (#778) The company name has two distinct words. Signed-off-by: Dan Kenigsberg --- ChangeLog | 2 +- cloudinit/config/cc_resolv_conf.py | 4 ++-- cloudinit/config/cc_rh_subscription.py | 8 ++++---- doc/rtd/topics/examples.rst | 4 ++-- tests/cloud_tests/testcases/examples/TODO.md | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ChangeLog b/ChangeLog index 33b2bf741f3..fb87a765f28 100644 --- a/ChangeLog +++ b/ChangeLog @@ -528,7 +528,7 @@ - docs: add additional details to per-instance/once [Joshua Powers] - Update doc-requirements.txt [Joshua Powers] - doc-requirements: add missing dep [Joshua Powers] - - dhcp: Support RedHat dhcp rfc3442 lease format for option 121 (#76) + - dhcp: Support Red Hat dhcp rfc3442 lease format for option 121 (#76) [Eric Lafontaine] (LP: #1850642) - network_state: handle empty v1 config (#45) (LP: #1852496) - docs: Add document on how to report bugs [Joshua Powers] diff --git a/cloudinit/config/cc_resolv_conf.py b/cloudinit/config/cc_resolv_conf.py index 7beb11ca3a9..466dad03c5b 100644 --- a/cloudinit/config/cc_resolv_conf.py +++ b/cloudinit/config/cc_resolv_conf.py @@ -14,12 +14,12 @@ This module is intended to manage resolv.conf in environments where early configuration of resolv.conf is necessary for further bootstrapping and/or where configuration management such as puppet or chef own dns configuration. -As Debian/Ubuntu will, by default, utilize resolvconf, and similarly RedHat +As Debian/Ubuntu will, by default, utilize resolvconf, and similarly Red Hat will use sysconfig, this module is likely to be of little use unless those are configured correctly. .. note:: - For RedHat with sysconfig, be sure to set PEERDNS=no for all DHCP + For Red Hat with sysconfig, be sure to set PEERDNS=no for all DHCP enabled NICs. .. note:: diff --git a/cloudinit/config/cc_rh_subscription.py b/cloudinit/config/cc_rh_subscription.py index 28d62e9d304..693317c24b2 100644 --- a/cloudinit/config/cc_rh_subscription.py +++ b/cloudinit/config/cc_rh_subscription.py @@ -5,15 +5,15 @@ # This file is part of cloud-init. See LICENSE file for license information. """ -RedHat Subscription -------------------- +Red Hat Subscription +-------------------- **Summary:** register red hat enterprise linux based system -Register a RedHat system either by username and password *or* activation and +Register a Red Hat system either by username and password *or* activation and org. Following a sucessful registration, you can auto-attach subscriptions, set the service level, add subscriptions based on pool id, enable/disable yum repositories based on repo id, and alter the rhsm_baseurl and server-hostname -in ``/etc/rhsm/rhs.conf``. For more details, see the ``Register RedHat +in ``/etc/rhsm/rhs.conf``. For more details, see the ``Register Red Hat Subscription`` example config. **Internal name:** ``cc_rh_subscription`` diff --git a/doc/rtd/topics/examples.rst b/doc/rtd/topics/examples.rst index 81860f858e5..97fd616da7b 100644 --- a/doc/rtd/topics/examples.rst +++ b/doc/rtd/topics/examples.rst @@ -149,8 +149,8 @@ Disk setup :language: yaml :linenos: -Register RedHat Subscription -============================ +Register Red Hat Subscription +============================= .. literalinclude:: ../../examples/cloud-config-rh_subscription.txt :language: yaml diff --git a/tests/cloud_tests/testcases/examples/TODO.md b/tests/cloud_tests/testcases/examples/TODO.md index 8db0e98e001..cde699a7c96 100644 --- a/tests/cloud_tests/testcases/examples/TODO.md +++ b/tests/cloud_tests/testcases/examples/TODO.md @@ -6,7 +6,7 @@ Below lists each of the issing examples and why it is not currently added. - Puppet (takes > 60 seconds to run) - Manage resolve.conf (lxd backend overrides changes) - Adding a yum repository (need centos system) - - Register RedHat Subscription (need centos system + subscription) + - Register Red Hat Subscription (need centos system + subscription) - Adjust mount points mounted (need multiple disks) - Call a url when finished (need end point) - Reboot/poweroff when finished (how to test) From 6bde21bda7b6eb0ceca4e66f917f9a4bbc1e7acc Mon Sep 17 00:00:00 2001 From: Dan Kenigsberg Date: Thu, 21 Jan 2021 17:17:57 +0200 Subject: [PATCH 06/17] doc: avoid two warnings (#781) Two shell code blocks are not marked as such, confusing rst to consider them as yaml. Be explicit about their syntax, and use $ prompt to match elsewhere in the docs. /home/travis/build/canonical/cloud-init/doc/rtd/topics/format.rst:28: WARNING: Could not lex literal_block as "yaml". Highlighting skipped. /home/travis/build/canonical/cloud-init/doc/rtd/topics/format.rst:52: WARNING: Could not lex literal_block as "yaml". Highlighting skipped. Signed-off-by: Dan Kenigsberg --- doc/rtd/topics/format.rst | 14 +++++++++----- tools/.github-cla-signers | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/rtd/topics/format.rst b/doc/rtd/topics/format.rst index d03e4caf398..fa8aa925da5 100644 --- a/doc/rtd/topics/format.rst +++ b/doc/rtd/topics/format.rst @@ -23,9 +23,11 @@ Using a mime-multi part file, the user can specify more than one type of data. For example, both a user data script and a cloud-config type could be specified. -Supported content-types are listed from the cloud-init subcommand make-mime:: +Supported content-types are listed from the cloud-init subcommand make-mime: - % cloud-init devel make-mime --list-types +.. code-block:: shell-session + + $ cloud-init devel make-mime --list-types cloud-boothook cloud-config cloud-config-archive @@ -47,9 +49,11 @@ The cloud-init subcommand can generate MIME multi-part files: `make-mime`_. separated by a colon (e.g. ``config.yaml:cloud-config``) and emits a MIME multipart message to stdout. An example invocation, assuming you have your cloud config in ``config.yaml`` and a shell script in ``script.sh`` and want -to store the multipart message in ``user-data``:: +to store the multipart message in ``user-data``: + +.. code-block:: shell-session - % cloud-init devel make-mime -a config.yaml:cloud-config -a script.sh:x-shellscript > user-data + $ cloud-init devel make-mime -a config.yaml:cloud-config -a script.sh:x-shellscript > user-data .. _make-mime: https://github.com/canonical/cloud-init/blob/master/cloudinit/cmd/devel/make_mime.py @@ -70,7 +74,7 @@ archive. Example ------- -:: +.. code-block:: shell-session $ cat myscript.sh diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers index 4cee82acab5..a2354594846 100644 --- a/tools/.github-cla-signers +++ b/tools/.github-cla-signers @@ -9,6 +9,7 @@ bipinbachhao BirknerAlex candlerb cawamata +dankenigsberg dermotbradley dhensby eandersson From 7c80e783fe5102f62a40466eeb7ade4a853e452c Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 21 Jan 2021 13:50:25 -0600 Subject: [PATCH 07/17] Adding self to cla signers (#776) --- tools/.github-cla-signers | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers index a2354594846..48b4b96de29 100644 --- a/tools/.github-cla-signers +++ b/tools/.github-cla-signers @@ -2,6 +2,7 @@ ader1990 ajmyyra AlexBaranowski Aman306 +andrewbogott antonyc aswinrajamannar beezly From 66bb59e1ece9395a3142dee696445df145180ff4 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 21 Jan 2021 17:00:46 -0600 Subject: [PATCH 08/17] flake8 fixes --- cloudinit/sources/__init__.py | 3 ++- tests/unittests/test_data.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 572adff7f2f..1ad1880ded0 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -187,7 +187,8 @@ class DataSource(metaclass=abc.ABCMeta): cached_attr_defaults = ( ('ec2_metadata', UNSET), ('network_json', UNSET), ('metadata', {}), ('userdata', None), ('userdata_raw', None), - ('vendordata', None), ('vendordata_raw', None), ('vendordata2', None), ('vendordata2_raw', None)) + ('vendordata', None), ('vendordata_raw', None), + ('vendordata2', None), ('vendordata2_raw', None)) _dirty_cache = False diff --git a/tests/unittests/test_data.py b/tests/unittests/test_data.py index f4139838185..8c968ae9b31 100644 --- a/tests/unittests/test_data.py +++ b/tests/unittests/test_data.py @@ -135,7 +135,9 @@ def test_simple_jsonp_vendor_and_vendor2_and_user(self): ''' self.reRoot() initer = stages.Init() - initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob, vendordata2=vendor2_blob) + initer.datasource = FakeDataSource(user_blob, + vendordata=vendor_blob, + vendordata2=vendor2_blob) initer.read_cfg() initer.initialize() initer.fetch() @@ -324,7 +326,9 @@ def test_vendordata_script(self): ''' new_root = self.reRoot() initer = stages.Init() - initer.datasource = FakeDataSource(user_blob, vendordata=vendor_blob, vendordata2=vendor2_blob) + initer.datasource = FakeDataSource(user_blob, + vendordata=vendor_blob, + vendordata2=vendor2_blob) initer.read_cfg() initer.initialize() initer.fetch() From dc778cf39a6c375c01dc262d4b2de067700c7a68 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Sat, 23 Jan 2021 15:51:48 -0600 Subject: [PATCH 09/17] Another flake8 fix because I'm hopeless --- cloudinit/helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py index 4bef3eaa4f4..fc5011ecfbb 100644 --- a/cloudinit/helpers.py +++ b/cloudinit/helpers.py @@ -230,7 +230,8 @@ def _get_instance_configs(self): cc_paths = ['cloud_config'] if self._include_vendor: - # the order is important here: we want vendor2 (dynamic vendor data from OpenStack) + # the order is important here: we want vendor2 + # (dynamic vendor data from OpenStack) # to override vendor (static data from OpenStack) cc_paths.append('vendor2_cloud_config') cc_paths.append('vendor_cloud_config') From 5679729f6cf6c700941b25708a15a783d0f5b359 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Wed, 3 Feb 2021 13:55:56 -0600 Subject: [PATCH 10/17] Refactor _store_userdata/_store_vendordata/_store_vendordata2 to be DRYer --- cloudinit/stages.py | 61 ++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index c2ac69c4932..01ed2dddbc2 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -360,9 +360,18 @@ def cloudify(self): reporter=self.reporter) def update(self): - self._store_userdata() - self._store_vendordata() - self._store_vendordata2() + self._store_rawdata(self.datasource.get_userdata_raw(), + 'userdata') + self._store_processeddata(self.datasource.get_userdata(), + 'userdata') + self._store_rawdata(self.datasource.get_vendordata_raw(), + 'vendordata') + self._store_processeddata(self.datasource.get_vendordata(), + 'vendordata') + self._store_rawdata(self.datasource.get_vendordata2_raw(), + 'vendordata2') + self._store_processeddata(self.datasource.get_vendordata2(), + 'vendordata2') def setup_datasource(self): with events.ReportEventStack("setup-datasource", @@ -382,40 +391,18 @@ def activate_datasource(self): is_new_instance=self.is_new_instance()) self._write_to_cache() - def _store_userdata(self): - raw_ud = self.datasource.get_userdata_raw() - if raw_ud is None: - raw_ud = b'' - util.write_file(self._get_ipath('userdata_raw'), raw_ud, 0o600) - # processed userdata is a Mime message, so write it as string. - processed_ud = self.datasource.get_userdata() - if processed_ud is None: - raw_ud = '' - util.write_file(self._get_ipath('userdata'), str(processed_ud), 0o600) - - def _store_vendordata(self): - raw_vd = self.datasource.get_vendordata_raw() - if raw_vd is None: - raw_vd = b'' - util.write_file(self._get_ipath('vendordata_raw'), raw_vd, 0o600) - # processed vendor data is a Mime message, so write it as string. - processed_vd = str(self.datasource.get_vendordata()) - if processed_vd is None: - processed_vd = '' - util.write_file(self._get_ipath('vendordata'), str(processed_vd), - 0o600) - - def _store_vendordata2(self): - raw_vd = self.datasource.get_vendordata2_raw() - if raw_vd is None: - raw_vd = b'' - util.write_file(self._get_ipath('vendordata2_raw'), raw_vd, 0o600) - # processed vendor data is a Mime message, so write it as string. - processed_vd = str(self.datasource.get_vendordata2()) - if processed_vd is None: - processed_vd = '' - util.write_file(self._get_ipath('vendordata2'), str(processed_vd), - 0o600) + def _store_rawdata(self, data, datasource): + # Raw data is bytes, not a string + if data is None: + data = b'' + util.write_file(self._get_ipath('%s_raw' % datasource), data, 0o600) + + def _store_processeddata(self, data, datasource): + # processed is a Mime message, so write as string. + processeddata = str(data) + if processeddata is None: + processeddata = '' + util.write_file(self._get_ipath(datasource), str(processeddata), 0o600) def _default_handlers(self, opts=None): if opts is None: From 9f4ff703627d40f8c235d943c7c2fc591c7e4e28 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Wed, 3 Feb 2021 14:09:09 -0600 Subject: [PATCH 11/17] Make _consume_vendordata/_consume_vendordata2 DRYer --- cloudinit/stages.py | 92 +++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 01ed2dddbc2..2ee5f0ade5a 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -560,12 +560,12 @@ def consume_data(self, frequency=PER_INSTANCE): with events.ReportEventStack("consume-vendor-data", "reading and applying vendor-data", parent=self.reporter): - self._consume_vendordata(frequency) + self._consume_vendordata("vendordata", frequency) with events.ReportEventStack("consume-vendor-data2", "reading and applying vendor-data2", parent=self.reporter): - self._consume_vendordata2(frequency) + self._consume_vendordata("vendordata2", frequency) # Perform post-consumption adjustments so that # modules that run during the init stage reflect @@ -578,90 +578,66 @@ def consume_data(self, frequency=PER_INSTANCE): # objects before the load of the userdata happened, # this is expected. - def _consume_vendordata(self, frequency=PER_INSTANCE): + def _consume_vendordata(self, vendor_source, frequency=PER_INSTANCE): """ Consume the vendordata and run the part handlers on it """ + # User-data should have been consumed first. # So we merge the other available cloud-configs (everything except # vendor provided), and check whether or not we should consume # vendor data at all. That gives user or system a chance to override. - if not self.datasource.get_vendordata_raw(): - LOG.debug("no vendordata from datasource") - return + if vendor_source == 'vendordata': + if not self.datasource.get_vendordata_raw(): + LOG.debug("no vendordata from datasource") + return + cfg_name = 'vendor_data' + elif vendor_source == 'vendordata2': + if not self.datasource.get_vendordata2_raw(): + LOG.debug("no vendordata2 from datasource") + return + cfg_name = 'vendor_data2' + else: + raise RuntimeError("vendor_source arg must be either 'vendordata'" + " or 'vendordata2'") _cc_merger = helpers.ConfigMerger(paths=self._paths, datasource=self.datasource, additional_fns=[], base_cfg=self.cfg, include_vendor=False) - vdcfg = _cc_merger.cfg.get('vendor_data', {}) + vdcfg = _cc_merger.cfg.get(cfg_name, {}) if not isinstance(vdcfg, dict): vdcfg = {'enabled': False} - LOG.warning("invalid 'vendor_data' setting. resetting to: %s", - vdcfg) + LOG.warning("invalid %s setting. resetting to: %s", + cfg_name, vdcfg) enabled = vdcfg.get('enabled') no_handlers = vdcfg.get('disabled_handlers', None) if not util.is_true(enabled): - LOG.debug("vendordata consumption is disabled.") - return - - LOG.debug("vendor data will be consumed. disabled_handlers=%s", - no_handlers) - - # Ensure vendordata source fetched before activation (just incase) - vendor_data_msg = self.datasource.get_vendordata() - - # This keeps track of all the active handlers, while excluding what the - # users doesn't want run, i.e. boot_hook, cloud_config, shell_script - c_handlers_list = self._default_vendordata_handlers() - - # Run the handlers - self._do_handlers(vendor_data_msg, c_handlers_list, frequency, - excluded=no_handlers) - - def _consume_vendordata2(self, frequency=PER_INSTANCE): - """ - Consume the vendordata2 and run the part handlers on it - """ - if not self.datasource.get_vendordata2_raw(): - LOG.debug("no vendordata2 from datasource") + LOG.debug("%s consumption is disabled." % vendor_source) return - _cc_merger = helpers.ConfigMerger(paths=self._paths, - datasource=self.datasource, - additional_fns=[], - base_cfg=self.cfg, - include_vendor=False) - vdcfg = _cc_merger.cfg.get('vendor_data2', {}) - - if not isinstance(vdcfg, dict): - vdcfg = {'enabled': False} - LOG.warning("invalid 'vendor_data' setting. resetting to: %s", - vdcfg) + LOG.debug("%s will be consumed. disabled_handlers=%s", + vendor_source, no_handlers) - enabled = vdcfg.get('enabled') - no_handlers = vdcfg.get('disabled_handlers', None) - - if not util.is_true(enabled): - LOG.debug("vendordata2 consumption is disabled.") - return - - LOG.debug("vendordata2 will be consumed. disabled_handlers=%s", - no_handlers) + # Ensure vendordata source fetched before activation (just in case.) - # Ensure vendordata source fetched before activation (just incase) - vendor2_data_msg = self.datasource.get_vendordata2() + # c_handlers_list keeps track of all the active handlers, while + # excluding what the users doesn't want run, i.e. boot_hook, + # cloud_config, shell_script + if vendor_source == 'vendordata': + vendor_data_msg = self.datasource.get_vendordata() + c_handlers_list = self._default_vendordata_handlers() + else: + vendor_data_msg = self.datasource.get_vendordata2() + c_handlers_list = self._default_vendordata2_handlers() - # This keeps track of all the active handlers, while excluding what the - # users doesn't want run, i.e. boot_hook, cloud_config, shell_script - c_handlers_list = self._default_vendordata2_handlers() # Run the handlers - self._do_handlers(vendor2_data_msg, c_handlers_list, frequency, + self._do_handlers(vendor_data_msg, c_handlers_list, frequency, excluded=no_handlers) def _consume_userdata(self, frequency=PER_INSTANCE): From 1dd2b8a4301bf28eb269c6cb777e01145f7656ef Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Wed, 3 Feb 2021 14:40:53 -0600 Subject: [PATCH 12/17] Add some docs for OpenStack dynamic vendordata (aka vendordata2) --- doc/rtd/topics/datasources/openstack.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst index b23b4b7c57f..9b752007649 100644 --- a/doc/rtd/topics/datasources/openstack.rst +++ b/doc/rtd/topics/datasources/openstack.rst @@ -83,3 +83,11 @@ including how it can be disabled by users on instances, see :doc:`/topics/vendordata`. .. vi: textwidth=78 + +OpenStack can also be configured to provide 'dynamic vendordata' which is provided by +the DynamicJSON provider and appears under a different metadata path, +/vendor_data2.json. + +Cloud-init will look for a ``cloud-init`` at the vendor_data2 path; if found, +settings are applied after (and, hence, overriding) the settings from static +vendor data. Both sets of vendor data can be overridden by user data. From 95887857f21a7cbd9d80eef2276edff367ec4825 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 4 Feb 2021 12:19:30 -0600 Subject: [PATCH 13/17] More linter fixes --- cloudinit/stages.py | 1 - doc/rtd/topics/datasources/openstack.rst | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 2ee5f0ade5a..9bcc209eceb 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -635,7 +635,6 @@ def _consume_vendordata(self, vendor_source, frequency=PER_INSTANCE): vendor_data_msg = self.datasource.get_vendordata2() c_handlers_list = self._default_vendordata2_handlers() - # Run the handlers self._do_handlers(vendor_data_msg, c_handlers_list, frequency, excluded=no_handlers) diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst index 9b752007649..ae9f46acb44 100644 --- a/doc/rtd/topics/datasources/openstack.rst +++ b/doc/rtd/topics/datasources/openstack.rst @@ -84,9 +84,9 @@ including how it can be disabled by users on instances, see .. vi: textwidth=78 -OpenStack can also be configured to provide 'dynamic vendordata' which is provided by -the DynamicJSON provider and appears under a different metadata path, -/vendor_data2.json. +OpenStack can also be configured to provide 'dynamic vendordata' +which is provided by the DynamicJSON provider and appears under a +different metadata path, /vendor_data2.json. Cloud-init will look for a ``cloud-init`` at the vendor_data2 path; if found, settings are applied after (and, hence, overriding) the settings from static From a72442f88889cf1b35f915137f1c77ee20ceb121 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 4 Feb 2021 13:51:01 -0600 Subject: [PATCH 14/17] Use lazy % formatting in a logline --- cloudinit/stages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 9bcc209eceb..f4b3ff83c02 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -617,7 +617,7 @@ def _consume_vendordata(self, vendor_source, frequency=PER_INSTANCE): no_handlers = vdcfg.get('disabled_handlers', None) if not util.is_true(enabled): - LOG.debug("%s consumption is disabled." % vendor_source) + LOG.debug("%s consumption is disabled.", vendor_source) return LOG.debug("%s will be consumed. disabled_handlers=%s", From fdb954115767adecac40d0b83fbc2f6932b33892 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 4 Feb 2021 20:50:34 -0600 Subject: [PATCH 15/17] _store_processeddata: remove an unneeded typecast Co-authored-by: James Falcon --- cloudinit/stages.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index f4b3ff83c02..7505dee5fc5 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -397,12 +397,11 @@ def _store_rawdata(self, data, datasource): data = b'' util.write_file(self._get_ipath('%s_raw' % datasource), data, 0o600) - def _store_processeddata(self, data, datasource): + def _store_processeddata(self, processed_data, datasource): # processed is a Mime message, so write as string. - processeddata = str(data) - if processeddata is None: - processeddata = '' - util.write_file(self._get_ipath(datasource), str(processeddata), 0o600) + if processed_data is None: + processed_data = '' + util.write_file(self._get_ipath(datasource), str(processed_data), 0o600) def _default_handlers(self, opts=None): if opts is None: From 1f87a287f1b9bf55363e64c07b016cbed2d70924 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 4 Feb 2021 19:09:47 -0600 Subject: [PATCH 16/17] Fix misplaced vi hint in openstack.rst --- doc/rtd/topics/datasources/openstack.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/rtd/topics/datasources/openstack.rst b/doc/rtd/topics/datasources/openstack.rst index ae9f46acb44..62d0fc03665 100644 --- a/doc/rtd/topics/datasources/openstack.rst +++ b/doc/rtd/topics/datasources/openstack.rst @@ -82,8 +82,6 @@ For more general information about how cloud-init handles vendor data, including how it can be disabled by users on instances, see :doc:`/topics/vendordata`. -.. vi: textwidth=78 - OpenStack can also be configured to provide 'dynamic vendordata' which is provided by the DynamicJSON provider and appears under a different metadata path, /vendor_data2.json. @@ -91,3 +89,5 @@ different metadata path, /vendor_data2.json. Cloud-init will look for a ``cloud-init`` at the vendor_data2 path; if found, settings are applied after (and, hence, overriding) the settings from static vendor data. Both sets of vendor data can be overridden by user data. + +.. vi: textwidth=78 From 381baccdedf54c6dedef947d9339a91eaa0f6c30 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Thu, 4 Feb 2021 19:21:45 -0600 Subject: [PATCH 17/17] flake8 fix --- cloudinit/stages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 7505dee5fc5..3ef4491c894 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -401,7 +401,8 @@ def _store_processeddata(self, processed_data, datasource): # processed is a Mime message, so write as string. if processed_data is None: processed_data = '' - util.write_file(self._get_ipath(datasource), str(processed_data), 0o600) + util.write_file(self._get_ipath(datasource), + str(processed_data), 0o600) def _default_handlers(self, opts=None): if opts is None: