From f1f4c0a8902a8bc31912b34d84e3960461a4996a Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Tue, 25 Jun 2024 02:58:27 -0400 Subject: [PATCH] * Tue Jun 25 2024 Miroslav Rezanina - 23.4-13 - ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch [RHEL-17961] - ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch [RHEL-44337] - ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch [RHEL-44337] - ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch [RHEL-44598] - ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch [RHEL-44598] - ci-Update-pylint-version-to-support-python-3.12-5338.patch [RHEL-44598] - Resolves: RHEL-17961 ([RHEL-9] cloud-init fails to configure DNS search domains) - Resolves: RHEL-44337 ([rhel-9] fix `SUDO` configuration schema for users and groups) - Resolves: RHEL-44598 (fix pylint error and support python 3.12) --- ...-version-to-support-python-3.12-5338.patch | 235 ++++++++++++ ...les-to-reflect-alternative-ways-to-p.patch | 51 +++ ...dd-DNS-from-interface-config-to-reso.patch | 351 ++++++++++++++++++ ...E-abstract-interpretation-issues-486.patch | 73 ++++ ...gainst-FileNotFoundError-and-NameErr.patch | 132 +++++++ ...ema-Add-missing-sudo-definition-5418.patch | 53 +++ cloud-init.spec | 28 +- 7 files changed, 922 insertions(+), 1 deletion(-) create mode 100644 ci-Update-pylint-version-to-support-python-3.12-5338.patch create mode 100644 ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch create mode 100644 ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch create mode 100644 ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch create mode 100644 ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch create mode 100644 ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch diff --git a/ci-Update-pylint-version-to-support-python-3.12-5338.patch b/ci-Update-pylint-version-to-support-python-3.12-5338.patch new file mode 100644 index 0000000..a9154e9 --- /dev/null +++ b/ci-Update-pylint-version-to-support-python-3.12-5338.patch @@ -0,0 +1,235 @@ +From c34f5c4275c3ef7bee9a99e87bf6e37c5886b160 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Wed, 29 May 2024 03:34:38 +0530 +Subject: [PATCH 6/6] Update pylint version to support python 3.12 (#5338) + +RH-Author: Ani Sinha +RH-MergeRequest: 92: Update pylint version to support python 3.12 +RH-Jira: RHEL-44598 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Miroslav Rezanina +RH-Commit: [3/3] d6185e0a126e1589260ee59709fe933b5a780b78 (anisinha/cloud-init) + +Fedora 39 and above comes with python version 3.12. When running `tox -e pylint` +on cloud-init, we may experience issue such as the one reported here: +https://github.com/pylint-dev/pylint/issues/8782 + +Minimum version of pylint required in order to support python 3.12 is 3.0.2. +Please see https://github.com/pylint-dev/astroid/issues/2201 . Upon further +experimentation, it is seen that we need minimum pylint version 3.2.0 for +cloud-init. Update tox.ini in order to use this pylint version. + +Signed-off-by: Ani Sinha +(cherry picked from commit 5ad609ffdf4bb76c5665e12e34e1867b72bd4435) + + Conflicts: + cloudinit/sources/DataSourceWSL.py (does not exist) + cloudinit/util.py (doc added upstream) +--- + cloudinit/config/cc_mounts.py | 4 ++++ + cloudinit/distros/bsd.py | 2 ++ + cloudinit/distros/netbsd.py | 2 +- + cloudinit/sources/DataSourceAzure.py | 2 +- + cloudinit/sources/DataSourceEc2.py | 3 ++- + cloudinit/sources/DataSourceLXD.py | 2 +- + tests/integration_tests/conftest.py | 6 +++--- + tests/integration_tests/util.py | 2 +- + tests/unittests/config/test_cc_ntp.py | 2 ++ + tests/unittests/sources/test_gce.py | 1 + + tests/unittests/test_util.py | 2 ++ + tox.ini | 2 +- + 12 files changed, 21 insertions(+), 9 deletions(-) + +diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py +index 4efa2a29..d445b440 100644 +--- a/cloudinit/config/cc_mounts.py ++++ b/cloudinit/config/cc_mounts.py +@@ -304,6 +304,10 @@ def create_swapfile(fname: str, size: str) -> None: + "bs=1M", + "count=%s" % size, + ] ++ else: ++ raise subp.ProcessExecutionError( ++ "Missing dependency: 'dd' and 'fallocate' are not available" ++ ) + + try: + subp.subp(cmd, capture=True) +diff --git a/cloudinit/distros/bsd.py b/cloudinit/distros/bsd.py +index 761cf5c4..77e0385f 100644 +--- a/cloudinit/distros/bsd.py ++++ b/cloudinit/distros/bsd.py +@@ -120,6 +120,8 @@ class BSD(distros.Distro): + if not self.pkg_cmd_upgrade_prefix: + return + cmd = self.pkg_cmd_upgrade_prefix ++ else: ++ cmd = [] + + if args and isinstance(args, str): + cmd.append(args) +diff --git a/cloudinit/distros/netbsd.py b/cloudinit/distros/netbsd.py +index a5678907..b7f3f3d8 100644 +--- a/cloudinit/distros/netbsd.py ++++ b/cloudinit/distros/netbsd.py +@@ -12,7 +12,7 @@ import cloudinit.distros.bsd + from cloudinit import subp, util + + try: +- import crypt ++ import crypt # pylint: disable=W4901 + + salt = crypt.METHOD_BLOWFISH # pylint: disable=E1101 + blowfish_hash: Any = functools.partial( +diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py +index 11c14e20..eb0304c3 100644 +--- a/cloudinit/sources/DataSourceAzure.py ++++ b/cloudinit/sources/DataSourceAzure.py +@@ -51,7 +51,7 @@ from cloudinit.sources.helpers.azure import ( + from cloudinit.url_helper import UrlError + + try: +- import crypt ++ import crypt # pylint: disable=W4901 + + blowfish_hash: Any = functools.partial( + crypt.crypt, salt=f"$6${util.rand_str(strlen=16)}" +diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py +index 9e6bfbd1..384e4074 100644 +--- a/cloudinit/sources/DataSourceEc2.py ++++ b/cloudinit/sources/DataSourceEc2.py +@@ -312,6 +312,8 @@ class DataSourceEc2(sources.DataSource): + return None + + def wait_for_metadata_service(self): ++ urls = [] ++ start_time = 0 + mcfg = self.ds_cfg + + url_params = self.get_url_params() +@@ -345,7 +347,6 @@ class DataSourceEc2(sources.DataSource): + and self.cloud_name not in IDMSV2_SUPPORTED_CLOUD_PLATFORMS + ): + # if we can't get a token, use instance-id path +- urls = [] + url2base = {} + url_path = "{ver}/meta-data/instance-id".format( + ver=self.min_metadata_version +diff --git a/cloudinit/sources/DataSourceLXD.py b/cloudinit/sources/DataSourceLXD.py +index cd316101..4c95b907 100644 +--- a/cloudinit/sources/DataSourceLXD.py ++++ b/cloudinit/sources/DataSourceLXD.py +@@ -331,7 +331,7 @@ class MetaDataKeys(Flag): + CONFIG = auto() + DEVICES = auto() + META_DATA = auto() +- ALL = CONFIG | DEVICES | META_DATA ++ ALL = CONFIG | DEVICES | META_DATA # pylint: disable=E1131 + + + class _MetaDataReader: +diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py +index fa729b7d..ec211a00 100644 +--- a/tests/integration_tests/conftest.py ++++ b/tests/integration_tests/conftest.py +@@ -241,7 +241,7 @@ def _client( + + + @pytest.fixture +-def client( ++def client( # pylint: disable=W0135 + request, fixture_utils, session_cloud, setup_image + ) -> Iterator[IntegrationInstance]: + """Provide a client that runs for every test.""" +@@ -250,7 +250,7 @@ def client( + + + @pytest.fixture(scope="module") +-def module_client( ++def module_client( # pylint: disable=W0135 + request, fixture_utils, session_cloud, setup_image + ) -> Iterator[IntegrationInstance]: + """Provide a client that runs once per module.""" +@@ -259,7 +259,7 @@ def module_client( + + + @pytest.fixture(scope="class") +-def class_client( ++def class_client( # pylint: disable=W0135 + request, fixture_utils, session_cloud, setup_image + ) -> Iterator[IntegrationInstance]: + """Provide a client that runs once per class.""" +diff --git a/tests/integration_tests/util.py b/tests/integration_tests/util.py +index 0a15203c..e26e466c 100644 +--- a/tests/integration_tests/util.py ++++ b/tests/integration_tests/util.py +@@ -182,7 +182,7 @@ def wait_for_cloud_init(client: IntegrationInstance, num_retries: int = 30): + except Exception as e: + last_exception = e + time.sleep(1) +- raise Exception( ++ raise Exception( # pylint: disable=W0719 + "cloud-init status did not return successfully." + ) from last_exception + +diff --git a/tests/unittests/config/test_cc_ntp.py b/tests/unittests/config/test_cc_ntp.py +index a9444ec5..c9ce5daa 100644 +--- a/tests/unittests/config/test_cc_ntp.py ++++ b/tests/unittests/config/test_cc_ntp.py +@@ -248,6 +248,7 @@ class TestNtp(FilesystemMockingTestCase): + ) + + def _get_expected_pools(self, pools, distro, client): ++ expected_pools = None + if client in ["ntp", "chrony"]: + if client == "ntp" and distro == "alpine": + # NTP for Alpine Linux is Busybox's ntp which does not +@@ -263,6 +264,7 @@ class TestNtp(FilesystemMockingTestCase): + return expected_pools + + def _get_expected_servers(self, servers, distro, client): ++ expected_servers = None + if client in ["ntp", "chrony"]: + if client == "ntp" and distro == "alpine": + # NTP for Alpine Linux is Busybox's ntp which only supports +diff --git a/tests/unittests/sources/test_gce.py b/tests/unittests/sources/test_gce.py +index c0b19d3c..30a50236 100644 +--- a/tests/unittests/sources/test_gce.py ++++ b/tests/unittests/sources/test_gce.py +@@ -101,6 +101,7 @@ class TestDataSourceGCE(test_helpers.ResponsesTestCase): + gce_meta = GCE_META + + def _request_callback(request): ++ recursive = False + url_path = urlparse(request.url).path + if url_path.startswith("/computeMetadata/v1/"): + path = url_path.split("/computeMetadata/v1/")[1:][0] +diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py +index 519ef63c..de1326d4 100644 +--- a/tests/unittests/test_util.py ++++ b/tests/unittests/test_util.py +@@ -1677,6 +1677,8 @@ class TestRedirectOutputPreexecFn: + args = (test_string, None) + elif request.param == "errfmt": + args = (None, test_string) ++ else: ++ args = (None, None) + with mock.patch(M_PATH + "subprocess.Popen") as m_popen: + util.redirect_output(*args) + +diff --git a/tox.ini b/tox.ini +index 5f01a9a8..5199ca13 100644 +--- a/tox.ini ++++ b/tox.ini +@@ -25,7 +25,7 @@ hypothesis==6.31.6 + hypothesis_jsonschema==0.20.1 + isort==5.10.1 + mypy==0.950 +-pylint==2.13.9 ++pylint==3.2.0 + pytest==7.0.1 + ruff==0.0.285 + types-jsonschema==4.4.2 +-- +2.39.3 + diff --git a/ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch b/ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch new file mode 100644 index 0000000..8a68c53 --- /dev/null +++ b/ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch @@ -0,0 +1,51 @@ +From 52c04e1a523a450dfce70bc441963eb6a026eb59 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 20 Jun 2024 11:18:40 +0530 +Subject: [PATCH 3/6] doc: update examples to reflect alternative ways to + provide `sudo` option (#5418) + +RH-Author: Ani Sinha +RH-MergeRequest: 90: fix(jsonschema): Add missing sudo definition (#5418) +RH-Jira: RHEL-44337 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Miroslav Rezanina +RH-Commit: [2/2] 62eac6d731cb725c32cd0beac0219ecc2b407198 (anisinha/cloud-init) + +For creating users and groups, it is possible to pass a `sudo` option to the +config file that accepts a sudo rule. The option can be a sudo rule string, +a list of sudo rule strings or `False` to explicitly deny sudo usage. Update +examples to show how a list of strings can be used with `sudo` option. + +Signed-off-by: Ani Sinha +(cherry picked from commit cbcb05349e35023ee6e81ccaf13e79adb8f65f63) +--- + doc/examples/cloud-config-user-groups.txt | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/doc/examples/cloud-config-user-groups.txt b/doc/examples/cloud-config-user-groups.txt +index 87fc52e8..56eb674f 100644 +--- a/doc/examples/cloud-config-user-groups.txt ++++ b/doc/examples/cloud-config-user-groups.txt +@@ -35,6 +35,10 @@ users: + lock_passwd: true + ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj8cgWyIOaspgKdVy0cKJ+UTjfv7jBOjG2H/GN8bJVXy72XAvnhM0dUM+CCs8FOf0YlPX+Frvz2hKInrmRhZVwRSL129PasD12MlI3l44u6IwS1o/W86Q+tkQYEljtqDOo0a+cOsaZkvUNzUyEXUwz/lmYa6G4hMKZH4NBj7nbAAF96wsMCoyNwbWryBnDYUr6wMbjRR1J9Pw7Xh7WRC73wy4Va2YuOgbD3V/5ZrFPLbWZW/7TFXVrql04QVbyei4aiFR5n//GvoqwQDNe58LmbzX/xvxyKJYdny2zXmdAhMxbrpFQsfpkJ9E/H5w0yOdSvnWbUoG5xNGoOB csmith@fringe ++ - name: testuser ++ gecos: Mr. Test ++ homedir: /local/testdir ++ sudo: ["ALL=(ALL) NOPASSWD:ALL"] + - name: cloudy + gecos: Magic Cloud App Daemon User + inactive: '5' +@@ -100,6 +104,8 @@ users: + # + # Allow a user unrestricted sudo access. + # sudo: ALL=(ALL) NOPASSWD:ALL ++# or ++# sudo: ["ALL=(ALL) NOPASSWD:ALL"] + # + # Adding multiple sudo rule strings. + # sudo: +-- +2.39.3 + diff --git a/ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch b/ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch new file mode 100644 index 0000000..20c60fb --- /dev/null +++ b/ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch @@ -0,0 +1,351 @@ +From 6b32b371bfd37759ddce3d7f29d15546500698e6 Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Thu, 20 Jun 2024 22:27:03 +0530 +Subject: [PATCH 1/6] feat(sysconfig): Add DNS from interface config to + resolv.conf (#5401) + +RH-Author: Ani Sinha +RH-MergeRequest: 88: feat(sysconfig): Add DNS from interface config to resolv.conf (#5401) +RH-Jira: RHEL-17961 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/1] f353b73cc0f4bb9e1aee037708a1d3cb23b83cc3 (anisinha/cloud-init) + +sysconfig renderer currently only uses global dns and search domain +configuration in order to populate /etc/resolv.conf. This means it ignores +interface specific dns configuration completely. This means, when global dns +information is absent and only interface specific dns configuration is present, +/etc/resolv.conf will not have complete dns information. Fix this so that +per interface dns information is also taken into account along with global dns +configuration in order to populate /etc/resolv.conf. + +Fixes: GH-5400 + +Signed-off-by: Ani Sinha +(cherry picked from commit 1b8030e0c7fd6fbff7e38ad1e3e6266ae50c83a5) +--- + cloudinit/net/sysconfig.py | 52 +++++++++- + tests/unittests/test_net.py | 184 +++++++++++++++++++++++++++++++++++- + 2 files changed, 230 insertions(+), 6 deletions(-) + +diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py +index d39f4fe3..7eb430ed 100644 +--- a/cloudinit/net/sysconfig.py ++++ b/cloudinit/net/sysconfig.py +@@ -825,20 +825,62 @@ class Renderer(renderer.Renderer): + + @staticmethod + def _render_dns(network_state, existing_dns_path=None): +- # skip writing resolv.conf if network_state doesn't include any input. ++ ++ found_nameservers = [] ++ found_dns_search = [] ++ ++ for iface in network_state.iter_interfaces(): ++ for subnet in iface["subnets"]: ++ # Add subnet-level DNS ++ if "dns_nameservers" in subnet: ++ found_nameservers.extend(subnet["dns_nameservers"]) ++ if "dns_search" in subnet: ++ found_dns_search.extend(subnet["dns_search"]) ++ ++ # Add interface-level DNS ++ if "dns" in iface: ++ found_nameservers += [ ++ dns ++ for dns in iface["dns"]["nameservers"] ++ if dns not in found_nameservers ++ ] ++ found_dns_search += [ ++ search ++ for search in iface["dns"]["search"] ++ if search not in found_dns_search ++ ] ++ ++ # When both global and interface specific entries are present, ++ # use them both to generate /etc/resolv.conf eliminating duplicate ++ # entries. Otherwise use global or interface specific entries whichever ++ # is provided. ++ if network_state.dns_nameservers: ++ found_nameservers += [ ++ nameserver ++ for nameserver in network_state.dns_nameservers ++ if nameserver not in found_nameservers ++ ] ++ if network_state.dns_searchdomains: ++ found_dns_search += [ ++ search ++ for search in network_state.dns_searchdomains ++ if search not in found_dns_search ++ ] ++ ++ # skip writing resolv.conf if no dns information is provided in conf. + if not any( + [ +- len(network_state.dns_nameservers), +- len(network_state.dns_searchdomains), ++ len(found_nameservers), ++ len(found_dns_search), + ] + ): + return None + content = resolv_conf.ResolvConf("") + if existing_dns_path and os.path.isfile(existing_dns_path): + content = resolv_conf.ResolvConf(util.load_file(existing_dns_path)) +- for nameserver in network_state.dns_nameservers: ++ for nameserver in found_nameservers: + content.add_nameserver(nameserver) +- for searchdomain in network_state.dns_searchdomains: ++ for searchdomain in found_dns_search: + content.add_search_domain(searchdomain) + header = _make_header(";") + content_str = str(content) +diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py +index d7c9a414..2d716f4b 100644 +--- a/tests/unittests/test_net.py ++++ b/tests/unittests/test_net.py +@@ -516,6 +516,8 @@ OS_SAMPLES = [ + } + ], + "ip_address": "172.19.1.34", ++ "dns_search": ["testweb.com"], ++ "dns_nameservers": ["172.19.0.13"], + "id": "network0", + } + ], +@@ -550,7 +552,9 @@ STARTMODE=auto + """ + ; Created by cloud-init automatically, do not edit. + ; ++nameserver 172.19.0.13 + nameserver 172.19.0.12 ++search testweb.com + """.lstrip(), + ), + ( +@@ -582,6 +586,8 @@ AUTOCONNECT_PRIORITY=120 + BOOTPROTO=none + DEFROUTE=yes + DEVICE=eth0 ++DNS1=172.19.0.13 ++DOMAIN=testweb.com + GATEWAY=172.19.3.254 + HWADDR=fa:16:3e:ed:9a:59 + IPADDR=172.19.1.34 +@@ -596,7 +602,174 @@ USERCTL=no + """ + ; Created by cloud-init automatically, do not edit. + ; ++nameserver 172.19.0.13 + nameserver 172.19.0.12 ++search testweb.com ++""".lstrip(), ++ ), ++ ( ++ "etc/NetworkManager/conf.d/99-cloud-init.conf", ++ """ ++# Created by cloud-init automatically, do not edit. ++# ++[main] ++dns = none ++""".lstrip(), ++ ), ++ ( ++ "etc/udev/rules.d/70-persistent-net.rules", ++ "".join( ++ [ ++ 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', ++ 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n', ++ ] ++ ), ++ ), ++ ], ++ "expected_network_manager": [ ++ ( ++ "".join( ++ [ ++ "etc/NetworkManager/system-connections", ++ "/cloud-init-eth0.nmconnection", ++ ] ++ ), ++ """ ++# Generated by cloud-init. Changes will be lost. ++ ++[connection] ++id=cloud-init eth0 ++uuid=1dd9a779-d327-56e1-8454-c65e2556c12c ++autoconnect-priority=120 ++type=ethernet ++ ++[user] ++org.freedesktop.NetworkManager.origin=cloud-init ++ ++[ethernet] ++mac-address=FA:16:3E:ED:9A:59 ++ ++[ipv4] ++method=manual ++may-fail=false ++address1=172.19.1.34/22 ++route1=0.0.0.0/0,172.19.3.254 ++dns=172.19.0.13; ++dns-search=testweb.com; ++ ++""".lstrip(), ++ ), ++ ], ++ }, ++ { ++ "in_data": { ++ "services": [ ++ { ++ "type": "dns", ++ "address": "172.19.0.12", ++ "search": ["example1.com", "example2.com"], ++ } ++ ], ++ "networks": [ ++ { ++ "network_id": "dacd568d-5be6-4786-91fe-750c374b78b4", ++ "type": "ipv4", ++ "netmask": "255.255.252.0", ++ "link": "eth0", ++ "routes": [ ++ { ++ "netmask": "0.0.0.0", ++ "network": "0.0.0.0", ++ "gateway": "172.19.3.254", ++ } ++ ], ++ "ip_address": "172.19.1.34", ++ "dns_search": ["example3.com"], ++ "dns_nameservers": ["172.19.0.12"], ++ "id": "network0", ++ } ++ ], ++ "links": [ ++ { ++ "ethernet_mac_address": "fa:16:3e:ed:9a:59", ++ "mtu": None, ++ "type": "physical", ++ "id": "eth0", ++ }, ++ ], ++ }, ++ "in_macs": { ++ "fa:16:3e:ed:9a:59": "eth0", ++ }, ++ "out_sysconfig_opensuse": [ ++ ( ++ "etc/sysconfig/network/ifcfg-eth0", ++ """ ++# Created by cloud-init automatically, do not edit. ++# ++BOOTPROTO=static ++IPADDR=172.19.1.34 ++LLADDR=fa:16:3e:ed:9a:59 ++NETMASK=255.255.252.0 ++STARTMODE=auto ++""".lstrip(), ++ ), ++ ( ++ "etc/resolv.conf", ++ """ ++; Created by cloud-init automatically, do not edit. ++; ++nameserver 172.19.0.12 ++search example3.com example1.com example2.com ++""".lstrip(), ++ ), ++ ( ++ "etc/NetworkManager/conf.d/99-cloud-init.conf", ++ """ ++# Created by cloud-init automatically, do not edit. ++# ++[main] ++dns = none ++""".lstrip(), ++ ), ++ ( ++ "etc/udev/rules.d/85-persistent-net-cloud-init.rules", ++ "".join( ++ [ ++ 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ', ++ 'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n', ++ ] ++ ), ++ ), ++ ], ++ "out_sysconfig_rhel": [ ++ ( ++ "etc/sysconfig/network-scripts/ifcfg-eth0", ++ """ ++# Created by cloud-init automatically, do not edit. ++# ++AUTOCONNECT_PRIORITY=120 ++BOOTPROTO=none ++DEFROUTE=yes ++DEVICE=eth0 ++DNS1=172.19.0.12 ++DOMAIN=example3.com ++GATEWAY=172.19.3.254 ++HWADDR=fa:16:3e:ed:9a:59 ++IPADDR=172.19.1.34 ++NETMASK=255.255.252.0 ++ONBOOT=yes ++TYPE=Ethernet ++USERCTL=no ++""".lstrip(), ++ ), ++ ( ++ "etc/resolv.conf", ++ """ ++; Created by cloud-init automatically, do not edit. ++; ++nameserver 172.19.0.12 ++search example3.com example1.com example2.com + """.lstrip(), + ), + ( +@@ -647,6 +820,7 @@ may-fail=false + address1=172.19.1.34/22 + route1=0.0.0.0/0,172.19.3.254 + dns=172.19.0.12; ++dns-search=example3.com; + + """.lstrip(), + ), +@@ -654,7 +828,13 @@ dns=172.19.0.12; + }, + { + "in_data": { +- "services": [{"type": "dns", "address": "172.19.0.12"}], ++ "services": [ ++ { ++ "type": "dns", ++ "address": "172.19.0.12", ++ "search": "example.com", ++ } ++ ], + "networks": [ + { + "network_id": "public-ipv4", +@@ -715,6 +895,7 @@ STARTMODE=auto + ; Created by cloud-init automatically, do not edit. + ; + nameserver 172.19.0.12 ++search example.com + """.lstrip(), + ), + ( +@@ -763,6 +944,7 @@ USERCTL=no + ; Created by cloud-init automatically, do not edit. + ; + nameserver 172.19.0.12 ++search example.com + """.lstrip(), + ), + ( +-- +2.39.3 + diff --git a/ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch b/ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch new file mode 100644 index 0000000..68d5884 --- /dev/null +++ b/ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch @@ -0,0 +1,73 @@ +From 8ead44cb39f7726a695aa21a34820f6d40270829 Mon Sep 17 00:00:00 2001 +From: James Falcon +Date: Mon, 12 Feb 2024 14:48:01 -0600 +Subject: [PATCH 5/6] fix: Address TIOBE abstract interpretation issues (#4866) + +RH-Author: Ani Sinha +RH-MergeRequest: 92: Update pylint version to support python 3.12 +RH-Jira: RHEL-44598 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Miroslav Rezanina +RH-Commit: [2/3] 3ca11206fa159ab45b2db21e78c4cfaf358b1e01 (anisinha/cloud-init) + +These involve operations on possibly null variables or impossible logic. + +(cherry picked from commit 5e7ef1032a12267a9a518358fbf89da0a88ddb99) +--- + cloudinit/config/cc_lxd.py | 2 +- + cloudinit/distros/parsers/ifconfig.py | 6 ++++++ + cloudinit/util.py | 1 + + 3 files changed, 8 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/config/cc_lxd.py b/cloudinit/config/cc_lxd.py +index cb9fc4f3..9f267b4c 100644 +--- a/cloudinit/config/cc_lxd.py ++++ b/cloudinit/config/cc_lxd.py +@@ -432,7 +432,7 @@ def bridge_to_cmd(bridge_cfg): + % (bridge_cfg.get("ipv6_address"), bridge_cfg.get("ipv6_netmask")) + ) + +- if bridge_cfg.get("ipv6_nat", "false") == "true": ++ if bridge_cfg.get("ipv6_nat") == "true": + cmd_create.append("ipv6.nat=true") + + else: +diff --git a/cloudinit/distros/parsers/ifconfig.py b/cloudinit/distros/parsers/ifconfig.py +index 516b5eb5..d671df1f 100644 +--- a/cloudinit/distros/parsers/ifconfig.py ++++ b/cloudinit/distros/parsers/ifconfig.py +@@ -102,6 +102,7 @@ class Ifconfig: + """ + ifindex = 0 + ifs_by_mac = defaultdict(list) ++ dev = None + for line in text.splitlines(): + if len(line) == 0: + continue +@@ -119,6 +120,11 @@ class Ifconfig: + dev.index = ifindex + self._ifs_by_name[curif] = dev + ++ if not dev: ++ # This shouldn't happen with normal ifconfig output, but ++ # if it does, ensure we don't Traceback ++ continue ++ + toks = line.lower().strip().split() + + if len(toks) > 1 and toks[1].startswith("flags="): +diff --git a/cloudinit/util.py b/cloudinit/util.py +index 3295735c..5f787c5c 100644 +--- a/cloudinit/util.py ++++ b/cloudinit/util.py +@@ -1417,6 +1417,7 @@ def find_devs_with_netbsd( + devlist = [] + label = None + _type = None ++ mscdlabel_out = "" + if criteria: + if criteria.startswith("LABEL="): + label = criteria.lstrip("LABEL=") +-- +2.39.3 + diff --git a/ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch b/ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch new file mode 100644 index 0000000..b7eb978 --- /dev/null +++ b/ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch @@ -0,0 +1,132 @@ +From 2b74b0eb94edfd7caa42bc0d8affc37311ba041b Mon Sep 17 00:00:00 2001 +From: Brett Holman +Date: Wed, 3 Jan 2024 09:11:21 -0700 +Subject: [PATCH 4/6] fix(dhcp): Guard against FileNotFoundError and NameError + exceptions + +RH-Author: Ani Sinha +RH-MergeRequest: 92: Update pylint version to support python 3.12 +RH-Jira: RHEL-44598 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/3] 730b8de9ceb2c380d3b15573d83691ab95a1487e (anisinha/cloud-init) + +(cherry picked from commit 53eb8555e091474803b724700815adc09aa84f05) +--- + cloudinit/net/dhcp.py | 20 ++++++++++------ + tests/unittests/net/test_dhcp.py | 40 ++++++++++++++++++++++++++++++++ + 2 files changed, 53 insertions(+), 7 deletions(-) + +diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py +index 07c13390..a0aee98c 100644 +--- a/cloudinit/net/dhcp.py ++++ b/cloudinit/net/dhcp.py +@@ -5,15 +5,15 @@ + # This file is part of cloud-init. See LICENSE file for license information. + + import abc +-import contextlib + import glob + import logging + import os + import re + import signal + import time ++from contextlib import suppress + from io import StringIO +-from typing import Any, Dict, List ++from typing import Any, Dict, List, Optional + + import configobj + +@@ -268,7 +268,7 @@ class IscDhclient(DhcpClient): + + # this function waits for these files to exist, clean previous runs + # to avoid false positive in wait_for_files +- with contextlib.suppress(FileNotFoundError): ++ with suppress(FileNotFoundError): + os.remove(pid_file) + os.remove(lease_file) + +@@ -514,9 +514,15 @@ class IscDhclient(DhcpClient): + return latest_file + + @staticmethod +- def parse_dhcp_server_from_lease_file(lease_file): +- with open(lease_file, "r") as fd: +- for line in fd: ++ def parse_dhcp_server_from_lease_file(lease_file) -> Optional[str]: ++ """Parse a lease file for the dhcp server address ++ ++ @param lease_file: Name of a file to be parsed ++ @return: An address if found, or None ++ """ ++ latest_address = None ++ with suppress(FileNotFoundError), open(lease_file, "r") as file: ++ for line in file: + if "dhcp-server-identifier" in line: + words = line.strip(" ;\r\n").split(" ") + if len(words) > 2: +@@ -561,7 +567,7 @@ class Udhcpc(DhcpClient): + + tmp_dir = temp_utils.get_tmp_ancestor(needs_exe=True) + lease_file = os.path.join(tmp_dir, interface + ".lease.json") +- with contextlib.suppress(FileNotFoundError): ++ with suppress(FileNotFoundError): + os.remove(lease_file) + + # udhcpc needs the interface up to send initial discovery packets +diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py +index a7b62312..8ec96eef 100644 +--- a/tests/unittests/net/test_dhcp.py ++++ b/tests/unittests/net/test_dhcp.py +@@ -32,6 +32,46 @@ LEASE_F = "/run/dhclient.lease" + DHCLIENT = "/sbin/dhclient" + + ++@pytest.mark.parametrize( ++ "server_address,lease_file_content", ++ ( ++ pytest.param(None, None, id="no_server_addr_on_absent_lease_file"), ++ pytest.param(None, "", id="no_server_addr_on_empty_lease_file"), ++ pytest.param( ++ None, ++ "lease {\n fixed-address: 10.1.2.3;\n}\n", ++ id="no_server_addr_when_no_server_ident", ++ ), ++ pytest.param( ++ "10.4.5.6", ++ "lease {\n fixed-address: 10.1.2.3;\n" ++ " option dhcp-server-identifier 10.4.5.6;\n" ++ " option dhcp-renewal-time 1800;\n}\n", ++ id="server_addr_found_when_server_ident_present", ++ ), ++ ), ++) ++class TestParseDHCPServerFromLeaseFile: ++ def test_find_server_address_when_present( ++ self, server_address, lease_file_content, tmp_path ++ ): ++ """Test that we return None in the case of no file or file contains no ++ server address, otherwise return the address. ++ """ ++ lease_file = tmp_path / "dhcp.leases" ++ if server_address: ++ if lease_file_content: ++ lease_file.write_text(lease_file_content) ++ assert ( ++ server_address ++ == IscDhclient.parse_dhcp_server_from_lease_file(lease_file) ++ ) ++ else: ++ assert not IscDhclient.parse_dhcp_server_from_lease_file( ++ lease_file ++ ) ++ ++ + class TestParseDHCPLeasesFile(CiTestCase): + def test_parse_empty_lease_file_errors(self): + """parse_dhcp_lease_file errors when file content is empty.""" +-- +2.39.3 + diff --git a/ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch b/ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch new file mode 100644 index 0000000..221d24f --- /dev/null +++ b/ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch @@ -0,0 +1,53 @@ +From b7fddab36d805099639358736dab474d2924906b Mon Sep 17 00:00:00 2001 +From: Brett Holman +Date: Wed, 19 Jun 2024 17:07:56 -0600 +Subject: [PATCH 2/6] fix(jsonschema): Add missing sudo definition (#5418) + +RH-Author: Ani Sinha +RH-MergeRequest: 90: fix(jsonschema): Add missing sudo definition (#5418) +RH-Jira: RHEL-44337 +RH-Acked-by: Emanuele Giuseppe Esposito +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/2] 9e56c7ab35744c6530c8cef2f122ffdcc0480d29 (anisinha/cloud-init) + +This configuration: + +``` +users: + - name: osadmin + lock_passwd: false + sudo: ["ALL=(ALL) NOPASSWD:ALL"] +``` + +Is valid syntax but is missing from the jsonschema definition. + +Fixes GH-5399 + +(cherry picked from commit b533fa51acb850ed754e2b1925e276ff8e5f3507) +--- + cloudinit/config/schemas/schema-cloud-config-v1.json | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/cloudinit/config/schemas/schema-cloud-config-v1.json b/cloudinit/config/schemas/schema-cloud-config-v1.json +index a553c52c..8b10fe70 100644 +--- a/cloudinit/config/schemas/schema-cloud-config-v1.json ++++ b/cloudinit/config/schemas/schema-cloud-config-v1.json +@@ -299,6 +299,15 @@ + ], + "description": "Sudo rule to use or false. Absence of a sudo value or ``null`` will result in no sudo rules added for this user." + }, ++ { ++ "type": "array", ++ "items": { ++ "type": [ ++ "string", ++ "null" ++ ] ++ } ++ }, + { + "type": "boolean", + "changed": true, +-- +2.39.3 + diff --git a/cloud-init.spec b/cloud-init.spec index d6d1790..db50d60 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -1,6 +1,6 @@ Name: cloud-init Version: 23.4 -Release: 12%{?dist} +Release: 13%{?dist} Summary: Cloud instance init scripts License: ASL 2.0 or GPLv3 URL: http://launchpad.net/cloud-init @@ -35,6 +35,18 @@ Patch16: ci-fix-Fall-back-to-cached-local-ds-if-no-valid-ds-foun.patch Patch17: ci-fix-Always-use-single-datasource-if-specified-5098.patch # For RHEL-40217 - [Cloud-init] CloudstackDataSource cannot work with NetworkManager Patch18: ci-fix-cloudstack-Use-parsed-lease-file-for-virtual-rou.patch +# For RHEL-17961 - [RHEL-9] cloud-init fails to configure DNS search domains +Patch19: ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch +# For RHEL-44337 - [rhel-9] fix `SUDO` configuration schema for users and groups +Patch20: ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch +# For RHEL-44337 - [rhel-9] fix `SUDO` configuration schema for users and groups +Patch21: ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch +# For RHEL-44598 - fix pylint error and support python 3.12 +Patch22: ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch +# For RHEL-44598 - fix pylint error and support python 3.12 +Patch23: ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch +# For RHEL-44598 - fix pylint error and support python 3.12 +Patch24: ci-Update-pylint-version-to-support-python-3.12-5338.patch BuildArch: noarch @@ -249,6 +261,20 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Tue Jun 25 2024 Miroslav Rezanina - 23.4-13 +- ci-feat-sysconfig-Add-DNS-from-interface-config-to-reso.patch [RHEL-17961] +- ci-fix-jsonschema-Add-missing-sudo-definition-5418.patch [RHEL-44337] +- ci-doc-update-examples-to-reflect-alternative-ways-to-p.patch [RHEL-44337] +- ci-fix-dhcp-Guard-against-FileNotFoundError-and-NameErr.patch [RHEL-44598] +- ci-fix-Address-TIOBE-abstract-interpretation-issues-486.patch [RHEL-44598] +- ci-Update-pylint-version-to-support-python-3.12-5338.patch [RHEL-44598] +- Resolves: RHEL-17961 + ([RHEL-9] cloud-init fails to configure DNS search domains) +- Resolves: RHEL-44337 + ([rhel-9] fix `SUDO` configuration schema for users and groups) +- Resolves: RHEL-44598 + (fix pylint error and support python 3.12) + * Mon Jun 17 2024 Miroslav Rezanina - 23.4-12 - ci-fix-cloudstack-Use-parsed-lease-file-for-virtual-rou.patch [RHEL-40217] - Resolves: RHEL-40217