diff -Nru cloud-init-23.2/ChangeLog cloud-init-23.2.1/ChangeLog --- cloud-init-23.2/ChangeLog 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/ChangeLog 2023-06-27 22:49:12.000000000 +0000 @@ -1,3 +1,6 @@ +23.2.1 + - nocloud: parse_cmdline no longer detects nocloud-net datasource (#4204) + (Fixes: 4203) (LP: #2025180) 23.2 - BSD: simplify finding MBR partitions by removing duplicate code [Mina Galić] diff -Nru cloud-init-23.2/cloudinit/sources/__init__.py cloud-init-23.2.1/cloudinit/sources/__init__.py --- cloud-init-23.2/cloudinit/sources/__init__.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/cloudinit/sources/__init__.py 2023-06-27 22:49:12.000000000 +0000 @@ -365,7 +365,10 @@ if self.override_ds_detect(): return self._get_data() elif self.ds_detect(): - LOG.debug("Machine is running on %s.", self) + LOG.debug( + "Detected platform: %s. Checking for active instance data", + self, + ) return self._get_data() else: LOG.debug("Datasource type %s is not detected.", self) @@ -1177,9 +1180,9 @@ Passing by command line overrides runtime datasource detection """ cmdline = util.get_cmdline() - ds_parse_0 = re.search(r"ds=([a-zA-Z]+)(\s|$|;)", cmdline) - ds_parse_1 = re.search(r"ci\.ds=([a-zA-Z]+)(\s|$|;)", cmdline) - ds_parse_2 = re.search(r"ci\.datasource=([a-zA-Z]+)(\s|$|;)", cmdline) + ds_parse_0 = re.search(r"ds=([^\s;]+)", cmdline) + ds_parse_1 = re.search(r"ci\.ds=([^\s;]+)", cmdline) + ds_parse_2 = re.search(r"ci\.datasource=([^\s;]+)", cmdline) ds = ds_parse_0 or ds_parse_1 or ds_parse_2 deprecated = ds_parse_1 or ds_parse_2 if deprecated: diff -Nru cloud-init-23.2/cloudinit/version.py cloud-init-23.2.1/cloudinit/version.py --- cloud-init-23.2/cloudinit/version.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/cloudinit/version.py 2023-06-27 22:49:12.000000000 +0000 @@ -4,7 +4,7 @@ # # This file is part of cloud-init. See LICENSE file for license information. -__VERSION__ = "23.2" +__VERSION__ = "23.2.1" _PACKAGED_VERSION = "@@PACKAGED_VERSION@@" FEATURES = [ diff -Nru cloud-init-23.2/debian/changelog cloud-init-23.2.1/debian/changelog --- cloud-init-23.2/debian/changelog 2023-06-06 22:09:06.000000000 +0000 +++ cloud-init-23.2.1/debian/changelog 2023-06-28 22:16:26.000000000 +0000 @@ -1,3 +1,11 @@ +cloud-init (23.2.1-0ubuntu0~22.04.1) jammy; urgency=medium + + * Upstream snapshot based on 23.2.1. (LP: #2025180). + List of changes from upstream can be found at + https://raw.githubusercontent.com/canonical/cloud-init/23.2.1/ChangeLog + + -- Brett Holman Wed, 28 Jun 2023 16:16:26 -0600 + cloud-init (23.2-0ubuntu0~22.04.1) jammy; urgency=medium * d/control: Remove pep8 dependency. It is no longer used. diff -Nru cloud-init-23.2/tests/data/kernel_cmdline_match/meta-data cloud-init-23.2.1/tests/data/kernel_cmdline_match/meta-data --- cloud-init-23.2/tests/data/kernel_cmdline_match/meta-data 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-23.2.1/tests/data/kernel_cmdline_match/meta-data 2023-06-27 22:49:12.000000000 +0000 @@ -0,0 +1 @@ +instance=id: dfa16c21-4b25-4767-b94c-f98f5d73736d diff -Nru cloud-init-23.2/tests/data/kernel_cmdline_match/user-data cloud-init-23.2.1/tests/data/kernel_cmdline_match/user-data --- cloud-init-23.2/tests/data/kernel_cmdline_match/user-data 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-23.2.1/tests/data/kernel_cmdline_match/user-data 2023-06-27 22:49:12.000000000 +0000 @@ -0,0 +1,3 @@ +#cloud-config +runcmd: +- touch /cmdline-userdata-success diff -Nru cloud-init-23.2/tests/data/kernel_cmdline_match/vendor-data cloud-init-23.2.1/tests/data/kernel_cmdline_match/vendor-data --- cloud-init-23.2/tests/data/kernel_cmdline_match/vendor-data 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-23.2.1/tests/data/kernel_cmdline_match/vendor-data 2023-06-27 22:49:12.000000000 +0000 @@ -0,0 +1,2 @@ +#cloud-config +{} diff -Nru cloud-init-23.2/tests/integration_tests/cmd/test_status.py cloud-init-23.2.1/tests/integration_tests/cmd/test_status.py --- cloud-init-23.2/tests/integration_tests/cmd/test_status.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/tests/integration_tests/cmd/test_status.py 2023-06-27 22:49:12.000000000 +0000 @@ -1,33 +1,11 @@ """Tests for `cloud-init status`""" -from time import sleep - import pytest from tests.integration_tests.clouds import IntegrationCloud from tests.integration_tests.instances import IntegrationInstance from tests.integration_tests.integration_settings import PLATFORM from tests.integration_tests.releases import CURRENT_RELEASE, IS_UBUNTU, JAMMY - - -# We're implementing our own here in case cloud-init status --wait -# isn't working correctly (LP: #1966085) -def _wait_for_cloud_init(client: IntegrationInstance): - last_exception = None - for _ in range(30): - try: - result = client.execute("cloud-init status") - if ( - result - and result.ok - and ("running" not in result or "not run" not in result) - ): - return result - except Exception as e: - last_exception = e - sleep(1) - raise Exception( - "cloud-init status did not return successfully." - ) from last_exception +from tests.integration_tests.util import wait_for_cloud_init def _remove_nocloud_dir_and_reboot(client: IntegrationInstance): @@ -66,6 +44,7 @@ # Jammy and above will use LXD datasource by default if CURRENT_RELEASE < JAMMY: _remove_nocloud_dir_and_reboot(client) - status_out = _wait_for_cloud_init(client).stdout.strip() + status_out = wait_for_cloud_init(client).stdout.strip() assert "status: disabled" in status_out assert client.execute("cloud-init status --wait").ok + assert client.execute("test -f /cmdline-userdata-success").ok diff -Nru cloud-init-23.2/tests/integration_tests/conftest.py cloud-init-23.2.1/tests/integration_tests/conftest.py --- cloud-init-23.2/tests/integration_tests/conftest.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/tests/integration_tests/conftest.py 2023-06-27 22:49:12.000000000 +0000 @@ -206,7 +206,6 @@ lxd_use_exec = fixture_utils.closest_marker_args_or( request, "lxd_use_exec", None ) - launch_kwargs = {} if name is not None: launch_kwargs["name"] = name diff -Nru cloud-init-23.2/tests/integration_tests/test_kernel_commandline_match.py cloud-init-23.2.1/tests/integration_tests/test_kernel_commandline_match.py --- cloud-init-23.2/tests/integration_tests/test_kernel_commandline_match.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/tests/integration_tests/test_kernel_commandline_match.py 2023-06-27 22:49:12.000000000 +0000 @@ -1,7 +1,14 @@ +import logging + import pytest +from tests.integration_tests.clouds import IntegrationCloud +from tests.integration_tests.conftest import get_validated_source from tests.integration_tests.instances import IntegrationInstance from tests.integration_tests.integration_settings import PLATFORM +from tests.integration_tests.util import wait_for_cloud_init + +log = logging.getLogger("integration_testing") def override_kernel_cmdline(ds_str: str, c: IntegrationInstance) -> str: @@ -37,7 +44,16 @@ # most likely be as simple as updating the output path for grub-mkconfig client.execute("grub-mkconfig -o /boot/efi/EFI/ubuntu/grub.cfg") client.execute("cloud-init clean --logs") - client.instance.shutdown() + client.instance.shutdown(wait=False) + try: + client.instance.wait_for_state("STOPPED", num_retries=20) + except RuntimeError as e: + log.warning( + "Retrying shutdown due to timeout on initial shutdown request %s", + str(e), + ) + client.instance.shutdown() + client.instance.execute_via_ssh = False client.instance.start() client.execute("cloud-init status --wait") @@ -75,18 +91,51 @@ ) in override_kernel_cmdline(ds_str, client) +GH_REPO_PATH = "https://raw.githubusercontent.com/canonical/cloud-init/main/" + + @pytest.mark.skipif(PLATFORM != "lxd_vm", reason="Modifies grub config") @pytest.mark.lxd_use_exec -@pytest.mark.parametrize("ds_str", ("ci.ds=nocloud-net",)) +@pytest.mark.parametrize( + "ds_str", + (f"ds=nocloud-net;s={GH_REPO_PATH}tests/data/kernel_cmdline_match/",), +) def test_lxd_datasource_kernel_override_nocloud_net( - ds_str, client: IntegrationInstance + ds_str, session_cloud: IntegrationCloud ): """NoCloud requirements vary slightly from other datasources with parsing nocloud-net due to historical reasons. Do to this variation, this is implemented in NoCloud's ds_detect and therefore has a different log message. """ - - assert ( - "Machine is running on DataSourceNoCloud [seed=None][dsmode=net]." - ) in override_kernel_cmdline(ds_str, client) + _ds_name, _, seed_url = ds_str.partition(";") + _key, _, url_val = seed_url.partition("=") + source = get_validated_source(session_cloud) + with session_cloud.launch( + launch_kwargs={ + # On Jammy and above, we detect the LXD datasource using a + # socket available to the container. This prevents the socket + # from being exposed in the container, so LXD will not be detected. + # This allows us to wait for detection in 'init' stage with + # DataSourceNoCloudNet. + "config_dict": {"security.devlxd": False}, + "wait": False, # to prevent cloud-init status --wait + } + ) as client: + # We know this will be an LXD instance due to our pytest mark + client.instance.execute_via_ssh = False # pyright: ignore + assert wait_for_cloud_init(client, num_retries=60).ok + if source.installs_new_version(): + client.install_new_cloud_init( + source, take_snapshot=False, clean=False + ) + logs = override_kernel_cmdline(ds_str, client) + assert ( + "nocloud" + == client.execute("cloud-init query platform").stdout.strip() + ) + assert url_val in client.execute("cloud-init query subplatform").stdout + assert ( + "Detected platform: DataSourceNoCloudNet [seed=None]" + "[dsmode=net]. Checking for active instance data" + ) in logs diff -Nru cloud-init-23.2/tests/integration_tests/util.py cloud-init-23.2.1/tests/integration_tests/util.py --- cloud-init-23.2/tests/integration_tests/util.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/tests/integration_tests/util.py 2023-06-27 22:49:12.000000000 +0000 @@ -155,6 +155,27 @@ return key_pair(public_key, private_key) +# We're implementing our own here in case cloud-init status --wait +# isn't working correctly (LP: #1966085) +def wait_for_cloud_init(client: IntegrationInstance, num_retries: int = 30): + last_exception = None + for _ in range(num_retries): + try: + result = client.execute("cloud-init status") + if ( + result + and result.ok + and ("running" not in result or "not run" not in result) + ): + return result + except Exception as e: + last_exception = e + time.sleep(1) + raise Exception( + "cloud-init status did not return successfully." + ) from last_exception + + def get_console_log(client: IntegrationInstance): try: console_log = client.instance.console_log() diff -Nru cloud-init-23.2/tests/unittests/sources/test___init__.py cloud-init-23.2.1/tests/unittests/sources/test___init__.py --- cloud-init-23.2/tests/unittests/sources/test___init__.py 2023-05-24 00:17:20.000000000 +0000 +++ cloud-init-23.2.1/tests/unittests/sources/test___init__.py 2023-06-27 22:49:12.000000000 +0000 @@ -4,42 +4,45 @@ from cloudinit.sources import DataSourceOpenStack as ds from tests.unittests.helpers import mock +openstack_ds_name = ds.DataSourceOpenStack.dsname.lower() + @pytest.mark.parametrize( - "m_cmdline", + "m_cmdline, expected_ds", ( # test ci.ds= - "aosiejfoij ci.ds=OpenStack ", - "ci.ds=OpenStack", - "aosiejfoij ci.ds=OpenStack blah", - "aosiejfoij ci.ds=OpenStack faljskebflk", - "ci.ds=OpenStack;", - "ci.ds=openstack;", + ("aosiejfoij ci.ds=OpenStack ", openstack_ds_name), + ("ci.ds=OpenStack", openstack_ds_name), + ("aosiejfoij ci.ds=OpenStack blah", openstack_ds_name), + ("aosiejfoij ci.ds=OpenStack faljskebflk", openstack_ds_name), + ("ci.ds=OpenStack;", openstack_ds_name), + ("ci.ds=openstack;", openstack_ds_name), # test ci.datasource= - "aosiejfoij ci.datasource=OpenStack ", - "ci.datasource=OpenStack", - "aosiejfoij ci.datasource=OpenStack blah", - "aosiejfoij ci.datasource=OpenStack faljskebflk", - "ci.datasource=OpenStack;", - "ci.datasource=openstack;", + ("aosiejfoij ci.datasource=OpenStack ", openstack_ds_name), + ("ci.datasource=OpenStack", openstack_ds_name), + ("aosiejfoij ci.datasource=OpenStack blah", openstack_ds_name), + ("aosiejfoij ci.datasource=OpenStack faljskebflk", openstack_ds_name), + ("ci.datasource=OpenStack;", openstack_ds_name), + ("ci.datasource=openstack;", openstack_ds_name), # weird whitespace - "ci.datasource=OpenStack\n", - "ci.datasource=OpenStack\t", - "ci.datasource=OpenStack\r", - "ci.datasource=OpenStack\v", - "ci.ds=OpenStack\n", - "ci.ds=OpenStack\t", - "ci.ds=OpenStack\r", - "ci.ds=OpenStack\v", + ("ci.datasource=OpenStack\n", openstack_ds_name), + ("ci.datasource=OpenStack\t", openstack_ds_name), + ("ci.datasource=OpenStack\r", openstack_ds_name), + ("ci.datasource=OpenStack\v", openstack_ds_name), + ("ci.ds=OpenStack\n", openstack_ds_name), + ("ci.ds=OpenStack\t", openstack_ds_name), + ("ci.ds=OpenStack\r", openstack_ds_name), + ("ci.ds=OpenStack\v", openstack_ds_name), + ("ci.ds=nocloud-net\v", "nocloud-net"), + ("ci.datasource=nocloud\v", "nocloud"), ), ) -def test_ds_detect_kernel_commandline(m_cmdline): +def test_ds_detect_kernel_commandline(m_cmdline, expected_ds): """check commandline match""" with mock.patch( "cloudinit.util.get_cmdline", return_value=m_cmdline, ): assert ( - ds.DataSourceOpenStack.dsname.lower() - == sources.parse_cmdline().lower() + expected_ds == sources.parse_cmdline().lower() ), f"could not parse [{m_cmdline}]"