Skip to content

Commit

Permalink
[Policy] Consolidate check() across policies
Browse files Browse the repository at this point in the history
There is a common theme for new policies to subclass an existing one and
just update the `check()` method. In almost all instances, the `check()`
method remains almost entirely the same, simply changing a release file
name or string.

Reduce repeating ourselves by consolidating this into the base
`LinuxPolicy.check()`, and leverage per-policy class attributes
`os_release_file`, `os_release_name`, and `os_release_id`. The first of
which will enable a policy if the specified file is found. Failing that,
`os_release_name` is used to check the `NAME` field in
`/etc/os-release`, and `os_release_id` is used to check the `ID` field
in the same file if the name pattern does not match (or is not set).

Note that we do _not_ check the `ID_LIKE` field.

This allows a policy to be defined with as little as something like the
following:

```
class MyPolicy(LinuxPolicy):
    vendor = 'Myself'
    os_release_file = '/etc/mylinux-release'
    os_release_name = 'MyLinux'
    os_release_id ='mynix'
```

Resolves: #1934

Signed-off-by: Jake Hunsaker <[email protected]>
  • Loading branch information
TurboTurtle committed Sep 19, 2024
1 parent 5177384 commit a0035b6
Show file tree
Hide file tree
Showing 19 changed files with 108 additions and 347 deletions.
6 changes: 3 additions & 3 deletions sos/collector/sosnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def set_node_manifest(self, manifest):
"""
self.manifest = manifest
self.manifest.add_field('hostname', self._hostname)
self.manifest.add_field('policy', self.host.distro)
self.manifest.add_field('policy', self.host.os_release_name)
self.manifest.add_field('sos_version', self.sos_info['version'])
self.manifest.add_field('final_sos_command', '')
self.manifest.add_field('transport', self._transport.name)
Expand Down Expand Up @@ -390,14 +390,14 @@ def determine_host_policy(self):
"""
if self.local:
self.log_info(
f"using local policy {self.commons['policy'].distro}")
f"using local policy {self.commons['policy'].os_release_name}")
return self.commons['policy']
host = load(cache={}, sysroot=self.opts.sysroot, init=InitSystem(),
probe_runtime=True,
remote_exec=self._transport.run_command,
remote_check=self.read_file('/etc/os-release'))
if host:
self.log_info(f"loaded policy {host.distro} for host")
self.log_info(f"loaded policy {host.os_release_name} for host")
return host
self.log_error('Unable to determine host installation. Ignoring node')
raise UnsupportedHostException
Expand Down
2 changes: 1 addition & 1 deletion sos/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def __init__(self, parser, parsed_args, cmdline_args):
self.manifest.add_field('compression', '')
self.manifest.add_field('tmpdir', self.tmpdir)
self.manifest.add_field('tmpdir_fs_type', self.tmpfstype)
self.manifest.add_field('policy', self.policy.distro)
self.manifest.add_field('policy', self.policy.os_release_name)
self.manifest.add_section('components')

def load_local_policy(self):
Expand Down
27 changes: 19 additions & 8 deletions sos/policies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def load(cache={}, sysroot=None, init=None, probe_runtime=True,
cache['policy'] = policy(sysroot=sysroot, init=init,
probe_runtime=probe_runtime,
remote_exec=remote_exec)
break

if sys.platform != 'linux':
raise Exception("SoS is not supported on this platform")
Expand Down Expand Up @@ -75,8 +76,16 @@ class Policy():
SoSTransport in use
:type remote_exec: ``SoSTranport.run_command()``
:cvar distro: The name of the distribution the Policy represents
:vartype distro: ``str``
:cvar os_release_name: The name of the distribution as it appears in the
os-release (-esque) file for the NAME variable.
:vartype os_release_name: ``str``
:cvar os_release_id: The ID variable to match in a distribution's release
file.
:vartype os_release_id: ``str``
:cvar os_release_file: The filepath of the distribution's os-release file
:vartype os_release_file: ``str``
:cvar vendor: The name of the vendor producing the distribution
:vartype vendor: ``str``
Expand All @@ -97,7 +106,7 @@ class Policy():

msg = _("""\
This command will collect system configuration and diagnostic information \
from this %(distro)s system.
from this %(os_release_name)s system.
For more information on %(vendor)s visit:
Expand All @@ -111,8 +120,9 @@ class Policy():
%(vendor_text)s
""")

distro = "Unknown"
os_release_name = 'Unknown'
os_release_file = ''
os_release_id = ''
vendor = "Unknown"
vendor_urls = [('Example URL', "http://www.example.com/")]
vendor_text = ""
Expand Down Expand Up @@ -459,8 +469,8 @@ def display_results(self, archive, directory, checksum, archivestat=None,

def get_msg(self):
"""This method is used to prepare the preamble text to display to
the user in non-batch mode. If your policy sets self.distro that
text will be substituted accordingly. You can also override this
the user in non-batch mode. If your policy sets self.os_release_name,
that text will be substituted accordingly. You can also override this
method to do something more complicated.
:returns: Formatted banner message string
Expand All @@ -471,7 +481,8 @@ def get_msg(self):
else:
changes_text = "No changes will be made to system configuration."
width = 72
_msg = self.msg % {'distro': self.distro, 'vendor': self.vendor,
_msg = self.msg % {'os_release_name': self.os_release_name,
'vendor': self.vendor,
'vendor_urls': self._fmt_vendor_urls(),
'vendor_text': self.vendor_text,
'tmpdir': self.commons['tmpdir'],
Expand Down
30 changes: 26 additions & 4 deletions sos/policies/distros/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
except ImportError:
BOTO3_LOADED = False

OS_RELEASE = "/etc/os-release"
# Container environment variables for detecting if we're in a container
ENV_CONTAINER = 'container'
ENV_HOST_SYSROOT = 'HOST'
Expand All @@ -48,11 +49,14 @@
class LinuxPolicy(Policy):
"""This policy is meant to be an abc class that provides common
implementations used in Linux distros"""

distro = "Linux"
vendor = "None"
PATH = "/bin:/sbin:/usr/bin:/usr/sbin"
init = None
# the following will be used, in order, as part of check() to validate that
# we are running on a particular distro
os_release_file = ''
os_release_name = ''
os_release_id = ''
# _ prefixed class attrs are used for storing any vendor-defined defaults
# the non-prefixed attrs are used by the upload methods, and will be set
# to the cmdline/config file values, if provided. If not provided, then
Expand Down Expand Up @@ -145,7 +149,25 @@ def check(cls, remote=''):
This function is responsible for determining if the underlying system
is supported by this policy.
"""
raise NotImplementedError
def _check_release(content):
_matches = [cls.os_release_name]
if cls.os_release_id:
_matches.append(cls.os_release_id)
for line in content.splitlines():
if line.startswith(('NAME=', 'ID=')):
_distro = line.split('=')[1:][0].strip("\"'")
if _distro in _matches:
return True
return False

if remote:
return _check_release(remote)
# use the os-specific file primarily
if os.path.isfile(cls.os_release_file):
return True
# next check os-release for a NAME or ID value we expect
with open(OS_RELEASE, "r", encoding='utf-8') as f:
return _check_release(f.read())

def kernel_version(self):
return self.release
Expand All @@ -171,7 +193,7 @@ def display_help(cls, section):
if cls == LinuxPolicy:
cls.display_self_help(section)
else:
section.set_title(f"{cls.distro} Distribution Policy")
section.set_title(f"{cls.os_release_name} Distribution Policy")
cls.display_distro_help(section)

@classmethod
Expand Down
24 changes: 4 additions & 20 deletions sos/policies/distros/almalinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
#
# See the LICENSE file in the source distribution for further information.

import os
from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE
from sos.policies.distros.redhat import RedHatPolicy


class AlmaLinuxPolicy(RedHatPolicy):
distro = "AlmaLinux"
vendor = "AlmaLinux OS Foundation"
os_release_file = '/etc/almalinux-release'
os_release_name = 'AlmaLinux'

vendor_urls = [
('Distribution Website', 'https://www.almalinux.org/'),
('Commercial Support', 'https://tuxcare.com/linux-support-services/')
Expand All @@ -26,21 +27,4 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True,
probe_runtime=probe_runtime,
remote_exec=remote_exec)

@classmethod
def check(cls, remote=''):
if remote:
return cls.distro in remote

if not os.path.isfile('/etc/almalinux-release'):
return False

if os.path.exists(OS_RELEASE):
with open(OS_RELEASE, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('NAME'):
if 'AlmaLinux' in line:
return True

return False

# vim: set et ts=4 sw=4 :
23 changes: 3 additions & 20 deletions sos/policies/distros/amazon.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,19 @@
#
# See the LICENSE file in the source distribution for further information.

import os
from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE
from sos.policies.distros.redhat import RedHatPolicy


class AmazonPolicy(RedHatPolicy):

distro = "Amazon Linux"
vendor = "Amazon"
vendor_urls = [('Distribution Website', 'https://aws.amazon.com')]
os_release_file = ''
os_release_name = 'Amazon Linux'

def __init__(self, sysroot=None, init=None, probe_runtime=True,
remote_exec=None):
super().__init__(sysroot=sysroot, init=init,
probe_runtime=probe_runtime,
remote_exec=remote_exec)

@classmethod
def check(cls, remote=''):

if remote:
return cls.distro in remote

if not os.path.exists(OS_RELEASE):
return False

with open(OS_RELEASE, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('NAME'):
if 'Amazon Linux' in line:
return True
return False

# vim: set et ts=4 sw=4 :
28 changes: 3 additions & 25 deletions sos/policies/distros/anolis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,19 @@
#
# See the LICENSE file in the source distribution for further information.

import os
from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE
from sos.policies.distros.redhat import RedHatPolicy


class AnolisPolicy(RedHatPolicy):

distro = "Anolis OS"
vendor = "The OpenAnolis Project"
vendor_urls = [('Distribution Website', 'https://openanolis.org/')]
os_release_file = '/etc/anolis-release'
os_release_name = 'Anolis OS'

def __init__(self, sysroot=None, init=None, probe_runtime=True,
remote_exec=None):
super().__init__(sysroot=sysroot, init=init,
probe_runtime=probe_runtime,
remote_exec=remote_exec)

@classmethod
def check(cls, remote=''):

if remote:
return cls.distro in remote

# Return False if /etc/os-release is missing
if not os.path.exists(OS_RELEASE):
return False

# Return False if /etc/anolis-release is missing
if not os.path.isfile('/etc/anolis-release'):
return False

with open(OS_RELEASE, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('NAME'):
if 'Anolis OS' in line:
return True
return False

# vim: set et ts=4 sw=4 :
25 changes: 5 additions & 20 deletions sos/policies/distros/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
#
# See the LICENSE file in the source distribution for further information.

import os
from sos.report.plugins import AzurePlugin
from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE
from sos.policies.distros.redhat import RedHatPolicy


class AzurePolicy(RedHatPolicy):

distro = "Azure Linux"
vendor = "Microsoft"
vendor_urls = [
('Distribution Website', 'https://github.com/microsoft/azurelinux')
]
os_release_name = 'Microsoft Azure Linux'
os_release_file = ''

def __init__(self, sysroot=None, init=None, probe_runtime=True,
remote_exec=None):
Expand All @@ -28,22 +27,8 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True,
remote_exec=remote_exec)
self.valid_subclasses += [AzurePlugin]

@classmethod
def check(cls, remote=''):

if remote:
return cls.distro in remote

if not os.path.exists(OS_RELEASE):
return False

with open(OS_RELEASE, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('NAME'):
if 'Common Base Linux Mariner' in line:
return True
if 'Microsoft Azure Linux' in line:
return True
return False
class CBLMarinerPolicy(AzurePolicy):
os_release_name = 'Common Base Linux Mariner'

# vim: set et ts=4 sw=4 :
29 changes: 3 additions & 26 deletions sos/policies/distros/circle.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,19 @@
#
# See the LICENSE file in the source distribution for further information.

import os
from sos.policies.distros.redhat import RedHatPolicy, OS_RELEASE
from sos.policies.distros.redhat import RedHatPolicy


class CirclePolicy(RedHatPolicy):

distro = "Circle Linux"
vendor = "The Circle Linux Project"
vendor_urls = [('Distribution Website', 'https://cclinux.org')]
os_release_file = '/etc/circle-release'
os_release_name = 'Circle Linux'

def __init__(self, sysroot=None, init=None, probe_runtime=True,
remote_exec=None):
super().__init__(sysroot=sysroot, init=init,
probe_runtime=probe_runtime,
remote_exec=remote_exec)

@classmethod
def check(cls, remote=''):

if remote:
return cls.distro in remote

# Return False if /etc/os-release is missing
if not os.path.exists(OS_RELEASE):
return False

# Return False if /etc/circle-release is missing
if not os.path.isfile('/etc/circle-release'):
return False

with open(OS_RELEASE, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('NAME'):
if 'Circle Linux' in line:
return True

return False

# vim: set et ts=4 sw=4 :
Loading

0 comments on commit a0035b6

Please sign in to comment.