Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 6 additions & 35 deletions coriolis/osmorphing/osdetect/centos.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,17 @@
# Copyright 2020 Cloudbase Solutions Srl
# All Rights Reserved.

import re

from coriolis import constants
from coriolis.osmorphing.osdetect import base
from oslo_log import log as logging
from coriolis.osmorphing.osdetect import common


LOG = logging.getLogger(__name__)
CENTOS_DISTRO_IDENTIFIER = "CentOS"
CENTOS_STREAM_DISTRO_IDENTIFIER = "CentOS Stream"
ALMA_IDENTIFIER = "AlmaLinux"
CENTOS_DISTRO_IDENTIFIER = common.CENTOS_DISTRO_IDENTIFIER
CENTOS_STREAM_DISTRO_IDENTIFIER = common.CENTOS_STREAM_DISTRO_IDENTIFIER


class CentOSOSDetectTools(base.BaseLinuxOSDetectTools):

def detect_os(self):
info = {}
redhat_release_path = "etc/redhat-release"
if self._test_path(redhat_release_path):
release_info = self._read_file(
redhat_release_path).decode().splitlines()
if release_info:
m = re.match(r"^(.*) release ([0-9]+(\.[0-9]+)*)( \(.*\))?.*$",
release_info[0].strip())
if m:
distro, version, _, _ = m.groups()
if (CENTOS_DISTRO_IDENTIFIER not in distro and
ALMA_IDENTIFIER not in distro):
LOG.debug(
"Distro does not appear to be a CentOS or Alma: "
f"{distro}")
return {}

distribution_name = CENTOS_DISTRO_IDENTIFIER
if CENTOS_STREAM_DISTRO_IDENTIFIER in distro:
distribution_name = CENTOS_STREAM_DISTRO_IDENTIFIER
info = {
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": distribution_name,
"release_version": version,
"friendly_release_name": "%s Version %s" % (
distribution_name, version)}
return info
return common.detect_os_from_os_release(
self._get_os_release(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RHEL 6 and derivate OS-es do not have the /etc/os-release file. Are we going to drop RHEL 6 support? If so, the PR description must clearly state it. We can also remove RHEL 6 specific os-morphing code.

match_ids={"centos", "almalinux"})
55 changes: 55 additions & 0 deletions coriolis/osmorphing/osdetect/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2026 Cloudbase Solutions Srl
# All Rights Reserved.

"""Shared /etc/os-release parsing for RHEL-family osdetect tools."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rename the module to "redhat_common.py"


from coriolis import constants

ROCKY_LINUX_DISTRO_IDENTIFIER = "Rocky Linux"
CENTOS_DISTRO_IDENTIFIER = "CentOS"
CENTOS_STREAM_DISTRO_IDENTIFIER = "CentOS Stream"
RED_HAT_DISTRO_IDENTIFIER = "Red Hat Enterprise Linux"
ORACLE_DISTRO_IDENTIFIER = "Oracle Linux"

OS_RELEASE_ID_MAP = {
"rocky": ROCKY_LINUX_DISTRO_IDENTIFIER,
"centos": CENTOS_DISTRO_IDENTIFIER,
"almalinux": CENTOS_DISTRO_IDENTIFIER,
"rhel": RED_HAT_DISTRO_IDENTIFIER,
"ol": ORACLE_DISTRO_IDENTIFIER,
}


def detect_os_from_os_release(os_release, *, match_ids):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use type hints and document the "os_release" parameter, stating that it's a dict populated by /etc/os-release values.

"""Detect a RHEL-family distro from /etc/os-release.

Each osdetect module passes the ``ID`` value(s) it handles, e.g.
``match_ids={"rocky"}`` or ``match_ids={"centos", "almalinux"}``.
"""
if not os_release:
return {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the very least, we should log a warning if we haven't received the os_release dict.


os_id = (os_release.get("ID") or "").lower()
if os_id not in match_ids:
return {}

default_name = OS_RELEASE_ID_MAP.get(os_id)
if not default_name:
return {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning? The id matches but we don't have a default distribution name for that given identifier.


version = os_release.get("VERSION_ID")
if not version:
return {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, the OS id matches but it doesn't include a version. Let's log a warning.


name = os_release.get("NAME") or default_name
if os_id == "centos" and "Stream" in name:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this special check for centos stream? I tried out a docker image and got NAME="CentOS Stream", which already matches CENTOS_STREAM_DISTRO_IDENTIFIER

Speaking of which, won't there always be a NAME entry? Do we actually need a dict of distro names?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Stream was added starting with CentOS 8, regarding the NAME entry, I agree on using NAME, this was the previous name that was used (didn't want to diverge too much)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that the default will probably never be used, making OS_RELEASE_ID_MAP redundant.

distribution_name = CENTOS_STREAM_DISTRO_IDENTIFIER
else:
distribution_name = default_name

return {
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": distribution_name,
"release_version": version,
"friendly_release_name": "%s Version %s" % (
distribution_name, version)}
26 changes: 5 additions & 21 deletions coriolis/osmorphing/osdetect/oracle.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
# Copyright 2020 Cloudbase Solutions Srl
# All Rights Reserved.

import re

from coriolis import constants
from coriolis.osmorphing.osdetect import base
from coriolis.osmorphing.osdetect import common


ORACLE_DISTRO_IDENTIFIER = "Oracle Linux"
ORACLE_DISTRO_IDENTIFIER = common.ORACLE_DISTRO_IDENTIFIER


class OracleOSDetectTools(base.BaseLinuxOSDetectTools):

def detect_os(self):
info = {}
oracle_release_path = "etc/oracle-release"
if self._test_path(oracle_release_path):
release_info = self._read_file(
oracle_release_path).decode().splitlines()
if release_info:
m = re.match(r"^(.*) release ([0-9].*)$",
release_info[0].strip())
if m:
distro, version = m.groups()
info = {
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": ORACLE_DISTRO_IDENTIFIER,
"release_version": version,
"friendly_release_name": "%s Version %s" % (
distro, version)}
return info
return common.detect_os_from_os_release(
self._get_os_release(),
match_ids={"ol"})
34 changes: 5 additions & 29 deletions coriolis/osmorphing/osdetect/redhat.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,16 @@
# Copyright 2020 Cloudbase Solutions Srl
# All Rights Reserved.

import re

from oslo_log import log as logging

from coriolis import constants
from coriolis.osmorphing.osdetect import base
from coriolis.osmorphing.osdetect import common


LOG = logging.getLogger(__name__)
RED_HAT_DISTRO_IDENTIFIER = "Red Hat Enterprise Linux"
RED_HAT_DISTRO_IDENTIFIER = common.RED_HAT_DISTRO_IDENTIFIER


class RedHatOSDetectTools(base.BaseLinuxOSDetectTools):

def detect_os(self):
info = {}
redhat_release_path = "etc/redhat-release"
if self._test_path(redhat_release_path):
release_info = self._read_file(
redhat_release_path).decode().splitlines()
if release_info:
m = re.match(r"^(.*) release ([0-9].*) \((.*)\).*$",
release_info[0].strip())
if m:
distro, version, _ = m.groups()
if RED_HAT_DISTRO_IDENTIFIER not in distro:
LOG.debug(
"Distro does not appear to be a RHEL: %s", distro)
return {}

info = {
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": RED_HAT_DISTRO_IDENTIFIER,
"release_version": version,
"friendly_release_name": "%s Version %s" % (
RED_HAT_DISTRO_IDENTIFIER, version)}
return info
return common.detect_os_from_os_release(
self._get_os_release(),
match_ids={"rhel"})
34 changes: 5 additions & 29 deletions coriolis/osmorphing/osdetect/rocky.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,16 @@
# Copyright 2023 Cloudbase Solutions Srl
# All Rights Reserved.

import re

from coriolis import constants
from coriolis.osmorphing.osdetect import base
from oslo_log import log as logging
from coriolis.osmorphing.osdetect import common


LOG = logging.getLogger(__name__)
ROCKY_LINUX_DISTRO_IDENTIFIER = "Rocky Linux"
ROCKY_LINUX_DISTRO_IDENTIFIER = common.ROCKY_LINUX_DISTRO_IDENTIFIER


class RockyLinuxOSDetectTools(base.BaseLinuxOSDetectTools):

def detect_os(self):
info = {}
redhat_release_path = "etc/redhat-release"
if self._test_path(redhat_release_path):
release_info = self._read_file(
redhat_release_path).decode().splitlines()
if release_info:
m = re.match(r"^(.*) release ([0-9]+(\.[0-9]+)*)( \(.*\))?.*$",
release_info[0].strip())
if m:
distro, version, _, _ = m.groups()
if ROCKY_LINUX_DISTRO_IDENTIFIER not in distro:
LOG.debug(
"Distro does not appear to be a Rocky Linux: %s",
distro)
return {}

info = {
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": ROCKY_LINUX_DISTRO_IDENTIFIER,
"release_version": version,
"friendly_release_name": "%s Version %s" % (
ROCKY_LINUX_DISTRO_IDENTIFIER, version)}
return info
return common.detect_os_from_os_release(
self._get_os_release(),
match_ids={"rocky"})
91 changes: 57 additions & 34 deletions coriolis/tests/osmorphing/osdetect/test_centos.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Copyright 2024 Cloudbase Solutions Srl
# All Rights Reserved.

import logging
from unittest import mock

from coriolis import constants
from coriolis.osmorphing.osdetect import base
from coriolis.osmorphing.osdetect import centos
from coriolis.tests import test_base
Expand All @@ -18,14 +18,16 @@ def setUp(self):
mock.sentinel.conn, mock.sentinel.os_root_dir,
mock.sentinel.operation_timeout)

@mock.patch.object(base.BaseLinuxOSDetectTools, '_test_path')
@mock.patch.object(base.BaseLinuxOSDetectTools, '_read_file')
def test_detect_os(self, mock_read_file, mock_test_path):
mock_test_path.return_value = True
mock_read_file.return_value = b"CentOS Linux release 7.9 (Core)"
@mock.patch.object(base.BaseLinuxOSDetectTools, '_get_os_release')
def test_detect_os(self, mock_get_os_release):
mock_get_os_release.return_value = {
"ID": "centos",
"VERSION_ID": "7.9",
"NAME": "CentOS Linux",
}

expected_info = {
"os_type": centos.constants.OS_TYPE_LINUX,
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": centos.CENTOS_DISTRO_IDENTIFIER,
"release_version": '7.9',
"friendly_release_name": "%s Version %s" % (
Expand All @@ -34,19 +36,20 @@ def test_detect_os(self, mock_read_file, mock_test_path):

result = self.centos_os_detect_tools.detect_os()

mock_test_path.assert_called_once_with("etc/redhat-release")
mock_read_file.assert_called_once_with("etc/redhat-release")
mock_get_os_release.assert_called_once_with()

self.assertEqual(result, expected_info)

@mock.patch.object(base.BaseLinuxOSDetectTools, '_test_path')
@mock.patch.object(base.BaseLinuxOSDetectTools, '_read_file')
def test_detect_os_centos_stream(self, mock_read_file, mock_test_path):
mock_test_path.return_value = True
mock_read_file.return_value = b"CentOS Stream release 8.3"
@mock.patch.object(base.BaseLinuxOSDetectTools, '_get_os_release')
def test_detect_os_centos_stream(self, mock_get_os_release):
mock_get_os_release.return_value = {
"ID": "centos",
"VERSION_ID": "8.3",
"NAME": "CentOS Stream",
}

expected_info = {
"os_type": centos.constants.OS_TYPE_LINUX,
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": centos.CENTOS_STREAM_DISTRO_IDENTIFIER,
"release_version": '8.3',
"friendly_release_name": "%s Version %s" % (
Expand All @@ -55,19 +58,20 @@ def test_detect_os_centos_stream(self, mock_read_file, mock_test_path):

result = self.centos_os_detect_tools.detect_os()

mock_test_path.assert_called_once_with("etc/redhat-release")
mock_read_file.assert_called_once_with("etc/redhat-release")
mock_get_os_release.assert_called_once_with()

self.assertEqual(result, expected_info)

@mock.patch.object(base.BaseLinuxOSDetectTools, '_test_path')
@mock.patch.object(base.BaseLinuxOSDetectTools, '_read_file')
def test_detect_os_centos_stream_10(self, mock_read_file, mock_test_path):
mock_test_path.return_value = True
mock_read_file.return_value = b"CentOS Stream release 10"
@mock.patch.object(base.BaseLinuxOSDetectTools, '_get_os_release')
def test_detect_os_centos_stream_10(self, mock_get_os_release):
mock_get_os_release.return_value = {
"ID": "centos",
"VERSION_ID": "10",
"NAME": "CentOS Stream",
}

expected_info = {
"os_type": centos.constants.OS_TYPE_LINUX,
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": centos.CENTOS_STREAM_DISTRO_IDENTIFIER,
"release_version": '10',
"friendly_release_name": "%s Version %s" % (
Expand All @@ -78,17 +82,36 @@ def test_detect_os_centos_stream_10(self, mock_read_file, mock_test_path):

self.assertEqual(result, expected_info)

@mock.patch.object(base.BaseLinuxOSDetectTools, '_test_path')
@mock.patch.object(base.BaseLinuxOSDetectTools, '_read_file')
def test_detect_os_not_centos(self, mock_read_file, mock_test_path):
mock_test_path.return_value = True
mock_read_file.return_value = b"dummy release 8.3"
@mock.patch.object(base.BaseLinuxOSDetectTools, '_get_os_release')
def test_detect_os_almalinux(self, mock_get_os_release):
mock_get_os_release.return_value = {
"ID": "almalinux",
"VERSION_ID": "9.4",
"NAME": "AlmaLinux",
"ID_LIKE": "rhel centos fedora",
}

with self.assertLogs('coriolis.osmorphing.osdetect.centos',
level=logging.DEBUG):
result = self.centos_os_detect_tools.detect_os()
expected_info = {
"os_type": constants.OS_TYPE_LINUX,
"distribution_name": centos.CENTOS_DISTRO_IDENTIFIER,
"release_version": '9.4',
"friendly_release_name": "%s Version %s" % (
centos.CENTOS_DISTRO_IDENTIFIER, '9.4')
}

self.assertEqual(result, {})
result = self.centos_os_detect_tools.detect_os()

self.assertEqual(result, expected_info)

@mock.patch.object(base.BaseLinuxOSDetectTools, '_get_os_release')
def test_detect_os_not_centos(self, mock_get_os_release):
mock_get_os_release.return_value = {
"ID": "rocky",
"VERSION_ID": "8.3",
"NAME": "Rocky Linux",
}

result = self.centos_os_detect_tools.detect_os()

mock_test_path.assert_called_once_with("etc/redhat-release")
mock_read_file.assert_called_once_with("etc/redhat-release")
self.assertEqual(result, {})
mock_get_os_release.assert_called_once_with()
Loading
Loading