From 6bccbaa8630355c5cf2335486381f42b0a543ef9 Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Wed, 11 Sep 2024 14:37:12 +0800 Subject: [PATCH 01/17] chore: add sensitive data checkpoint to PR template (#4210) - Jira: RHINENG-12397 Signed-off-by: Xiangce Liu --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8ff08196cc..94c90917fd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,6 +3,7 @@ Check all that apply: * [ ] Have you followed the guidelines in our Contributing document, including the instructions about commit messages? +* [ ] No Sensitive Data in this change? * [ ] Is this PR to correct an issue? * [ ] Is this PR an enhancement? From e8ce760d251efafffeb9321178897769e03dcda6 Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Wed, 11 Sep 2024 15:37:01 +0800 Subject: [PATCH 02/17] spec: stop collecting cloud_init_cfg_run (#4212) - RHINENG-11298 Signed-off-by: Xiangce Liu --- insights/specs/__init__.py | 1 - insights/specs/default.py | 1 - 2 files changed, 2 deletions(-) diff --git a/insights/specs/__init__.py b/insights/specs/__init__.py index eed4aadf5f..3c7205d46d 100644 --- a/insights/specs/__init__.py +++ b/insights/specs/__init__.py @@ -91,7 +91,6 @@ class Specs(SpecSet): cinder_volume_log = RegistryPoint(filterable=True) cloud_cfg = RegistryPoint(filterable=True) cloud_cfg_filtered = RegistryPoint() - cloud_init_cfg_run = RegistryPoint() cloud_init_custom_network = RegistryPoint() cloud_init_log = RegistryPoint(filterable=True) cluster_conf = RegistryPoint(filterable=True) diff --git a/insights/specs/default.py b/insights/specs/default.py index bcf926212a..8440a895e7 100644 --- a/insights/specs/default.py +++ b/insights/specs/default.py @@ -152,7 +152,6 @@ class DefaultSpecs(Specs): cinder_conf = first_file(["/var/lib/config-data/puppet-generated/cinder/etc/cinder/cinder.conf", "/etc/cinder/cinder.conf"]) cloud_cfg_filtered = cloud_init.cloud_cfg cloud_init_custom_network = simple_file("/etc/cloud/cloud.cfg.d/99-custom-networking.cfg") - cloud_init_cfg_run = simple_file("/run/cloud-init/cloud.cfg") cloud_init_log = simple_file("/var/log/cloud-init.log") cluster_conf = simple_file("/etc/cluster/cluster.conf") cmdline = simple_file("/proc/cmdline") From 26f1c353afdea2ce32e0ee3d8a77b7c07f045b6f Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Thu, 12 Sep 2024 10:06:40 +0800 Subject: [PATCH 03/17] feat(ci): add coverage check for changed python file (#4200) - add a script for checking python coverage in insights.tools it can be not "required" - use the script in ci/cd pipelines to check updated python - remove unused mocking of os.path.join in test_copy_dir - Jira: RHINENG-12134 Signed-off-by: Xiangce Liu --- .flake8 | 2 +- .github/workflows/main.yml | 38 +++++++++++++- insights/parsers/nginx_log.py | 2 +- insights/tests/client/test_archive.py | 3 +- insights/tests/parsers/test_nginx_log.py | 6 +-- insights/tools/coverage.py | 63 ++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 9 deletions(-) create mode 100755 insights/tools/coverage.py diff --git a/.flake8 b/.flake8 index afb717891e..c6faa33d0c 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] ignore = E501,E126,E127,E128,E722,E741,W504 -exclude = insights/contrib,bin,docs,include,lib,lib64,.git,.collections.py,insights/parsers/tests/lvm_test_data.py,insights/client/apps/ansible/playbook_verifier/contrib +exclude = insights/contrib,bin,docs,include,lib,lib64,.git,.collections.py,insights/parsers/tests/lvm_test_data.py,insights/client/apps/ansible/playbook_verifier/contrib,insights/tools/coverage.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 930ff654f2..b35fc8328c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,11 +28,45 @@ jobs: run: | pip install -e .[linting] flake8 . - - name: pytest + - name: pytest with coverage report run: | pip install 'urllib3' pip install -e .[testing] - pytest + pytest --cov --cov-branch --cov-report= + coverage json + - name: coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.python-versions }}.json + path: ./coverage.json + overwrite: true + + coverage-check: + + runs-on: ubuntu-latest + needs: code-test + strategy: + matrix: + python-versions: ["3.6", "3.9", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + - name: Get Changed files + run: git diff --name-only --diff-filter=AM -r HEAD^1 HEAD | grep -e "insights/.*\.py" | egrep -v "tests/|tools/|contrib/|parsr/|formats/|plugins/" > changes + continue-on-error: true + - uses: actions/setup-python@v5 + - uses: actions/download-artifact@master + with: + name: coverage-${{ matrix.python-versions }}.json + path: coverage_json + - name: coverage analysis + run: | + python -m pip install --upgrade pip + pip install -e . + mv ./coverage_json/coverage.json ./coverage.json + python insights/tools/coverage.py changes python27-test: diff --git a/insights/parsers/nginx_log.py b/insights/parsers/nginx_log.py index af0dd92278..010a493b72 100644 --- a/insights/parsers/nginx_log.py +++ b/insights/parsers/nginx_log.py @@ -78,7 +78,7 @@ class ContainerNginxErrorLog(ContainerParser, NginxErrorLog): >>> container_nginx_error_log.container_id '2869b4e2541c' >>> container_nginx_error_log.image - 'registry.access.redhat.com/ubi8/nginx-120' + 'reg.host.example.com/ubi8/nginx-120' >>> container_nginx_error_log.lines[0] '2022/04/02 04:07:59 [warn] 1591#1591: *697425 an upstream response is buffered to a temporary file /var/lib/nginx/tmp/uwsgi/2/25/0000003252 while reading upstream, client: 10.245.136.148, server: _, request: "GET /api/v2/hosts/?not__name=localhost&page_size=400&page=46 HTTP/1.1", upstream: "uwsgi://unix:/var/run/tower/uwsgi.sock:", host: "host.example.com"' >>> '711881' in container_nginx_error_log diff --git a/insights/tests/client/test_archive.py b/insights/tests/client/test_archive.py index aceba94527..29175e1113 100644 --- a/insights/tests/client/test_archive.py +++ b/insights/tests/client/test_archive.py @@ -221,12 +221,11 @@ def test_get_full_archive_path(self, create_archive_dir, cleanup, _, __): archive.get_full_archive_path('test') create_archive_dir.assert_called_once() - @patch('insights.client.archive.os.path.join', return_value=test_archive_dir) @patch('insights.client.archive.os.path.isdir', return_value=False) @patch('insights.client.archive.shutil.copytree', return_value=None) @patch('insights.client.archive.InsightsArchive.cleanup_previous_archive', return_value=None) @patch('insights.client.archive.InsightsArchive.create_archive_dir', return_value=test_archive_dir) - def test_copy_dir(self, create_archive_dir, _1, _2, _3, _4, _5, _6): + def test_copy_dir(self, create_archive_dir, _1, _2, _3, _4, _5): ''' Verify create_archive_dir is called when calling copy_dir ''' diff --git a/insights/tests/parsers/test_nginx_log.py b/insights/tests/parsers/test_nginx_log.py index f84752fc30..28931d82a2 100644 --- a/insights/tests/parsers/test_nginx_log.py +++ b/insights/tests/parsers/test_nginx_log.py @@ -28,12 +28,12 @@ def test_container_nginx_error_log(): context_wrap( NGINX_ERROR_LOG, container_id='2869b4e2541c', - image='registry.access.redhat.com/ubi8/nginx-120', + image='reg.host.example.com/ubi8/nginx-120', engine='podman', path='insights_containers/2869b4e2541c/var/log/nginx/error.log' ) ) - assert container_error_log.image == "registry.access.redhat.com/ubi8/nginx-120" + assert container_error_log.image == "reg.host.example.com/ubi8/nginx-120" assert container_error_log.engine == "podman" assert container_error_log.container_id == "2869b4e2541c" assert len(container_error_log.lines) == 6 @@ -49,7 +49,7 @@ def test_doc(): context_wrap( NGINX_ERROR_LOG, container_id='2869b4e2541c', - image='registry.access.redhat.com/ubi8/nginx-120', + image='reg.host.example.com/ubi8/nginx-120', engine='podman' ) ) diff --git a/insights/tools/coverage.py b/insights/tools/coverage.py new file mode 100755 index 0000000000..e111696f2a --- /dev/null +++ b/insights/tools/coverage.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +import json +import logging +import os +import sys + +from insights.parsr import query + +log = logging.getLogger(__name__) +logging.basicConfig(format='%(message)s', level=logging.INFO) + +coverage_json = "coverage.json" + +if len(sys.argv) < 2: + log.error("Provide changed files.") + sys.exit(1) + +if not os.path.exists(coverage_json): + log.error("'coverage.json' does not exist.") + sys.exit(1) + +coverage_report = None +with open(coverage_json) as fp: + coverage_report = query.from_dict(json.load(fp)) + +if not coverage_report: + log.error("No coverage report.") + sys.exit(1) + +changed_files = [] +with open(sys.argv[1]) as fp: + changed_files = list(filter(None, (l.strip() for l in fp.read().splitlines()))) + +if not changed_files: + log.info("===================================================================") + log.info("No coverage check is required for this PR.") + sys.exit(0) + +log.info(f"File(s) need to check coverage:") +for chf in changed_files: + log.info(f" - {chf}") + +log.info("===================================================================") +log.info(f"Total Coverage: {coverage_report.totals.percent_covered_display.value}%") + +okay_flag = True +missed_cov_files = [] +for chf in changed_files: + cov = coverage_report.find(chf).summary.percent_covered_display.value + if cov and int(cov) != 100: + okay_flag = False + missed_cov_files.append((chf, cov)) + +if not okay_flag: + log.info("File(s) Missing Coverage:") + for chf, cov in missed_cov_files: + log.info(f" - {chf}: {cov}%") + log.info(f"Use 'pytest --cov' to check the detailed coverage.") + sys.exit(1) + +log.info("Coverage is fine") +sys.exit(0) From ac4f45e442b2533398eab56f7bb1640605696bd0 Mon Sep 17 00:00:00 2001 From: Larry O'Leary Date: Wed, 11 Sep 2024 21:33:14 -0500 Subject: [PATCH 04/17] Fix skipped assertions for itests (#4207) * Fix skipped assert when `expected` is false `run_test` ignores `expected` if its value does not evaluate to a true condition. This means that if, for example, the expected result from a component is an empty list or the actual value `None`, no assertion is performed leading to undetected test failures. To fix this, the default value for the `expected` parameter is a sentinel object that allows the detection of an expected value being passed in regardless if it is `None` or any other condition that does not evaluate to true. Signed-off-by: Larry O'Leary * Remove expected value from integration tests Assertion is performed by the test itself. The epxected value of `None` is invalid for these tests. Remove `expected` argument from `run_test` call. Signed-off-by: Larry O'Leary --- insights/tests/__init__.py | 6 ++++-- insights/tests/test_integration_support.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/insights/tests/__init__.py b/insights/tests/__init__.py index 379546c0a0..a9f65b965a 100644 --- a/insights/tests/__init__.py +++ b/insights/tests/__init__.py @@ -71,6 +71,8 @@ def inner(ds, pattern): MAKE_NONE_RESULT = make_none() +_UNDEFINED = object() + def _beautify_deep_compare_diff(result, expected): if not (isinstance(result, dict) and isinstance(expected, dict)): @@ -153,7 +155,7 @@ def run_input_data(component, input_data, store_skips=False): def run_test(component, input_data, - expected=None, return_make_none=False, do_filter=True): + expected=_UNDEFINED, return_make_none=False, do_filter=True): """ Arguments: component: The insights component need to test. @@ -189,7 +191,7 @@ def get_filtered_specs(module): broker = run_input_data(component, input_data) result = broker.get(component) - if expected: + if expected is not _UNDEFINED: deep_compare(result, expected) elif result == MAKE_NONE_RESULT and not return_make_none: # Convert make_none() result to None as default unless diff --git a/insights/tests/test_integration_support.py b/insights/tests/test_integration_support.py index c1d05cf215..38cc7a4617 100644 --- a/insights/tests/test_integration_support.py +++ b/insights/tests/test_integration_support.py @@ -52,7 +52,7 @@ def test_run_test_parser_is_not_used(): """ input_data = InputData("fake_input") input_data.add(Specs.dmesg, "FAKE_CONTENT") - result = run_test(parser_is_not_used, input_data, None) + result = run_test(parser_is_not_used, input_data) # No Exception raised assert result @@ -65,7 +65,7 @@ def test_run_test_parser_is_filtered(): """ input_data = InputData("fake_input") input_data.add(Specs.dmesg, "FAKE_CONTENT") - result = run_test(parser_is_filtered, input_data, None) + result = run_test(parser_is_filtered, input_data) # No Exception raised assert result From 270cb97e6195fa5e54d66fd71db9e4fddcedb7ea Mon Sep 17 00:00:00 2001 From: wushiqinlou Date: Thu, 19 Sep 2024 14:22:17 +0800 Subject: [PATCH 05/17] Enhance datasource httpd ignore include expanded inner (#4214) - and add test cases for coverage Signed-off-by: jiazhang --- insights/specs/datasources/httpd.py | 35 ++++++----- insights/tests/datasources/test_httpd.py | 78 ++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 16 deletions(-) diff --git a/insights/specs/datasources/httpd.py b/insights/specs/datasources/httpd.py index 1c8adde76d..7192cad9e5 100644 --- a/insights/specs/datasources/httpd.py +++ b/insights/specs/datasources/httpd.py @@ -71,8 +71,14 @@ def _get_all_include_conf(root, glob_path): _paths.add(conf) with open(conf) as cfp: _includes = None + section_number = 0 for line in cfp.readlines(): - if line.strip().startswith("Include"): + line = line.strip() + if line.startswith(" + # ModSecurity Core Rules Set and Local configuration + IncludeOptional modsecurity.d/*.conf + +""".strip() + +data_lines_httpd_conf_section_test = """ +ServerRoot "/etc/httpd" + + # ModSecurity Core Rules Set and Local configuration + IncludeOptional modsecurity.d/*.conf + +""".strip() + +data_lines_crs_setup_conf = """ +SecAction \ + "id:900990,\ """.strip() @@ -103,6 +148,39 @@ def test_httpd_conf_files(m_open, m_glob, m_isdir, m_isfile): assert result == set(['/etc/httpd/conf.d/ssl.conf', '/etc/httpd/conf/httpd.conf']) +@patch("os.path.isfile", return_value=True) +@patch("os.path.isdir", return_value=True) +@patch("glob.glob", return_value=["/etc/httpd/modsecurity.d/crs-setup.conf"]) +@patch(builtin_open, new_callable=mock_open, read_data=data_lines_httpd_conf_section_test) +def test_httpd_conf_files_section(m_open, m_glob, m_isdir, m_isfile): + handlers = (m_open.return_value, mock_open(read_data=data_lines_crs_setup_conf).return_value) + m_open.side_effect = handlers + broker = {HostContext: None} + result = httpd_configuration_files(broker) + assert result == set(['/etc/httpd/conf/httpd.conf']) + + +@patch("os.path.isfile", return_value=False) +@patch("os.path.isdir", return_value=False) +@patch("glob.glob", return_value=["/etc/httpd/conf.d/ssl.conf"]) +@patch(builtin_open, new_callable=mock_open, read_data=data_lines_httpd_conf) +def test_httpd_conf_files_ssl_miss(m_open, m_glob, m_isdir, m_isfile): + handlers = (m_open.return_value, mock_open(read_data=data_lines_ssl_conf).return_value) + m_open.side_effect = handlers + broker = {HostContext: None} + result = httpd_configuration_files(broker) + assert result == set(['/etc/httpd/conf/httpd.conf']) + + +@patch("os.path.isfile", return_value=True) +@patch("os.path.isdir", return_value=True) +@patch("glob.glob", return_value=["/etc/httpd/conf.d/ssl.conf"]) +def test_httpd_conf_files_main_miss(m_glob, m_isdir, m_isfile): + broker = {HostContext: None} + with pytest.raises(SkipComponent): + httpd_configuration_files(broker) + + @patch("os.path.isfile", return_value=True) @patch("os.path.isdir", return_value=True) @patch("glob.glob", return_value=["/opt/rh/httpd24/root/etc/httpd/conf.d/ssl.conf"]) From fc8b6397611e78363f264892ac2ed7eb01c7e6be Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Thu, 19 Sep 2024 14:33:10 +0800 Subject: [PATCH 06/17] test: refine the messages for coverage check in CI/CD (#4213) Signed-off-by: Xiangce Liu --- insights/tools/coverage.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/insights/tools/coverage.py b/insights/tools/coverage.py index e111696f2a..1166081b79 100755 --- a/insights/tools/coverage.py +++ b/insights/tools/coverage.py @@ -34,7 +34,7 @@ if not changed_files: log.info("===================================================================") - log.info("No coverage check is required for this PR.") + log.info("No coverage check is required for this change.") sys.exit(0) log.info(f"File(s) need to check coverage:") @@ -43,21 +43,29 @@ log.info("===================================================================") log.info(f"Total Coverage: {coverage_report.totals.percent_covered_display.value}%") +log.info("-------------------------------------------------------------------") okay_flag = True missed_cov_files = [] +log.info("File(s) Coverage:") for chf in changed_files: cov = coverage_report.find(chf).summary.percent_covered_display.value - if cov and int(cov) != 100: - okay_flag = False - missed_cov_files.append((chf, cov)) + if cov: + if int(cov) != 100: + okay_flag = False + missed_cov_files.append((chf, cov)) + else: + log.info(f" - {chf}: {cov}%") + if not okay_flag: - log.info("File(s) Missing Coverage:") + log.info("") for chf, cov in missed_cov_files: log.info(f" - {chf}: {cov}%") - log.info(f"Use 'pytest --cov' to check the detailed coverage.") + log.info("-------------------------------------------------------------------") + log.info(f"Check details of coverage less than 100% with 'pytest --cov'.") sys.exit(1) -log.info("Coverage is fine") +log.info("-------------------------------------------------------------------") +log.info("Coverage of this change is fine.") sys.exit(0) From 51036a33531e3c9a64dbb4ab167fbb2487a3e144 Mon Sep 17 00:00:00 2001 From: Bob Fahr <20520336+bfahr@users.noreply.github.com> Date: Tue, 24 Sep 2024 02:43:05 -0500 Subject: [PATCH 07/17] fix: Fix issue 4218 in lspci combiner (#4219) * Fix issue 4218 in lspci combiner * Closes #4218 where data is not being combined correctly * Added new test with data specific to failure Signed-off-by: Bob Fahr <20520336+bfahr@users.noreply.github.com> * Fix logic error in combiner Signed-off-by: Bob Fahr <20520336+bfahr@users.noreply.github.com> --- insights/combiners/lspci.py | 9 +- insights/tests/combiners/test_lspci.py | 112 +++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/insights/combiners/lspci.py b/insights/combiners/lspci.py index 42d28acc3b..6dc9bb3304 100644 --- a/insights/combiners/lspci.py +++ b/insights/combiners/lspci.py @@ -122,8 +122,8 @@ def __init__(self, lspci_k, lspci_vmmkn): if lspci_vmmkn: for dev in lspci_vmmkn: # use the local copy to prevent from writing back to the parser - dev = dev.copy() - if lspci_k and dev['Slot'] in lspci_k: + dev_t = dev.copy() + if lspci_k and (dev['Slot'] in lspci_k.data or '0000:' + dev['Slot'] in lspci_k.data): # use the local copy to prevent from writing back to the parser dev_k = [v for v in lspci_k.data.values() if v['Slot'].endswith(dev['Slot'])][0].copy() # Since the 'lspci -k' is a more common command than the @@ -133,8 +133,9 @@ def __init__(self, lspci_k, lspci_vmmkn): # dev_k.pop('Slot') if 'Slot' in dev_k else None dev_k.pop('Kernel driver in use') if 'Kernel driver in use' in dev_k else None dev_k.pop('Kernel modules') if 'Kernel modules' in dev_k else None - dev.update(dev_k) - self.append(dev) + dev_t.update(dev_k) + + self.append(dev_t) self._pci_dev_list = (lspci_k if lspci_k else lspci_vmmkn).pci_dev_list else: for dev in lspci_k.data.values(): diff --git a/insights/tests/combiners/test_lspci.py b/insights/tests/combiners/test_lspci.py index 7bc5feace9..32e2c4a413 100644 --- a/insights/tests/combiners/test_lspci.py +++ b/insights/tests/combiners/test_lspci.py @@ -166,6 +166,69 @@ NUMANode: 0 """ +LSPCI_K_3 = """ +pcilib: Error reading /sys/bus/pci/devices/0000:00:0c.0/label: Operation not permitted +pcilib: Error reading /sys/bus/pci/devices/0000:00:0e.0/label: Operation not permitted +00:00.0 System peripheral: Intel Corporation Ice Lake Memory Map/VT-d (rev 20) + Subsystem: Dell Device 0b73 +bb:00.0 Processing accelerators: Habana Labs Ltd. Gaudi2 AI Training Accelerator (rev 01) + Subsystem: Habana Labs Ltd. Gaudi2 AI Training Accelerator + Kernel driver in use: habanalabs + Kernel modules: habanalabs +cb:00.0 Processing accelerators: Habana Labs Ltd. Gaudi2 AI Training Accelerator (rev 01) + Subsystem: Habana Labs Ltd. Gaudi2 AI Training Accelerator + Kernel driver in use: habanalabs + Kernel modules: habanalabs +db:00.0 Processing accelerators: Habana Labs Ltd. Gaudi2 AI Training Accelerator (rev 01) + Subsystem: Habana Labs Ltd. Gaudi2 AI Training Accelerator + Kernel driver in use: habanalabs + Kernel modules: habanalabs +""".strip() # noqa + +LSPCI_V_3 = """ +Slot: 00:00.0 +Class: 0880 +Vendor: 8086 +Device: 09a2 +SVendor: 1028 +SDevice: 0b73 +Rev: 20 +NUMANode: 0 + +Slot: bb:00.0 +Class: 1200 +Vendor: 1da3 +Device: 1020 +SVendor: 1da3 +SDevice: 1020 +Rev: 01 +Driver: habanalabs +Module: habanalabs +NUMANode: 1 + +Slot: cb:00.0 +Class: 1200 +Vendor: 1da3 +Device: 1020 +SVendor: 1da3 +SDevice: 1020 +Rev: 01 +Driver: habanalabs +Module: habanalabs +NUMANode: 1 + +Slot: db:00.0 +Class: 1200 +Vendor: 1da3 +Device: 1020 +SVendor: 1da3 +SDevice: 1020 +Rev: 01 +Driver: habanalabs +Module: habanalabs +NUMANode: 1 +""".strip() # noqa + def test_lspci_k(): lspci_k = LsPciParser(context_wrap(LSPCI_K)) @@ -271,6 +334,55 @@ def test_lspci_both_long_slot(): 'PhySlot', 'NUMANode']) +def test_lspci_both_2(): + lspci_vmmkn = LsPciVmmkn(context_wrap(LSPCI_V_3)) + lspci_k = LsPciParser(context_wrap(LSPCI_K_3)) + lspci = LsPci(lspci_k, lspci_vmmkn) + assert sorted(lspci.pci_dev_list) == ['00:00.0', 'bb:00.0', 'cb:00.0', 'db:00.0'] + assert lspci.search(Slot='bb:00.0') == [{ + 'Slot': 'bb:00.0', + 'Module': ['habanalabs', ], + 'Driver': 'habanalabs', + 'Class': '1200', + 'Vendor': '1da3', + 'Device': '1020', + 'Rev': '01', + 'Dev_Details': 'Processing accelerators: Habana Labs Ltd. Gaudi2 AI Training Accelerator (rev 01)', + 'SVendor': '1da3', + 'SDevice': '1020', + 'Subsystem': 'Habana Labs Ltd. Gaudi2 AI Training Accelerator', + 'NUMANode': '1', + }, ] + assert lspci.search(Slot='cb:00.0') == [{ + 'Slot': 'cb:00.0', + 'Module': ['habanalabs', ], + 'Driver': 'habanalabs', + 'Class': '1200', + 'Vendor': '1da3', + 'Device': '1020', + 'Rev': '01', + 'Dev_Details': 'Processing accelerators: Habana Labs Ltd. Gaudi2 AI Training Accelerator (rev 01)', + 'SVendor': '1da3', + 'SDevice': '1020', + 'Subsystem': 'Habana Labs Ltd. Gaudi2 AI Training Accelerator', + 'NUMANode': '1', + }, ] + assert lspci.search(Slot='db:00.0') == [{ + 'Slot': 'db:00.0', + 'Module': ['habanalabs', ], + 'Driver': 'habanalabs', + 'Class': '1200', + 'Vendor': '1da3', + 'Device': '1020', + 'Rev': '01', + 'Dev_Details': 'Processing accelerators: Habana Labs Ltd. Gaudi2 AI Training Accelerator (rev 01)', + 'SVendor': '1da3', + 'SDevice': '1020', + 'Subsystem': 'Habana Labs Ltd. Gaudi2 AI Training Accelerator', + 'NUMANode': '1', + }, ] + + def test_doc_examples(): lspci_vmmkn = LsPciVmmkn(context_wrap(LSPCI_VMMKN)) lspci_k = LsPciParser(context_wrap(LSPCI_K)) From 50626faf39573af5058f2a5252863445e1df0f89 Mon Sep 17 00:00:00 2001 From: Pino Toscano Date: Wed, 25 Sep 2024 04:17:25 +0200 Subject: [PATCH 08/17] feat(client): write .last-upload.results also after non-legacy uploads (#4217) While that file is deprecated and it is good that it is not written after non-legacy uploads, in practice it is actually used by tooling that uses insights-client. To avoid breaking functionalities in other software when switching to non-legacy, write /etc/insights-client/.last-upload.results also after non-legacy uploads. While those softwares are fixed to not rely on that file, at least this is one small step ahead in switching the default away to non-legacy without regressions elsewhere. Implements CCT-710 Signed-off-by: Pino Toscano --- insights/client/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/insights/client/client.py b/insights/client/client.py index 7e656a0935..4bea53d34a 100644 --- a/insights/client/client.py +++ b/insights/client/client.py @@ -413,6 +413,9 @@ def upload(config, pconn, tar_file, content_type, collection_duration=None): continue if upload.status_code in (200, 202): + # Write to last upload file + write_to_disk(constants.last_upload_results_file, upload.text) + os.chmod(constants.last_upload_results_file, 0o644) write_to_disk(constants.lastupload_file) os.chmod(constants.lastupload_file, 0o644) msg_name = determine_hostname(config.display_name) From 9baadb4c519b5e1429a7c544a3fbe6b9c051abf6 Mon Sep 17 00:00:00 2001 From: Xiaoxue Wang Date: Thu, 26 Sep 2024 14:56:25 +0800 Subject: [PATCH 09/17] feat: add spec and parser for etc_sysconfig_kernel (#4221) * feat: add spec and parser for etc_sysconfig_kernel Signed-off-by: Xiaoxue Wang * fix: test coverage to 100% for insights/specs/default.py Signed-off-by: Xiaoxue Wang --- insights/parsers/sysconfig.py | 26 +++++++++++++++++++ insights/specs/__init__.py | 1 + insights/specs/default.py | 1 + .../parsers/test_sysconfig_doc_examples.py | 11 ++++++++ .../tests/parsers/test_sysconfig_kernel.py | 22 ++++++++++++++++ insights/tests/test_specs.py | 9 +++++++ 6 files changed, 70 insertions(+) create mode 100644 insights/tests/parsers/test_sysconfig_kernel.py diff --git a/insights/parsers/sysconfig.py b/insights/parsers/sysconfig.py index 0bb1a01d40..d49d8f9e9d 100644 --- a/insights/parsers/sysconfig.py +++ b/insights/parsers/sysconfig.py @@ -36,6 +36,9 @@ KdumpSysconfig - file ``/etc/sysconfig/kdump`` ---------------------------------------------- +KernelSysconfig - file ``/etc/sysconfig/kernel`` +------------------------------------------------ + LibvirtGuestsSysconfig - file ``/etc/sysconfig/libvirt-guests`` --------------------------------------------------------------- @@ -350,6 +353,29 @@ def parse_content(self, content): setattr(self, key, self.data.get(key, '')) +@parser(Specs.sysconfig_kernel) +class KernelSysconfig(SysconfigOptions): + """ + This parser reads data from the ``/etc/sysconfig/kernel`` file. + + Typical content example:: + + # UPDATEDEFAULT specifies if new-kernel-pkg should make + # new kernels the default + UPDATEDEFAULT=yes + + # DEFAULTKERNEL specifies the default kernel package type + DEFAULTKERNEL=kernel + + Examples: + >>> kernel_syscfg.get('UPDATEDEFAULT') + 'yes' + >>> 'DEFAULTKERNEL' in kernel_syscfg + True + """ + pass + + @parser(Specs.sysconfig_libvirt_guests) class LibvirtGuestsSysconfig(SysconfigOptions): """ diff --git a/insights/specs/__init__.py b/insights/specs/__init__.py index 3c7205d46d..f45a4e2c70 100644 --- a/insights/specs/__init__.py +++ b/insights/specs/__init__.py @@ -756,6 +756,7 @@ class Specs(SpecSet): sysconfig_httpd = RegistryPoint() sysconfig_irqbalance = RegistryPoint() sysconfig_kdump = RegistryPoint() + sysconfig_kernel = RegistryPoint() sysconfig_libvirt_guests = RegistryPoint() sysconfig_memcached = RegistryPoint() sysconfig_mongod = RegistryPoint(multi_output=True) diff --git a/insights/specs/default.py b/insights/specs/default.py index 8440a895e7..94cd24508e 100644 --- a/insights/specs/default.py +++ b/insights/specs/default.py @@ -661,6 +661,7 @@ class DefaultSpecs(Specs): sys_vmbus_device_id = glob_file('/sys/bus/vmbus/devices/*/device_id') sysconfig_grub = simple_file("/etc/default/grub") # This is the file where the "/etc/sysconfig/grub" point to sysconfig_kdump = simple_file("etc/sysconfig/kdump") + sysconfig_kernel = simple_file("etc/sysconfig/kernel") sysconfig_libvirt_guests = simple_file("etc/sysconfig/libvirt-guests") sysconfig_network = simple_file("etc/sysconfig/network") sysconfig_nfs = simple_file("/etc/sysconfig/nfs") diff --git a/insights/tests/parsers/test_sysconfig_doc_examples.py b/insights/tests/parsers/test_sysconfig_doc_examples.py index e81026608c..72b050b438 100755 --- a/insights/tests/parsers/test_sysconfig_doc_examples.py +++ b/insights/tests/parsers/test_sysconfig_doc_examples.py @@ -2,6 +2,7 @@ from insights.parsers import sysconfig from insights.parsers.sysconfig import ChronydSysconfig, DockerSysconfig, DockerSysconfigStorage from insights.parsers.sysconfig import HttpdSysconfig, IrqbalanceSysconfig +from insights.parsers.sysconfig import KernelSysconfig from insights.parsers.sysconfig import LibvirtGuestsSysconfig, MemcachedSysconfig from insights.parsers.sysconfig import MongodSysconfig, NtpdSysconfig from insights.parsers.sysconfig import PrelinkSysconfig, VirtWhoSysconfig @@ -236,6 +237,15 @@ #PCSD_BIND_ADDR='::' """.strip() +KERNEL_SYSCONFIG = """ +# UPDATEDEFAULT specifies if new-kernel-pkg should make +# new kernels the default +UPDATEDEFAULT=yes + +# DEFAULTKERNEL specifies the default kernel package type +DEFAULTKERNEL=kernel +""".strip() + def test_sysconfig_doc(): env = { @@ -266,6 +276,7 @@ def test_sysconfig_doc(): 'oracleasm_syscfg': OracleasmSysconfig(context_wrap(ORACLEASM_SYSCONFIG)), 'stonith_syscfg': StonithSysconfig(context_wrap(STONITH_CONFIG)), 'pcsd_syscfg': PcsdSysconfig(context_wrap(PCSD_SYSCONFIG)), + 'kernel_syscfg': KernelSysconfig(context_wrap(KERNEL_SYSCONFIG)), } failed, total = doctest.testmod(sysconfig, globs=env) assert failed == 0 diff --git a/insights/tests/parsers/test_sysconfig_kernel.py b/insights/tests/parsers/test_sysconfig_kernel.py new file mode 100644 index 0000000000..3d4fa111cb --- /dev/null +++ b/insights/tests/parsers/test_sysconfig_kernel.py @@ -0,0 +1,22 @@ +from insights.parsers.sysconfig import KernelSysconfig +from insights.tests import context_wrap + +SYSCONFIG_KERNEL = """ +# UPDATEDEFAULT specifies if new-kernel-pkg should make +# new kernels the default +UPDATEDEFAULT=yes + +# DEFAULTKERNEL specifies the default kernel package type +DEFAULTKERNEL=kernel + +# MAKEDEBUG specifies if new-kernel-pkg should create non-default +# "debug" entries for new kernels. +MAKEDEBUG=yes +""".strip() + + +def test_sysconfig_nfs(): + result = KernelSysconfig(context_wrap(SYSCONFIG_KERNEL)) + assert result['UPDATEDEFAULT'] == 'yes' + assert result.get('DEFAULTKERNEL') == 'kernel' + assert result.get('MAKEDEBUG') == 'yes' diff --git a/insights/tests/test_specs.py b/insights/tests/test_specs.py index 89b372c959..da6a98604e 100644 --- a/insights/tests/test_specs.py +++ b/insights/tests/test_specs.py @@ -21,6 +21,7 @@ DatasourceProvider, RegistryPoint, SpecSet, command_with_args, foreach_collect, foreach_execute, glob_file, simple_command, simple_file, first_file, first_of) +from insights.specs.default import _make_rpm_formatter here = os.path.abspath(os.path.dirname(__file__)) @@ -436,3 +437,11 @@ def test_specs_collect(obfuscate): dr.COMPONENTS = defaultdict(lambda: defaultdict(set)) dr.TYPE_OBSERVERS = defaultdict(set) dr.ENABLED = defaultdict(lambda: True) + + +def test_specs_default_module_utils(): + rpm_formatter = _make_rpm_formatter([ + '"name":"%{NAME}"', + '"version":"%{VERSION}"' + ]) + assert ',"version":' in rpm_formatter From 3725e1aace388a16c084a30315ff124f37a5ee77 Mon Sep 17 00:00:00 2001 From: huali027 <44796653+huali027@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:19:48 +0800 Subject: [PATCH 10/17] fix: ssl_certificate no longer depends on HttpdConfTree (#4220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The combiner and parser cannot handle special characters like 'é', so remove the dependency on it first - Jira: RHINENG-12609 Signed-off-by: Huanhuan Li --- insights/collect.py | 7 -- insights/specs/datasources/ssl_certificate.py | 72 +++++++++++----- .../tests/datasources/test_ssl_certificate.py | 83 ++++++++++--------- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/insights/collect.py b/insights/collect.py index df3ae49498..3b7f158d13 100755 --- a/insights/collect.py +++ b/insights/collect.py @@ -167,13 +167,6 @@ - name: insights.combiners.ps enabled: true - # needed for httpd_certificate - - name: insights.combiners.httpd_conf.HttpdConfTree - enabled: true - - - name: insights.parsers.httpd_conf.HttpdConf - enabled: true - # needed for httpd_on_nfs - name: insights.parsers.mount.ProcMounts enabled: true diff --git a/insights/specs/datasources/ssl_certificate.py b/insights/specs/datasources/ssl_certificate.py index 308bea082e..431d34177b 100644 --- a/insights/specs/datasources/ssl_certificate.py +++ b/insights/specs/datasources/ssl_certificate.py @@ -1,16 +1,17 @@ """ Custom datasource to get ssl certificate file path. """ -from insights.combiners.httpd_conf import HttpdConfTree + from insights.combiners.nginx_conf import NginxConfTree +from insights.combiners.rsyslog_confs import RsyslogAllConf from insights.core.context import HostContext from insights.core.exceptions import SkipComponent from insights.core.plugins import datasource from insights.parsers.mssql_conf import MsSQLConf -from insights.combiners.rsyslog_confs import RsyslogAllConf +from insights.specs.datasources import httpd -@datasource(HttpdConfTree, HostContext) +@datasource(httpd.httpd_configuration_files, HostContext) def httpd_certificate_info_in_nss(broker): """ Get the certificate info configured in nss database @@ -25,22 +26,35 @@ def httpd_certificate_info_in_nss(broker): SkipComponent: Raised when NSSEngine isn't enabled or "NSSCertificateDatabase" and "NSSNickname" directives aren't found """ - conf = broker[HttpdConfTree] + confs = broker[httpd.httpd_configuration_files] path_pairs = [] - virtual_hosts = conf.find('VirtualHost') - for host in virtual_hosts: - nss_engine = nss_database = cert_name = None - nss_engine = host.select('NSSEngine') - nss_database = host.select('NSSCertificateDatabase') - cert_name = host.select('NSSNickname') - if nss_engine and nss_engine.value and nss_database and cert_name: - path_pairs.append((nss_database[0].value, cert_name[0].value)) + nss_engine = nss_database = cert_name = None + virtual_host_start = False + for conf in confs: + with open(conf) as a_f: + for line in a_f.readlines(): + line = line.strip() + if line.startswith(''): + if nss_engine == 'on' and nss_database and cert_name: + path_pairs.append((nss_database, cert_name)) + virtual_host_start = False + nss_engine = nss_database = cert_name = None if path_pairs: return path_pairs raise SkipComponent -@datasource(HttpdConfTree, HostContext) +@datasource(httpd.httpd_configuration_files, HostContext) def httpd_ssl_certificate_files(broker): """ Get the httpd SSL certificate file path configured by "SSLCertificateFile" @@ -54,17 +68,29 @@ def httpd_ssl_certificate_files(broker): Raises: SkipComponent: Raised if "SSLCertificateFile" directive isn't found """ - conf = broker[HttpdConfTree] - virtual_hosts = conf.find('VirtualHost') - ssl_certs = [] - for host in virtual_hosts: - ssl_cert = ssl_engine = None - ssl_engine = host.select('SSLEngine') - ssl_cert = host.select('SSLCertificateFile') - if ssl_engine and ssl_engine.value and ssl_cert: - ssl_certs.append(str(ssl_cert.value)) + confs = broker[httpd.httpd_configuration_files] + ssl_engine = ssl_cert = None + ssl_certs = set() + virtual_host_start = False + for conf in confs: + with open(conf) as a_f: + for line in a_f.readlines(): + line = line.strip() + if line.startswith(''): + if ssl_engine == 'on' and ssl_cert: + ssl_certs.add(ssl_cert) + virtual_host_start = False + ssl_engine = ssl_cert = None if ssl_certs: - return ssl_certs + return sorted(ssl_certs) raise SkipComponent diff --git a/insights/tests/datasources/test_ssl_certificate.py b/insights/tests/datasources/test_ssl_certificate.py index 7652a3e549..15bfe4549a 100644 --- a/insights/tests/datasources/test_ssl_certificate.py +++ b/insights/tests/datasources/test_ssl_certificate.py @@ -1,13 +1,20 @@ +# -*- coding: utf-8 -*- + import pytest +try: + from unittest.mock import patch, mock_open + builtin_open = "builtins.open" +except Exception: + from mock import patch, mock_open + builtin_open = "__builtin__.open" -from insights.combiners.httpd_conf import HttpdConfTree from insights.combiners.nginx_conf import NginxConfTree +from insights.combiners.rsyslog_confs import RsyslogAllConf from insights.core.exceptions import SkipComponent -from insights.parsers.httpd_conf import HttpdConf from insights.parsers.mssql_conf import MsSQLConf from insights.parsers.nginx_conf import NginxConfPEG from insights.parsers.rsyslog_conf import RsyslogConf -from insights.combiners.rsyslog_confs import RsyslogAllConf +from insights.specs.datasources import httpd from insights.specs.datasources.ssl_certificate import ( httpd_ssl_certificate_files, nginx_ssl_certificate_files, mssql_tls_cert_file, httpd_certificate_info_in_nss, @@ -26,7 +33,7 @@ ## SSL directives SSLEngine on - SSLCertificateFile "/etc/pki/katello/certs/katello-apache.crt" + SSLCertificateFile "/etc/pki/katello/certs/gént-katello-apache.crt" SSLCertificateKeyFile "/etc/pki/katello/private/katello-apache.key" SSLCertificateChainFile "/etc/pki/katello/certs/katello-server-ca.crt" SSLVerifyClient optional @@ -41,6 +48,7 @@ ## SSL directives ServerName a.b.c.com SSLEngine on + # SSLCertificateFile "/etc/pki/katello/certs/old_katello-apache.crt" SSLCertificateFile "/etc/pki/katello/certs/katello-apache.crt" SSLCertificateKeyFile "/etc/pki/katello/private/katello-apache.key" SSLCertificateChainFile "/etc/pki/katello/certs/katello-server-ca.crt" @@ -53,6 +61,7 @@ ## SSL directives ServerName d.c.e.com SSLEngine on + # bellow is SSLCertificateFile configuration SSLCertificateFile "/etc/pki/katello/certs/katello-apache_d.crt" SSLCertificateKeyFile "/etc/pki/katello/private/katello-apache_d.key" SSLCertificateChainFile "/etc/pki/katello/certs/katello-server-ca_d.crt" @@ -162,8 +171,10 @@ ServerName www.examplea.com:8443 NSSEngine on +# NSSCertificateDatabase old_path NSSCertificateDatabase /etc/httpd/aliasa -NSSNickname testcerta +# bellow is the NSSNickname configuration +NSSNickname testcertaê ServerName www.exampleb.com:8443 @@ -304,23 +315,19 @@ """ -def test_httpd_certificate(): - conf1 = HttpdConf(context_wrap(HTTPD_CONF, path='/etc/httpd/conf/httpd.conf')) - conf2 = HttpdConf(context_wrap(HTTPD_SSL_CONF, path='/etc/httpd/conf.d/ssl.conf')) - conf_tree = HttpdConfTree([conf1, conf2]) - +@patch("os.path.exists", return_value=True) +@patch(builtin_open, new_callable=mock_open, read_data=HTTPD_CONF) +def test_httpd_certificate(m_open, m_exist): + m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_SSL_CONF).return_value] broker = { - HttpdConfTree: conf_tree + httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/ssl.conf'} } result = httpd_ssl_certificate_files(broker) - assert result == ['/etc/pki/katello/certs/katello-apache.crt'] - - conf1 = HttpdConf(context_wrap(HTTPD_CONF, path='/etc/httpd/conf/httpd.conf')) - conf2 = HttpdConf(context_wrap(HTTPD_SSL_CONF_2, path='/etc/httpd/conf.d/ssl.conf')) - conf_tree = HttpdConfTree([conf1, conf2]) + assert result == ['/etc/pki/katello/certs/gént-katello-apache.crt'] + m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_SSL_CONF_2).return_value] broker = { - HttpdConfTree: conf_tree + httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/ssl.conf'} } result = httpd_ssl_certificate_files(broker) # "/etc/pki/katello/certs/katello-apache_e.crt" not in the result @@ -349,21 +356,21 @@ def test_nginx_certificate(): assert result == ['/a/b/www.example.com.crt', '/a/b/www.example.com.cecdsa.crt', '/a/b/www.example.org.crt'] -def test_httpd_ssl_cert_exception(): - conf1 = HttpdConf(context_wrap(HTTPD_CONF, path='/etc/httpd/conf/httpd.conf')) - conf2 = HttpdConf(context_wrap(HTTPD_CONF_WITHOUT_SSL, path='/etc/httpd/conf.d/no_ssl.conf')) - conf_tree = HttpdConfTree([conf1, conf2]) +@patch("os.path.exists", return_value=True) +@patch(builtin_open, new_callable=mock_open, read_data=HTTPD_CONF) +def test_httpd_ssl_cert_exception(m_open, m_exists): + m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_CONF_WITHOUT_SSL).return_value] broker1 = { - HttpdConfTree: conf_tree + httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/no_ssl.conf'} } - conf1 = HttpdConf(context_wrap(HTTPD_CONF, path='/etc/httpd/conf/httpd.conf')) - conf2 = HttpdConf(context_wrap(HTTPD_SSL_CONF_NO_VALUE, path='/etc/httpd/conf.d/no_ssl.conf')) - conf_tree = HttpdConfTree([conf1, conf2]) + with pytest.raises(SkipComponent): + httpd_ssl_certificate_files(broker1) + + m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_SSL_CONF_NO_VALUE).return_value] broker2 = { - HttpdConfTree: conf_tree + httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/no_ssl.conf'} } with pytest.raises(SkipComponent): - httpd_ssl_certificate_files(broker1) httpd_ssl_certificate_files(broker2) @@ -396,23 +403,23 @@ def test_mssql_tls_no_cert_exception(): mssql_tls_cert_file(broker1) -def test_httpd_certificate_info_in_nss(): - conf1 = HttpdConf(context_wrap(HTTPD_CONF, path='/etc/httpd/conf/httpd.conf')) - conf2 = HttpdConf(context_wrap(HTTPD_WITH_NSS, path='/etc/httpd/conf.d/nss.conf')) - conf_tree = HttpdConfTree([conf1, conf2]) +@patch("os.path.exists", return_value=True) +@patch(builtin_open, new_callable=mock_open, read_data=HTTPD_CONF) +def test_httpd_certificate_info_in_nss(m_open, m_exists): + m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_WITH_NSS).return_value] broker = { - HttpdConfTree: conf_tree + httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/nss.conf'} } result = httpd_certificate_info_in_nss(broker) - assert result == [('/etc/httpd/aliasa', 'testcerta'), ('/etc/httpd/aliasb', 'testcertb')] + assert result == [('/etc/httpd/aliasa', 'testcertaê'), ('/etc/httpd/aliasb', 'testcertb')] -def test_httpd_certificate_info_in_nss_exception(): - conf1 = HttpdConf(context_wrap(HTTPD_CONF, path='/etc/httpd/conf/httpd.conf')) - conf2 = HttpdConf(context_wrap(HTTPD_WITH_NSS_OFF, path='/etc/httpd/conf.d/nss.conf')) - conf_tree = HttpdConfTree([conf1, conf2]) +@patch("os.path.exists", return_value=True) +@patch(builtin_open, new_callable=mock_open, read_data=HTTPD_CONF) +def test_httpd_certificate_info_in_nss_exception(m_open, m_exists): + m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_WITH_NSS_OFF).return_value] broker = { - HttpdConfTree: conf_tree + httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/nss.conf'} } with pytest.raises(SkipComponent): httpd_certificate_info_in_nss(broker) From eb7b44b5ab946aa20d6b712404fd863029227e7b Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Fri, 27 Sep 2024 13:19:09 +0800 Subject: [PATCH 11/17] test: fix the py26 flake8 error in ssl_certificate test (#4223) - it's introduced by #4220 Signed-off-by: Xiangce Liu rh-pre-commit.version: 2.3.1 rh-pre-commit.check-secrets: ENABLED --- insights/tests/datasources/test_ssl_certificate.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/insights/tests/datasources/test_ssl_certificate.py b/insights/tests/datasources/test_ssl_certificate.py index 15bfe4549a..b496cf036d 100644 --- a/insights/tests/datasources/test_ssl_certificate.py +++ b/insights/tests/datasources/test_ssl_certificate.py @@ -320,14 +320,14 @@ def test_httpd_certificate(m_open, m_exist): m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_SSL_CONF).return_value] broker = { - httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/ssl.conf'} + httpd.httpd_configuration_files: ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/ssl.conf'] } result = httpd_ssl_certificate_files(broker) assert result == ['/etc/pki/katello/certs/gént-katello-apache.crt'] m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_SSL_CONF_2).return_value] broker = { - httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/ssl.conf'} + httpd.httpd_configuration_files: ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/ssl.conf'] } result = httpd_ssl_certificate_files(broker) # "/etc/pki/katello/certs/katello-apache_e.crt" not in the result @@ -361,14 +361,14 @@ def test_nginx_certificate(): def test_httpd_ssl_cert_exception(m_open, m_exists): m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_CONF_WITHOUT_SSL).return_value] broker1 = { - httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/no_ssl.conf'} + httpd.httpd_configuration_files: ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/no_ssl.conf'] } with pytest.raises(SkipComponent): httpd_ssl_certificate_files(broker1) m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_SSL_CONF_NO_VALUE).return_value] broker2 = { - httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/no_ssl.conf'} + httpd.httpd_configuration_files: ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/no_ssl.conf'] } with pytest.raises(SkipComponent): httpd_ssl_certificate_files(broker2) @@ -408,7 +408,7 @@ def test_mssql_tls_no_cert_exception(): def test_httpd_certificate_info_in_nss(m_open, m_exists): m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_WITH_NSS).return_value] broker = { - httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/nss.conf'} + httpd.httpd_configuration_files: ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/nss.conf'] } result = httpd_certificate_info_in_nss(broker) assert result == [('/etc/httpd/aliasa', 'testcertaê'), ('/etc/httpd/aliasb', 'testcertb')] @@ -419,7 +419,7 @@ def test_httpd_certificate_info_in_nss(m_open, m_exists): def test_httpd_certificate_info_in_nss_exception(m_open, m_exists): m_open.side_effect = [m_open.return_value, mock_open(read_data=HTTPD_WITH_NSS_OFF).return_value] broker = { - httpd.httpd_configuration_files: {'/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/nss.conf'} + httpd.httpd_configuration_files: ['/etc/httpd/conf/httpd.conf', '/etc/httpd/conf.d/nss.conf'] } with pytest.raises(SkipComponent): httpd_certificate_info_in_nss(broker) From c1d8ec28ac679f2b6d9b8053417d12afaf35596c Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Fri, 27 Sep 2024 13:56:04 +0800 Subject: [PATCH 12/17] test: use Codecov instead of self script (#4224) - follow up of RHINENG-12134 Signed-off-by: Xiangce Liu rh-pre-commit.version: 2.3.1 rh-pre-commit.check-secrets: ENABLED --- .github/workflows/main.yml | 43 +++++++------------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b35fc8328c..d42c681417 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,43 +30,16 @@ jobs: flake8 . - name: pytest with coverage report run: | - pip install 'urllib3' + pip install urllib3 pip install -e .[testing] pytest --cov --cov-branch --cov-report= - coverage json - - name: coverage - uses: actions/upload-artifact@v4 + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 with: - name: coverage-${{ matrix.python-versions }}.json - path: ./coverage.json - overwrite: true - - coverage-check: - - runs-on: ubuntu-latest - needs: code-test - strategy: - matrix: - python-versions: ["3.6", "3.9", "3.11", "3.12"] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - name: Get Changed files - run: git diff --name-only --diff-filter=AM -r HEAD^1 HEAD | grep -e "insights/.*\.py" | egrep -v "tests/|tools/|contrib/|parsr/|formats/|plugins/" > changes - continue-on-error: true - - uses: actions/setup-python@v5 - - uses: actions/download-artifact@master - with: - name: coverage-${{ matrix.python-versions }}.json - path: coverage_json - - name: coverage analysis - run: | - python -m pip install --upgrade pip - pip install -e . - mv ./coverage_json/coverage.json ./coverage.json - python insights/tools/coverage.py changes + verbose: true + flags: unittests + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} python27-test: @@ -87,7 +60,7 @@ jobs: flake8 . - name: pytest run: | - pip install 'urllib3' + pip install urllib3 pip install -e .[testing] pytest From 42649ee095fb0a25942c8fc14bab1289f06db110 Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Fri, 27 Sep 2024 15:04:53 +0800 Subject: [PATCH 13/17] test(ci/cd): add gitleaks pipeline (#4225) - follow up of RHINENG-12134 Signed-off-by: Xiangce Liu --- .github/workflows/main.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d42c681417..22a6f174d3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,3 +132,17 @@ jobs: run: | pip install -e .[docs] sphinx-build -W -b html -qa -E docs docs/_build/html + + gitleaks: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + # - uses: gitleaks/gitleaks-action@v2 + - uses: gitleaks/gitleaks-action@v1.6.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} From 16695a573c5ddada2ac4f292193fe2ce09c6ad16 Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Fri, 27 Sep 2024 17:42:25 +0800 Subject: [PATCH 14/17] =?UTF-8?q?Revert=20"feat(client):=20write=20.last-u?= =?UTF-8?q?pload.results=20also=20after=20non-legacy=20upload=E2=80=A6"=20?= =?UTF-8?q?(#4228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 50626faf39573af5058f2a5252863445e1df0f89. --- insights/client/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/insights/client/client.py b/insights/client/client.py index 4bea53d34a..7e656a0935 100644 --- a/insights/client/client.py +++ b/insights/client/client.py @@ -413,9 +413,6 @@ def upload(config, pconn, tar_file, content_type, collection_duration=None): continue if upload.status_code in (200, 202): - # Write to last upload file - write_to_disk(constants.last_upload_results_file, upload.text) - os.chmod(constants.last_upload_results_file, 0o644) write_to_disk(constants.lastupload_file) os.chmod(constants.lastupload_file, 0o644) msg_name = determine_hostname(config.display_name) From 19e6afa1a95aac6a1999852b7a32fe0417db747b Mon Sep 17 00:00:00 2001 From: wushiqinlou Date: Fri, 27 Sep 2024 17:49:48 +0800 Subject: [PATCH 15/17] feat: Add spec cups_browsed_conf (#4227) * Add spec cups_browsed_conf Signed-off-by: jiazhang * enhance coverage to 100% Signed-off-by: Xiangce Liu rh-pre-commit.version: 2.3.1 rh-pre-commit.check-secrets: ENABLED Signed-off-by: jiazhang Co-authored-by: Xiangce Liu --- insights/parsers/cups_confs.py | 36 +++++++++++++++++++++++ insights/specs/__init__.py | 1 + insights/specs/default.py | 1 + insights/tests/parsers/test_cups_confs.py | 36 +++++++++++++++++++++-- 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/insights/parsers/cups_confs.py b/insights/parsers/cups_confs.py index 830346aa31..8330c011d3 100644 --- a/insights/parsers/cups_confs.py +++ b/insights/parsers/cups_confs.py @@ -6,6 +6,8 @@ CupsdConf - file ``/etc/cups/cupsd.conf`` ----------------------------------------- +CupsBrowsedConf - file ``/etc/cups/cups-browsed.conf`` +------------------------------------------------------ CupsFilesConf - file ``/etc/cups/cups-files.conf`` -------------------------------------------------- """ @@ -90,6 +92,40 @@ def parse_doc(self, content): return Entry(children=result, src=self) +@parser(Specs.cups_browsed_conf) +class CupsBrowsedConf(Parser, dict): + """ + Class for parsing the file ``/etc/cups/cups-browsed.conf`` + + Sample file content:: + + BrowseRemoteProtocols dnssd cups + BrowseAllow cups.example.com + + Examples: + >>> type(cups_browsed_conf) + + >>> 'dnssd' in cups_browsed_conf['BrowseRemoteProtocols'] + True + >>> 'cups.example.com' in cups_browsed_conf['BrowseAllow'] + True + """ + def parse_content(self, content): + if not content: + raise ParseException('Empty Content') + + for line in get_active_lines(content): + k, v = [i.strip() for i in line.split(None, 1)] + if k not in self: + self[k] = v if len(v.split()) == 1 else v.split() + else: + _v = self[k] + _v = [_v] if not isinstance(_v, list) else _v + if v not in _v: + _v.append(v) + self[k] = _v + + @parser(Specs.cups_files_conf) class CupsFilesConf(Parser, dict): """ diff --git a/insights/specs/__init__.py b/insights/specs/__init__.py index f45a4e2c70..a339a1bd1c 100644 --- a/insights/specs/__init__.py +++ b/insights/specs/__init__.py @@ -124,6 +124,7 @@ class Specs(SpecSet): crypto_policies_state_current = RegistryPoint(no_obfuscate=['hostname', 'ip']) cryptsetup_luksDump = RegistryPoint(multi_output=True, no_obfuscate=['hostname', 'ip']) cupsd_conf = RegistryPoint() + cups_browsed_conf = RegistryPoint(filterable=True) cups_files_conf = RegistryPoint() cups_ppd = RegistryPoint(multi_output=True, no_obfuscate=['hostname', 'ip']) current_clocksource = RegistryPoint(no_obfuscate=['hostname', 'ip']) diff --git a/insights/specs/default.py b/insights/specs/default.py index 94cd24508e..c2460f5616 100644 --- a/insights/specs/default.py +++ b/insights/specs/default.py @@ -176,6 +176,7 @@ class DefaultSpecs(Specs): crypto_policies_state_current = simple_file("/etc/crypto-policies/state/current") cryptsetup_luksDump = luks_devices.luks_data_sources cupsd_conf = simple_file("/etc/cups/cupsd.conf") + cups_browsed_conf = simple_file("/etc/cups/cups-browsed.conf") cups_files_conf = simple_file("/etc/cups/cups-files.conf") current_clocksource = simple_file("/sys/devices/system/clocksource/clocksource0/current_clocksource") date = simple_command("/bin/date") diff --git a/insights/tests/parsers/test_cups_confs.py b/insights/tests/parsers/test_cups_confs.py index 0adedeb24e..91f984f786 100644 --- a/insights/tests/parsers/test_cups_confs.py +++ b/insights/tests/parsers/test_cups_confs.py @@ -3,7 +3,7 @@ from insights.core.exceptions import ParseException from insights.parsers import cups_confs -from insights.parsers.cups_confs import CupsdConf, CupsFilesConf +from insights.parsers.cups_confs import CupsdConf, CupsFilesConf, CupsBrowsedConf from insights.parsr.query import first, last from insights.tests import context_wrap @@ -127,6 +127,21 @@ """.strip() +CUPS_BROWSED_CONF = """ +# Which protocols will we use to discover printers on the network? +# Can use DNSSD and/or CUPS and/or LDAP, or 'none' for neither. + +BrowseRemoteProtocols dnssd cups +BrowseAllow 192.168.0.1 +BrowseAllow 192.168.0.255 +BrowseAllow cups.example.com +BrowseAllow 192.168.0.255 +""".strip() + +CUPS_BROWSED_CONF_CONF_EMPTY = """ +""".strip() + + def test_cupsd_conf(): context = context_wrap(CUPSD_CONF) result = CupsdConf(context) @@ -151,16 +166,31 @@ def test_cups_files_conf(): assert result['AccessLog'] == 'syslog' -def test_cups_files_conf_empyt(): +def test_cups_files_conf_empty(): with pytest.raises(ParseException) as exc: CupsFilesConf(context_wrap(CUPS_FILES_CONF_EMPTY)) assert str(exc.value) == "Empty Content" +def test_cups_browsed_files_conf(): + result = CupsBrowsedConf(context_wrap(CUPS_BROWSED_CONF)) + assert len(result) == 2 + assert 'BrowseRemoteProtocols' in result + assert result['BrowseRemoteProtocols'] == ['dnssd', 'cups'] + assert sorted(result['BrowseAllow']) == sorted(['192.168.0.1', '192.168.0.255', 'cups.example.com']) + + +def test_cups_browsed_conf_empty(): + with pytest.raises(ParseException) as exc: + CupsBrowsedConf(context_wrap(CUPS_BROWSED_CONF_CONF_EMPTY)) + assert str(exc.value) == "Empty Content" + + def test_doc(): env = { 'cupsd_conf': CupsdConf(context_wrap(CUPSD_CONF_EXAMPLE)), - 'cups_files_conf': CupsFilesConf(context_wrap(CUPS_FILES_CONF)) + 'cups_files_conf': CupsFilesConf(context_wrap(CUPS_FILES_CONF)), + 'cups_browsed_conf': CupsBrowsedConf(context_wrap(CUPS_BROWSED_CONF)) } failed, total = doctest.testmod(cups_confs, globs=env) assert failed == 0 From 31bfcdb05fc71f22974caa6036dba933f931f9a4 Mon Sep 17 00:00:00 2001 From: wushiqinlou Date: Sat, 28 Sep 2024 21:51:07 +0800 Subject: [PATCH 16/17] fix: Update value format of CupsBrowsedConf (#4230) - Per the manual and tests of cups_browsed.conf, different directives will be handled differently. E.g, for BrowseAllow, all its configured values will be kept, but for BrowseRemoteProtocols, only its last one will work. Signed-off-by: jiazhang --- insights/parsers/cups_confs.py | 17 ++++++++++------- insights/tests/parsers/test_cups_confs.py | 5 +++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/insights/parsers/cups_confs.py b/insights/parsers/cups_confs.py index 8330c011d3..aabb79cdfe 100644 --- a/insights/parsers/cups_confs.py +++ b/insights/parsers/cups_confs.py @@ -97,6 +97,13 @@ class CupsBrowsedConf(Parser, dict): """ Class for parsing the file ``/etc/cups/cups-browsed.conf`` + .. note:: + + The admin can add multiple directives into the configuration file, and restart service without issue. + However, item like BrowseRemoteProtocols only works for the last one, and the item like BrowseAllow works + for all directives. So the values of each directives will get stored to a list with the original order, + and duplicated values will be kept without de-duplication. + Sample file content:: BrowseRemoteProtocols dnssd cups @@ -105,7 +112,7 @@ class CupsBrowsedConf(Parser, dict): Examples: >>> type(cups_browsed_conf) - >>> 'dnssd' in cups_browsed_conf['BrowseRemoteProtocols'] + >>> 'dnssd cups' in cups_browsed_conf['BrowseRemoteProtocols'] True >>> 'cups.example.com' in cups_browsed_conf['BrowseAllow'] True @@ -117,13 +124,9 @@ def parse_content(self, content): for line in get_active_lines(content): k, v = [i.strip() for i in line.split(None, 1)] if k not in self: - self[k] = v if len(v.split()) == 1 else v.split() + self[k] = [v] else: - _v = self[k] - _v = [_v] if not isinstance(_v, list) else _v - if v not in _v: - _v.append(v) - self[k] = _v + self[k].append(v) @parser(Specs.cups_files_conf) diff --git a/insights/tests/parsers/test_cups_confs.py b/insights/tests/parsers/test_cups_confs.py index 91f984f786..bb56653612 100644 --- a/insights/tests/parsers/test_cups_confs.py +++ b/insights/tests/parsers/test_cups_confs.py @@ -132,6 +132,7 @@ # Can use DNSSD and/or CUPS and/or LDAP, or 'none' for neither. BrowseRemoteProtocols dnssd cups +BrowseRemoteProtocols none BrowseAllow 192.168.0.1 BrowseAllow 192.168.0.255 BrowseAllow cups.example.com @@ -176,8 +177,8 @@ def test_cups_browsed_files_conf(): result = CupsBrowsedConf(context_wrap(CUPS_BROWSED_CONF)) assert len(result) == 2 assert 'BrowseRemoteProtocols' in result - assert result['BrowseRemoteProtocols'] == ['dnssd', 'cups'] - assert sorted(result['BrowseAllow']) == sorted(['192.168.0.1', '192.168.0.255', 'cups.example.com']) + assert result['BrowseRemoteProtocols'] == ['dnssd cups', 'none'] + assert result['BrowseAllow'] == ['192.168.0.1', '192.168.0.255', 'cups.example.com', '192.168.0.255'] def test_cups_browsed_conf_empty(): From ccb65c1bad89dab23068550817fa20cc273b69b1 Mon Sep 17 00:00:00 2001 From: Xiangce Liu Date: Sun, 29 Sep 2024 16:26:16 +0800 Subject: [PATCH 17/17] fix: collect uname on RHEL 6 (#4231) - On RHEL 6, uname was not in /usr/bin/ yet. Use first_of to collect "/bin/uname" as an alternative for RHEL 6 - Refine the test_fixed_by_warning due to unexpected failure in py2 CI/CD And fixed typo in warning message Signed-off-by: Xiangce Liu --- insights/parsers/uname.py | 7 ++- insights/specs/default.py | 5 +- insights/tests/parsers/test_uname.py | 87 +++++++++++++++------------- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/insights/parsers/uname.py b/insights/parsers/uname.py index cf09fb84ee..b3f1b33f56 100644 --- a/insights/parsers/uname.py +++ b/insights/parsers/uname.py @@ -36,10 +36,12 @@ False """ +import warnings from collections import namedtuple from distutils.version import LooseVersion, StrictVersion -from .. import parser, CommandParser + +from insights import parser, CommandParser from insights.core.context import Context from insights.specs import Specs @@ -416,8 +418,7 @@ def _best_lv_release(self, other): is_o_with_dist = o_release_parts[-1].startswith(dist_opts) if not (is_s_with_dist ^ is_o_with_dist): return self._lv_release, other._lv_release - import warnings - warnings.warn('Comparison of distribution part will be ingored.') + warnings.warn('Comparison of distribution part will be ignored.') s_release = ( self._lv_release.vstring if not is_s_with_dist else pad_release(".".join(s_release_parts[:-1]), len(s_release_parts)) diff --git a/insights/specs/default.py b/insights/specs/default.py index c2460f5616..5a9149f361 100644 --- a/insights/specs/default.py +++ b/insights/specs/default.py @@ -706,7 +706,10 @@ class DefaultSpecs(Specs): tuned_adm = simple_command("/usr/sbin/tuned-adm list") udev_66_md_rules = first_file(["/etc/udev/rules.d/66-md-auto-readd.rules", "/usr/lib/udev/rules.d/66-md-auto-readd.rules"]) udev_fc_wwpn_id_rules = simple_file("/usr/lib/udev/rules.d/59-fc-wwpn-id.rules") - uname = simple_command("/usr/bin/uname -a") + uname = first_of([ + simple_command("/usr/bin/uname -a"), + simple_command("/bin/uname -a") # RHEL 6 + ]) up2date = simple_file("/etc/sysconfig/rhn/up2date") up2date_log = simple_file("/var/log/up2date") uptime = simple_command("/usr/bin/uptime") diff --git a/insights/tests/parsers/test_uname.py b/insights/tests/parsers/test_uname.py index 07f5c2870c..5fb355ee2c 100644 --- a/insights/tests/parsers/test_uname.py +++ b/insights/tests/parsers/test_uname.py @@ -1,11 +1,15 @@ +import doctest import pytest -import warnings -from insights.parsers import uname -from insights.tests import context_wrap + +try: + from unittest.mock import patch +except Exception: + from mock import patch from distutils.version import LooseVersion, StrictVersion -import doctest +from insights.parsers import uname +from insights.tests import context_wrap UNAME1 = "Linux foo.example.com 2.6.32-504.el6.x86_64 #1 SMP Tue Sep 16 01:56:35 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux" @@ -240,45 +244,46 @@ def test_fixed_by(): assert ['2.6.32-600.el6'] == u.fixed_by('2.6.32-220.1.el6', '2.6.32-600.el6') -def test_fixed_by_warning(): +@patch("insights.parsers.uname.warnings.warn") +def test_fixed_by_warning(warn): # For all remaining tests, cause the comparation warnings # to always be caught, and ignore other warnings. - warning_msg = "Comparison of distribution part will be ingored" - warnings.simplefilter("ignore") - warnings.filterwarnings("always", message=warning_msg) - with warnings.catch_warnings(record=True) as w: - - u = uname.Uname.from_uname_str("Linux qqhrycsq2 2.6.32-504.el6.x86_64 #1 SMP Wed Jun 13 18:24:36 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux") - # should be no comparation warning reported - assert [] == u.fixed_by('2.6.32-220.1.el6', '2.6.32-504.el6') - assert len(w) == 0 - - # fixes without distribution part - assert [] == u.fixed_by('2.6.32-504') - assert len(w) == 1 - - # fixes only has kernel version part - assert [] == u.fixed_by('2.6.32-') - assert len(w) == 2 - - # introduce without distribution part - assert ['2.6.32-600.el6'] == u.fixed_by('2.6.32-600.el6', introduced_in='2.6.32-504') - assert len(w) == 3 - - # introduce only has kernel version part - assert ['2.6.32-600.el6'] == u.fixed_by('2.6.32-600.el6', introduced_in='2.6.32-') - assert len(w) == 4 - - rt_u = uname.Uname.from_uname_str("Linux qqhrycsq2 4.18.0-305.rt7.72.el8.x86_64 #1 SMP Wed Jun 13 18:24:36 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux") - # fixes without distribution part - assert [] == rt_u.fixed_by('4.18.0-305.rt7.72') - assert len(w) == 5 - - # introduce without distribution part - assert ['4.18.0-307.rt7.72.el8'] == rt_u.fixed_by('4.18.0-307.rt7.72.el8', introduced_in='4.18.0-305.rt7.72') - assert len(w) == 6 - - warnings.resetwarnings() + warning_msg = "Comparison of distribution part will be ignored." + u = uname.Uname.from_uname_str("Linux qqhrycsq2 2.6.32-504.el6.x86_64 #1 SMP Wed Jun 13 18:24:36 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux") + # should be no comparation warning reported + assert [] == u.fixed_by('2.6.32-220.1.el6', '2.6.32-504.el6') + warn.reset_mock() + + # fixes without distribution part + assert [] == u.fixed_by('2.6.32-504') + warn.assert_any_call(warning_msg) + warn.reset_mock() + + # fixes only has kernel version part + assert [] == u.fixed_by('2.6.32-') + warn.assert_any_call(warning_msg) + warn.reset_mock() + + # introduce without distribution part + assert ['2.6.32-600.el6'] == u.fixed_by('2.6.32-600.el6', introduced_in='2.6.32-504') + warn.assert_any_call(warning_msg) + warn.reset_mock() + + # introduce only has kernel version part + assert ['2.6.32-600.el6'] == u.fixed_by('2.6.32-600.el6', introduced_in='2.6.32-') + warn.assert_any_call(warning_msg) + warn.reset_mock() + + # fixes without distribution part + rt_u = uname.Uname.from_uname_str("Linux qqhrycsq2 4.18.0-305.rt7.72.el8.x86_64 #1 SMP Wed Jun 13 18:24:36 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux") + assert [] == rt_u.fixed_by('4.18.0-305.rt7.72') + warn.assert_any_call(warning_msg) + warn.reset_mock() + + # introduce without distribution part + assert ['4.18.0-307.rt7.72.el8'] == rt_u.fixed_by('4.18.0-307.rt7.72.el8', introduced_in='4.18.0-305.rt7.72') + warn.assert_any_call(warning_msg) + warn.reset_mock() def test_unknown_release():