Merge branch 'c9' into a9

This commit is contained in:
eabdullin 2022-06-28 08:30:53 +00:00 committed by Stepan Oksanichenko
commit 1c6dbca3b9
3 changed files with 1043 additions and 2 deletions

View File

@ -0,0 +1,481 @@
From 8e539e045371769a63f3b33fc53eed568d644695 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Tue, 31 May 2022 09:33:08 +0200
Subject: [PATCH] Leave the details of service management to the distro (#1074)
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-MergeRequest: 73: Leave the details of service management to the distro (#1074)
RH-Commit: [1/1] d33e0976d8811a6cb2755eb08fd3a4c811be53f5 (eesposit/cloud-init)
RH-Bugzilla: 2091935
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
commit 8c89009e75c7cf6c2f87635b82656f07f58095e1
Author: Andy Fiddaman <omnios@citrus-it.co.uk>
Date: Wed Oct 20 20:58:27 2021 +0000
Leave the details of service management to the distro (#1074)
Various modules restart services and they all have logic to try and
detect if they are running on a system that needs 'systemctl' or
'service', and then have code to decide which order the arguments
need to be etc. On top of that, not all modules do this in the same way.
The duplication and different approaches are not ideal but this also
makes it hard to add support for a new distribution that does not use
either 'systemctl' or 'service'.
This change adds a new manage_service() method to the distro class
and updates several modules to use it.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
cloudinit/config/cc_fan.py | 42 ++++++++-----------
cloudinit/config/cc_ntp.py | 20 ++-------
cloudinit/config/cc_rsyslog.py | 17 +++-----
cloudinit/config/cc_set_passwords.py | 17 ++------
cloudinit/config/tests/test_set_passwords.py | 41 ++++++++----------
cloudinit/distros/__init__.py | 28 +++++++++++++
.../test_distros/test_manage_service.py | 38 +++++++++++++++++
.../test_handler/test_handler_ntp.py | 29 +++----------
8 files changed, 119 insertions(+), 113 deletions(-)
create mode 100644 tests/unittests/test_distros/test_manage_service.py
diff --git a/cloudinit/config/cc_fan.py b/cloudinit/config/cc_fan.py
index 77984bca..91f50e22 100644
--- a/cloudinit/config/cc_fan.py
+++ b/cloudinit/config/cc_fan.py
@@ -52,35 +52,26 @@ BUILTIN_CFG = {
}
-def stop_update_start(service, config_file, content, systemd=False):
- if systemd:
- cmds = {'stop': ['systemctl', 'stop', service],
- 'start': ['systemctl', 'start', service],
- 'enable': ['systemctl', 'enable', service]}
- else:
- cmds = {'stop': ['service', 'stop'],
- 'start': ['service', 'start']}
-
- def run(cmd, msg):
- try:
- return subp.subp(cmd, capture=True)
- except subp.ProcessExecutionError as e:
- LOG.warning("failed: %s (%s): %s", service, cmd, e)
- return False
-
- stop_failed = not run(cmds['stop'], msg='stop %s' % service)
+def stop_update_start(distro, service, config_file, content):
+ try:
+ distro.manage_service('stop', service)
+ stop_failed = False
+ except subp.ProcessExecutionError as e:
+ stop_failed = True
+ LOG.warning("failed to stop %s: %s", service, e)
+
if not content.endswith('\n'):
content += '\n'
util.write_file(config_file, content, omode="w")
- ret = run(cmds['start'], msg='start %s' % service)
- if ret and stop_failed:
- LOG.warning("success: %s started", service)
-
- if 'enable' in cmds:
- ret = run(cmds['enable'], msg='enable %s' % service)
+ try:
+ distro.manage_service('start', service)
+ if stop_failed:
+ LOG.warning("success: %s started", service)
+ except subp.ProcessExecutionError as e:
+ LOG.warning("failed to start %s: %s", service, e)
- return ret
+ distro.manage_service('enable', service)
def handle(name, cfg, cloud, log, args):
@@ -99,7 +90,8 @@ def handle(name, cfg, cloud, log, args):
distro.install_packages(['ubuntu-fan'])
stop_update_start(
+ distro,
service='ubuntu-fan', config_file=mycfg.get('config_path'),
- content=mycfg.get('config'), systemd=distro.uses_systemd())
+ content=mycfg.get('config'))
# vi: ts=4 expandtab
diff --git a/cloudinit/config/cc_ntp.py b/cloudinit/config/cc_ntp.py
index e183993f..4f358c8a 100644
--- a/cloudinit/config/cc_ntp.py
+++ b/cloudinit/config/cc_ntp.py
@@ -459,21 +459,6 @@ def write_ntp_config_template(distro_name, service_name=None, servers=None,
util.del_file(template_fn)
-def reload_ntp(service, systemd=False):
- """Restart or reload an ntp system service.
-
- @param service: A string specifying the name of the service to be affected.
- @param systemd: A boolean indicating if the distro uses systemd, defaults
- to False.
- @returns: A tuple of stdout, stderr results from executing the action.
- """
- if systemd:
- cmd = ['systemctl', 'reload-or-restart', service]
- else:
- cmd = ['service', service, 'restart']
- subp.subp(cmd, capture=True)
-
-
def supplemental_schema_validation(ntp_config):
"""Validate user-provided ntp:config option values.
@@ -583,10 +568,11 @@ def handle(name, cfg, cloud, log, _args):
packages=ntp_client_config['packages'],
check_exe=ntp_client_config['check_exe'])
try:
- reload_ntp(ntp_client_config['service_name'],
- systemd=cloud.distro.uses_systemd())
+ cloud.distro.manage_service('reload',
+ ntp_client_config.get('service_name'))
except subp.ProcessExecutionError as e:
LOG.exception("Failed to reload/start ntp service: %s", e)
raise
+
# vi: ts=4 expandtab
diff --git a/cloudinit/config/cc_rsyslog.py b/cloudinit/config/cc_rsyslog.py
index 2a2bc931..dd2bbd00 100644
--- a/cloudinit/config/cc_rsyslog.py
+++ b/cloudinit/config/cc_rsyslog.py
@@ -207,16 +207,11 @@ HOST_PORT_RE = re.compile(
r'([:](?P<port>[0-9]+))?$')
-def reload_syslog(command=DEF_RELOAD, systemd=False):
- service = 'rsyslog'
+def reload_syslog(distro, command=DEF_RELOAD):
if command == DEF_RELOAD:
- if systemd:
- cmd = ['systemctl', 'reload-or-try-restart', service]
- else:
- cmd = ['service', service, 'restart']
- else:
- cmd = command
- subp.subp(cmd, capture=True)
+ service = distro.get_option('rsyslog_svcname', 'rsyslog')
+ return distro.manage_service('try-reload', service)
+ return subp.subp(command, capture=True)
def load_config(cfg):
@@ -429,9 +424,7 @@ def handle(name, cfg, cloud, log, _args):
return
try:
- restarted = reload_syslog(
- command=mycfg[KEYNAME_RELOAD],
- systemd=cloud.distro.uses_systemd()),
+ restarted = reload_syslog(cloud.distro, command=mycfg[KEYNAME_RELOAD])
except subp.ProcessExecutionError as e:
restarted = False
log.warning("Failed to reload syslog", e)
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
index 433de751..9efbf61f 100755
--- a/cloudinit/config/cc_set_passwords.py
+++ b/cloudinit/config/cc_set_passwords.py
@@ -94,18 +94,15 @@ PW_SET = (''.join([x for x in ascii_letters + digits
if x not in 'loLOI01']))
-def handle_ssh_pwauth(pw_auth, service_cmd=None, service_name="ssh"):
+def handle_ssh_pwauth(pw_auth, distro):
"""Apply sshd PasswordAuthentication changes.
@param pw_auth: config setting from 'pw_auth'.
Best given as True, False, or "unchanged".
- @param service_cmd: The service command list (['service'])
- @param service_name: The name of the sshd service for the system.
+ @param distro: an instance of the distro class for the target distribution
@return: None"""
cfg_name = "PasswordAuthentication"
- if service_cmd is None:
- service_cmd = ["service"]
if util.is_true(pw_auth):
cfg_val = 'yes'
@@ -124,11 +121,7 @@ def handle_ssh_pwauth(pw_auth, service_cmd=None, service_name="ssh"):
LOG.debug("No need to restart SSH service, %s not updated.", cfg_name)
return
- if 'systemctl' in service_cmd:
- cmd = list(service_cmd) + ["restart", service_name]
- else:
- cmd = list(service_cmd) + [service_name, "restart"]
- subp.subp(cmd)
+ distro.manage_service('restart', distro.get_option('ssh_svcname', 'ssh'))
LOG.debug("Restarted the SSH daemon.")
@@ -229,9 +222,7 @@ def handle(_name, cfg, cloud, log, args):
if expired_users:
log.debug("Expired passwords for: %s users", expired_users)
- handle_ssh_pwauth(
- cfg.get('ssh_pwauth'), service_cmd=cloud.distro.init_cmd,
- service_name=cloud.distro.get_option('ssh_svcname', 'ssh'))
+ handle_ssh_pwauth(cfg.get('ssh_pwauth'), cloud.distro)
if len(errors):
log.debug("%s errors occured, re-raising the last one", len(errors))
diff --git a/cloudinit/config/tests/test_set_passwords.py b/cloudinit/config/tests/test_set_passwords.py
index bbe2ee8f..79118a12 100644
--- a/cloudinit/config/tests/test_set_passwords.py
+++ b/cloudinit/config/tests/test_set_passwords.py
@@ -14,57 +14,52 @@ class TestHandleSshPwauth(CiTestCase):
with_logs = True
- @mock.patch(MODPATH + "subp.subp")
+ @mock.patch("cloudinit.distros.subp.subp")
def test_unknown_value_logs_warning(self, m_subp):
- setpass.handle_ssh_pwauth("floo")
+ cloud = self.tmp_cloud(distro='ubuntu')
+ setpass.handle_ssh_pwauth("floo", cloud.distro)
self.assertIn("Unrecognized value: ssh_pwauth=floo",
self.logs.getvalue())
m_subp.assert_not_called()
@mock.patch(MODPATH + "update_ssh_config", return_value=True)
- @mock.patch(MODPATH + "subp.subp")
+ @mock.patch("cloudinit.distros.subp.subp")
def test_systemctl_as_service_cmd(self, m_subp, m_update_ssh_config):
"""If systemctl in service cmd: systemctl restart name."""
- setpass.handle_ssh_pwauth(
- True, service_cmd=["systemctl"], service_name="myssh")
- self.assertEqual(mock.call(["systemctl", "restart", "myssh"]),
- m_subp.call_args)
-
- @mock.patch(MODPATH + "update_ssh_config", return_value=True)
- @mock.patch(MODPATH + "subp.subp")
- def test_service_as_service_cmd(self, m_subp, m_update_ssh_config):
- """If systemctl in service cmd: systemctl restart name."""
- setpass.handle_ssh_pwauth(
- True, service_cmd=["service"], service_name="myssh")
- self.assertEqual(mock.call(["service", "myssh", "restart"]),
- m_subp.call_args)
+ cloud = self.tmp_cloud(distro='ubuntu')
+ cloud.distro.init_cmd = ['systemctl']
+ setpass.handle_ssh_pwauth(True, cloud.distro)
+ m_subp.assert_called_with(
+ ["systemctl", "restart", "ssh"], capture=True)
@mock.patch(MODPATH + "update_ssh_config", return_value=False)
- @mock.patch(MODPATH + "subp.subp")
+ @mock.patch("cloudinit.distros.subp.subp")
def test_not_restarted_if_not_updated(self, m_subp, m_update_ssh_config):
"""If config is not updated, then no system restart should be done."""
- setpass.handle_ssh_pwauth(True)
+ cloud = self.tmp_cloud(distro='ubuntu')
+ setpass.handle_ssh_pwauth(True, cloud.distro)
m_subp.assert_not_called()
self.assertIn("No need to restart SSH", self.logs.getvalue())
@mock.patch(MODPATH + "update_ssh_config", return_value=True)
- @mock.patch(MODPATH + "subp.subp")
+ @mock.patch("cloudinit.distros.subp.subp")
def test_unchanged_does_nothing(self, m_subp, m_update_ssh_config):
"""If 'unchanged', then no updates to config and no restart."""
- setpass.handle_ssh_pwauth(
- "unchanged", service_cmd=["systemctl"], service_name="myssh")
+ cloud = self.tmp_cloud(distro='ubuntu')
+ setpass.handle_ssh_pwauth("unchanged", cloud.distro)
m_update_ssh_config.assert_not_called()
m_subp.assert_not_called()
- @mock.patch(MODPATH + "subp.subp")
+ @mock.patch("cloudinit.distros.subp.subp")
def test_valid_change_values(self, m_subp):
"""If value is a valid changen value, then update should be called."""
+ cloud = self.tmp_cloud(distro='ubuntu')
upname = MODPATH + "update_ssh_config"
optname = "PasswordAuthentication"
for value in util.FALSE_STRINGS + util.TRUE_STRINGS:
optval = "yes" if value in util.TRUE_STRINGS else "no"
with mock.patch(upname, return_value=False) as m_update:
- setpass.handle_ssh_pwauth(value)
+ setpass.handle_ssh_pwauth(value, cloud.distro)
m_update.assert_called_with({optname: optval})
m_subp.assert_not_called()
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 220bd11f..a8c57cb8 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -784,6 +784,34 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
args.append(message)
return args
+ def manage_service(self, action, service):
+ """
+ Perform the requested action on a service. This handles the common
+ 'systemctl' and 'service' cases and may be overridden in subclasses
+ as necessary.
+ May raise ProcessExecutionError
+ """
+ init_cmd = self.init_cmd
+ if self.uses_systemd() or 'systemctl' in init_cmd:
+ init_cmd = ['systemctl']
+ cmds = {'stop': ['stop', service],
+ 'start': ['start', service],
+ 'enable': ['enable', service],
+ 'restart': ['restart', service],
+ 'reload': ['reload-or-restart', service],
+ 'try-reload': ['reload-or-try-restart', service],
+ }
+ else:
+ cmds = {'stop': [service, 'stop'],
+ 'start': [service, 'start'],
+ 'enable': [service, 'start'],
+ 'restart': [service, 'restart'],
+ 'reload': [service, 'restart'],
+ 'try-reload': [service, 'restart'],
+ }
+ cmd = list(init_cmd) + list(cmds[action])
+ return subp.subp(cmd, capture=True)
+
def _apply_hostname_transformations_to_url(url: str, transformations: list):
"""
diff --git a/tests/unittests/test_distros/test_manage_service.py b/tests/unittests/test_distros/test_manage_service.py
new file mode 100644
index 00000000..47e7cfb0
--- /dev/null
+++ b/tests/unittests/test_distros/test_manage_service.py
@@ -0,0 +1,38 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.tests.helpers import (CiTestCase, mock)
+from tests.unittests.util import TestingDistro
+
+
+class TestManageService(CiTestCase):
+
+ with_logs = True
+
+ def setUp(self):
+ super(TestManageService, self).setUp()
+ self.dist = TestingDistro()
+
+ @mock.patch.object(TestingDistro, 'uses_systemd', return_value=False)
+ @mock.patch("cloudinit.distros.subp.subp")
+ def test_manage_service_systemctl_initcmd(self, m_subp, m_sysd):
+ self.dist.init_cmd = ['systemctl']
+ self.dist.manage_service('start', 'myssh')
+ m_subp.assert_called_with(['systemctl', 'start', 'myssh'],
+ capture=True)
+
+ @mock.patch.object(TestingDistro, 'uses_systemd', return_value=False)
+ @mock.patch("cloudinit.distros.subp.subp")
+ def test_manage_service_service_initcmd(self, m_subp, m_sysd):
+ self.dist.init_cmd = ['service']
+ self.dist.manage_service('start', 'myssh')
+ m_subp.assert_called_with(['service', 'myssh', 'start'], capture=True)
+
+ @mock.patch.object(TestingDistro, 'uses_systemd', return_value=True)
+ @mock.patch("cloudinit.distros.subp.subp")
+ def test_manage_service_systemctl(self, m_subp, m_sysd):
+ self.dist.init_cmd = ['ignore']
+ self.dist.manage_service('start', 'myssh')
+ m_subp.assert_called_with(['systemctl', 'start', 'myssh'],
+ capture=True)
+
+# vi: ts=4 sw=4 expandtab
diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py
index 6b9c8377..c059e2e6 100644
--- a/tests/unittests/test_handler/test_handler_ntp.py
+++ b/tests/unittests/test_handler/test_handler_ntp.py
@@ -112,22 +112,6 @@ class TestNtp(FilesystemMockingTestCase):
check_exe='timesyncd')
install_func.assert_called_once_with([])
- @mock.patch("cloudinit.config.cc_ntp.subp")
- def test_reload_ntp_defaults(self, mock_subp):
- """Test service is restarted/reloaded (defaults)"""
- service = 'ntp_service_name'
- cmd = ['service', service, 'restart']
- cc_ntp.reload_ntp(service)
- mock_subp.subp.assert_called_with(cmd, capture=True)
-
- @mock.patch("cloudinit.config.cc_ntp.subp")
- def test_reload_ntp_systemd(self, mock_subp):
- """Test service is restarted/reloaded (systemd)"""
- service = 'ntp_service_name'
- cc_ntp.reload_ntp(service, systemd=True)
- cmd = ['systemctl', 'reload-or-restart', service]
- mock_subp.subp.assert_called_with(cmd, capture=True)
-
def test_ntp_rename_ntp_conf(self):
"""When NTP_CONF exists, rename_ntp moves it."""
ntpconf = self.tmp_path("ntp.conf", self.new_root)
@@ -488,10 +472,11 @@ class TestNtp(FilesystemMockingTestCase):
cc_ntp.handle('notimportant', cfg, mycloud, None, None)
self.assertEqual(0, m_select.call_count)
+ @mock.patch("cloudinit.distros.subp")
@mock.patch("cloudinit.config.cc_ntp.subp")
@mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
@mock.patch("cloudinit.distros.Distro.uses_systemd")
- def test_ntp_the_whole_package(self, m_sysd, m_select, m_subp):
+ def test_ntp_the_whole_package(self, m_sysd, m_select, m_subp, m_dsubp):
"""Test enabled config renders template, and restarts service """
cfg = {'ntp': {'enabled': True}}
for distro in cc_ntp.distros:
@@ -509,7 +494,7 @@ class TestNtp(FilesystemMockingTestCase):
if distro == 'alpine':
uses_systemd = False
- expected_service_call = ['service', service_name, 'restart']
+ expected_service_call = ['rc-service', service_name, 'restart']
# _mock_ntp_client_config call above did not specify a client
# value and so it defaults to "ntp" which on Alpine Linux only
# supports servers and not pools.
@@ -525,7 +510,7 @@ class TestNtp(FilesystemMockingTestCase):
m_util.is_false.return_value = util.is_false(
cfg['ntp']['enabled'])
cc_ntp.handle('notimportant', cfg, mycloud, None, None)
- m_subp.subp.assert_called_with(
+ m_dsubp.subp.assert_called_with(
expected_service_call, capture=True)
self.assertEqual(expected_content, util.load_file(confpath))
@@ -673,9 +658,8 @@ class TestNtp(FilesystemMockingTestCase):
self.assertEqual(sorted(expected_cfg), sorted(result))
m_which.assert_has_calls([])
- @mock.patch('cloudinit.config.cc_ntp.reload_ntp')
@mock.patch('cloudinit.config.cc_ntp.install_ntp_client')
- def test_ntp_user_provided_config_with_template(self, m_install, m_reload):
+ def test_ntp_user_provided_config_with_template(self, m_install):
custom = r'\n#MyCustomTemplate'
user_template = NTP_TEMPLATE + custom
confpath = os.path.join(self.new_root, 'etc/myntp/myntp.conf')
@@ -702,11 +686,10 @@ class TestNtp(FilesystemMockingTestCase):
util.load_file(confpath))
@mock.patch('cloudinit.config.cc_ntp.supplemental_schema_validation')
- @mock.patch('cloudinit.config.cc_ntp.reload_ntp')
@mock.patch('cloudinit.config.cc_ntp.install_ntp_client')
@mock.patch('cloudinit.config.cc_ntp.select_ntp_client')
def test_ntp_user_provided_config_template_only(self, m_select, m_install,
- m_reload, m_schema):
+ m_schema):
"""Test custom template for default client"""
custom = r'\n#MyCustomTemplate'
user_template = NTP_TEMPLATE + custom
--
2.31.1

View File

@ -0,0 +1,547 @@
From 304be0559f6e6e471b648e559175408b59d15909 Mon Sep 17 00:00:00 2001
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Thu, 2 Jun 2022 15:56:02 +0200
Subject: [PATCH] cc_set_hostname: do not write "localhost" when no hostname is
given (#1453)
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-MergeRequest: 77: cc_set_hostname: do not write "localhost" when no hostname is given (#1453)
RH-Commit: [1/1] 9b54be1a52eff87bf2b9e396edbf998bb9d68d49 (eesposit/cloud-init)
RH-Bugzilla: 2092909
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
commit 74e43496f353db52e15d96abeb54ad63baac5be9
Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Date: Tue May 31 16:03:44 2022 +0200
cc_set_hostname: do not write "localhost" when no hostname is given (#1453)
Systemd used to sometimes ignore localhost in /etc/hostnames, and many programs
like cloud-init used this as a workaround to set a default hostname.
From https://github.com/systemd/systemd/commit/d39079fcaa05e23540d2b1f0270fa31c22a7e9f1:
We would sometimes ignore localhost-style names in /etc/hostname. That is
brittle. If the user configured some hostname, it's most likely because they
want to use that as the hostname. If they don't want to use such a hostname,
they should just not create the config. Everything becomes simples if we just
use the configured hostname as-is.
This behaviour seems to have been a workaround for Anaconda installer and other
tools writing out /etc/hostname with the default of "localhost.localdomain".
Anaconda PR to stop doing that: rhinstaller/anaconda#3040.
That might have been useful as a work-around for other programs misbehaving if
/etc/hostname was not present, but nowadays it's not useful because systemd
mostly controls the hostname and it is perfectly happy without that file.
Apart from making things simpler, this allows users to set a hostname like
"localhost" and have it honoured, if such a whim strikes them.
As also suggested by the Anaconda PR, we need to stop writing default "localhost"
in /etc/hostnames, and let the right service (networking, user) do that if they
need to. Otherwise, "localhost" will permanently stay as hostname and will
prevent other tools like NetworkManager from setting the right one.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RHBZ: 1980403
Conflicts:
almost all test files had conflicts because they use '' instead of ""
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
---
cloudinit/cmd/main.py | 5 ++--
cloudinit/config/cc_apt_configure.py | 2 +-
cloudinit/config/cc_debug.py | 2 +-
cloudinit/config/cc_phone_home.py | 6 ++--
cloudinit/config/cc_set_hostname.py | 14 ++++++++-
cloudinit/config/cc_spacewalk.py | 11 ++++---
cloudinit/config/cc_update_etc_hosts.py | 6 ++--
cloudinit/config/cc_update_hostname.py | 14 ++++++++-
cloudinit/sources/DataSourceAliYun.py | 8 ++++-
cloudinit/sources/DataSourceCloudSigma.py | 9 ++++--
cloudinit/sources/DataSourceGCE.py | 6 +++-
cloudinit/sources/DataSourceScaleway.py | 3 +-
cloudinit/sources/__init__.py | 28 ++++++++++++++----
cloudinit/util.py | 29 +++++++++++++++----
.../unittests/test_datasource/test_aliyun.py | 5 ++--
.../test_datasource/test_cloudsigma.py | 14 +++++----
.../unittests/test_datasource/test_hetzner.py | 2 +-
.../unittests/test_datasource/test_vmware.py | 4 ++-
18 files changed, 125 insertions(+), 43 deletions(-)
diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
index baf1381f..ebd3719d 100644
--- a/cloudinit/cmd/main.py
+++ b/cloudinit/cmd/main.py
@@ -701,8 +701,9 @@ def _maybe_set_hostname(init, stage, retry_stage):
@param retry_stage: String represented logs upon error setting hostname.
"""
cloud = init.cloudify()
- (hostname, _fqdn) = util.get_hostname_fqdn(
- init.cfg, cloud, metadata_only=True)
+ (hostname, _fqdn, _) = util.get_hostname_fqdn(
+ init.cfg, cloud, metadata_only=True
+ )
if hostname: # meta-data or user-data hostname content
try:
cc_set_hostname.handle('set-hostname', init.cfg, cloud, LOG, None)
diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
index bb8a1278..8a2b57b5 100644
--- a/cloudinit/config/cc_apt_configure.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -926,7 +926,7 @@ def search_for_mirror_dns(configured, mirrortype, cfg, cloud):
raise ValueError("unknown mirror type")
# if we have a fqdn, then search its domain portion first
- (_, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ fqdn = util.get_hostname_fqdn(cfg, cloud).fqdn
mydom = ".".join(fqdn.split(".")[1:])
if mydom:
doms.append(".%s" % mydom)
diff --git a/cloudinit/config/cc_debug.py b/cloudinit/config/cc_debug.py
index 4d5a6aa2..7ca4ea0f 100644
--- a/cloudinit/config/cc_debug.py
+++ b/cloudinit/config/cc_debug.py
@@ -88,7 +88,7 @@ def handle(name, cfg, cloud, log, args):
to_print.write("Datasource: %s\n" %
(type_utils.obj_name(cloud.datasource)))
to_print.write("Distro: %s\n" % (type_utils.obj_name(cloud.distro)))
- to_print.write("Hostname: %s\n" % (cloud.get_hostname(True)))
+ to_print.write("Hostname: %s\n" % (cloud.get_hostname(True).hostname))
to_print.write("Instance ID: %s\n" % (cloud.get_instance_id()))
to_print.write("Locale: %s\n" % (cloud.get_locale()))
to_print.write("Launch IDX: %s\n" % (cloud.launch_index))
diff --git a/cloudinit/config/cc_phone_home.py b/cloudinit/config/cc_phone_home.py
index 733c3910..a71237dc 100644
--- a/cloudinit/config/cc_phone_home.py
+++ b/cloudinit/config/cc_phone_home.py
@@ -99,9 +99,9 @@ def handle(name, cfg, cloud, log, args):
post_list = POST_LIST_ALL
all_keys = {}
- all_keys['instance_id'] = cloud.get_instance_id()
- all_keys['hostname'] = cloud.get_hostname()
- all_keys['fqdn'] = cloud.get_hostname(fqdn=True)
+ all_keys["instance_id"] = cloud.get_instance_id()
+ all_keys["hostname"] = cloud.get_hostname().hostname
+ all_keys["fqdn"] = cloud.get_hostname(fqdn=True).hostname
pubkeys = {
'pub_key_dsa': '/etc/ssh/ssh_host_dsa_key.pub',
diff --git a/cloudinit/config/cc_set_hostname.py b/cloudinit/config/cc_set_hostname.py
index 1d23d80d..37e5ac1d 100644
--- a/cloudinit/config/cc_set_hostname.py
+++ b/cloudinit/config/cc_set_hostname.py
@@ -62,7 +62,15 @@ def handle(name, cfg, cloud, log, _args):
log.debug(("Configuration option 'preserve_hostname' is set,"
" not setting the hostname in module %s"), name)
return
- (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+
+ # Set prefer_fqdn_over_hostname value in distro
+ hostname_fqdn = util.get_cfg_option_bool(
+ cfg, "prefer_fqdn_over_hostname", None
+ )
+ if hostname_fqdn is not None:
+ cloud.distro.set_option("prefer_fqdn_over_hostname", hostname_fqdn)
+
+ (hostname, fqdn, is_default) = util.get_hostname_fqdn(cfg, cloud)
# Check for previous successful invocation of set-hostname
# set-hostname artifact file accounts for both hostname and fqdn
@@ -79,6 +87,10 @@ def handle(name, cfg, cloud, log, _args):
if not hostname_changed:
log.debug('No hostname changes. Skipping set-hostname')
return
+ if is_default and hostname == "localhost":
+ # https://github.com/systemd/systemd/commit/d39079fcaa05e23540d2b1f0270fa31c22a7e9f1
+ log.debug("Hostname is localhost. Let other services handle this.")
+ return
log.debug("Setting the hostname to %s (%s)", fqdn, hostname)
try:
cloud.distro.set_hostname(hostname, fqdn)
diff --git a/cloudinit/config/cc_spacewalk.py b/cloudinit/config/cc_spacewalk.py
index 95083607..29bf8415 100644
--- a/cloudinit/config/cc_spacewalk.py
+++ b/cloudinit/config/cc_spacewalk.py
@@ -79,10 +79,13 @@ def handle(name, cfg, cloud, log, _args):
# Need to have this installed before further things will work.
cloud.distro.install_packages(required_packages)
if not is_registered():
- do_register(spacewalk_server,
- cloud.datasource.get_hostname(fqdn=True),
- proxy=cfg.get("proxy"), log=log,
- activation_key=cfg.get('activation_key'))
+ do_register(
+ spacewalk_server,
+ cloud.datasource.get_hostname(fqdn=True).hostname,
+ proxy=cfg.get("proxy"),
+ log=log,
+ activation_key=cfg.get("activation_key"),
+ )
else:
log.debug("Skipping module named %s, 'spacewalk/server' key"
" was not found in configuration", name)
diff --git a/cloudinit/config/cc_update_etc_hosts.py b/cloudinit/config/cc_update_etc_hosts.py
index 03fffb96..69c5a2f6 100644
--- a/cloudinit/config/cc_update_etc_hosts.py
+++ b/cloudinit/config/cc_update_etc_hosts.py
@@ -59,8 +59,8 @@ frequency = PER_ALWAYS
def handle(name, cfg, cloud, log, _args):
manage_hosts = util.get_cfg_option_str(cfg, "manage_etc_hosts", False)
- if util.translate_bool(manage_hosts, addons=['template']):
- (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ if util.translate_bool(manage_hosts, addons=["template"]):
+ (hostname, fqdn, _) = util.get_hostname_fqdn(cfg, cloud)
if not hostname:
log.warning(("Option 'manage_etc_hosts' was set,"
" but no hostname was found"))
@@ -78,7 +78,7 @@ def handle(name, cfg, cloud, log, _args):
{'hostname': hostname, 'fqdn': fqdn})
elif manage_hosts == "localhost":
- (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ (hostname, fqdn, _) = util.get_hostname_fqdn(cfg, cloud)
if not hostname:
log.warning(("Option 'manage_etc_hosts' was set,"
" but no hostname was found"))
diff --git a/cloudinit/config/cc_update_hostname.py b/cloudinit/config/cc_update_hostname.py
index d5f4eb5a..61496525 100644
--- a/cloudinit/config/cc_update_hostname.py
+++ b/cloudinit/config/cc_update_hostname.py
@@ -45,7 +45,19 @@ def handle(name, cfg, cloud, log, _args):
" not updating the hostname in module %s"), name)
return
- (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+ # Set prefer_fqdn_over_hostname value in distro
+ hostname_fqdn = util.get_cfg_option_bool(
+ cfg, "prefer_fqdn_over_hostname", None
+ )
+ if hostname_fqdn is not None:
+ cloud.distro.set_option("prefer_fqdn_over_hostname", hostname_fqdn)
+
+ (hostname, fqdn, is_default) = util.get_hostname_fqdn(cfg, cloud)
+ if is_default and hostname == "localhost":
+ # https://github.com/systemd/systemd/commit/d39079fcaa05e23540d2b1f0270fa31c22a7e9f1
+ log.debug("Hostname is localhost. Let other services handle this.")
+ return
+
try:
prev_fn = os.path.join(cloud.get_cpath('data'), "previous-hostname")
log.debug("Updating hostname to %s (%s)", fqdn, hostname)
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
index 09052873..365ce176 100644
--- a/cloudinit/sources/DataSourceAliYun.py
+++ b/cloudinit/sources/DataSourceAliYun.py
@@ -3,6 +3,7 @@
from cloudinit import dmi
from cloudinit import sources
from cloudinit.sources import DataSourceEc2 as EC2
+from cloudinit.sources import DataSourceHostname
ALIYUN_PRODUCT = "Alibaba Cloud ECS"
@@ -17,7 +18,12 @@ class DataSourceAliYun(EC2.DataSourceEc2):
extended_metadata_versions = []
def get_hostname(self, fqdn=False, resolve_ip=False, metadata_only=False):
- return self.metadata.get('hostname', 'localhost.localdomain')
+ hostname = self.metadata.get("hostname")
+ is_default = False
+ if hostname is None:
+ hostname = "localhost.localdomain"
+ is_default = True
+ return DataSourceHostname(hostname, is_default)
def get_public_ssh_keys(self):
return parse_public_keys(self.metadata.get('public-keys', {}))
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index f63baf74..9f6f1d42 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -12,6 +12,8 @@ from cloudinit.cs_utils import Cepko, SERIAL_PORT
from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import sources
+from cloudinit.sources import DataSourceHostname
+from cloudinit.sources.helpers.cloudsigma import SERIAL_PORT, Cepko
LOG = logging.getLogger(__name__)
@@ -89,10 +91,11 @@ class DataSourceCloudSigma(sources.DataSource):
Cleans up and uses the server's name if the latter is set. Otherwise
the first part from uuid is being used.
"""
- if re.match(r'^[A-Za-z0-9 -_\.]+$', self.metadata['name']):
- return self.metadata['name'][:61]
+ if re.match(r"^[A-Za-z0-9 -_\.]+$", self.metadata["name"]):
+ ret = self.metadata["name"][:61]
else:
- return self.metadata['uuid'].split('-')[0]
+ ret = self.metadata["uuid"].split("-")[0]
+ return DataSourceHostname(ret, False)
def get_public_ssh_keys(self):
return [self.ssh_public_key]
diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py
index 746caddb..ac5d3e5b 100644
--- a/cloudinit/sources/DataSourceGCE.py
+++ b/cloudinit/sources/DataSourceGCE.py
@@ -13,6 +13,8 @@ from cloudinit import log as logging
from cloudinit import sources
from cloudinit import url_helper
from cloudinit import util
+from cloudinit.net.dhcp import EphemeralDHCPv4
+from cloudinit.sources import DataSourceHostname
LOG = logging.getLogger(__name__)
@@ -100,7 +102,9 @@ class DataSourceGCE(sources.DataSource):
def get_hostname(self, fqdn=False, resolve_ip=False, metadata_only=False):
# GCE has long FDQN's and has asked for short hostnames.
- return self.metadata['local-hostname'].split('.')[0]
+ return DataSourceHostname(
+ self.metadata["local-hostname"].split(".")[0], False
+ )
@property
def availability_zone(self):
diff --git a/cloudinit/sources/DataSourceScaleway.py b/cloudinit/sources/DataSourceScaleway.py
index 41be7665..875b5bb3 100644
--- a/cloudinit/sources/DataSourceScaleway.py
+++ b/cloudinit/sources/DataSourceScaleway.py
@@ -33,6 +33,7 @@ from cloudinit import util
from cloudinit import net
from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError
from cloudinit.event import EventType
+from cloudinit.sources import DataSourceHostname
LOG = logging.getLogger(__name__)
@@ -271,7 +272,7 @@ class DataSourceScaleway(sources.DataSource):
return ssh_keys
def get_hostname(self, fqdn=False, resolve_ip=False, metadata_only=False):
- return self.metadata['hostname']
+ return DataSourceHostname(self.metadata["hostname"], False)
@property
def availability_zone(self):
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 7d74f8d9..6f5d7ea6 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -134,6 +134,11 @@ def redact_sensitive_keys(metadata, redact_value=REDACT_SENSITIVE_VALUE):
URLParams = namedtuple(
'URLParms', ['max_wait_seconds', 'timeout_seconds', 'num_retries'])
+DataSourceHostname = namedtuple(
+ "DataSourceHostname",
+ ["hostname", "is_default"],
+)
+
class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta):
@@ -233,7 +238,7 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta):
def _get_standardized_metadata(self, instance_data):
"""Return a dictionary of standardized metadata keys."""
- local_hostname = self.get_hostname()
+ local_hostname = self.get_hostname().hostname
instance_id = self.get_instance_id()
availability_zone = self.availability_zone
# In the event of upgrade from existing cloudinit, pickled datasource
@@ -593,22 +598,33 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta):
@param metadata_only: Boolean, set True to avoid looking up hostname
if meta-data doesn't have local-hostname present.
- @return: hostname or qualified hostname. Optionally return None when
+ @return: a DataSourceHostname namedtuple
+ <hostname or qualified hostname>, <is_default> (str, bool).
+ is_default is a bool and
+ it's true only if hostname is localhost and was
+ returned by util.get_hostname() as a default.
+ This is used to differentiate with a user-defined
+ localhost hostname.
+ Optionally return (None, False) when
metadata_only is True and local-hostname data is not available.
"""
defdomain = "localdomain"
defhost = "localhost"
domain = defdomain
+ is_default = False
if not self.metadata or not self.metadata.get('local-hostname'):
if metadata_only:
- return None
+ return DataSourceHostname(None, is_default)
# this is somewhat questionable really.
# the cloud datasource was asked for a hostname
# and didn't have one. raising error might be more appropriate
# but instead, basically look up the existing hostname
toks = []
hostname = util.get_hostname()
+ if hostname == "localhost":
+ # default hostname provided by socket.gethostname()
+ is_default = True
hosts_fqdn = util.get_fqdn_from_hosts(hostname)
if hosts_fqdn and hosts_fqdn.find(".") > 0:
toks = str(hosts_fqdn).split(".")
@@ -641,9 +657,9 @@ class DataSource(CloudInitPickleMixin, metaclass=abc.ABCMeta):
hostname = toks[0]
if fqdn and domain != defdomain:
- return "%s.%s" % (hostname, domain)
- else:
- return hostname
+ hostname = "%s.%s" % (hostname, domain)
+
+ return DataSourceHostname(hostname, is_default)
def get_package_mirror_info(self):
return self.distro.get_package_mirror_info(data_source=self)
diff --git a/cloudinit/util.py b/cloudinit/util.py
index fe37ae89..8a2b13ba 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -32,7 +32,8 @@ import subprocess
import sys
import time
from base64 import b64decode, b64encode
-from errno import ENOENT
+from collections import deque, namedtuple
+from errno import EACCES, ENOENT
from functools import lru_cache
from urllib import parse
from typing import List
@@ -967,6 +968,12 @@ def dos2unix(contents):
return contents.replace('\r\n', '\n')
+HostnameFqdnInfo = namedtuple(
+ "HostnameFqdnInfo",
+ ["hostname", "fqdn", "is_default"],
+)
+
+
def get_hostname_fqdn(cfg, cloud, metadata_only=False):
"""Get hostname and fqdn from config if present and fallback to cloud.
@@ -974,9 +981,17 @@ def get_hostname_fqdn(cfg, cloud, metadata_only=False):
@param cloud: Cloud instance from init.cloudify().
@param metadata_only: Boolean, set True to only query cloud meta-data,
returning None if not present in meta-data.
- @return: a Tuple of strings <hostname>, <fqdn>. Values can be none when
+ @return: a namedtuple of
+ <hostname>, <fqdn>, <is_default> (str, str, bool).
+ Values can be none when
metadata_only is True and no cfg or metadata provides hostname info.
+ is_default is a bool and
+ it's true only if hostname is localhost and was
+ returned by util.get_hostname() as a default.
+ This is used to differentiate with a user-defined
+ localhost hostname.
"""
+ is_default = False
if "fqdn" in cfg:
# user specified a fqdn. Default hostname then is based off that
fqdn = cfg['fqdn']
@@ -990,12 +1005,16 @@ def get_hostname_fqdn(cfg, cloud, metadata_only=False):
else:
# no fqdn set, get fqdn from cloud.
# get hostname from cfg if available otherwise cloud
- fqdn = cloud.get_hostname(fqdn=True, metadata_only=metadata_only)
+ fqdn = cloud.get_hostname(
+ fqdn=True, metadata_only=metadata_only
+ ).hostname
if "hostname" in cfg:
hostname = cfg['hostname']
else:
- hostname = cloud.get_hostname(metadata_only=metadata_only)
- return (hostname, fqdn)
+ hostname, is_default = cloud.get_hostname(
+ metadata_only=metadata_only
+ )
+ return HostnameFqdnInfo(hostname, fqdn, is_default)
def get_fqdn_from_hosts(hostname, filename="/etc/hosts"):
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index cab1ac2b..acdee0c9 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -127,8 +127,9 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
self.ds.get_instance_id())
def _test_host_name(self):
- self.assertEqual(self.default_metadata['hostname'],
- self.ds.get_hostname())
+ self.assertEqual(
+ self.default_metadata["hostname"], self.ds.get_hostname().hostname
+ )
@mock.patch("cloudinit.sources.DataSourceAliYun._is_aliyun")
def test_with_mock_server(self, m_is_aliyun):
diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py
index 7aa3b1d1..79d52725 100644
--- a/tests/unittests/test_datasource/test_cloudsigma.py
+++ b/tests/unittests/test_datasource/test_cloudsigma.py
@@ -57,12 +57,14 @@ class DataSourceCloudSigmaTest(test_helpers.CiTestCase):
def test_get_hostname(self):
self.datasource.get_data()
- self.assertEqual("test_server", self.datasource.get_hostname())
- self.datasource.metadata['name'] = ''
- self.assertEqual("65b2fb23", self.datasource.get_hostname())
- utf8_hostname = b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82'.decode('utf-8')
- self.datasource.metadata['name'] = utf8_hostname
- self.assertEqual("65b2fb23", self.datasource.get_hostname())
+ self.assertEqual(
+ "test_server", self.datasource.get_hostname().hostname
+ )
+ self.datasource.metadata["name"] = ""
+ self.assertEqual("65b2fb23", self.datasource.get_hostname().hostname)
+ utf8_hostname = b"\xd1\x82\xd0\xb5\xd1\x81\xd1\x82".decode("utf-8")
+ self.datasource.metadata["name"] = utf8_hostname
+ self.assertEqual("65b2fb23", self.datasource.get_hostname().hostname)
def test_get_public_ssh_keys(self):
self.datasource.get_data()
diff --git a/tests/unittests/test_datasource/test_hetzner.py b/tests/unittests/test_datasource/test_hetzner.py
index eadb92f1..0ccb07c9 100644
--- a/tests/unittests/test_datasource/test_hetzner.py
+++ b/tests/unittests/test_datasource/test_hetzner.py
@@ -97,7 +97,7 @@ class TestDataSourceHetzner(CiTestCase):
self.assertTrue(m_readmd.called)
- self.assertEqual(METADATA.get('hostname'), ds.get_hostname())
+ self.assertEqual(METADATA.get("hostname"), ds.get_hostname().hostname)
self.assertEqual(METADATA.get('public-keys'),
ds.get_public_ssh_keys())
diff --git a/tests/unittests/test_datasource/test_vmware.py b/tests/unittests/test_datasource/test_vmware.py
index 597db7c8..3bffd136 100644
--- a/tests/unittests/test_datasource/test_vmware.py
+++ b/tests/unittests/test_datasource/test_vmware.py
@@ -356,7 +356,9 @@ class TestDataSourceVMwareGuestInfo_InvalidPlatform(FilesystemMockingTestCase):
def assert_metadata(test_obj, ds, metadata):
test_obj.assertEqual(metadata.get("instance-id"), ds.get_instance_id())
- test_obj.assertEqual(metadata.get("local-hostname"), ds.get_hostname())
+ test_obj.assertEqual(
+ metadata.get("local-hostname"), ds.get_hostname().hostname
+ )
expected_public_keys = metadata.get("public_keys")
if not isinstance(expected_public_keys, list):
--
2.31.1

View File

@ -1,6 +1,6 @@
Name: cloud-init
Version: 21.1
Release: 19%{?dist}.1.alma
Release: 19%{?dist}.3.alma
Summary: Cloud instance init scripts
License: ASL 2.0 or GPLv3
URL: http://launchpad.net/cloud-init
@ -66,6 +66,10 @@ Patch29: ci-Adding-_netdev-to-the-default-mount-configuration.patch
Patch30: ci-Setting-highest-autoconnect-priority-for-network-scr.patch
# For bz#2088027 - [RHEL-9.1] SSH keys with \r\n line breaks are not properly handled on Azure [rhel-9.0.0.z]
Patch31: ci-Add-r-n-check-for-SSH-keys-in-Azure-889.patch
# For bz#2091935 - cloud-init has an undeclared dependency on the initscripts rpm [rhel-9.0.0.z]
Patch32: ci-Leave-the-details-of-service-management-to-the-distr.patch
# For bz#2092909 - [RHV] RHEL 9 VM with cloud-init without hostname set doesn't result in the FQDN as hostname [rhel-9.0.0.z]
Patch33: ci-cc_set_hostname-do-not-write-localhost-when-no-hostn.patch
# AlmaLinux patches
Patch100: cloud-init-20.3-add_almalinux.patch
@ -270,9 +274,18 @@ fi
%config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf
%changelog
* Mon May 23 2022 Eduard Abdullin <eabdullin@almalinux.org> - 21.1-19.el9_0.1.alma
* Tue Jun 28 2022 Eduard Abdullin <eabdullin@almalinux.org> - 21.1-19.el9_0.3.alma
- AlmaLinux support
* Wed Jun 08 2022 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-19.el9_0.3
- ci-cc_set_hostname-do-not-write-localhost-when-no-hostn.patch [bz#2092909]
- Resolves: bz#2092909
([RHV] RHEL 9 VM with cloud-init without hostname set doesn't result in the FQDN as hostname [rhel-9.0.0.z])
* Fri Jun 03 2022 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-19.el9_0.2
- ci-Leave-the-details-of-service-management-to-the-distr.patch [bz#2091935]
- Resolves: bz#2091935
(cloud-init has an undeclared dependency on the initscripts rpm [rhel-9.0.0.z])
* Wed May 18 2022 Miroslav Rezanina <mrezanin@redhat.com> - 21.1-19.el9_0.1
- ci-Add-r-n-check-for-SSH-keys-in-Azure-889.patch [bz#2088027]
- Resolves: bz#2088027