From 73f34575da4f99a376998516a13c3a79cc640ae3 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Fri, 12 May 2023 11:29:15 -0600 Subject: schema: read_cfg_paths call init.fetch to lookup /v/l/c/instance Fix cloud-init schema --system being unable to find merged userdata stored at /var/lib/cloud/instance/cloud_config.txt. Init.paths.get_ipath only has visibility to merged cloud config in /var/lib/cloud//cloud-config.txt after fetching the existing cached datasource which provides instance-id from metadata in order to determine the unique instance-id which represents the path to the cloud-config.txt. To support reuse of read_cfg_paths helper function, add an optional parameter fetch_existing_datasource which indicates whether reading the existing datasource is necessary for this helper function. cloud-init schema --system calls read_cfg_paths providing fetch_existing_datasource="trust" prior to calls to paths.get_ipath(). --- cloudinit/cmd/devel/__init__.py | 12 ++++++++++-- cloudinit/config/schema.py | 2 +- tests/integration_tests/modules/test_cli.py | 5 ++++- tests/unittests/cmd/devel/test_init.py | 29 +++++++++++++++++++++++++++++ tests/unittests/config/test_schema.py | 3 +++ 5 files changed, 47 insertions(+), 4 deletions(-) mode change 100755 => 100644 cloudinit/cmd/devel/__init__.py create mode 100644 tests/unittests/cmd/devel/test_init.py diff --git a/cloudinit/cmd/devel/__init__.py b/cloudinit/cmd/devel/__init__.py old mode 100755 new mode 100644 index 9a8f2ebd..357c4ae7 --- a/cloudinit/cmd/devel/__init__.py +++ b/cloudinit/cmd/devel/__init__.py @@ -17,9 +17,17 @@ def addLogHandlerCLI(logger, log_level): return logger -def read_cfg_paths() -> Paths: - """Return a Paths object based on the system configuration on disk.""" +def read_cfg_paths(fetch_existing_datasource: str = "") -> Paths: + """Return a Paths object based on the system configuration on disk. + + :param fetch_existing_datasource: String one of check or trust. Whether to + load the pickled datasource before returning Paths. This is necessary + when using instance paths via Paths.get_ipath method which are only + known from the instance-id metadata in the detected datasource. + """ init = Init(ds_deps=[]) + if fetch_existing_datasource: + init.fetch(existing=fetch_existing_datasource) init.read_cfg() return init.paths diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py index 9005e924..4b7e8016 100644 --- a/cloudinit/config/schema.py +++ b/cloudinit/config/schema.py @@ -1264,7 +1264,7 @@ def handle_schema_args(name, args): if args.docs: print(load_doc(args.docs)) return - paths = read_cfg_paths() + paths = read_cfg_paths(fetch_existing_datasource="trust") if args.instance_data: instance_data_path = args.instance_data elif os.getuid() != 0: diff --git a/tests/integration_tests/modules/test_cli.py b/tests/integration_tests/modules/test_cli.py index 30f56ad7..e1bcd7c7 100644 --- a/tests/integration_tests/modules/test_cli.py +++ b/tests/integration_tests/modules/test_cli.py @@ -58,7 +58,10 @@ def test_invalid_userdata(client: IntegrationInstance): result = client.execute("cloud-init schema --system") assert not result.ok assert "Cloud config schema errors" in result.stderr - assert 'needs to begin with "#cloud-config"' in result.stderr + assert ( + "Expected first line to be one of: #!, ## template: jinja," + " #cloud-boothook, #cloud-config" in result.stderr + ) result = client.execute("cloud-init status --long") if not result.ok: raise AssertionError( diff --git a/tests/unittests/cmd/devel/test_init.py b/tests/unittests/cmd/devel/test_init.py new file mode 100644 index 00000000..503bc08f --- /dev/null +++ b/tests/unittests/cmd/devel/test_init.py @@ -0,0 +1,29 @@ +from unittest import mock + +from cloudinit import stages +from cloudinit.cmd.devel import read_cfg_paths +from tests.unittests.util import TEST_INSTANCE_ID, FakeDataSource + + +class TestReadCfgPaths: + def test_read_cfg_paths_fetches_cached_datasource(self, tmpdir): + init = stages.Init() + init._cfg = { + "system_info": { + "distro": "ubuntu", + "paths": {"cloud_dir": tmpdir, "run_dir": tmpdir}, + } + } + with mock.patch("cloudinit.cmd.devel.Init") as m_init: + with mock.patch.object(init, "_restore_from_cache") as restore: + restore.return_value = FakeDataSource(paths=init.paths) + with mock.patch( + "cloudinit.util.read_conf_from_cmdline", return_value={} + ): + m_init.return_value = init + paths = read_cfg_paths() + assert paths.get_ipath() is None + paths = read_cfg_paths(fetch_existing_datasource="trust") + assert ( + paths.get_ipath() == f"/var/lib/cloud/instances/{TEST_INSTANCE_ID}" + ) diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py index ceb689a4..b5314b69 100644 --- a/tests/unittests/config/test_schema.py +++ b/tests/unittests/config/test_schema.py @@ -2060,3 +2060,6 @@ apt_reboot_if_required: Default: ``false``. Deprecated in version 22.2.\ expected_err.format(cfg_file=user_data_fn, id_path=id_path) == err ) assert "deprec" not in caplog.text + assert read_cfg_paths.call_args_list == [ + mock.call(fetch_existing_datasource="trust") + ] -- cgit v1.2.1