From 5231c595b0ad962a9b0c8662448efb5ca4c85800 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 28 Mar 2023 09:52:20 +0000 Subject: [PATCH] import cloud-init-22.1-9.el9 --- ...-to-resize-encrypted-partitions-1316.patch | 516 ++++++++++++++++++ ...eady-before-cloud-init-service-runs-.patch | 36 ++ ...ignore-var-lib-cloud-data-set-hostna.patch | 77 +++ ...ake-sure-centos-settings-are-identic.patch | 139 +++++ SPECS/cloud-init.spec | 30 +- 5 files changed, 797 insertions(+), 1 deletion(-) create mode 100644 SOURCES/ci-Allow-growpart-to-resize-encrypted-partitions-1316.patch create mode 100644 SOURCES/ci-Ensure-network-ready-before-cloud-init-service-runs-.patch create mode 100644 SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch create mode 100644 SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch diff --git a/SOURCES/ci-Allow-growpart-to-resize-encrypted-partitions-1316.patch b/SOURCES/ci-Allow-growpart-to-resize-encrypted-partitions-1316.patch new file mode 100644 index 0000000..93d922f --- /dev/null +++ b/SOURCES/ci-Allow-growpart-to-resize-encrypted-partitions-1316.patch @@ -0,0 +1,516 @@ +From 1176a788c23697099093b4d8a9a21f10f71ebb12 Mon Sep 17 00:00:00 2001 +From: Vitaly Kuznetsov +Date: Wed, 1 Feb 2023 10:47:07 +0100 +Subject: [PATCH] Allow growpart to resize encrypted partitions (#1316) + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2166245 + +commit d95a331d1035d52443c470e0c00765a2c2b271cc +Author: James Falcon +Date: Tue Apr 26 19:03:13 2022 -0500 + + Allow growpart to resize encrypted partitions (#1316) + + Adds the ability for growpart to resize a LUKS formatted partition. + This involves resizing the underlying partition as well as the + filesystem. 'cryptsetup' is used for resizing. + + This relies on a file present at /cc_growpart_keydata containing + json formatted 'key' and 'slot' keys, with the key being + base64 encoded. After resize, cloud-init will destroy + the luks slot used for resizing and remove the key file. + +Conflicts: + cloudinit/config/cc_growpart.py (includes only) + +Signed-off-by: Vitaly Kuznetsov +--- + cloudinit/config/cc_growpart.py | 171 +++++++++++++++- + test-requirements.txt | 1 + + tests/unittests/config/test_cc_growpart.py | 228 +++++++++++++++++++++ + tox.ini | 1 + + 4 files changed, 400 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py +index 43334caa..bdf17aba 100644 +--- a/cloudinit/config/cc_growpart.py ++++ b/cloudinit/config/cc_growpart.py +@@ -64,10 +64,16 @@ growpart is:: + ignore_growroot_disabled: + """ + ++import base64 ++import copy ++import json + import os + import os.path + import re + import stat ++from contextlib import suppress ++from pathlib import Path ++from typing import Tuple + + from cloudinit import log as logging + from cloudinit import subp, temp_utils, util +@@ -81,6 +87,8 @@ DEFAULT_CONFIG = { + "ignore_growroot_disabled": False, + } + ++KEYDATA_PATH = Path("/cc_growpart_keydata") ++ + + class RESIZE(object): + SKIPPED = "SKIPPED" +@@ -289,10 +297,128 @@ def devent2dev(devent): + return dev + + ++def get_mapped_device(blockdev): ++ """Returns underlying block device for a mapped device. ++ ++ If it is mapped, blockdev will usually take the form of ++ /dev/mapper/some_name ++ ++ If blockdev is a symlink pointing to a /dev/dm-* device, return ++ the device pointed to. Otherwise, return None. ++ """ ++ realpath = os.path.realpath(blockdev) ++ if realpath.startswith("/dev/dm-"): ++ LOG.debug("%s is a mapped device pointing to %s", blockdev, realpath) ++ return realpath ++ return None ++ ++ ++def is_encrypted(blockdev, partition) -> bool: ++ """ ++ Check if a device is an encrypted device. blockdev should have ++ a /dev/dm-* path whereas partition is something like /dev/sda1. ++ """ ++ if not subp.which("cryptsetup"): ++ LOG.debug("cryptsetup not found. Assuming no encrypted partitions") ++ return False ++ try: ++ subp.subp(["cryptsetup", "status", blockdev]) ++ except subp.ProcessExecutionError as e: ++ if e.exit_code == 4: ++ LOG.debug("Determined that %s is not encrypted", blockdev) ++ else: ++ LOG.warning( ++ "Received unexpected exit code %s from " ++ "cryptsetup status. Assuming no encrypted partitions.", ++ e.exit_code, ++ ) ++ return False ++ with suppress(subp.ProcessExecutionError): ++ subp.subp(["cryptsetup", "isLuks", partition]) ++ LOG.debug("Determined that %s is encrypted", blockdev) ++ return True ++ return False ++ ++ ++def get_underlying_partition(blockdev): ++ command = ["dmsetup", "deps", "--options=devname", blockdev] ++ dep: str = subp.subp(command)[0] # type: ignore ++ # Returned result should look something like: ++ # 1 dependencies : (vdb1) ++ if not dep.startswith("1 depend"): ++ raise RuntimeError( ++ f"Expecting '1 dependencies' from 'dmsetup'. Received: {dep}" ++ ) ++ try: ++ return f'/dev/{dep.split(": (")[1].split(")")[0]}' ++ except IndexError as e: ++ raise RuntimeError( ++ f"Ran `{command}`, but received unexpected stdout: `{dep}`" ++ ) from e ++ ++ ++def resize_encrypted(blockdev, partition) -> Tuple[str, str]: ++ """Use 'cryptsetup resize' to resize LUKS volume. ++ ++ The loaded keyfile is json formatted with 'key' and 'slot' keys. ++ key is base64 encoded. Example: ++ {"key":"XFmCwX2FHIQp0LBWaLEMiHIyfxt1SGm16VvUAVledlY=","slot":5} ++ """ ++ if not KEYDATA_PATH.exists(): ++ return (RESIZE.SKIPPED, "No encryption keyfile found") ++ try: ++ with KEYDATA_PATH.open() as f: ++ keydata = json.load(f) ++ key = keydata["key"] ++ decoded_key = base64.b64decode(key) ++ slot = keydata["slot"] ++ except Exception as e: ++ raise RuntimeError( ++ "Could not load encryption key. This is expected if " ++ "the volume has been previously resized." ++ ) from e ++ ++ try: ++ subp.subp( ++ ["cryptsetup", "--key-file", "-", "resize", blockdev], ++ data=decoded_key, ++ ) ++ finally: ++ try: ++ subp.subp( ++ [ ++ "cryptsetup", ++ "luksKillSlot", ++ "--batch-mode", ++ partition, ++ str(slot), ++ ] ++ ) ++ except subp.ProcessExecutionError as e: ++ LOG.warning( ++ "Failed to kill luks slot after resizing encrypted volume: %s", ++ e, ++ ) ++ try: ++ KEYDATA_PATH.unlink() ++ except Exception: ++ util.logexc( ++ LOG, "Failed to remove keyfile after resizing encrypted volume" ++ ) ++ ++ return ( ++ RESIZE.CHANGED, ++ f"Successfully resized encrypted volume '{blockdev}'", ++ ) ++ ++ + def resize_devices(resizer, devices): + # returns a tuple of tuples containing (entry-in-devices, action, message) ++ devices = copy.copy(devices) + info = [] +- for devent in devices: ++ ++ while devices: ++ devent = devices.pop(0) + try: + blockdev = devent2dev(devent) + except ValueError as e: +@@ -329,6 +455,49 @@ def resize_devices(resizer, devices): + ) + continue + ++ underlying_blockdev = get_mapped_device(blockdev) ++ if underlying_blockdev: ++ try: ++ # We need to resize the underlying partition first ++ partition = get_underlying_partition(blockdev) ++ if is_encrypted(underlying_blockdev, partition): ++ if partition not in [x[0] for x in info]: ++ # We shouldn't attempt to resize this mapped partition ++ # until the underlying partition is resized, so re-add ++ # our device to the beginning of the list we're ++ # iterating over, then add our underlying partition ++ # so it can get processed first ++ devices.insert(0, devent) ++ devices.insert(0, partition) ++ continue ++ status, message = resize_encrypted(blockdev, partition) ++ info.append( ++ ( ++ devent, ++ status, ++ message, ++ ) ++ ) ++ else: ++ info.append( ++ ( ++ devent, ++ RESIZE.SKIPPED, ++ f"Resizing mapped device ({blockdev}) skipped " ++ "as it is not encrypted.", ++ ) ++ ) ++ except Exception as e: ++ info.append( ++ ( ++ devent, ++ RESIZE.FAILED, ++ f"Resizing encrypted device ({blockdev}) failed: {e}", ++ ) ++ ) ++ # At this point, we WON'T resize a non-encrypted mapped device ++ # though we should probably grow the ability to ++ continue + try: + (disk, ptnum) = device_part_info(blockdev) + except (TypeError, ValueError) as e: +diff --git a/test-requirements.txt b/test-requirements.txt +index 06dfbbec..7160416a 100644 +--- a/test-requirements.txt ++++ b/test-requirements.txt +@@ -2,6 +2,7 @@ + httpretty>=0.7.1 + pytest + pytest-cov ++pytest-mock + + # Only really needed on older versions of python + setuptools +diff --git a/tests/unittests/config/test_cc_growpart.py b/tests/unittests/config/test_cc_growpart.py +index ba66f136..7d4e2629 100644 +--- a/tests/unittests/config/test_cc_growpart.py ++++ b/tests/unittests/config/test_cc_growpart.py +@@ -8,6 +8,7 @@ import shutil + import stat + import unittest + from contextlib import ExitStack ++from itertools import chain + from unittest import mock + + from cloudinit import cloud, subp, temp_utils +@@ -342,6 +343,233 @@ class TestResize(unittest.TestCase): + os.stat = real_stat + + ++class TestEncrypted: ++ """Attempt end-to-end scenarios using encrypted devices. ++ ++ Things are mocked such that: ++ - "/fake_encrypted" is mounted onto "/dev/mapper/fake" ++ - "/dev/mapper/fake" is a LUKS device and symlinked to /dev/dm-1 ++ - The partition backing "/dev/mapper/fake" is "/dev/vdx1" ++ - "/" is not encrypted and mounted onto "/dev/vdz1" ++ ++ Note that we don't (yet) support non-encrypted mapped drives, such ++ as LVM volumes. If our mount point is /dev/mapper/*, then we will ++ not resize it if it is not encrypted. ++ """ ++ ++ def _subp_side_effect(self, value, good=True, **kwargs): ++ if value[0] == "dmsetup": ++ return ("1 dependencies : (vdx1)",) ++ return mock.Mock() ++ ++ def _device_part_info_side_effect(self, value): ++ if value.startswith("/dev/mapper/"): ++ raise TypeError(f"{value} not a partition") ++ return (1024, 1024) ++ ++ def _devent2dev_side_effect(self, value): ++ if value == "/fake_encrypted": ++ return "/dev/mapper/fake" ++ elif value == "/": ++ return "/dev/vdz" ++ elif value.startswith("/dev"): ++ return value ++ raise Exception(f"unexpected value {value}") ++ ++ def _realpath_side_effect(self, value): ++ return "/dev/dm-1" if value.startswith("/dev/mapper") else value ++ ++ def assert_resize_and_cleanup(self): ++ all_subp_args = list( ++ chain(*[args[0][0] for args in self.m_subp.call_args_list]) ++ ) ++ assert "resize" in all_subp_args ++ assert "luksKillSlot" in all_subp_args ++ self.m_unlink.assert_called_once() ++ ++ def assert_no_resize_or_cleanup(self): ++ all_subp_args = list( ++ chain(*[args[0][0] for args in self.m_subp.call_args_list]) ++ ) ++ assert "resize" not in all_subp_args ++ assert "luksKillSlot" not in all_subp_args ++ self.m_unlink.assert_not_called() ++ ++ @pytest.fixture ++ def common_mocks(self, mocker): ++ # These are all "happy path" mocks which will get overridden ++ # when needed ++ mocker.patch( ++ "cloudinit.config.cc_growpart.device_part_info", ++ side_effect=self._device_part_info_side_effect, ++ ) ++ mocker.patch("os.stat") ++ mocker.patch("stat.S_ISBLK") ++ mocker.patch("stat.S_ISCHR") ++ mocker.patch( ++ "cloudinit.config.cc_growpart.devent2dev", ++ side_effect=self._devent2dev_side_effect, ++ ) ++ mocker.patch( ++ "os.path.realpath", side_effect=self._realpath_side_effect ++ ) ++ # Only place subp.which is used in cc_growpart is for cryptsetup ++ mocker.patch( ++ "cloudinit.config.cc_growpart.subp.which", ++ return_value="/usr/sbin/cryptsetup", ++ ) ++ self.m_subp = mocker.patch( ++ "cloudinit.config.cc_growpart.subp.subp", ++ side_effect=self._subp_side_effect, ++ ) ++ mocker.patch( ++ "pathlib.Path.open", ++ new_callable=mock.mock_open, ++ read_data=( ++ '{"key":"XFmCwX2FHIQp0LBWaLEMiHIyfxt1SGm16VvUAVledlY=",' ++ '"slot":5}' ++ ), ++ ) ++ mocker.patch("pathlib.Path.exists", return_value=True) ++ self.m_unlink = mocker.patch("pathlib.Path.unlink", autospec=True) ++ ++ self.resizer = mock.Mock() ++ self.resizer.resize = mock.Mock(return_value=(1024, 1024)) ++ ++ def test_resize_when_encrypted(self, common_mocks, caplog): ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ assert len(info) == 2 ++ assert info[0][0] == "/dev/vdx1" ++ assert info[0][2].startswith("no change necessary") ++ assert info[1][0] == "/fake_encrypted" ++ assert ( ++ info[1][2] ++ == "Successfully resized encrypted volume '/dev/mapper/fake'" ++ ) ++ assert ( ++ "/dev/mapper/fake is a mapped device pointing to /dev/dm-1" ++ in caplog.text ++ ) ++ assert "Determined that /dev/dm-1 is encrypted" in caplog.text ++ ++ self.assert_resize_and_cleanup() ++ ++ def test_resize_when_unencrypted(self, common_mocks): ++ info = cc_growpart.resize_devices(self.resizer, ["/"]) ++ assert len(info) == 1 ++ assert info[0][0] == "/" ++ assert "encrypted" not in info[0][2] ++ self.assert_no_resize_or_cleanup() ++ ++ def test_encrypted_but_cryptsetup_not_found( ++ self, common_mocks, mocker, caplog ++ ): ++ mocker.patch( ++ "cloudinit.config.cc_growpart.subp.which", ++ return_value=None, ++ ) ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ ++ assert len(info) == 1 ++ assert "skipped as it is not encrypted" in info[0][2] ++ assert "cryptsetup not found" in caplog.text ++ self.assert_no_resize_or_cleanup() ++ ++ def test_dmsetup_not_found(self, common_mocks, mocker, caplog): ++ def _subp_side_effect(value, **kwargs): ++ if value[0] == "dmsetup": ++ raise subp.ProcessExecutionError() ++ ++ mocker.patch( ++ "cloudinit.config.cc_growpart.subp.subp", ++ side_effect=_subp_side_effect, ++ ) ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ assert len(info) == 1 ++ assert info[0][0] == "/fake_encrypted" ++ assert info[0][1] == "FAILED" ++ assert ( ++ "Resizing encrypted device (/dev/mapper/fake) failed" in info[0][2] ++ ) ++ self.assert_no_resize_or_cleanup() ++ ++ def test_unparsable_dmsetup(self, common_mocks, mocker, caplog): ++ def _subp_side_effect(value, **kwargs): ++ if value[0] == "dmsetup": ++ return ("2 dependencies",) ++ return mock.Mock() ++ ++ mocker.patch( ++ "cloudinit.config.cc_growpart.subp.subp", ++ side_effect=_subp_side_effect, ++ ) ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ assert len(info) == 1 ++ assert info[0][0] == "/fake_encrypted" ++ assert info[0][1] == "FAILED" ++ assert ( ++ "Resizing encrypted device (/dev/mapper/fake) failed" in info[0][2] ++ ) ++ self.assert_no_resize_or_cleanup() ++ ++ def test_missing_keydata(self, common_mocks, mocker, caplog): ++ # Note that this will be standard behavior after first boot ++ # on a system with an encrypted root partition ++ mocker.patch("pathlib.Path.open", side_effect=FileNotFoundError()) ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ assert len(info) == 2 ++ assert info[0][0] == "/dev/vdx1" ++ assert info[0][2].startswith("no change necessary") ++ assert info[1][0] == "/fake_encrypted" ++ assert info[1][1] == "FAILED" ++ assert ( ++ info[1][2] ++ == "Resizing encrypted device (/dev/mapper/fake) failed: Could " ++ "not load encryption key. This is expected if the volume has " ++ "been previously resized." ++ ) ++ self.assert_no_resize_or_cleanup() ++ ++ def test_resize_failed(self, common_mocks, mocker, caplog): ++ def _subp_side_effect(value, **kwargs): ++ if value[0] == "dmsetup": ++ return ("1 dependencies : (vdx1)",) ++ elif value[0] == "cryptsetup" and "resize" in value: ++ raise subp.ProcessExecutionError() ++ return mock.Mock() ++ ++ self.m_subp = mocker.patch( ++ "cloudinit.config.cc_growpart.subp.subp", ++ side_effect=_subp_side_effect, ++ ) ++ ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ assert len(info) == 2 ++ assert info[0][0] == "/dev/vdx1" ++ assert info[0][2].startswith("no change necessary") ++ assert info[1][0] == "/fake_encrypted" ++ assert info[1][1] == "FAILED" ++ assert ( ++ "Resizing encrypted device (/dev/mapper/fake) failed" in info[1][2] ++ ) ++ # Assert we still cleanup ++ all_subp_args = list( ++ chain(*[args[0][0] for args in self.m_subp.call_args_list]) ++ ) ++ assert "luksKillSlot" in all_subp_args ++ self.m_unlink.assert_called_once() ++ ++ def test_resize_skipped(self, common_mocks, mocker, caplog): ++ mocker.patch("pathlib.Path.exists", return_value=False) ++ info = cc_growpart.resize_devices(self.resizer, ["/fake_encrypted"]) ++ assert len(info) == 2 ++ assert info[1] == ( ++ "/fake_encrypted", ++ "SKIPPED", ++ "No encryption keyfile found", ++ ) ++ ++ + def simple_device_part_info(devpath): + # simple stupid return (/dev/vda, 1) for /dev/vda + ret = re.search("([^0-9]*)([0-9]*)$", devpath) +diff --git a/tox.ini b/tox.ini +index c494cb94..04a206f2 100644 +--- a/tox.ini ++++ b/tox.ini +@@ -108,6 +108,7 @@ deps = + # test-requirements + pytest==3.3.2 + pytest-cov==2.5.1 ++ pytest-mock==1.7.1 + # Needed by pytest and default causes failures + attrs==17.4.0 + +-- +2.39.1 + diff --git a/SOURCES/ci-Ensure-network-ready-before-cloud-init-service-runs-.patch b/SOURCES/ci-Ensure-network-ready-before-cloud-init-service-runs-.patch new file mode 100644 index 0000000..cd93ee6 --- /dev/null +++ b/SOURCES/ci-Ensure-network-ready-before-cloud-init-service-runs-.patch @@ -0,0 +1,36 @@ +From 14d1952c17637b80923d1bfaf3b6b5f8cf032147 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Wed, 14 Dec 2022 09:31:51 +0100 +Subject: [PATCH] Ensure network ready before cloud-init service runs on RHEL + (#1893) + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2152100 + +commit 6e725f36647407d201af0603d7db11fc96a93d4d +Author: James Falcon +Date: Tue Dec 13 10:55:23 2022 -0600 + + Ensure network ready before cloud-init service runs on RHEL (#1893) + + LP: #1998655 + +Signed-off-by: Emanuele Giuseppe Esposito +--- + systemd/cloud-init.service.tmpl | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/systemd/cloud-init.service.tmpl b/systemd/cloud-init.service.tmpl +index c170aef7..fc984d5c 100644 +--- a/systemd/cloud-init.service.tmpl ++++ b/systemd/cloud-init.service.tmpl +@@ -16,6 +16,7 @@ After=networking.service + "miraclelinux", "openEuler", "rhel", "rocky", "virtuozzo"] %} + After=network.service + After=NetworkManager.service ++After=NetworkManager-wait-online.service + {% endif %} + {% if variant in ["suse"] %} + After=wicked.service +-- +2.38.1 + diff --git a/SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch b/SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch new file mode 100644 index 0000000..c26c847 --- /dev/null +++ b/SOURCES/ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch @@ -0,0 +1,77 @@ +From d51546dee17c9abbb9d44fb33cf81be085a46dae Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Thu, 19 Jan 2023 09:40:10 +0100 +Subject: [PATCH 22/22] cc_set_hostname: ignore + /var/lib/cloud/data/set-hostname if it's empty (#1967) + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2140893 + +commit 9c7502a801763520639c66125eb373123d1e4f44 +Author: Emanuele Giuseppe Esposito +Date: Wed Jan 18 17:55:16 2023 +0100 + + cc_set_hostname: ignore /var/lib/cloud/data/set-hostname if it's empty (#1967) + + If the file exists but is empty, do nothing. + Otherwise cloud-init will crash because it does not handle the empty file. + + RHBZ: 2140893 + + Signed-off-by: Emanuele Giuseppe Esposito + +Signed-off-by: Emanuele Giuseppe Esposito +--- + cloudinit/config/cc_set_hostname.py | 2 +- + tests/unittests/config/test_cc_set_hostname.py | 17 +++++++++++++++++ + 2 files changed, 18 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/config/cc_set_hostname.py b/cloudinit/config/cc_set_hostname.py +index 2674fa20..7e3d5b74 100644 +--- a/cloudinit/config/cc_set_hostname.py ++++ b/cloudinit/config/cc_set_hostname.py +@@ -86,7 +86,7 @@ def handle(name, cfg, cloud, log, _args): + # distro._read_hostname implementation so we only validate one artifact. + prev_fn = os.path.join(cloud.get_cpath("data"), "set-hostname") + prev_hostname = {} +- if os.path.exists(prev_fn): ++ if os.path.exists(prev_fn) and os.stat(prev_fn).st_size > 0: + prev_hostname = util.load_json(util.load_file(prev_fn)) + hostname_changed = hostname != prev_hostname.get( + "hostname" +diff --git a/tests/unittests/config/test_cc_set_hostname.py b/tests/unittests/config/test_cc_set_hostname.py +index 3d1d86ee..2c92949f 100644 +--- a/tests/unittests/config/test_cc_set_hostname.py ++++ b/tests/unittests/config/test_cc_set_hostname.py +@@ -5,6 +5,7 @@ import os + import shutil + import tempfile + from io import BytesIO ++from pathlib import Path + from unittest import mock + + from configobj import ConfigObj +@@ -242,5 +243,21 @@ class TestHostname(t_help.FilesystemMockingTestCase): + str(ctx_mgr.exception), + ) + ++ def test_ignore_empty_previous_artifact_file(self): ++ cfg = { ++ "hostname": "blah", ++ "fqdn": "blah.blah.blah.yahoo.com", ++ } ++ distro = self._fetch_distro("debian") ++ paths = helpers.Paths({"cloud_dir": self.tmp}) ++ ds = None ++ cc = cloud.Cloud(ds, paths, {}, distro, None) ++ self.patchUtils(self.tmp) ++ prev_fn = Path(cc.get_cpath("data")) / "set-hostname" ++ prev_fn.touch() ++ cc_set_hostname.handle("cc_set_hostname", cfg, cc, LOG, []) ++ contents = util.load_file("/etc/hostname") ++ self.assertEqual("blah", contents.strip()) ++ + + # vi: ts=4 expandtab +-- +2.39.1 + diff --git a/SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch b/SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch new file mode 100644 index 0000000..df94668 --- /dev/null +++ b/SOURCES/ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch @@ -0,0 +1,139 @@ +From dd5ae3081491a2a98bd74e1655b22c9354707630 Mon Sep 17 00:00:00 2001 +From: Emanuele Giuseppe Esposito +Date: Thu, 8 Sep 2022 17:46:45 +0200 +Subject: [PATCH] cloud.cfg.tmpl: make sure "centos" settings are identical to + "rhel" (#1639) + +Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2115576 + +commit 7593243a1abe2ccaf4698579720999380a4da73b +Author: Emanuele Giuseppe Esposito +Date: Wed Sep 7 14:53:26 2022 +0200 + + cloud.cfg.tmpl: make sure "centos" settings are identical to "rhel" (#1639) + + We have a couple of bugs where centos does not have the default user as rhel. + This PR makes sure the configuration is exactly the same. + + Signed-off-by: Emanuele Giuseppe Esposito + + RHBZ: 2115565 + RHBZ: 2115576 + Conflicts: + config/cloud.cfg.tmpl: "openmandriva" distro added in the options + +Signed-off-by: Emanuele Giuseppe Esposito +--- + config/cloud.cfg.tmpl | 27 +++++++++++++------------ + tests/unittests/test_render_cloudcfg.py | 1 + + 2 files changed, 15 insertions(+), 13 deletions(-) + +diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl +index 80ab4f96..08b6efbc 100644 +--- a/config/cloud.cfg.tmpl ++++ b/config/cloud.cfg.tmpl +@@ -2,6 +2,7 @@ + # The top level settings are used as module + # and system configuration. + {% set is_bsd = variant in ["dragonfly", "freebsd", "netbsd", "openbsd"] %} ++{% set is_rhel = variant in ["rhel", "centos"] %} + {% if is_bsd %} + syslog_fix_perms: root:wheel + {% elif variant in ["suse"] %} +@@ -32,9 +33,9 @@ disable_root: false + disable_root: true + {% endif %} + +-{% if variant in ["almalinux", "alpine", "amazon", "centos", "cloudlinux", "eurolinux", +- "fedora", "miraclelinux", "openEuler", "rhel", "rocky", "virtuozzo"] %} +-{% if variant == "rhel" %} ++{% if variant in ["almalinux", "alpine", "amazon", "cloudlinux", "eurolinux", ++ "fedora", "miraclelinux", "openEuler", "openmandriva", "rocky", "virtuozzo"] or is_rhel %} ++{% if is_rhel %} + mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service,_netdev', '0', '2'] + {% else %} + mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2'] +@@ -70,7 +71,7 @@ network: + config: disabled + {% endif %} + +-{% if variant == "rhel" %} ++{% if is_rhel %} + # Default redhat settings: + ssh_deletekeys: true + ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519'] +@@ -119,16 +120,16 @@ cloud_config_modules: + {% endif %} + {% if variant not in ["photon"] %} + - ssh-import-id +-{% if variant not in ["rhel"] %} ++{% if not is_rhel %} + - keyboard + {% endif %} + - locale + {% endif %} + - set-passwords +-{% if variant in ["rhel"] %} ++{% if is_rhel %} + - rh_subscription + {% endif %} +-{% if variant in ["rhel", "fedora", "photon"] %} ++{% if variant in ["fedora", "openmandriva", "photon"] or is_rhel %} + {% if variant not in ["photon"] %} + - spacewalk + {% endif %} +@@ -193,9 +194,9 @@ cloud_final_modules: + # (not accessible to handlers/transforms) + system_info: + # This will affect which distro class gets used +-{% if variant in ["almalinux", "alpine", "amazon", "arch", "centos", "cloudlinux", "debian", ++{% if variant in ["almalinux", "alpine", "amazon", "arch", "cloudlinux", "debian", + "eurolinux", "fedora", "freebsd", "gentoo", "netbsd", "miraclelinux", "openbsd", "openEuler", +- "photon", "rhel", "rocky", "suse", "ubuntu", "virtuozzo"] %} ++ "openmandriva", "photon", "rocky", "suse", "ubuntu", "virtuozzo"] or is_rhel %} + distro: {{ variant }} + {% elif variant in ["dragonfly"] %} + distro: dragonflybsd +@@ -248,15 +249,15 @@ system_info: + primary: http://ports.ubuntu.com/ubuntu-ports + security: http://ports.ubuntu.com/ubuntu-ports + ssh_svcname: ssh +-{% elif variant in ["almalinux", "alpine", "amazon", "arch", "centos", "cloudlinux", "eurolinux", +- "fedora", "gentoo", "miraclelinux", "openEuler", "rhel", "rocky", "suse", "virtuozzo"] %} ++{% elif variant in ["almalinux", "alpine", "amazon", "arch", "cloudlinux", "eurolinux", ++ "fedora", "gentoo", "miraclelinux", "openEuler", "openmandriva", "rocky", "suse", "virtuozzo"] or is_rhel %} + # Default user name + that default users groups (if added/used) + default_user: + {% if variant == "amazon" %} + name: ec2-user + lock_passwd: True + gecos: EC2 Default User +-{% elif variant == "rhel" %} ++{% elif is_rhel %} + name: cloud-user + lock_passwd: true + gecos: Cloud User +@@ -275,7 +276,7 @@ system_info: + groups: [adm, sudo] + {% elif variant == "arch" %} + groups: [wheel, users] +-{% elif variant == "rhel" %} ++{% elif is_rhel %} + groups: [adm, systemd-journal] + {% else %} + groups: [wheel, adm, systemd-journal] +diff --git a/tests/unittests/test_render_cloudcfg.py b/tests/unittests/test_render_cloudcfg.py +index 9f95d448..1a6e2715 100644 +--- a/tests/unittests/test_render_cloudcfg.py ++++ b/tests/unittests/test_render_cloudcfg.py +@@ -69,6 +69,7 @@ class TestRenderCloudCfg: + "amazon": "ec2-user", + "debian": "ubuntu", + "rhel": "cloud-user", ++ "centos": "cloud-user", + "unknown": "ubuntu", + } + default_user = system_cfg["system_info"]["default_user"]["name"] +-- +2.37.3 + diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec index f0f9c43..3078090 100644 --- a/SPECS/cloud-init.spec +++ b/SPECS/cloud-init.spec @@ -1,6 +1,6 @@ Name: cloud-init Version: 22.1 -Release: 5%{?dist} +Release: 9%{?dist} Summary: Cloud instance init scripts License: ASL 2.0 or GPLv3 URL: http://launchpad.net/cloud-init @@ -46,6 +46,14 @@ Patch17: ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch # For bz#2117532 - [RHEL9.1] Revert patch of configuring networking by NM keyfiles # For bz#2098501 - [RHEL-9.1] IPv6 not workable when cloud-init configure network using NM keyfiles Patch18: ci-Revert-Revert-Setting-highest-autoconnect-priority-f.patch +# For bz#2115565 - cloud-init configures user "centos" or "rhel" instead of "cloud-user" with cloud-init-22.1 +Patch19: ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch +# For bz#2152100 - [RHEL-9] Ensure network ready before cloud-init service runs on RHEL +Patch20: ci-Ensure-network-ready-before-cloud-init-service-runs-.patch +# For bz#2140893 - systemd[1]: Failed to start Initial cloud-init job after reboot system via sysrq 'b' +Patch21: ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch +# For bz#2166245 - Add support for resizing encrypted root volume +Patch22: ci-Allow-growpart-to-resize-encrypted-partitions-1316.patch # Source-git patches @@ -236,6 +244,26 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Wed Feb 08 2023 Camilla Conte - 22.1-9 +- ci-Allow-growpart-to-resize-encrypted-partitions-1316.patch [bz#2166245] +- Resolves: bz#2166245 + (Add support for resizing encrypted root volume) + +* Fri Jan 27 2023 Camilla Conte - 22.1-8 +- ci-cc_set_hostname-ignore-var-lib-cloud-data-set-hostna.patch [bz#2140893] +- Resolves: bz#2140893 +(systemd[1]: Failed to start Initial cloud-init job after reboot system via sysrq 'b') + +* Wed Dec 21 2022 Camilla Conte - 22.1-7 +- ci-Ensure-network-ready-before-cloud-init-service-runs-.patch [bz#2152100] +- Resolves: bz#2152100 + ([RHEL-9] Ensure network ready before cloud-init service runs on RHEL) + +* Tue Sep 27 2022 Camilla Conte - 22.1-6 +- ci-cloud.cfg.tmpl-make-sure-centos-settings-are-identic.patch [bz#2115565] +- Resolves: bz#2115565 + (cloud-init configures user "centos" or "rhel" instead of "cloud-user" with cloud-init-22.1) + * Wed Aug 17 2022 Miroslav Rezanina - 22.1-5 - ci-Revert-Add-native-NetworkManager-support-1224.patch [bz#2107463 bz#2104389 bz#2117532 bz#2098501] - ci-Revert-Use-Network-Manager-and-Netplan-as-default-re.patch [bz#2107463 bz#2104389 bz#2117532 bz#2098501]