├── .fmf └── version ├── integration-tests ├── __init__.py ├── playbook_verifier │ ├── __init__.py │ ├── playbooks │ │ ├── README.md │ │ ├── compliance_openscap_setup.yml │ │ ├── insights_setup.yml │ │ └── bugs.yml │ └── test_verifier.py ├── requirements.txt ├── custom_betelgeuse_config.py ├── constants.py ├── test_version.py ├── test_e2e.py ├── test_manpage.py ├── README.md ├── test_unregister.py ├── test_checkin.py ├── test_compliance.py ├── conftest.py ├── test_status.py ├── test_client_systemd.py ├── test_motd.py ├── test_ros.py ├── test_connection.py ├── test_client.py ├── test_upload.py ├── test_display_name_option.py ├── test_collection.py └── test_registration.py ├── data ├── systemd │ ├── 80-insights.preset │ ├── 80-insights-register.preset │ ├── insights-register.path.in │ ├── insights-unregister.path.in │ ├── insights-client-results.path.in │ ├── insights-client-checkin.timer │ ├── insights-client.timer │ ├── insights-client-results.service.in │ ├── insights-client-checkin.service.in │ ├── insights-client.service │ ├── insights-register.service.in │ ├── insights-client-boot.service │ ├── insights-unregister.service.in │ └── meson.build ├── tmpfiles.d │ ├── insights-client.conf │ └── meson.build ├── redhattools.pub.gpg ├── logrotate.d │ ├── meson.build │ └── insights-client ├── meson.build ├── insights-client.motd ├── insights-client.conf └── cert-api.access.redhat.com.pem ├── src ├── insights_client │ ├── tests │ │ ├── requirements.txt │ │ ├── requirements-core.txt │ │ ├── meson.build │ │ ├── conftest.py │ │ ├── test_commands.py │ │ ├── test_client.py │ │ └── test_motd.py │ ├── meson.build │ ├── constants.py.in │ ├── utc.py │ ├── run.py │ └── __init__.py ├── insights-client.in ├── redhat-access-insights.in └── meson.build ├── systemtest ├── tests │ └── integration │ │ ├── main.fmf │ │ └── test.sh ├── guest-setup.sh └── plans │ └── main.fmf ├── pyproject.toml ├── .sourcery.yaml ├── pytest.ini ├── requirements-dev.txt ├── .github ├── dependabot.yml ├── workflows │ ├── codespell.yml │ ├── stylish.yml │ ├── docstring_validation.yml │ └── pytest.yml └── pull_request_template.md ├── docs ├── meson.build ├── file-redaction.yaml.example ├── file-content-redaction.yaml.example ├── insights-client.conf.5 └── insights-client.8 ├── .gitleaks.toml ├── .flake8 ├── meson_options.txt ├── TESTING.md ├── LICENSE ├── .testing-farm.yaml ├── .packit.yaml ├── meson.build ├── README.md └── insights-client.spec /.fmf/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /integration-tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /integration-tests/playbook_verifier/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/systemd/80-insights.preset: -------------------------------------------------------------------------------- 1 | enable insights-client-boot.service 2 | -------------------------------------------------------------------------------- /data/tmpfiles.d/insights-client.conf: -------------------------------------------------------------------------------- 1 | R /var/tmp/insights-client* - - - 24h 2 | -------------------------------------------------------------------------------- /src/insights_client/tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | -r requirements-core.txt 3 | -------------------------------------------------------------------------------- /src/insights_client/tests/requirements-core.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | requests 3 | setuptools 4 | six 5 | -------------------------------------------------------------------------------- /systemtest/tests/integration/main.fmf: -------------------------------------------------------------------------------- 1 | summary: Runs tmt tests 2 | test: ./test.sh 3 | duration: 5h 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | include = '(\.py(\.in)?|src/insights-client.in)$' 3 | line-length = 100 4 | -------------------------------------------------------------------------------- /data/systemd/80-insights-register.preset: -------------------------------------------------------------------------------- 1 | enable insights-register.path 2 | enable insights-unregister.path 3 | -------------------------------------------------------------------------------- /data/redhattools.pub.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHatInsights/insights-client/HEAD/data/redhattools.pub.gpg -------------------------------------------------------------------------------- /integration-tests/requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/RedHatInsights/pytest-client-tools@main 2 | pyyaml 3 | pytest-subtests 4 | -------------------------------------------------------------------------------- /.sourcery.yaml: -------------------------------------------------------------------------------- 1 | rule_settings: 2 | enable: [default] 3 | disable: 4 | - no-loop-in-tests 5 | - no-conditionals-in-tests 6 | -------------------------------------------------------------------------------- /data/logrotate.d/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 2 | ['insights-client'], 3 | install_dir: get_option('sysconfdir') / 'logrotate.d' 4 | ) 5 | -------------------------------------------------------------------------------- /data/tmpfiles.d/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 2 | ['insights-client.conf'], 3 | install_dir: systemd.get_pkgconfig_variable('tmpfilesdir') 4 | ) 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | tier1: Mark a test to be Tier 1 4 | tier2: Mark a test to be Tier 2 5 | tier3: Mark a test to be Tier 3 6 | -------------------------------------------------------------------------------- /data/logrotate.d/insights-client: -------------------------------------------------------------------------------- 1 | /var/log/insights-client/*.log { 2 | rotate 4 3 | weekly 4 | missingok 5 | notifempty 6 | copytruncate 7 | } 8 | -------------------------------------------------------------------------------- /systemtest/guest-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # this is a general use version of the guest setup that happens in testing farm 3 | 4 | dnf -y install insights-client 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # the version of black is specified also in the stylish.yml github workflow; 2 | # please update the version there in case it is bumped here 3 | black==24.3.0 4 | flake8 5 | -------------------------------------------------------------------------------- /src/insights-client.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | import sys 4 | 5 | from insights_client import _main 6 | 7 | sys.path.insert(1, "@pythondir@") 8 | 9 | 10 | if __name__ == "__main__": 11 | _main() 12 | -------------------------------------------------------------------------------- /src/redhat-access-insights.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | >&2 echo "WARNING: $(basename "$0") is deprecated and will be removed in a future release; use 'insights-client' instead." 4 | sleep 3 5 | exec @bindir@/insights-client "$@" 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | target-branch: "main" 8 | commit-message: 9 | prefix: "ci" 10 | -------------------------------------------------------------------------------- /docs/meson.build: -------------------------------------------------------------------------------- 1 | install_man( 2 | ['insights-client.8', 'insights-client.conf.5'] 3 | ) 4 | 5 | install_data( 6 | ['file-redaction.yaml.example', 'file-content-redaction.yaml.example'], 7 | install_dir: get_option('datadir') / 'doc' / meson.project_name() 8 | ) 9 | -------------------------------------------------------------------------------- /src/insights_client/tests/meson.build: -------------------------------------------------------------------------------- 1 | pytest = find_program('pytest', 'pytest-3') 2 | 3 | env = environment() 4 | env.append('PYTHONPATH', meson.source_root() / 'src') 5 | 6 | test_sources = files('test_client.py', 'test_commands.py') 7 | 8 | test('test', 9 | pytest, 10 | args: test_sources, 11 | env: env 12 | ) 13 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | [extend] 2 | useDefault = true 3 | 4 | 5 | [allowlist] 6 | description = "Repository-specific configuration" 7 | 8 | paths = [ 9 | # Tests for insights-core contain public and private GPG keypair. They were 10 | # generated specifically for this usecase. 11 | "systemtest/insights-core-setup.sh", 12 | ] 13 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | name: codespell 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | codespell: 11 | runs-on: "ubuntu-latest" 12 | 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - uses: codespell-project/actions-codespell@v2 17 | -------------------------------------------------------------------------------- /integration-tests/custom_betelgeuse_config.py: -------------------------------------------------------------------------------- 1 | from betelgeuse import default_config 2 | 3 | TESTCASE_CUSTOM_FIELDS = default_config.TESTCASE_CUSTOM_FIELDS + ( 4 | "casecomponent", 5 | "subsystemteam", 6 | "reference", 7 | ) 8 | 9 | DEFAULT_COMPONENT_VALUE = "" 10 | DEFAULT_POOLTEAM_VALUE = "" 11 | DEFAULT_REFERENCE_VALUE = "" 12 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | sysconf_sources = [ 2 | 'cert-api.access.redhat.com.pem', 3 | 'insights-client.conf', 4 | 'insights-client.motd', 5 | 'redhattools.pub.gpg' 6 | ] 7 | 8 | install_data( 9 | sysconf_sources, 10 | install_dir: get_option('sysconfdir') / 'insights-client' 11 | ) 12 | 13 | subdir('logrotate.d') 14 | subdir('systemd') 15 | subdir('tmpfiles.d') 16 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | filename = 3 | *.py, 4 | *.py.in, 5 | */src/insights-client.in, 6 | # same limit as black 7 | max-line-length = 100 8 | ignore = 9 | # E203 whitespace before ':' 10 | # result of black-formatted code 11 | E203, 12 | # W503: line break before binary operator 13 | W503 14 | extend-exclude = 15 | # default build directory 16 | build/, 17 | -------------------------------------------------------------------------------- /src/insights_client/meson.build: -------------------------------------------------------------------------------- 1 | insights_client_sources = [ 2 | '__init__.py', 3 | 'run.py', 4 | 'utc.py' 5 | ] 6 | 7 | insights_client_sources += configure_file( 8 | input: 'constants.py.in', 9 | output: '@BASENAME@', 10 | configuration: config_data 11 | ) 12 | 13 | python_installation.install_sources(insights_client_sources, subdir: 'insights_client') 14 | 15 | subdir('tests') 16 | -------------------------------------------------------------------------------- /data/insights-client.motd: -------------------------------------------------------------------------------- 1 | Register this system with Red Hat Lightspeed: rhc connect 2 | 3 | Example: 4 | # rhc connect --activation-key --organization 5 | 6 | The rhc client and Red Hat Lightspeed will enable analytics and additional 7 | management capabilities on your system. 8 | View your connected systems at https://console.redhat.com/insights 9 | 10 | You can learn more about how to register your system 11 | using rhc at https://red.ht/registration 12 | -------------------------------------------------------------------------------- /systemtest/plans/main.fmf: -------------------------------------------------------------------------------- 1 | summary: insights-client test suite 2 | discover: 3 | how: fmf 4 | 5 | prepare: 6 | - name: "Install test deps" 7 | how: install 8 | package: 9 | - bzip2 10 | - bzip2-devel 11 | - git-core 12 | - logrotate 13 | - man-db 14 | - openscap 15 | - openscap-scanner 16 | - podman 17 | - python3-pip 18 | - python3-pytest 19 | - scap-security-guide 20 | - zip 21 | 22 | execute: 23 | how: tmt 24 | -------------------------------------------------------------------------------- /integration-tests/constants.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | HOST_DETAILS: str = "/var/lib/insights/host-details.json" 4 | REGISTERED_FILE: str = "/etc/insights-client/.registered" 5 | UNREGISTERED_FILE: str = "/etc/insights-client/.unregistered" 6 | MACHINE_ID_FILE: str = "/etc/insights-client/machine-id" 7 | TAGS_FILE = pathlib.Path("/etc/insights-client/tags.yaml") 8 | CONFIG_FILE = "/etc/insights-client/insights-client.conf" 9 | INSIGHTS_CLIENT_LOG_FILE = "/var/log/insights-client/insights-client.log" 10 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | insights_client_sources = [ 2 | 'insights-client.in', 3 | ] 4 | 5 | if get_option('redhat_access_insights') 6 | insights_client_sources += 'redhat-access-insights.in' 7 | endif 8 | 9 | foreach source : insights_client_sources 10 | configure_file( 11 | input: source, 12 | output: '@BASENAME@', 13 | configuration: config_data, 14 | install_dir: get_option('bindir'), 15 | install_mode: 'rwxr-xr-x' 16 | ) 17 | endforeach 18 | 19 | subdir('insights_client') 20 | -------------------------------------------------------------------------------- /src/insights_client/constants.py.in: -------------------------------------------------------------------------------- 1 | """ 2 | Constants 3 | """ 4 | 5 | APP_NAME = "@PACKAGE@" 6 | VERSION = "@PACKAGE_VERSION@" 7 | PREFIX = "@PREFIX@" 8 | BINDIR = "@BINDIR@" 9 | SBINDIR = "@SBINDIR@" 10 | LIBEXECDIR = "@LIBEXECDIR@" 11 | DATAROOTDIR = "@DATAROOTDIR@" 12 | DATADIR = "@DATADIR@" 13 | SYSCONFDIR = "@SYSCONFDIR@" 14 | LOCALSTATEDIR = "@LOCALSTATEDIR@" 15 | DOCDIR = "@DOCDIR@" 16 | CORE_SELINUX_POLICY = "@CORE_SELINUX_POLICY@" 17 | 18 | 19 | class InsightsConstants(object): 20 | app_name = APP_NAME 21 | version = VERSION 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | --- 7 | 8 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /src/insights_client/utc.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | class UTC(datetime.tzinfo): 5 | """ 6 | UTC is a concrete subclass of datetime.tzinfo representing the UTC 7 | time zone. 8 | """ 9 | 10 | def utcoffset(self, dt): 11 | return datetime.timedelta(0) 12 | 13 | def tzname(self, dt): 14 | return "UTC" 15 | 16 | def dst(self, dt): 17 | return datetime.timedelta(0) 18 | 19 | 20 | def make_utc_datetime_rfc3339(): 21 | return datetime.datetime.utcnow().replace(microsecond=0, tzinfo=UTC()).isoformat("T") 22 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('python', type: 'string', value: 'python3', description: 'python interpreter to use when finding python installation') 2 | option('auto_registration', type: 'feature', value: 'disabled', description: 'enable automatic registration') 3 | option('checkin', type: 'feature', value: 'disabled', description: 'enable hourly check-in') 4 | option('redhat_access_insights', type: 'boolean', value: false, description: 'enable deprecated redhat-access-insights executable') 5 | option('core_selinux_policy', type: 'string', value: '', description: 'SELinux policy for insights-core (empty: none used)') 6 | -------------------------------------------------------------------------------- /src/insights_client/run.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | try: 9 | try: 10 | from insights.client.phase import v2 as client 11 | except ImportError as e: 12 | sys.exit( 13 | "Error importing insights.client for %s as %s: %s" 14 | % (os.environ["INSIGHTS_PHASE"], os.environ["PYTHONPATH"], e) 15 | ) 16 | 17 | phase = getattr(client, os.environ["INSIGHTS_PHASE"]) 18 | sys.exit(phase()) 19 | except KeyboardInterrupt: 20 | sys.exit(1) 21 | except Exception as e: 22 | print("Fatal: {0}".format(e)) 23 | sys.exit(1) 24 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing `insights-client` 2 | 3 | After installing the prerequisites, you can run the unit test suite using `pytest`: 4 | 5 | ```shell 6 | $ python3 -m pip install -r src/insights_client/tests/requirements.txt 7 | $ python3 -m pytest src/insights_client/tests 8 | ``` 9 | 10 | ## Specifying insights-core 11 | 12 | By default, the installed insights-core package is used to run the insights-client test suite. 13 | By adding a custom path to a local repository of insights-core in the `PYTHONPATH` environment variable, you can test with custom insights-core versions (such as upstream HEAD, for example). 14 | 15 | 16 | ## CI 17 | 18 | The unit tests are also run by GitHub Actions. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Red Hat Inc. 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | -------------------------------------------------------------------------------- /data/systemd/insights-register.path.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-register.path.d/override.conf. Put the desired 6 | # overrides in that file and reload systemd. 7 | # 8 | # For more information about systemd drop-in files, see systemd.unit(5). 9 | 10 | [Unit] 11 | Description=Automatically Register with Red Hat Insights Path Watch 12 | Documentation=man:insights-client(8) 13 | 14 | [Path] 15 | PathExists=@sysconfdir@/pki/consumer/cert.pem 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /data/systemd/insights-unregister.path.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-unregister.path.d/override.conf. Put the desired 6 | # overrides in that file and reload systemd. 7 | # 8 | # For more information about systemd drop-in files, see systemd.unit(5). 9 | 10 | [Unit] 11 | Description=Automatically Unregister from Red Hat Insights Path Watch 12 | Documentation=man:insights-client(8) 13 | 14 | [Path] 15 | PathChanged=@sysconfdir@/pki/consumer/cert.pem 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /src/insights_client/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import sys 3 | from unittest import mock 4 | 5 | 6 | def pytest_configure(config): 7 | repo_root = pathlib.Path(__file__).parents[3] 8 | 9 | # Mock the insights package before any imports happen 10 | sys.modules["insights"] = mock.MagicMock() 11 | sys.modules["insights.client"] = mock.MagicMock() 12 | sys.modules["insights.client.phase"] = mock.MagicMock() 13 | sys.modules["insights.client.phase.v2"] = mock.MagicMock() 14 | sys.modules["insights.client.config"] = mock.MagicMock() 15 | 16 | # Hijack sys.path, so we don't have to use 'PYTHONPATH=src/' 17 | sources: pathlib.Path = repo_root / "src" 18 | sys.path.insert(0, str(sources)) 19 | -------------------------------------------------------------------------------- /data/systemd/insights-client-results.path.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client-results.path.d/override.conf. Put the 6 | # desired overrides in that file and reload systemd. 7 | # 8 | # For more information about systemd drop-in files, see systemd.unit(5). 9 | 10 | [Unit] 11 | Description=Monitor @pkgsysconfdir@/.lastupload for modifications 12 | Documentation=man:insights-client(8) 13 | PartOf=insights-client.timer 14 | 15 | [Path] 16 | PathModified=@pkgsysconfdir@/.lastupload 17 | 18 | [Install] 19 | WantedBy=insights-client.timer 20 | -------------------------------------------------------------------------------- /.testing-farm.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | environments: 4 | secrets: 5 | SETTINGS_URL: "ea2a89aa-6a78-40e0-906e-140f623c45b0,qdeC9UxsCJa8e4UA/873Bv41RgSaYlbmFWz97gNEkiFjgxBF5wQiZGD3KDgki110oOXmAb4Ty0haFAi7XB2SAZvA7ooXRxrVRP/U3dyxNir5uYmHRurBx+pqb05eOn2zmygqqyTthXNglRvg2kJnjuYB44h4W/u2VUl8Id+s4h67IaX15toY3VKr3vmdH2OxFMUI6U/atyd6Z96jqP0muxhu+KuYv40nYUrgiFqoOedom0DOCPCMQUubeYQ3mexcc0HF/L28bbyt1eFNWYYuSXb61/Gjg3s2Shw7e7RhWquqsvzWlCTa0JFalGD+uELgVxOjwHDbn56drnnjh3oVcpD20YTQQbsAGHCgPhqCuxkqCe9ayIvKKCsYKapdQ+D0NvjXFRyj2QfFrOdQHf4KUOD6MXBWAOswM/3C6x+zj6O5YZTSmMagq1oLgeG2wqhL56zNUGoxqgcRv9MYmgPzbiF6SPlAniMwWjjcDNsmWhMAoJDAI5q7+Tazjs7OvziUsEgMp1T4jZooIdHtAU+ily4K8Gf8lPiQHhxFD1ieC9E1xzBeVuh+tHhuB7E6WN+sISzxXdyUDTd5lyZicqgxJ/GmEjB2K+/MDDW1VI9Yyk6Ee3iUVGGBktvaF0eV4WqrNPv8AAJL/RMDU+T/grUTmf703DQFNWPyISAJDuw8h+Y=" 6 | -------------------------------------------------------------------------------- /.github/workflows/stylish.yml: -------------------------------------------------------------------------------- 1 | name: stylish 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | stylish: 11 | name: "black & flake8" 12 | runs-on: ubuntu-latest 13 | container: 14 | image: fedora:latest 15 | 16 | steps: 17 | - name: Base setup 18 | run: | 19 | dnf --setopt install_weak_deps=False install -y \ 20 | git-core \ 21 | python3-flake8 \ 22 | python3-pip 23 | 24 | - uses: actions/checkout@v6 25 | 26 | - uses: psf/black@stable 27 | with: 28 | version: "24.3.0" 29 | 30 | - name: Setup flake8 annotations 31 | uses: rbialon/flake8-annotations@v1 32 | 33 | - name: Run flake8 34 | run: | 35 | flake8 36 | -------------------------------------------------------------------------------- /data/systemd/insights-client-checkin.timer: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client.timer.d/override.conf. Put the desired 6 | # overrides in that file, reload systemd and restart this timer. 7 | # 8 | # For more information about systemd drop-in files, see systemd.unit(5). 9 | 10 | [Unit] 11 | Description=Check-in with the platform timer 12 | Documentation=man:insights-client(8) 13 | After=network-online.target 14 | Wants=network-online.target 15 | 16 | [Timer] 17 | OnCalendar=hourly 18 | Persistent=true 19 | RandomizedDelaySec=600 20 | 21 | [Install] 22 | WantedBy=timers.target 23 | -------------------------------------------------------------------------------- /data/systemd/insights-client.timer: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client.timer.d/override.conf. Put the desired 6 | # overrides in that file, reload systemd and restart this timer. 7 | # 8 | # For more information about systemd drop-in files, see systemd.unit(5). 9 | 10 | [Unit] 11 | Description=Insights Client Timer Task 12 | Documentation=man:insights-client(8) 13 | After=network-online.target 14 | Wants=network-online.target 15 | 16 | [Timer] 17 | OnCalendar=daily 18 | Persistent=true 19 | RandomizedDelaySec=14400 20 | 21 | [Install] 22 | WantedBy=timers.target 23 | Also=insights-client-results.path 24 | -------------------------------------------------------------------------------- /.github/workflows/docstring_validation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test Docstrings Validation 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - "integration-tests/**" 10 | 11 | jobs: 12 | betelgeuse: 13 | name: "betelgeuse dry-run" 14 | runs-on: ubuntu-latest 15 | container: 16 | image: fedora:latest 17 | 18 | steps: 19 | - uses: actions/checkout@v6 20 | 21 | - name: Base setup for Betelgeuse 22 | run: | 23 | dnf --setopt install_weak_deps=False install -y \ 24 | python3-pip 25 | python3 -m pip install betelgeuse 26 | 27 | - name: Run Betelgeuse 28 | run: | 29 | PYTHONPATH=integration-tests/ betelgeuse --config-module \ 30 | custom_betelgeuse_config test-case --dry-run \ 31 | integration-tests/ dryrun_project ./test_case.xml 32 | -------------------------------------------------------------------------------- /data/systemd/insights-client-results.service.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client-results.service.d/override.conf. Put the 6 | # desired overrides in that file and reload systemd. The next time this service 7 | # is run (either manually or via another systemd unit), the overridden values 8 | # will be in effect. 9 | # 10 | # For more information about systemd drop-in files, see systemd.unit(5). 11 | 12 | [Unit] 13 | Description=Check for insights from Red Hat Cloud Services 14 | Documentation=man:insights-client(8) 15 | After=network-online.target 16 | 17 | [Service] 18 | Type=oneshot 19 | RemainAfterExit=no 20 | ExecStart=@bindir@/insights-client --check-results 21 | Restart=no 22 | -------------------------------------------------------------------------------- /data/systemd/insights-client-checkin.service.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client-results.service.d/override.conf. Put the 6 | # desired overrides in that file and reload systemd. The next time this service 7 | # is run (either manually or via another systemd unit), the overridden values 8 | # will be in effect. 9 | # 10 | # For more information about systemd drop-in files, see systemd.unit(5). 11 | 12 | [Unit] 13 | Description=Check-in with the platform 14 | Documentation=man:insights-client(8) 15 | After=network-online.target 16 | Wants=network-online.target 17 | 18 | [Service] 19 | Type=oneshot 20 | RemainAfterExit=no 21 | ExecStart=@bindir@/insights-client --checkin 22 | Restart=no 23 | -------------------------------------------------------------------------------- /data/systemd/insights-client.service: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client.service.d/override.conf. Put the desired 6 | # overrides in that file and reload systemd. The next time this service is run 7 | # (either manually or via a systemd timer), the overridden values will be in 8 | # effect. 9 | # 10 | # For more information about systemd drop-in files, see systemd.unit(5). 11 | 12 | [Unit] 13 | Description=Insights Client 14 | Documentation=man:insights-client(8) 15 | After=network.target 16 | StartLimitIntervalSec=12h 17 | StartLimitBurst=6 18 | 19 | [Service] 20 | Type=exec 21 | ExecStart=/usr/bin/insights-client 22 | Restart=on-failure 23 | RestartSec=1h 24 | WatchdogSec=900 25 | CPUQuota=30% 26 | MemoryHigh=1G 27 | MemoryMax=2G 28 | TasksMax=300 29 | BlockIOWeight=100 30 | -------------------------------------------------------------------------------- /integration-tests/playbook_verifier/playbooks/README.md: -------------------------------------------------------------------------------- 1 | # Test data for the Ansible playbook verifier 2 | 3 | To verify the functionality of the verifier, these playbooks have been downloaded from git repository of config-manager: 4 | - [insights_setup.yml](https://github.com/RedHatInsights/config-manager/blob/master/playbooks/insights_setup.yml) 5 | - [compliance_openscap_setup.yml](https://github.com/RedHatInsights/config-manager/blob/master/playbooks/compliance_openscap_setup.yml) 6 | 7 | Additionally, this playbook has been downloaded from git repository of insights-ansible-playbook-verifier: 8 | - [bugs.yml](https://github.com/RedHatInsights/insights-ansible-playbook-verifier/blob/main/data/playbooks/bugs.yml) 9 | 10 | The list of playbooks should be extended to improve coverage: 11 | - lines ending with Windows-style line endings (`\r\n`), 12 | - comments with weird indentation, 13 | - playbook with comments stripped before verifying, 14 | - playbooks containing non-ASCII UTF-8 characters. 15 | -------------------------------------------------------------------------------- /data/systemd/insights-register.service.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-register.service.d/override.conf. Put the desired 6 | # overrides in that file and reload systemd. The next time this service is run 7 | # (either manually or via a systemd timer), the overridden values will be in 8 | # effect. 9 | # 10 | # For more information about systemd drop-in files, see systemd.unit(5). 11 | 12 | [Unit] 13 | Description=Automatically Register with Red Hat Insights 14 | Documentation=man:insights-client(8) 15 | After=network-online.target 16 | 17 | [Service] 18 | Type=simple 19 | ExecStart=@bindir@/insights-client --register 20 | Restart=no 21 | WatchdogSec=900 22 | CPUQuota=30% 23 | MemoryHigh=1G 24 | MemoryMax=2G 25 | TasksMax=300 26 | BlockIOWeight=100 27 | ExecStopPost=systemctl mask --now insights-register.path 28 | -------------------------------------------------------------------------------- /data/systemd/insights-client-boot.service: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-client-boot.service.d/override.conf Put the desired 6 | # overrides in that file and reload systemd. 7 | # 8 | # For more information about systemd drop-in files, see systemd.unit(5). 9 | 10 | [Unit] 11 | Description=Run Insights Client at boot 12 | Documentation=man:insights-client(8) 13 | After=network-online.target 14 | ConditionPathExists=/etc/insights-client/.run_insights_client_next_boot 15 | 16 | [Service] 17 | Type=oneshot 18 | ExecStart=/usr/bin/insights-client --retry 3 19 | Restart=no 20 | WatchdogSec=900 21 | CPUQuota=30% 22 | MemoryHigh=1G 23 | MemoryMax=2G 24 | TasksMax=300 25 | BlockIOWeight=100 26 | ExecStartPre=/bin/rm -f /etc/insights-client/.run_insights_client_next_boot 27 | 28 | [Install] 29 | WantedBy=multi-user.target 30 | -------------------------------------------------------------------------------- /integration-tests/test_version.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import pytest 10 | 11 | 12 | @pytest.mark.tier1 13 | def test_version(insights_client): 14 | """ 15 | :id: 7ec671cb-39ae-4cda-b279-f05d7c835d5d 16 | :title: Test --version outputs client and core versions 17 | :description: 18 | This test verifies that running `insights-client --version` outputs 19 | both the client and core version information 20 | :tags: Tier 1 21 | :steps: 22 | 1. Run `insights-client --version` 23 | 2. Check the output for "Client: " and "Core: " 24 | :expectedresults: 25 | 1. Command executes without errors 26 | 2. Both "Client: " and "Core: " are present in the output 27 | """ 28 | proc = insights_client.run("--version", selinux_context=None) 29 | assert "Client: " in proc.stdout 30 | assert "Core: " in proc.stdout 31 | -------------------------------------------------------------------------------- /data/systemd/insights-unregister.service.in: -------------------------------------------------------------------------------- 1 | # This file is part of insights-client. 2 | # 3 | # Any changes made to this file will be overwritten during a software update. To 4 | # override a parameter in this file, create a drop-in file, typically located at 5 | # /etc/systemd/system/insights-unregister.service.d/override.conf. Put the 6 | # desired overrides in that file and reload systemd. The next time this service 7 | # is run (either manually or via a systemd timer), the overridden values will be 8 | # in effect. 9 | # 10 | # For more information about systemd drop-in files, see systemd.unit(5). 11 | 12 | [Unit] 13 | Description=Automatically Unregister from Red Hat Insights 14 | Documentation=man:insights-client(8) 15 | After=network-online.target 16 | ConditionPathExists=!@sysconfdir@/pki/consumer/cert.pem 17 | 18 | [Service] 19 | Type=simple 20 | ExecStart=@bindir@/insights-client --unregister --force 21 | ExecStopPost=systemctl unmask --now insights-register.path 22 | ExecStopPost=systemctl start insights-register.path 23 | Restart=no 24 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: pytest 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | pull_request: 7 | paths: 8 | - "src/**" 9 | - ".github/workflows/pytest.yml" 10 | 11 | jobs: 12 | pytest: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - name: "CentOS Stream 10" 18 | image: "quay.io/centos/centos:stream10" 19 | 20 | runs-on: "ubuntu-latest" 21 | container: 22 | image: ${{ matrix.image }} 23 | 24 | steps: 25 | - name: "Checkout the repository" 26 | uses: actions/checkout@v6 27 | 28 | - name: "Install dependencies" 29 | run: | 30 | dnf --setopt install_weak_deps=False install -y git-core python3-pip gpg 31 | python3 -m pip install --upgrade pip wheel 32 | python3 -m pip install -r src/insights_client/tests/requirements.txt 33 | 34 | - name: "Run pytest" 35 | env: 36 | PYTEST_ADDOPTS: "--color=yes --code-highlight=yes --showlocals" 37 | run: | 38 | python3 -m pytest src/insights_client/tests 39 | -------------------------------------------------------------------------------- /data/systemd/meson.build: -------------------------------------------------------------------------------- 1 | unit_sources = [ 2 | 'insights-client-boot.service', 3 | 'insights-client.service', 4 | 'insights-client.timer', 5 | ] 6 | 7 | preset_sources = [ 8 | '80-insights.preset' 9 | ] 10 | 11 | unit_inputs = [ 12 | 'insights-client-results.service.in', 13 | 'insights-client-results.path.in' 14 | ] 15 | 16 | if get_option('auto_registration').enabled() 17 | unit_inputs += [ 18 | 'insights-register.service.in', 19 | 'insights-register.path.in', 20 | 'insights-unregister.service.in', 21 | 'insights-unregister.path.in', 22 | ] 23 | preset_sources += '80-insights-register.preset' 24 | endif 25 | 26 | if get_option('checkin').enabled() 27 | unit_inputs += [ 28 | 'insights-client-checkin.service.in' 29 | ] 30 | unit_sources += 'insights-client-checkin.timer' 31 | endif 32 | 33 | foreach unit_input : unit_inputs 34 | unit_sources += configure_file( 35 | input: unit_input, 36 | output: '@BASENAME@', 37 | configuration: config_data, 38 | ) 39 | endforeach 40 | 41 | install_data( 42 | unit_sources, 43 | install_dir: systemd.get_pkgconfig_variable('systemdsystemunitdir') 44 | ) 45 | 46 | install_data( 47 | preset_sources, 48 | install_dir: systemd.get_pkgconfig_variable('systemdsystempresetdir') 49 | ) 50 | -------------------------------------------------------------------------------- /docs/file-redaction.yaml.example: -------------------------------------------------------------------------------- 1 | --- 2 | # Omit entire files and commands from the collection using parameters listed here. 3 | 4 | # Using YAML syntax, create lists of "components", "commands", and "files" to omit. 5 | 6 | # For a full list of files and commands run by insights-client, 7 | # refer to /etc/insights-client/.fallback.json 8 | 9 | # This file is deprecated and provided for compatibility only. 10 | # It will be removed in a future version. 11 | 12 | # Commands and files are provided here presently for compatibility only. 13 | # The use of components is preferred over commands and files, and any commands 14 | # or files that match components will be converted to components during parsing. 15 | # These commands and files must match entries in the .fallback.json file. 16 | 17 | # For a full list of Insights Core components, refer to the following: 18 | # https://insights-core.readthedocs.io/en/latest/specs_catalog.html 19 | 20 | # Components must be prefixed with "insights.specs.default.DefaultSpecs." 21 | 22 | # An example configuration is provided below. 23 | 24 | components: 25 | - insights.specs.default.DefaultSpecs.httpd_V 26 | - insights.specs.default.DefaultSpecs.mysql_log 27 | - insights.specs.default.DefaultSpecs.ifconfig 28 | commands: 29 | - /bin/rpm -qa 30 | - /bin/ls 31 | - ethtool_i 32 | files: 33 | - /etc/audit/auditd.conf 34 | - cluster_conf 35 | -------------------------------------------------------------------------------- /data/insights-client.conf: -------------------------------------------------------------------------------- 1 | [insights-client] 2 | # Example options in this file are the defaults 3 | 4 | # Change log level, valid options DEBUG, INFO, WARNING, ERROR, CRITICAL. Default DEBUG 5 | #loglevel=DEBUG 6 | 7 | # Attempt to auto configure with Satellite server 8 | #auto_config=True 9 | 10 | # Base URL for the Insights API 11 | #base_url=cert-api.access.redhat.com:443/r/insights 12 | 13 | # URL for your proxy. Example: http://user:pass@192.168.100.50:8080 14 | #proxy= 15 | 16 | # Automatically update the dynamic configuration 17 | #auto_update=True 18 | 19 | # Specify which data types should be obfuscated in the data collection 20 | # Supported options: ipv4, ipv6, hostname, mac (comma-separated list) 21 | #obfuscation_list= 22 | 23 | # Display name for registration 24 | #display_name= 25 | 26 | # Ansible hostname for this system 27 | #ansible_host= 28 | 29 | # Timeout for commands run during collection, in seconds 30 | #cmd_timeout=120 31 | 32 | # Timeout for HTTP calls, in seconds 33 | #http_timeout=120 34 | 35 | # Location of the redaction file for commands, files, and components 36 | #redaction_file=/etc/insights-client/file-redaction.yaml 37 | 38 | # Location of the redaction file for patterns and keywords 39 | #content_redaction_file=/etc/insights-client/file-content-redaction.yaml 40 | 41 | # Location of the tags file for this system 42 | #tags_file=/etc/insights-client/tags.yaml 43 | -------------------------------------------------------------------------------- /integration-tests/test_e2e.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import pytest 10 | from pytest_client_tools.util import Version, loop_until 11 | 12 | pytestmark = pytest.mark.usefixtures("register_subman") 13 | 14 | 15 | @pytest.mark.tier1 16 | def test_insights_client_version_in_inventory(insights_client, external_inventory): 17 | """ 18 | :id: 1d5d101e-94ad-4404-900f-f86a26450c3f 19 | :title: Verify insights-client version in Inventory 20 | :description: 21 | Ensure that running insights-client creates a new host entry in the 22 | Inventory and includes both the insights-client and egg version information 23 | :tags: Tier 1 24 | :steps: 25 | 1. Register the system with insights-client 26 | 2. Confirm registration status 27 | 3. Retrieve the system profile from the Inventory 28 | :expectedresults: 29 | 1. Insights-client is registered 30 | 2. The system profile is retrieved from the Inventory 31 | 3. The system profile includes insights-client and egg version information 32 | """ 33 | insights_client.register() 34 | assert loop_until(lambda: insights_client.is_registered) 35 | 36 | system_profile = external_inventory.this_system_profile() 37 | 38 | assert insights_client.version == Version(system_profile["insights_client_version"]) 39 | assert insights_client.core_version == Version( 40 | (system_profile["insights_egg_version"].split("-"))[0] 41 | ) 42 | -------------------------------------------------------------------------------- /.packit.yaml: -------------------------------------------------------------------------------- 1 | upstream_package_name: insights-client 2 | downstream_package_name: insights-client 3 | specfile_path: out/insights-client.spec 4 | 5 | srpm_build_deps: 6 | - gawk 7 | - rpkg 8 | 9 | actions: 10 | post-upstream-clone: 11 | - mkdir out 12 | - rpkg srpm --outdir out 13 | get-current-version: 14 | - awk '/^Version:/ {print $2;}' out/insights-client.spec 15 | create-archive: 16 | - bash -c 'echo out/insights-client-*.tar.*' 17 | fix-spec-file: 18 | - echo 'nothing to fix' 19 | 20 | jobs: 21 | - job: copr_build 22 | trigger: pull_request 23 | targets: 24 | - rhel-10-x86_64 25 | - rhel-10-aarch64 26 | - rhel-10-s390x 27 | - rhel-10-ppc64le 28 | - centos-stream-10-x86_64 29 | - centos-stream-10-aarch64 30 | - centos-stream-10-s390x 31 | - centos-stream-10-ppc64le 32 | 33 | - job: copr_build 34 | trigger: commit 35 | branch: main 36 | targets: 37 | - rhel-10-x86_64 38 | - rhel-10-aarch64 39 | - rhel-10-s390x 40 | - rhel-10-ppc64le 41 | - centos-stream-10-x86_64 42 | - centos-stream-10-aarch64 43 | - centos-stream-10-s390x 44 | - centos-stream-10-ppc64le 45 | 46 | - job: tests 47 | trigger: pull_request 48 | identifier: "unit/centos-stream" 49 | targets: 50 | - centos-stream-10-x86_64 51 | labels: 52 | - unit 53 | 54 | - job: tests 55 | trigger: pull_request 56 | identifier: "unit/rhel" 57 | targets: 58 | rhel-10-x86_64: 59 | distros: 60 | - RHEL-10-Nightly 61 | labels: 62 | - unit 63 | tf_extra_params: 64 | environments: 65 | - settings: 66 | provisioning: 67 | tags: 68 | BusinessUnit: sst_csi_client_tools 69 | use_internal_tf: true 70 | -------------------------------------------------------------------------------- /src/insights_client/tests/test_commands.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import insights_client 4 | from unittest.mock import patch 5 | from pytest import raises 6 | 7 | 8 | @patch("insights_client.sys.argv", ["insights-client", "--version"]) 9 | @patch("insights_client._main") 10 | def test_version_command(capsys): 11 | with patch("os.getuid", return_value=0): 12 | insights_client._main() 13 | captured = capsys.readouterr() 14 | output_sudo = captured.out 15 | with patch("os.getuid", return_value=1): 16 | insights_client._main() 17 | captured = capsys.readouterr() 18 | output_normal = captured.out 19 | 20 | assert output_sudo == output_normal 21 | 22 | 23 | @patch("insights_client.sys.argv", ["insights-client", "--help"]) 24 | @patch("insights_client._main") 25 | def test_help_command(capsys): 26 | with patch("os.getuid", return_value=0): 27 | insights_client._main() 28 | captured = capsys.readouterr() 29 | output_sudo = captured.out 30 | with patch("os.getuid", return_value=1): 31 | insights_client._main() 32 | captured = capsys.readouterr() 33 | output_normal = captured.out 34 | 35 | assert output_sudo == output_normal 36 | 37 | 38 | @patch("insights_client.sys.argv", ["insights-client"]) 39 | @patch("insights_client.InsightsConfig") 40 | def test_exit_when_run_phases_no_sudo(mock_config): 41 | # Mock config to return version=False so it doesn't exit early 42 | mock_config.return_value.load_all.return_value = {"version": False} 43 | 44 | with raises(SystemExit) as pytest_wrapped_e: 45 | with patch("os.getuid", return_value=1): 46 | insights_client._main() 47 | assert pytest_wrapped_e.type == SystemExit 48 | assert pytest_wrapped_e.value.args[0] == "Insights client must be run as root." 49 | -------------------------------------------------------------------------------- /docs/file-content-redaction.yaml.example: -------------------------------------------------------------------------------- 1 | --- 2 | # Omit lines from files and command output in the collection using parameters listed here. 3 | 4 | # Using YAML syntax, create lists of "patterns" and "keywords" to omit. 5 | 6 | # About the "patterns" section 7 | # ---------------------------- 8 | # Lines matching the parameters specified will be omitted 9 | # in the order that the parameters are given, e.g., 10 | 11 | # patterns: 12 | # - "example_string_1" 13 | # - "example_string_2" 14 | 15 | # Lines containing "example_string_1" or "example_string_2" will be 16 | # omitted from output. 17 | 18 | # Regular expression matching using egrep are available to use. 19 | # Wrap the list with "regex" like in the following example: 20 | 21 | # patterns: 22 | # regex: 23 | # - "abc.*" 24 | # - "localhost[[:digit:]]" 25 | 26 | # Lines matching these regular expressions will be omitted 27 | # from output. 28 | 29 | # NOTE: You cannot mix plain string matching and regular expression matching. 30 | 31 | # About the "keywords" section 32 | # ---------------------------- 33 | # Replace the specified keywords with generic identifiers by the soscleaner module. 34 | # NOTE: In order to use keyword replacement, the "obfuscate" option must be enabled in 35 | # the insights-client.conf file. 36 | 37 | # Refer to https://access.redhat.com/articles/4511681 for up-to-date configuration information. 38 | 39 | # An example configuration is provided below. 40 | 41 | patterns: 42 | regex: 43 | - "abc.*" 44 | 45 | keywords: 46 | - "1.1.1.1" 47 | - "keyword_example" 48 | 49 | # More examples 50 | # -------- 51 | # patterns: 52 | # regex: 53 | # # never send up lines with my host name suffix (example.com) 54 | # - "[a-zA-Z_.-]*example\.com" 55 | # 56 | # # never send up uncompressed IPv6 addresses 57 | # - "([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}" 58 | -------------------------------------------------------------------------------- /integration-tests/test_manpage.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import gzip 10 | import pytest 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "option", 15 | [ 16 | "--checkin", 17 | "--compliance", 18 | "--content-type", 19 | "--diagnosis", 20 | "--disable-schedule", 21 | "--display-name", 22 | "--enable-schedule", 23 | "--group", 24 | "--keep-archive", 25 | "--list-specs", 26 | "--net-debug", 27 | "--no-upload", 28 | "--offline", 29 | "--output-dir", 30 | "--output-file", 31 | "--payload", 32 | "--quiet", 33 | "--register", 34 | "--retry", 35 | "--show-results", 36 | "--silent", 37 | "--status", 38 | "--test-connection", 39 | "--unregister", 40 | "--validate", 41 | "--verbose", 42 | "--version", 43 | ], 44 | ) 45 | @pytest.mark.tier1 46 | def test_manpage(option): 47 | """ 48 | :id: bd8dbda3-930e-4081-b318-1e88b25e26ef 49 | :title: Test manual page entries for insights-client 50 | :parametrized: yes 51 | :description: 52 | This test verifies that the insights-client manual page includes 53 | all the specified options. 54 | :tags: Tier 1 55 | :steps: 56 | 1. Open the manual page 57 | 2. Verify that the specified options are present 58 | :expectedresults: 59 | 1. Manual page is opened successfully 60 | 2. All od the options are found in the manual page 61 | """ 62 | file = "/usr/share/man/man8/insights-client.8.gz" 63 | opened_file = gzip.open(file, "rt") 64 | content = opened_file.read() 65 | assert option in content, f"Option {option} is not present" 66 | -------------------------------------------------------------------------------- /systemtest/tests/integration/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | set -x 4 | 5 | # get to project root 6 | cd ../../../ 7 | 8 | # Check for bootc/image-mode deployments which should not run dnf 9 | if ! command -v bootc >/dev/null || bootc status | grep -q 'System is not deployed via bootc'; then 10 | # TEST_RPMS is set in jenkins jobs after parsing CI Messages in gating Jobs. 11 | # If TEST_RPMS is set then install the RPM builds for gating. 12 | if [[ -v TEST_RPMS ]]; then 13 | echo "Installing RPMs: ${TEST_RPMS}" 14 | dnf -y install --allowerasing ${TEST_RPMS} 15 | fi 16 | 17 | # Simulate the packit setup on downstream builds. 18 | # This is for ad-hoc and compose testing. 19 | rpm -q insights-client || ./systemtest/guest-setup.sh 20 | 21 | # In most cases these should already be installed by tmt, see systemtest/plans/main.fmf 22 | dnf --setopt install_weak_deps=False install -y \ 23 | podman git-core python3-pip python3-pytest logrotate bzip2 zip \ 24 | scap-security-guide openscap-scanner openscap bzip2-devel 25 | fi 26 | 27 | # If SETTINGS_URL is set (most likely in .testing-farm.yaml), download the settings 28 | # file from the provided URL. Back up any existing settings.toml before downloading. 29 | if [[ -v SETTINGS_URL ]]; then 30 | [ -f ./settings.toml ] && mv ./settings.toml ./settings.toml.bak 31 | if ! curl -f "$SETTINGS_URL" -o ./settings.toml; then 32 | echo "ERROR: Failed to download settings from: $SETTINGS_URL" >&2 33 | exit 1 34 | fi 35 | fi 36 | 37 | python3 -m venv venv 38 | # shellcheck disable=SC1091 39 | . venv/bin/activate 40 | 41 | pip install -r integration-tests/requirements.txt 42 | 43 | pytest --log-level debug --junit-xml=./junit.xml -v integration-tests ${PYTEST_FILTER:+-k "${PYTEST_FILTER}"} 44 | retval=$? 45 | 46 | if [ -d "$TMT_PLAN_DATA" ]; then 47 | cp ./junit.xml "$TMT_PLAN_DATA/junit.xml" 48 | cp -r ./artifacts "$TMT_PLAN_DATA/" 49 | fi 50 | 51 | exit $retval 52 | -------------------------------------------------------------------------------- /integration-tests/README.md: -------------------------------------------------------------------------------- 1 | # Running Betelgeuse 2 | 3 | ### Docs: 4 | https://betelgeuse.readthedocs.io/en/stable/ 5 | https://betelgeuse.readthedocs.io/en/stable/config.html 6 | 7 | ## Test-case command 8 | Command generates an XML file suited to be imported by the **Test Case XML Importer**. It reads the Python test suite source code and generated XML file with all the information necessary. 9 | 10 | The `test-case` requires: 11 | 12 | - The path to the Python test suite source code 13 | - The Polarion project ID 14 | - The output XML file path (will be overwritten if exists) 15 | 16 | 17 | There should also be a custom config file specified for pythonpath for Betelgeuse to correctly read all the custom fields in the docstrings. The file is saved in integration-tests/custom_betelgeuse_config.py 18 | 19 | Example: 20 | 21 | ```console 22 | $ PYTHONPATH=integration-tests/ \ 23 | betelgeuse --config-module \ 24 | custom_betelgeuse_config test-case \ 25 | integration-tests/ PROJECT ./test_case.xml 26 | ``` 27 | 28 | This will create a test_case.xml file in integration-tests/ 29 | 30 | ## Test-run command 31 | Command generates an XML file suited to be imported by the **Test Run XML Importer**. 32 | 33 | It takes: 34 | 35 | - A valid xUnit XML file 36 | - A Python test suite where test case IDs can be found 37 | 38 | And generates a resulting XML file with all the information necessary. 39 | 40 | It requires: 41 | 42 | - The path to the xUnit XML file 43 | - The path to the Python test suite source code 44 | - The Polarion user ID 45 | - The Polarion project ID 46 | - The output XML file path (will be overwritten if exists) 47 | 48 | It is also highly recommended to use `--response-property` as it will then be easier to monitor the importer messages 49 | 50 | Example: 51 | 52 | ```console 53 | $ PYTHONPATH=integration-tests/ \ 54 | betelgeuse test-run \ 55 | --response-property property_key=property_value \ 56 | junit.xml \ 57 | insights-client/integration-tests \ 58 | testuser \ 59 | betelgeuse-test-run.xml 60 | ``` 61 | 62 | NOTE: 63 | 64 | `--dry-run` can be used with `test-run` command when testing the functionality. -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('insights-client', 2 | version: '3.10.2', 3 | meson_version: '>=0.49' 4 | ) 5 | 6 | python = import('python') 7 | 8 | python_installation = python.find_installation(get_option('python')) 9 | 10 | python_exec = '/usr/bin/python3' 11 | 12 | systemd = dependency('systemd', version: '>=231') 13 | 14 | config_data = configuration_data({ 15 | 'bindir': get_option('prefix') / get_option('bindir'), 16 | 'BINDIR': get_option('prefix') / get_option('bindir'), 17 | 'DATADIR': get_option('prefix') / get_option('datadir'), 18 | 'DATAROOTDIR':get_option('prefix') / get_option('datadir'), 19 | 'DOCDIR': get_option('prefix') / get_option('datadir') / 'doc' / meson.project_name(), 20 | 'LIBEXECDIR': get_option('prefix') / get_option('libexecdir'), 21 | 'LOCALSTATEDIR': get_option('localstatedir'), 22 | 'PACKAGE': meson.project_name(), 23 | 'PACKAGE_VERSION': meson.project_version(), 24 | 'pkgsysconfdir': '/' / get_option('sysconfdir') / meson.project_name(), 25 | 'PREFIX': get_option('prefix'), 26 | 'PYTHON': python_exec, 27 | 'pythondir': python_installation.get_install_dir(), 28 | 'SBINDIR': get_option('prefix') / get_option('sbindir'), 29 | 'SYSCONFDIR': '/' / get_option('sysconfdir'), 30 | 'sysconfdir': '/' / get_option('sysconfdir'), 31 | 'top_srcdir': meson.source_root(), 32 | 'CORE_SELINUX_POLICY': get_option('core_selinux_policy'), 33 | }) 34 | 35 | subdir('data') 36 | subdir('docs') 37 | subdir('src') 38 | 39 | configuration = '**Configuration**\n' 40 | configuration += '\tpython\t\t\t: ' + get_option('python') + '\n' 41 | if get_option('checkin').enabled() 42 | configuration += '\tcheckin\t: ' + 'enabled' + '\n' 43 | else 44 | configuration += '\tcheckin\t: ' + 'disabled' + '\n' 45 | endif 46 | if get_option('auto_registration').enabled() 47 | configuration += '\tauto_registration\t: ' + 'enabled' + '\n' 48 | else 49 | configuration += '\tauto_registration\t: ' + 'disabled' + '\n' 50 | endif 51 | if get_option('core_selinux_policy') != '' 52 | configuration += '\tSELinux policy for insights-core\t: ' + get_option('core_selinux_policy') + '\n' 53 | else 54 | configuration += '\tSELinux policy for insights-core\t: ' + 'disabled' + '\n' 55 | endif 56 | message(configuration) 57 | -------------------------------------------------------------------------------- /docs/insights-client.conf.5: -------------------------------------------------------------------------------- 1 | .\" insights-client.conf - Red Hat Insights 2 | .TH "insights-client.conf" "5" "" "Red Hat Insights Configuration" "" 3 | .SH "NAME" 4 | insights\-client.conf \- Red Hat Insights client configuration 5 | 6 | .SH "DESCRIPTION" 7 | The \fBinsights\-client.conf\fP file contains configuration information for \fBinsights\-client\fP, the client for the Red Hat Insights service. This file is in an INI style format, option=value, default or example values are shown below. 8 | 9 | [insights-client]\& 10 | .IP "loglevel=DEBUG" 11 | Change log level, valid options DEBUG, INFO, WARNING, ERROR, CRITICAL. 12 | .IP "auto_config=True" 13 | Automatically attempt to configure connectivity to Red Hat Insights. If an RHSM or Satellite subscription is detected, CERT auth will be automatically selected. 14 | .IP "base_url=cert-api.access.redhat.com:443/r/insights" 15 | Base URL for API Interactions. 16 | .IP "proxy=http://user:pass@192.168.100.50:8080" 17 | URL for the proxy. 18 | .IP "auto_update=True" 19 | Automatically update the dynamic configuration. 20 | .IP "obfuscation_list=" 21 | Specify which data types should be obfuscated in the data collection. 22 | .IP "display_name=" 23 | Display name for this system. 24 | .IP "ansible_host=" 25 | Ansible hostname for this system. 26 | .IP "cmd_timeout=120" 27 | Timeout for commands run during collection, in seconds. 28 | .IP "http_timeout=120" 29 | Timeout for HTTP calls, in seconds. 30 | .IP "core_collect=True" 31 | Use Insights Core to perform the data collection when True. This option is provided for compatibility only and will be deprecated in a future release. 32 | .IP "redaction_file=/etc/insights-client/file-redaction.yaml" 33 | This file can be used to omit files or commands from the collection. 34 | .br 35 | See /usr/share/doc/insights-client/file-redaction.yaml.example or https://access.redhat.com/articles/4511681 for information on how to use it. 36 | .IP "content_redaction_file=/etc/insights-client/file-content-redaction.yaml" 37 | This file can be used to omit lines or keywords from files and commands in the collection. 38 | .br 39 | See /usr/share/doc/insights-client/file-content-redaction.yaml.example or https://access.redhat.com/articles/4511681 for information on how to use it. 40 | .IP "tags_file=/etc/insights-client/tags.yaml" 41 | Location of the tags file for this system. 42 | .SH "SEE ALSO" 43 | .BR insights-client (8) 44 | \& 45 | -------------------------------------------------------------------------------- /integration-tests/playbook_verifier/test_verifier.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import pathlib 10 | import subprocess 11 | import sys 12 | import pytest 13 | from pytest_client_tools.util import Version 14 | 15 | 16 | PLAYBOOK_DIRECTORY = pathlib.Path(__file__).parent.absolute() / "playbooks" 17 | 18 | 19 | @pytest.mark.parametrize( 20 | "filename", 21 | [ 22 | "insights_setup.yml", 23 | "compliance_openscap_setup.yml", 24 | "bugs.yml", 25 | ], 26 | ) 27 | @pytest.mark.tier1 28 | def test_official_playbook(insights_client, filename: str): 29 | """ 30 | :id: 3659e27f-3621-4591-b1c4-b5f0a277bb72 31 | :title: Test playbook verifier 32 | :parametrized: yes 33 | :description: 34 | This test verifies the official playbooks against the GPG key 35 | the application ships. 36 | :tags: Tier 1 37 | :steps: 38 | 1. Read playbook file content 39 | 2. Run insights-client verifier with playbook 40 | 3. Compare output to input 41 | :expectedresults: 42 | 1. File content is correctly read and loaded into memory 43 | 2. Subprocess executes successfully without errors 44 | 3. Verifier's output matches original playbook content 45 | """ 46 | if ( 47 | sys.version_info >= (3, 12) 48 | and insights_client.core_version < Version(3, 5, 2) 49 | and filename == "bugs.yml" 50 | ): 51 | pytest.xfail( 52 | f"Core {insights_client.core_version} suffers from CCT-1065, CCT-1101, CCT-1102." 53 | ) 54 | 55 | playbook_content: str = (PLAYBOOK_DIRECTORY / filename).read_text() 56 | 57 | result = subprocess.run( 58 | [ 59 | "insights-client", 60 | "-m", 61 | "insights.client.apps.ansible.playbook_verifier", 62 | "--quiet", 63 | "--payload", 64 | "noop", 65 | "--content-type", 66 | "noop", 67 | ], 68 | input=playbook_content, 69 | stdout=subprocess.PIPE, 70 | stderr=subprocess.PIPE, 71 | universal_newlines=True, 72 | check=True, 73 | ) 74 | 75 | # The playbooks may and may not include newline as EOF. 76 | assert result.stdout.strip() == playbook_content.strip() 77 | -------------------------------------------------------------------------------- /src/insights_client/tests/test_client.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | import insights_client 3 | import pytest 4 | 5 | 6 | # Test config load error 7 | @mock.patch("os.getuid", return_value=0) 8 | @mock.patch("insights_client.InsightsConfig") 9 | def test_load_config_error(mock_config, os_uid): 10 | mock_config.return_value.load_all.side_effect = ValueError("mocked error") 11 | 12 | with pytest.raises(SystemExit) as sys_exit: 13 | insights_client._main() 14 | assert sys_exit.value.code != 0 15 | 16 | 17 | # test keyboardinterrupt handler 18 | @mock.patch("os.getuid", return_value=0) 19 | @mock.patch("insights_client.InsightsConfig") 20 | def test_keyboard_interrupt(mock_config, os_uid): 21 | mock_config.return_value.load_all.side_effect = KeyboardInterrupt() 22 | 23 | with pytest.raises(SystemExit) as sys_exit: 24 | insights_client._main() 25 | assert sys_exit.value.code != 0 26 | 27 | 28 | # check run phase error 100 handler 29 | @mock.patch("insights_client.subprocess.Popen") 30 | def test_phase_error_100(mock_subprocess): 31 | mock_phase = {"name": "test_phase"} 32 | 33 | mock_subprocess.return_value.returncode = 100 34 | mock_subprocess.return_value.communicate.return_value = ("output", "error") 35 | 36 | with pytest.raises(SystemExit) as sys_exit: 37 | insights_client.run_phase(mock_phase) 38 | assert sys_exit.value.code == 0 39 | 40 | 41 | # Test version display 42 | @mock.patch("os.getuid", return_value=0) 43 | @mock.patch("insights_client.InsightsConfig") 44 | @mock.patch("insights_client.InsightsClient") 45 | @mock.patch("builtins.print") 46 | def test_version_display(mock_print, mock_client_class, mock_config, os_uid): 47 | mock_config.return_value.load_all.return_value = {"version": True} 48 | mock_client_instance = mock.MagicMock() 49 | mock_client_instance.version.return_value = "test_core_version" 50 | mock_client_class.return_value = mock_client_instance 51 | 52 | insights_client._main() 53 | 54 | # Check that version info was printed 55 | assert mock_print.call_count == 2 56 | mock_print.assert_any_call("Client: %s" % insights_client.InsightsConstants.version) 57 | mock_print.assert_any_call("Core: test_core_version") 58 | 59 | 60 | # Test non-root user 61 | @mock.patch("os.getuid", return_value=1000) 62 | @mock.patch("insights_client.InsightsConfig") 63 | def test_non_root_user(mock_config, os_uid): 64 | mock_config.return_value.load_all.return_value = {"version": False} 65 | 66 | with pytest.raises(SystemExit) as sys_exit: 67 | insights_client._main() 68 | assert "root" in str(sys_exit.value) 69 | -------------------------------------------------------------------------------- /integration-tests/playbook_verifier/playbooks/compliance_openscap_setup.yml: -------------------------------------------------------------------------------- 1 | # This playbook will install the required OpenSCAP packages to get your systems ready to be used 2 | # with Insights Compliance service. 3 | # 4 | # After running this playbook, perform the following steps manually: 5 | # 1. Create a Compliance policy in cloud.redhat.com and associate the required systems. 6 | # 2. Run `insights-client --compliance` on your system to report Compliance to cloud.redhat.com 7 | 8 | - name: Compliance OpenSCAP Setup 9 | hosts: localhost 10 | become: yes 11 | vars: 12 | insights_signature_exclude: /hosts,/vars/insights_signature 13 | insights_signature: !!binary | 14 | TFMwdExTMUNSVWRKVGlCUVIxQWdVMGxIVGtGVVZWSkZMUzB0TFMwS1ZtVnljMmx2YmpvZ1IyNTFV 15 | RWNnZGpFS0NtbFJTVlpCZDFWQldUbE1WREpqZG5jMU9FUXJhalZ3VGtGUmFIaDFRa0ZCWjA1cE9D 16 | OXJTVFZQYW5reWMySjJOR3BXWlVKdEx6WnZiR3RpTlV4SVVrRUtiRzVtY2tOSFp6Sm5NVEZoYUU1 17 | TEx6RmpkVzVPUWsxTmVUazFXV3RKTlU1cWRtNXpOVVpHVTNSTFowMHdWV0Z3YjFoaVZWQktSV1pT 18 | U2xWRUwzUXhaUXBJVURaUlEwTldTa1l5VFVWWldIcHZVVzFsTUVSc01FeERaMUlyU1hvd2NWaFJa 19 | R2t2WVRSWWNEVnBWRkZTVW5OT1l5OXBWa2cxZFZZemVscHNNbWN4Q2xsRE1sQlFOVzkyZDBwMGFU 20 | RlFjemRyYmtabmJIcERiVWxPVUdGdllYVlRTamhoUkU5a00wSkxZbXRTVW10cmVXNDJORlJzVWtn 21 | eE9HSXdSbGh0Y1VrS2VHaE5hMVZRVEhOd1UwVkxZWGhqTDA5UVNUUXJPVEkwSzI0NVRHRnlUWFZO 22 | THpncmVURlNTa1pTT1hNMk5GaFVaMmh6VEdkQ2JtNXlPRE41VTFKWmJ3cHVWelpDY0c0eVowdzNR 23 | MHBCVHpabmJsZFhZbUphZURVNVdYRTRkV2RpYldWTFJFUXJSalZqVmpSaFpFaENXV3BZUmpkbFRY 24 | bEpUbTlvUzA5WVNtUkRDbkpzU1VSUlJUUktSM2RGU1dJMmRHSnNVWEJMYVhZeFFWZzNNbFF4WjFS 25 | WFNVUXdPVEZEVEU1Rk0wZERVa2M0VnpWeVl6SkRTVkJRYW5KNlNUWldTVWdLTWpVclltbEVPVTFp 26 | YW5sSFVXNVZRMkV3SzBGVlZsVlVibmxYVUhWRFEyeHhlbWhHYXpsVGRYQjRlRXh2VDBkTkwxTnlj 27 | elp2UlZFMFptUk1RV3BKYVFwd1ptUkdRVkI0VEdJMmNGVldSRkJGTVZFeU5GWXZXamd6WkZwTll5 28 | dHhORVJzZWtKV09EaHFaMDlwVWxOc1lYSktTQzl2YUZkSGNVSjFZM1oxYjBZMkNqQTRNamxLYVhW 29 | ak5rOHlUa1ZOYTNjNU1YUjVaSEJPZG1kbU5HNVhhbmRPYmt0Q1ZsTkViR3gyTW01QlQwcHdaRVZx 30 | WVhoRFl5c3hVVkJTU1cxWk5WTUtPRTFQUjBjMlRuRmxWMDV1VGtwaVYzRTBXRVp3Y2xOSlJFZHJV 31 | MFpFVmxkQllURlNka3RyYVV4bVMxQTFlQzh4V2pkT2RITTVWbGhVU0VwQ09YbHVad3BoYUdJeFFs 32 | aHhaM3B2VFQwS1BVTkphbWtLTFMwdExTMUZUa1FnVUVkUUlGTkpSMDVCVkZWU1JTMHRMUzB0Q2c9 33 | PQ== 34 | tasks: 35 | - name: Install OpenSCAP packages for Compliance service 36 | ansible.builtin.package: 37 | name: 38 | - openscap 39 | - openscap-scanner 40 | - scap-security-guide 41 | state: latest 42 | when: 43 | - ansible_facts.ansible_cmdline.ostree is undefined -------------------------------------------------------------------------------- /integration-tests/playbook_verifier/playbooks/insights_setup.yml: -------------------------------------------------------------------------------- 1 | # Steps required to get your system ready to use the Insights Client: 2 | # 1. Yum install the insights-client 3 | # 2. Register the insights-client 4 | # 3. Modify file permissions 5 | - name: Insights Setup 6 | hosts: localhost 7 | become: yes 8 | vars: 9 | insights_signature_exclude: /hosts,/vars/insights_signature 10 | insights_signature: !!binary | 11 | TFMwdExTMUNSVWRKVGlCUVIxQWdVMGxIVGtGVVZWSkZMUzB0TFMwS1ZtVnljMmx2YmpvZ1IyNTFV 12 | RWNnZGpFS0NtbFJTVlpCZDFWQldUbG1jbHBqZG5jMU9FUXJhalZ3VGtGUmFrWjFaeTh2WTJJMWMw 13 | MVJkWEpPZG5oM05rMTRSVXRtUW1OU1MxbzBNVzVPUzBkRlFYQUtaVTh2VkZka00ySlVZVVZQYjBK 14 | bUx6RXpiVlUyUVRGWlJYQlJaM3BFTWtwMWFqVlNWWEJVT0RSdWJHazNMMWRMVDFwMmJURjRWR28w 15 | YTNWRWVXSlROQXB6TUZBd1pWRnBXR1V3ZUVwQmFVeDRRVkJ2Y25KRVlsbFNWR0Y2YVRNemJucENS 16 | bkIyVVVoMmEybG9hRFpVYkM5TWNITjZURXREZDNSSFZVWk5kazVxQ21wVFRGbFJXV0o1YlZWVFZ6 17 | RnBXR0ZyTW5SSU9FbEtaVTU2VFRWSmJFTm5TM1UzYVRsdVVUYzFORWswV0hFeFFUbEpOMGRJVG1J 18 | MU1WaFpkRTVyWm5vS1EwdzNRVk0zVHpCcGEwaGhZblJxSzJWdGIyWlhjVkJ4VVdwamRsUlZNa1Ey 19 | YldaMk1YRnNXRWhITjBSaU4zTmxWRk5hUW1oS1JuUkhOVk51Um5oMU1ncHJiMFZrVjJoV1dYZFNN 20 | MDlZWmtoWVRXcEdjVTEzWlROT2NuaHhaR2d6VWpRMlJ6Tk5WVk5zZGtSVlIwdzFUVzFqYWsweFNt 21 | TnZVSHBWWkdnMmRFeFdDbXhWZEhKVlJuVmxkbkJNYmxad1NFczRUemhWU0VOdGVHNTRNSGhQWTFW 22 | Wk5XZHlVMmhOYTAwMVlUVkhRa0ZvVVdOYU9FNXVZME5ETmpGTVNsRnFhM2NLUW1Vck1sVlhlSFZ6 23 | TW1sclFsUjJWeTlDYUVsRlRFTmtORUZSYmxGdFYxRXJkVmhTVUdJd2RHTkNVMVZDVlVOTVNERmpS 24 | VTgyWjFoWGNrUlRUbWxUTWdwd2VHdDNURnBKYlRSbGN6TXdVbWd3V0ZCeFZUVm1iazE0YTFaTlFX 25 | VmhOVlV4V0ZwdFkwUnpNeTlaUVRCNmFFbEJaRVJET0doelMxTkVURkJJUmtKdUNtVmxNRVJXUkdk 26 | SksycFVaUzlSUkhkMk0yMWpiRk0yWVUxMmFYVlJaa05uWjJkV1ZtOXFURTVJU2paNGVtNVZjMFl4 27 | WVRoVVZ6YzNkMlJyYkdkcWNrc0tPVlV5V0VabFJISm9iWFpxZGxWcmFXY3JlbEJLYTBwYWNXUmtk 28 | MHBVZEV4UWMwbGlOMFpFYjB0UFIwSXJiR3BSY1RkMFlURTBUR2R2YURZck1IaGxVd292VFZsb056 29 | WlRUWEp4UlQwS1BXZ3JOMEVLTFMwdExTMUZUa1FnVUVkUUlGTkpSMDVCVkZWU1JTMHRMUzB0Q2c9 30 | PQ== 31 | tasks: 32 | - name: Install latest insights-client, rhc, rhc-worker-playbook 33 | ansible.builtin.package: 34 | name: 35 | - insights-client 36 | - rhc 37 | - rhc-worker-playbook 38 | state: latest 39 | when: 40 | - ansible_facts.ansible_cmdline.ostree is undefined 41 | 42 | # With legacy_upload=True: Insights API says this machine is NOT registered. 43 | # With legacy_upload=False: This host is unregistered. 44 | - name: Get insights-client's status 45 | command: insights-client --status 46 | changed_when: false 47 | failed_when: false 48 | register: result 49 | - name: Register insights-client 50 | command: insights-client --register 51 | when: '"NOT registered" in result.stdout or "unregistered" in result.stdout' 52 | 53 | # insights_remove.yml stops and disables insights-client.timer, but leaves host registered. 54 | - name: Schedule insights-client runs 55 | command: insights-client --enable-schedule -------------------------------------------------------------------------------- /integration-tests/test_unregister.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import pytest 10 | from pytest_client_tools.util import Version, loop_until 11 | 12 | pytestmark = pytest.mark.usefixtures("register_subman") 13 | 14 | 15 | @pytest.mark.tier1 16 | def test_unregister(insights_client): 17 | """ 18 | :id: ecaeeddc-4c8b-4f17-8d69-1c81d2c7c744 19 | :title: Test unregister 20 | :description: 21 | This test verifies that the insights-client can be unregistered 22 | successfully after being registered. 23 | :tags: Tier 1 24 | :steps: 25 | 1. Register the insights-client if not registered 26 | 2. Run `insights-client --unregister` command 27 | 3. Confirm the client is unregistered 28 | :expectedresults: 29 | 1. The client registers successfully 30 | 2. On systems with Insights Core version >= 3.5.11, the command outputs 31 | "Successfully unregistered this host." Otherwise, the command 32 | outputs "Successfully unregistered from the Red Hat Insights Service" 33 | 3. Client unregistration is confirmed 34 | """ 35 | insights_client.register() 36 | assert loop_until(lambda: insights_client.is_registered) 37 | 38 | unregistration_status = insights_client.run("--unregister") 39 | if insights_client.core_version >= Version(3, 5, 11): 40 | assert "Successfully unregistered this host." in unregistration_status.stdout 41 | else: 42 | assert ( 43 | "Successfully unregistered from the Red Hat Insights Service" 44 | in unregistration_status.stdout 45 | ) 46 | assert loop_until(lambda: not insights_client.is_registered) 47 | 48 | 49 | @pytest.mark.tier1 50 | def test_unregister_twice(insights_client): 51 | """ 52 | :id: bfff1b33-5f19-42d2-a6ff-4598975873e5 53 | :title: Test unregister already unregistered system 54 | :description: 55 | This test verifies that attempting to unregister the insights client 56 | when it is already unregistered behaves as expected. It checks that 57 | the first unregistration succeeds and that subsequent unregistration 58 | attempts produce the appropriate error message and return code 59 | :tags: Tier 1 60 | :steps: 61 | 1. Register the insights-client 62 | 2. Unregister the client for the first time 63 | 3. Attempt to unregister the client a second time 64 | :expectedresults: 65 | 1. The client registers successfully 66 | 2. On systems with Insights Core version >= 3.5.11, the command outputs 67 | "Successfully unregistered this host." Otherwise, the command 68 | outputs "Successfully unregistered from the Red Hat Insights Service" 69 | 3. Command returns exit code 1 and outputs "This host is not registered, 70 | unregistration is not applicable." 71 | """ 72 | insights_client.register() 73 | assert loop_until(lambda: insights_client.is_registered) 74 | 75 | # unregister once 76 | unregistration_status = insights_client.run("--unregister") 77 | assert loop_until(lambda: not insights_client.is_registered) 78 | if insights_client.core_version >= Version(3, 5, 11): 79 | assert "Successfully unregistered this host." in unregistration_status.stdout 80 | else: 81 | assert ( 82 | "Successfully unregistered from the Red Hat Insights Service" 83 | in unregistration_status.stdout 84 | ) 85 | 86 | # unregister twice 87 | unregistration_status = insights_client.run("--unregister", check=False) 88 | assert loop_until(lambda: not insights_client.is_registered) 89 | assert unregistration_status.returncode == 1 90 | assert ( 91 | "This host is not registered, unregistration is not applicable." 92 | in unregistration_status.stdout 93 | ) 94 | -------------------------------------------------------------------------------- /integration-tests/test_checkin.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import contextlib 10 | import json 11 | import pytest 12 | from pytest_client_tools.util import Version, loop_until 13 | 14 | from constants import HOST_DETAILS 15 | 16 | pytestmark = pytest.mark.usefixtures("register_subman") 17 | 18 | 19 | @pytest.mark.tier2 20 | def test_ultralight_checkin(insights_client, test_config): 21 | """ 22 | :id: c662fd5e-0751-45e4-8477-6b0d27f735ac 23 | :title: Test lightweight check-in updates staleness timestamps 24 | :description: 25 | This test verifies that performing an ultra-light check-in with the 26 | insights-client updates the host's 'stale_timestamps' and 'updated' 27 | fields on the server 28 | :tags: Tier 2 29 | :steps: 30 | 1. Register the insights-client 31 | 2. Run '--check-results' and record the 'stale_timestamp' and 'updated' 32 | timestamps before check-in 33 | 3. Perform an ultra-light check-in ny running '--checkin' 34 | 4. Run '--check-results' and record the 'stale_timestamp' and 'updated' 35 | timestamps again 36 | 5. Verify that timestamps were updated successfully 37 | :expectedresults: 38 | 1. Insights-client is registered 39 | 2. The initial timestamps were retrieved and recorded 40 | 3. The check-in completes without any errors 41 | 4. The updated timestamps were retrieved and recorded 42 | 5. Both updated timestamps will be greater than before check-in 43 | """ 44 | insights_client.register() 45 | assert loop_until(lambda: insights_client.is_registered) 46 | 47 | # Performing check-results operation provides latest host data in host-details.json 48 | insights_client.run("--check-results") 49 | with open(HOST_DETAILS, "r") as data_file: 50 | data = json.load(data_file) 51 | stale_ts_before_checkin = data["results"][0]["stale_timestamp"] 52 | updated_ts_before_checkin = data["results"][0]["updated"] 53 | 54 | # Performing an ultra light check-in 55 | insights_client.run("--checkin") 56 | insights_client.run("--check-results") 57 | 58 | with open(HOST_DETAILS, "r") as data_file: 59 | data = json.load(data_file) 60 | stale_ts_after_checkin = data["results"][0]["stale_timestamp"] 61 | updated_ts_after_checkin = data["results"][0]["updated"] 62 | 63 | assert stale_ts_after_checkin > stale_ts_before_checkin 64 | assert updated_ts_after_checkin > updated_ts_before_checkin 65 | 66 | 67 | @pytest.mark.tier1 68 | def test_client_checkin_unregistered(insights_client): 69 | """ 70 | :id: 91331995-20c2-4d44-8abe-74a3e7d28309 71 | :title: Test check-in fails for unregistered client 72 | :description: 73 | This test verifies that attempting to perform check-in while unregistered 74 | fails with appropriate error message 75 | :tags: Tier 1 76 | :steps: 77 | 1. Unregister the insights-client if registered 78 | 2. Attempt to perform a check-in by running '--checkin' 79 | :expectedresults: 80 | 1. Insights-client is unregistered successfully 81 | 2. The check-in fails with return code 1 and message 'Error: failed 82 | to find host with matching machine-id' 83 | """ 84 | with contextlib.suppress(Exception): 85 | insights_client.unregister() 86 | assert loop_until(lambda: not insights_client.is_registered) 87 | 88 | checkin_result = insights_client.run("--checkin", check=False) 89 | if insights_client.core_version >= Version(3, 4, 25): 90 | assert checkin_result.returncode > 0 91 | assert "This host is not registered" in checkin_result.stdout 92 | else: 93 | assert checkin_result.returncode == 1 94 | assert "Error: failed to find host with matching machine-id" in checkin_result.stdout 95 | -------------------------------------------------------------------------------- /src/insights_client/tests/test_motd.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import tempfile 4 | import typing 5 | import unittest 6 | import unittest.mock 7 | 8 | import pytest 9 | 10 | import insights_client 11 | 12 | 13 | class MockFS: 14 | """Mock filesystem. 15 | 16 | Ensures that the files required for the MOTD manipulation are created 17 | in a temporary directory instead of working with actual system files. 18 | """ 19 | 20 | _dir: tempfile.TemporaryDirectory 21 | 22 | @property 23 | def _chroot(self) -> pathlib.Path: 24 | return pathlib.Path(self._dir.name) 25 | 26 | def touch(self, path: str): 27 | (self._chroot / path).open("w").close() 28 | 29 | def exists(self, path: str) -> bool: 30 | return (self._chroot / path).exists() 31 | 32 | def symlink_to(self, path: str, point_to: str): 33 | (self._chroot / path).symlink_to(point_to) 34 | 35 | def samefile(self, first: str, second: str) -> bool: 36 | return (self._chroot / first).samefile(self._chroot / second) 37 | 38 | def __init__(self): 39 | self._dir = tempfile.TemporaryDirectory(prefix="test-chroot-") 40 | 41 | paths: typing.Dict[str, str] = { 42 | "insights_client.MOTD_SRC": "etc/insights-client/insights-client.motd", 43 | "insights_client.MOTD_FILE": "etc/motd.d/insights-client", 44 | "insights_client.REGISTERED_FILE": "etc/insights-client/.registered", 45 | "insights_client.UNREGISTERED_FILE": "etc/insights-client/.unregistered", 46 | } 47 | 48 | self.patches: typing.List[unittest.mock.patch] = [] 49 | for target, path in paths.items(): 50 | mocked_path = self._chroot / path 51 | mocked_path.parent.mkdir(parents=True, exist_ok=True) 52 | patch = unittest.mock.patch(target, f"{mocked_path!s}") 53 | self.patches.append(patch) 54 | patch.start() 55 | 56 | self.touch("etc/insights-client/insights-client.motd") 57 | 58 | def __del__(self): 59 | for patch in self.patches: 60 | patch.stop() 61 | self._dir.cleanup() 62 | 63 | 64 | @pytest.fixture(scope="function") 65 | def mock_fs(): 66 | fs = MockFS() 67 | yield fs 68 | del fs 69 | 70 | 71 | def test_present(mock_fs): 72 | assert not mock_fs.exists("etc/motd.d/insights-client") 73 | 74 | # The file gets created/symlinked when .registered & .unregistered do not exist 75 | insights_client.update_motd_message() 76 | assert mock_fs.exists("etc/motd.d/insights-client") 77 | assert mock_fs.samefile( 78 | "etc/motd.d/insights-client", "etc/insights-client/insights-client.motd" 79 | ) 80 | 81 | 82 | @pytest.mark.parametrize( 83 | "filename", 84 | [".registered", ".unregistered"], 85 | ) 86 | def test_absent_on_dot(mock_fs, filename): 87 | # The MOTD gets created by default... 88 | insights_client.update_motd_message() 89 | assert mock_fs.exists("etc/motd.d/insights-client") 90 | 91 | # ...and gets removed when .registered/.unregistered exists. 92 | mock_fs.touch(f"etc/insights-client/{filename}") 93 | 94 | insights_client.update_motd_message() 95 | assert not mock_fs.exists("etc/motd.d/insights-client") 96 | 97 | # ...and it stays absent when run multiple times. 98 | insights_client.update_motd_message() 99 | assert not mock_fs.exists("etc/motd.d/insights-client") 100 | 101 | 102 | def test_ignored_on_dev_null(mock_fs): 103 | # When the /etc/motd.d/insights-client is a symbolic link to /dev/null... 104 | mock_fs.symlink_to("etc/motd.d/insights-client", os.devnull) 105 | 106 | # ...it should not be overwritten... 107 | insights_client.update_motd_message() 108 | assert mock_fs.samefile("etc/motd.d/insights-client", os.devnull) 109 | 110 | # ...whether the MOTD file should be present or not. 111 | mock_fs.touch("etc/insights-client/.registered") 112 | insights_client.update_motd_message() 113 | assert mock_fs.samefile("etc/motd.d/insights-client", os.devnull) 114 | -------------------------------------------------------------------------------- /docs/insights-client.8: -------------------------------------------------------------------------------- 1 | .\" insights-client - Red Hat Insights 2 | .TH "insights-client" "8" "" "Red Hat Insights" "" 3 | .SH "NAME" 4 | insights\-client \- Red Hat Insights Client tool 5 | 6 | .SH "SYNOPSIS" 7 | .B insights-client [options] 8 | .SH "DESCRIPTION" 9 | \fBinsights\-client\fP is designed to help customers proactively resolve issues affecting business operations in Red Hat Enterprise Linux and Red Hat Cloud Infrastructure environments. View alerts or learn more at https://console.redhat.com/insights/. Due to the dynamic nature of this application, some options may become available that are not described in the manual. Please refer to the output of --help for the most up-to-date options, or view https://access.redhat.com/articles/4099591 for more details. 10 | 11 | 12 | .SH "OPTIONS" 13 | .IP "-h, --help" 14 | Show this help message and exit. 15 | .IP "--register" 16 | Register host to the Red Hat Insights service. 17 | .IP "--unregister" 18 | Unregister host. 19 | .IP "--display-name=DISPLAYNAME" 20 | Display name to use for this host. May be used during registration. 21 | .IP "--group=GROUP" 22 | Add a tag name group with the specified value to this host. The tag will be saved to the tags file. 23 | .IP "--retry=RETRIES" 24 | Number of times to retry uploading. 180 seconds between tries. 25 | .IP "--validate" 26 | Validate the contents of the denylist configuration. 27 | .IP "--quiet" 28 | Only display error messages to stdout. 29 | .IP "--silent" 30 | Display no messages to stdout. 31 | .IP "--enable-schedule" 32 | Enable automatic scheduling. 33 | .IP "--disable-schedule" 34 | Disable automatic scheduling. 35 | .IP "--compressor" 36 | Specifies the compression algorithm to use. Choices are gz, bz2, xz, and none. Defaults to gz. 37 | .IP "--offline" 38 | Collect locally only, do not connect to Insights and do not upload. 39 | .IP "--payload=FILE" 40 | Skip collection and upload a specified archive. Requires --content-type. 41 | .IP "--content-type=TYPE" 42 | Content type of the archive specified with --payload. 43 | .IP "--diagnosis" 44 | Retrieve a diagnosis for this host. 45 | .IP "--compliance" 46 | Scan the system with OpenSCAP and upload the report. 47 | .IP "--check-results" 48 | Retrieve analysis results from the Red Hat Insights service. 49 | .IP "--show-results" 50 | Display analysis results from the Red Hat Insights service. 51 | .IP "--output-dir=DIR" 52 | Write the collection to a specified directory. Does not perform an upload. 53 | .IP "--output-file=FILE" 54 | Write the collection to a specified file. Does not perform an upload. 55 | .IP "--checkin" 56 | Perform a lightweight hourly check-in. 57 | .IP "--list-specs" 58 | Show insights-client collection specs. 59 | .IP "--compliance" 60 | Run a Compliance scan and upload. 61 | .IP "--module MODULE, -m MODULE" 62 | Run a module from within the insights.client package. Analogous to "python -m MODULE". Use only as directed. 63 | 64 | .SH "DEBUG OPTIONS" 65 | .IP "--version" 66 | Display version. 67 | .IP "--test-connection" 68 | Test connectivity to Red Hat. 69 | .IP "--verbose" 70 | DEBUG output to stdout 71 | .IP "--no-upload" 72 | Do not upload the archive. 73 | .IP "--keep-archive" 74 | Do not delete archive after upload. 75 | .IP "--support" 76 | Create a support logfile for Red Hat Insights. 77 | .IP "--status" 78 | Check this host's registration status by checking the presence of \fB/etc/insights-client/.registered\fP file. 79 | .IP "--net-debug" 80 | Log network activity to console. 81 | 82 | .SH "MOTD" 83 | A message will be displayed on login about \fBinsights\-client\fP if installed but has not yet been used at least once. 84 | 85 | To change this message, edit the \fB/etc/insights-client/insights-client.motd\fP file. 86 | 87 | To prevent this message to be shown on login, create the \fB/etc/motd.d/insights-client\fP symlink pointing to \fB/dev/null\fP. 88 | 89 | .SH "LOGGING" 90 | \fBinsights\-client\fP utilizes the 'logrotate' tool to rotate log files. To change log rotation options, edit the \fB/etc/logrotate.d/insights-client\fP file. Please refer to the 'logrotate' tool documentation to see all available configuration options. 91 | 92 | .SH "SEE ALSO" 93 | .BR insights-client.conf (5) 94 | 95 | \& 96 | -------------------------------------------------------------------------------- /integration-tests/test_compliance.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import os 10 | import pytest 11 | from pytest_client_tools.util import loop_until 12 | 13 | 14 | def _system_is_rhel() -> bool: 15 | """ 16 | Check if the OS is RHEL based on /etc/os-release file. 17 | """ 18 | if not os.path.isfile("/etc/os-release"): 19 | return False 20 | with open("/etc/os-release", encoding="utf-8") as f: 21 | for line in f: 22 | if line.startswith("ID="): 23 | os_id = line.strip().split("=")[-1].strip('"') 24 | return os_id == "rhel" 25 | return False 26 | 27 | 28 | pytestmark = [ 29 | pytest.mark.usefixtures("register_subman"), 30 | pytest.mark.skipif( 31 | not _system_is_rhel(), 32 | reason="Skipping tests because OS ID is not RHEL", 33 | allow_module_level=True, 34 | ), 35 | ] 36 | 37 | 38 | @pytest.mark.tier1 39 | def test_compliance_option(insights_client): 40 | """ 41 | :id: caa8b3e1-9347-494c-a1f5-1fa670136834 42 | :title: Test compliance option 43 | :reference: https://issues.redhat.com/browse/CCT-1229 44 | :description: 45 | This test verifies that running the --compliance will not result in a failure 46 | :tags: Tier 1 47 | :steps: 48 | 1. Run insights-client with --compliance option 49 | 2. Register insights-client 50 | 3. Run insights-client with --compliance option 51 | :expectedresults: 52 | 1. Command will fail with instructions for user to register 53 | 2. System is successfully registered 54 | 3. The output of the command either informs user that system is not associated 55 | with any policies or the report will be successfully uploaded 56 | """ 57 | compliance_before_registration = insights_client.run("--compliance", check=False) 58 | assert compliance_before_registration.returncode == 1 59 | assert ( 60 | "This host is unregistered. Use --register to register this host" 61 | in compliance_before_registration.stdout 62 | or "This host has not been registered. Use --register to register this host" 63 | in compliance_before_registration.stdout 64 | ) 65 | 66 | insights_client.register() 67 | assert loop_until(lambda: insights_client.is_registered) 68 | 69 | compliance_after_registration = insights_client.run("--compliance", check=False) 70 | if compliance_after_registration.returncode == 1: 71 | assert "System is not associated with any policies." in compliance_after_registration.stdout 72 | else: 73 | assert "Successfully uploaded report" in compliance_after_registration.stdout 74 | 75 | 76 | @pytest.mark.tier1 77 | def test_compliance_policies_option(insights_client): 78 | """ 79 | :id: ad3a2073-3a2e-485e-bc7b-fede2111a06a 80 | :title: Test compliance-policies option 81 | :reference: https://issues.redhat.com/browse/CCT-1229 82 | :description: 83 | This test verifies that running the --compliance-policies 84 | will not result in a failure 85 | :tags: Tier 1 86 | :steps: 87 | 1. Register insights-client 88 | 2. Run insights-client with --compliance-policies option 89 | :expectedresults: 90 | 1. System is successfully registered 91 | 2. The output of the command either informs the user that system is not 92 | assignable to any policy or ID of available policy is found and 93 | displayed 94 | """ 95 | insights_client.register() 96 | assert loop_until(lambda: insights_client.is_registered) 97 | 98 | compliance_policies = insights_client.run("--compliance-policies", check=False) 99 | if "An error has occurred while communicating with the API" in compliance_policies.stdout: 100 | pytest.skip(reason="Error communicating with API") 101 | if compliance_policies.returncode == 1: 102 | assert "System is not assignable to any policy." in compliance_policies.stdout 103 | assert "Create supported policy using the Compliance web UI." in compliance_policies.stdout 104 | else: 105 | assert "ID" in compliance_policies.stdout 106 | -------------------------------------------------------------------------------- /integration-tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | import subprocess 5 | import tempfile 6 | import logging 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @pytest.fixture(scope="session") 12 | def install_katello_rpm(test_config): 13 | if "satellite" in test_config.environment: 14 | # install katello rpm before register system against Satellite 15 | satellite_hostname = test_config.get("candlepin", "host") 16 | cmd = [ 17 | "rpm", 18 | "-Uvh", 19 | "http://%s/pub/katello-ca-consumer-latest.noarch.rpm" % satellite_hostname, 20 | ] 21 | subprocess.check_call(cmd) 22 | yield 23 | if "satellite" in test_config.environment: 24 | cmd = "rpm -qa 'katello-ca-consumer*' | xargs rpm -e" 25 | subprocess.check_call(cmd, shell=True) 26 | 27 | 28 | @pytest.fixture(scope="session") 29 | def register_subman(external_candlepin, install_katello_rpm, subman_session, test_config): 30 | if "satellite" in test_config.environment: 31 | subman_session.register( 32 | activationkey=test_config.get("candlepin", "activation_keys"), 33 | org=test_config.get("candlepin", "org"), 34 | ) 35 | else: 36 | subman_session.register( 37 | username=test_config.get("candlepin", "username"), 38 | password=test_config.get("candlepin", "password"), 39 | ) 40 | yield subman_session 41 | 42 | 43 | @pytest.fixture(scope="session", autouse=True) 44 | def insights_core_workaround(): 45 | """ 46 | Workaround for https://issues.redhat.com/browse/RHINENG-21918 47 | When insights-client is started by a user (running shell in unconfined mode) 48 | and the insights-client is run in insights_client_t SELinux context type 49 | (effectively switching the next python process that executes the insights_core 50 | code to insights_core_t SELinux context type), printing to PTY or pipe 51 | (through inherited file descriptor) that belongs to the user is not allowed. 52 | This Workaround fixture allows these actions in the active SELinux policy. 53 | """ 54 | policy = """module core_output 1.0; 55 | 56 | require { 57 | type insights_core_t; 58 | type unconfined_t; 59 | type user_devpts_t; 60 | class fifo_file write; 61 | class chr_file { ioctl read write }; 62 | } 63 | 64 | #============= insights_core_t ============== 65 | allow insights_core_t unconfined_t:fifo_file write; 66 | allow insights_core_t user_devpts_t:chr_file { ioctl read write }; 67 | """ 68 | origdir = os.getcwd() 69 | with tempfile.TemporaryDirectory() as tempdirname: 70 | try: 71 | os.chdir(tempdirname) 72 | with open("core_output.te", "wt") as selinux_file: # codespell:ignore te 73 | selinux_file.write(policy) 74 | subprocess.run( 75 | [ 76 | "checkmodule", 77 | "-M", 78 | "-m", 79 | "-o", 80 | "core_output.mod", 81 | "core_output.te", # codespell:ignore te 82 | ], 83 | check=True, 84 | ) 85 | subprocess.run( 86 | ["semodule_package", "-o", "core_output.pp", "-m", "core_output.mod"], 87 | check=True, 88 | ) 89 | subprocess.run( 90 | ["semodule", "-i", "core_output.pp"], 91 | check=True, 92 | ) 93 | finally: 94 | os.chdir(origdir) 95 | yield 96 | subprocess.run(["semodule", "-r", "core_output"], check=True) 97 | 98 | 99 | def check_is_bootc_system(): 100 | """ 101 | Check if the system is a bootc enabled system. 102 | This function duplicates the logic from pytest-client-tools' is_bootc_system fixture 103 | so it can be used in pytest.skipif decorators (which run at collection time). 104 | """ 105 | try: 106 | bootc_status = subprocess.run( 107 | ["bootc", "status", "--format", "humanreadable"], 108 | capture_output=True, 109 | text=True, 110 | ) 111 | return (bootc_status.returncode == 0) and ( 112 | not bootc_status.stdout.strip().startswith("System is not deployed via bootc") 113 | ) 114 | except FileNotFoundError: 115 | return False 116 | -------------------------------------------------------------------------------- /data/cert-api.access.redhat.com.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIHZDCCBUygAwIBAgIJAOb+QiglyeZeMA0GCSqGSIb3DQEBBQUAMIGwMQswCQYD 3 | VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcMB1JhbGVp 4 | Z2gxFjAUBgNVBAoMDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0 5 | d29yazEeMBwGA1UEAwwVRW50aXRsZW1lbnQgTWFzdGVyIENBMSQwIgYJKoZIhvcN 6 | AQkBFhVjYS1zdXBwb3J0QHJlZGhhdC5jb20wHhcNMTAwMzE3MTkwMDQ0WhcNMzAw 7 | MzEyMTkwMDQ0WjCBsDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s 8 | aW5hMRAwDgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMuMRgw 9 | FgYDVQQLDA9SZWQgSGF0IE5ldHdvcmsxHjAcBgNVBAMMFUVudGl0bGVtZW50IE1h 10 | c3RlciBDQTEkMCIGCSqGSIb3DQEJARYVY2Etc3VwcG9ydEByZWRoYXQuY29tMIIC 11 | IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2Z+mW7OYcBcGxWS+RSKG2GJ2 12 | csMXiGGfEp36vKVsIvypmNS60SkicKENMYREalbdSjrgfXxPJygZWsVWJ5lHPfBV 13 | o3WkFrFHTIXd/R6LxnaHD1m8Cx3GwEeuSlE/ASjc1ePtMnsHH7xqZ9wdl85b1C8O 14 | scgO7fwuM192kvv/veI/BogIqUQugtG6szXpV8dp4ml029LXFoNIy2lfFoa2wKYw 15 | MiUHwtYgAz7TDY63e8qGhd5PoqTv9XKQogo2ze9sF9y/npZjliNy5qf6bFE+24oW 16 | E8pGsp3zqz8h5mvw4v+tfIx5uj7dwjDteFrrWD1tcT7UmNrBDWXjKMG81zchq3h4 17 | etgF0iwMHEuYuixiJWNzKrLNVQbDmcLGNOvyJfq60tM8AUAd72OUQzivBegnWMit 18 | CLcT5viCT1AIkYXt7l5zc/duQWLeAAR2FmpZFylSukknzzeiZpPclRziYTboDYHq 19 | revM97eER1xsfoSYp4mJkBHfdlqMnf3CWPcNgru8NbEPeUGMI6+C0YvknPlqDDtU 20 | ojfl4qNdf6nWL+YNXpR1YGKgWGWgTU6uaG8Sc6qGfAoLHh6oGwbuz102j84OgjAJ 21 | DGv/S86svmZWSqZ5UoJOIEqFYrONcOSgztZ5tU+gP4fwRIkTRbTEWSgudVREOXhs 22 | bfN1YGP7HYvS0OiBKZUCAwEAAaOCAX0wggF5MB0GA1UdDgQWBBSIS6ZFxEbsj9bP 23 | pvYazyY8kMx/FzCB5QYDVR0jBIHdMIHagBSIS6ZFxEbsj9bPpvYazyY8kMx/F6GB 24 | tqSBszCBsDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMRAw 25 | DgYDVQQHDAdSYWxlaWdoMRYwFAYDVQQKDA1SZWQgSGF0LCBJbmMuMRgwFgYDVQQL 26 | DA9SZWQgSGF0IE5ldHdvcmsxHjAcBgNVBAMMFUVudGl0bGVtZW50IE1hc3RlciBD 27 | QTEkMCIGCSqGSIb3DQEJARYVY2Etc3VwcG9ydEByZWRoYXQuY29tggkA5v5CKCXJ 28 | 5l4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEG 29 | MCAGA1UdEQQZMBeBFWNhLXN1cHBvcnRAcmVkaGF0LmNvbTAgBgNVHRIEGTAXgRVj 30 | YS1zdXBwb3J0QHJlZGhhdC5jb20wDQYJKoZIhvcNAQEFBQADggIBAJ1hEdNBDTRr 31 | 6kI6W6stoogSUwjuiWPDY8DptwGhdpyIfbCoxvBR7F52DlwyXOpCunogfKMRklnE 32 | gH1Wt66RYkgNuJcenKHAhR5xgSLoPCOVF9rDjMunyyBuxjIbctM21R7BswVpsEIE 33 | OpV5nlJ6wkHsrn0/E+Zk5UJdCzM+Fp4hqHtEn/c97nvRspQcpWeDg6oUvaJSZTGM 34 | 8yFpzR90X8ZO4rOgpoERukvYutUfJUzZuDyS3LLc6ysamemH93rZXr52zc4B+C9G 35 | Em8zemDgIPaH42ce3C3TdVysiq/yk+ir7pxW8toeavFv75l1UojFSjND+Q2AlNQn 36 | pYkmRznbD5TZ3yDuPFQG2xYKnMPACepGgKZPyErtOIljQKCdgcvb9EqNdZaJFz1+ 37 | /iWKYBL077Y0CKwb+HGIDeYdzrYxbEd95YuVU0aStnf2Yii2tLcpQtK9cC2+DXjL 38 | Yf3kQs4xzH4ZejhG9wzv8PGXOS8wHYnfVNA3+fclDEQ1mEBKWHHmenGI6QKZUP8f 39 | g0SQ3PNRnSZu8R+rhABOEuVFIBRlaYijg2Pxe0NgL9FlHsNyRfo6EUrB2QFRKACW 40 | 3Mo6pZyDjQt7O8J7l9B9IIURoJ1niwygf7VSJTMl2w3fFleNJlZTGgdXw0V+5g+9 41 | Kg6Ay0rrsi4nw1JHue2GvdjdfVOaWSWC 42 | -----END CERTIFICATE----- 43 | -----BEGIN CERTIFICATE----- 44 | MIIFfTCCA2WgAwIBAgIJAJGKz8qFAAADMA0GCSqGSIb3DQEBBQUAMIGwMQswCQYD 45 | VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcMB1JhbGVp 46 | Z2gxFjAUBgNVBAoMDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsMD1JlZCBIYXQgTmV0 47 | d29yazEeMBwGA1UEAwwVRW50aXRsZW1lbnQgTWFzdGVyIENBMSQwIgYJKoZIhvcN 48 | AQkBFhVjYS1zdXBwb3J0QHJlZGhhdC5jb20wHhcNMTUwNTA1MTMwMzQ4WhcNMjUw 49 | NTAyMTMwMzQ4WjCBiTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5PUlRIIENBUk9M 50 | SU5BMRAwDgYDVQQHEwdSYWxlaWdoMRAwDgYDVQQKEwdSZWQgSGF0MRgwFgYDVQQL 51 | Ew9SZWQgSGF0IE5ldHdvcmsxIzAhBgNVBAMTGmNlcnQtYXBpLmFjY2Vzcy5yZWRo 52 | YXQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9hTNMtZMa7Kg 53 | Jlux6pnuUinP0Rv0aiiPFr7qNHFore4loGrPlpzUvQbUByy3xm7lhf4R4qbINCls 54 | veWg6HDidvQr174RXb5YLMXuBrYAiPWQTrRRLNuvXFHKzREghRWTv48IXTIDEo0G 55 | fZJUO+myY2RfwqugZKic5dR6ZakHSSpQO70O6H5R0eHlKa13k4eEpG2fVY/xqFto 56 | WkfZyEmSacZpqxp7gIjZqreLc4MFwpiVjGFrK3Jk+Px1Z6J94LTLx2SxrYzWIeUs 57 | 5j+lceQOvpV4/pkClnRCW1pkCKTccjFKQkpNPGwdIusRXUGl9IYc20Fa/7g9iUQc 58 | 5fXu9EAzfwIDAQABo4G+MIG7MAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZA 59 | MAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDATA5BglghkgBhvhCAQ0E 60 | LBYqTWFuYWdlZCBieSBSZWQgSGF0IChjYS1zdXBwb3J0QHJlZGhhdC5jb20pMB0G 61 | A1UdDgQWBBRfgCjd8aXf0U4VX8DKTVIn+paGBzAfBgNVHSMEGDAWgBSIS6ZFxEbs 62 | j9bPpvYazyY8kMx/FzANBgkqhkiG9w0BAQUFAAOCAgEAlC+r6UEEp5BUkI0Rj2T+ 63 | 1PH7oUCaGQeQoyVbGddz/WUcBk/lMMtyCEoxU+3tTwNWmCWWjYXtjoL9MlSAC/q+ 64 | NZfBi1iq0uuSus9JI/Uu8aRhoxTK56qGRed/JNixOHEmFn891cahIPpF0exWwtYD 65 | ThwXo7Z6PI7t8EMKdSrGTOowp58yho8xYFL/Z7JmjL55Pf85GIrdiniNZd4i178J 66 | 07R9zsiLvdXq9mT33iJwkm+uhO+FA9d8OE3ji21pBbGUAQSWOdkemvUCsy8zANW9 67 | fT+dBrMr5Buk7eaBBJ2PxECNiWLCRQJWmyff1O5zMT0daS2lBdEGUNhBZ0hnX13Q 68 | kabUp0bxRrNRq+WkomP7onZhfZS6SjKm0UmwoV6o3V1ED6y7muQNRmgDpA5PcbvO 69 | gl7OexNL4zcpyfMdAmTYf5yTRSvB42Yg5hVfuzPEnOIqupwES3mWkEHRlqbMUkHw 70 | qIQAxIwQqZd5PdPpElQ/6j/ZT9DwW/I6zgndX2rsS0oGYcwFTkSj0/rKKkC13hk7 71 | LchXMZu5ckdustM79U6OZIBairrJaL2OpR08un2nwIjgEGqhVFYc44UK1VpkE8mr 72 | qvqJS6OHVlTlKcEDnhVkPS3i5qjuS/PtSq0CwH8bzYKFJayLDY/z36Zv6PdttzmU 73 | Yb1NSDcJejHJ80pMINutyYQ= 74 | -----END CERTIFICATE----- 75 | -------------------------------------------------------------------------------- /integration-tests/test_status.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | from constants import REGISTERED_FILE, UNREGISTERED_FILE, MACHINE_ID_FILE 10 | import contextlib 11 | import os 12 | import pytest 13 | from pytest_client_tools.util import Version, loop_until 14 | 15 | pytestmark = pytest.mark.usefixtures("register_subman") 16 | 17 | 18 | @pytest.mark.tier1 19 | def test_status_registered(external_candlepin, insights_client): 20 | """ 21 | :id: 624b01fc-e841-4c26-afd8-bb28eaf7fe75 22 | :title: Test insights-client --status when registered 23 | :description: 24 | This test verifies that when the insights client is registered, the 25 | `insights-client --status` command outputs the correct registration status 26 | :tags: Tier 1 27 | :steps: 28 | 1. Register the insights-client 29 | 2. Run `insights-client --status` command 30 | :expectedresults: 31 | 1. The client registers successfully 32 | 2. Wait time completes without issues 33 | 3. If 'legacy_upload' is True, output contains "Insights API confirms 34 | registration." If 'legacy_upload' is False, output is "This host 35 | is registered." 36 | """ 37 | insights_client.register() 38 | assert loop_until(lambda: insights_client.is_registered) 39 | 40 | registration_status = insights_client.run("--status", selinux_context=None) 41 | if insights_client.config.legacy_upload: 42 | assert "Insights API confirms registration." in registration_status.stdout 43 | else: 44 | assert "This host is registered.\n" == registration_status.stdout 45 | 46 | 47 | @pytest.mark.tier1 48 | def test_status_registered_only_locally(external_candlepin, insights_client, external_inventory): 49 | """ 50 | :id: 2ca3be87-8322-47b8-b451-9ea7fa3dbeef 51 | :title: Test insights-client --status when registered only locally 52 | :description: 53 | This test verifies that when the insights client is registered only 54 | locally, the `insights-client --status` command outputs the correct 55 | registration status 56 | :tags: Tier 1 57 | :steps: 58 | 1. Set the legacy_upload to False 59 | 2. Register the insights-client 60 | 3. Delete the host from the Inventory 61 | 4. Run `insights-client --status` command 62 | :expectedresults: 63 | 1. The client registers successfully 64 | 2. Wait time completes without issues 65 | 3. The host is deleted from the Inventory 66 | 4. On systems with version 3.5.7 and higher, output is "This host is 67 | registered.", the registered file exists, the unregistered file 68 | does not exist, and the machine ID file exists. Otherwise, output 69 | is "This host is unregistered." 70 | """ 71 | insights_client.config.legacy_upload = False 72 | insights_client.register() 73 | assert loop_until(lambda: insights_client.is_registered) 74 | machine_id = insights_client.uuid 75 | external_inventory.delete(path=f"hosts/{external_inventory.this_system()['id']}") 76 | response = external_inventory.get(path=f"hosts?insights_id={machine_id}") 77 | assert response.json()["total"] == 0 78 | 79 | registration_status = insights_client.run("--status", check=False, selinux_context=None) 80 | if insights_client.core_version >= Version(3, 5, 7): 81 | assert "This host is registered.\n" == registration_status.stdout 82 | assert os.path.exists(REGISTERED_FILE) 83 | assert not os.path.exists(UNREGISTERED_FILE) 84 | assert os.path.exists(MACHINE_ID_FILE) 85 | else: 86 | assert "This host is unregistered.\n" == registration_status.stdout 87 | 88 | 89 | @pytest.mark.tier1 90 | def test_status_unregistered(external_candlepin, insights_client): 91 | """ 92 | :id: aa37831a-a581-44db-a7c9-de8161767c7e 93 | :title: Test insights-client --status when unregistered 94 | :description: 95 | This test verifies that when the insights client is unregistered, the 96 | `insights-client --status` command outputs the correct unregistration 97 | status 98 | :tags: Tier 1 99 | :steps: 100 | 1. Unregister the insights client to ensure it's unregistered 101 | 2. Run `insights-client --status` command 102 | :expectedresults: 103 | 1. The client unregisters successfully 104 | 2. If 'legacy_upload' is True, return code is 1 and output contains 105 | "Insights API says this machine is NOT registered." 106 | If 'legacy_upload' is False, on systems with version 3.5.3 and 107 | higher, return code is 1 and output contains "This host is 108 | unregistered.". Otherwise return code is 0 and output contains 109 | "This host is unregistered." 110 | """ 111 | # running unregistration to ensure system is unregistered 112 | with contextlib.suppress(Exception): 113 | insights_client.unregister() 114 | assert loop_until(lambda: not insights_client.is_registered) 115 | 116 | registration_status = insights_client.run("--status", check=False, selinux_context=None) 117 | if insights_client.config.legacy_upload: 118 | assert registration_status.returncode == 1 119 | assert "Insights API says this machine is NOT registered." in registration_status.stdout 120 | else: 121 | if insights_client.core_version >= Version(3, 5, 3): 122 | assert registration_status.returncode == 1 123 | else: 124 | assert registration_status.returncode == 0 125 | assert "This host is unregistered.\n" == registration_status.stdout 126 | -------------------------------------------------------------------------------- /integration-tests/test_client_systemd.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import pytest 10 | import subprocess 11 | from pathlib import Path 12 | import logging 13 | from pytest_client_tools.util import loop_until 14 | from constants import INSIGHTS_CLIENT_LOG_FILE 15 | 16 | logger = logging.getLogger(__name__) 17 | pytestmark = pytest.mark.usefixtures("register_subman") 18 | 19 | 20 | def _run_systemctl_command(cmd, description="systemctl operation"): 21 | """Helper function to run systemctl commands with proper error handling.""" 22 | logger.debug(f"Running systemctl command: {' '.join(cmd)}") 23 | try: 24 | result = subprocess.run(cmd, capture_output=True, text=True, check=True) 25 | return result 26 | except subprocess.CalledProcessError as e: 27 | logger.error(f"Failed {description}: {e.stderr}") 28 | raise RuntimeError(f"Failed {description}: {e.stderr}") from e 29 | 30 | 31 | def _wait_for_timer_execution_by_logfile(interval_minutes=3, additional_wait_sec=30): 32 | """Wait for the systemd timer to execute by monitoring log file creation.""" 33 | log_file = Path(INSIGHTS_CLIENT_LOG_FILE) 34 | 35 | # Remove existing log file to ensure we detect new execution 36 | log_file.unlink(missing_ok=True) 37 | logger.debug("Removed existing insights-client log file") 38 | 39 | # Wait for timer interval plus additional buffer time 40 | wait_time = (interval_minutes * 60) + additional_wait_sec 41 | logger.debug(f"Waiting {wait_time} seconds for timer execution...") 42 | 43 | # Use loop_until to check for log file recreation 44 | return loop_until( 45 | lambda: log_file.exists(), 46 | poll_sec=10, 47 | timeout_sec=wait_time, 48 | ) 49 | 50 | 51 | @pytest.mark.tier1 52 | def test_data_upload_systemd_timer(insights_client): 53 | """ 54 | :id: 6bbac679-cb37-47b1-9163-497f1a1758dd 55 | :title: Verify insights-client upload via systemd timer 56 | :description: 57 | Ensure that the insights-client data upload is triggered by the 58 | systemd timer by verifying log file creation 59 | :reference: https://issues.redhat.com/browse/CCT-1557 60 | :tags: Tier 1 61 | :steps: 62 | 1. Register the system 63 | 2. Edit insights-client timer to run every 3 minutes with minimal delay 64 | 3. Enable and restart the timer to apply new configuration 65 | 4. Remove existing log file 66 | 5. Wait for timer execution and upload to complete 67 | 6. Verify that log file was recreated, indicating successful upload 68 | :expectedresults: 69 | 1. System is registered successfully 70 | 2. The timer is adjusted to a 3-minute interval and restarted successfully 71 | 3. Timer is active with the new configuration 72 | 4. Existing log file is removed successfully 73 | 5. Timer executes and upload completes within expected timeframe 74 | 6. Log file is recreated, confirming the timer triggered the upload 75 | """ 76 | insights_client.register() 77 | assert loop_until(lambda: insights_client.is_registered) 78 | 79 | # Create override configuration for insights-client timer to run every 3 minutes 80 | override_path = Path("/etc/systemd/system/insights-client.timer.d/override.conf") 81 | override_content = """[Timer] 82 | OnCalendar=*:0/3 83 | Persistent=true 84 | RandomizedDelaySec=5 85 | """ 86 | 87 | # Check if timer was initially active 88 | _run_systemctl_command( 89 | ["systemctl", "is-active", "insights-client.timer"], "check timer status" 90 | ) 91 | logger.debug("Timer was initially active") 92 | 93 | # Create override directory and configuration 94 | override_path.parent.mkdir(parents=True, exist_ok=True) 95 | override_path.write_text(override_content) 96 | logger.debug(f"Created timer override at {override_path}") 97 | 98 | # Reload systemd configuration 99 | _run_systemctl_command(["systemctl", "daemon-reload"], "daemon reload") 100 | 101 | # Enable and restart the timer to apply new configuration 102 | _run_systemctl_command(["systemctl", "enable", "insights-client.timer"], "timer enable") 103 | _run_systemctl_command(["systemctl", "restart", "insights-client.timer"], "timer restart") 104 | 105 | # Verify timer is now active 106 | _run_systemctl_command( 107 | ["systemctl", "is-active", "insights-client.timer"], 108 | "verify timer is active", 109 | ) 110 | 111 | logger.debug("Timer is now active with override configuration") 112 | 113 | try: 114 | # Wait for the timer to execute by monitoring log file creation 115 | timer_executed = _wait_for_timer_execution_by_logfile(interval_minutes=3) 116 | 117 | assert timer_executed, ( 118 | "Timer did not execute within expected timeframe. " 119 | "Log file was not recreated, indicating upload did not occur." 120 | ) 121 | 122 | logger.debug("Timer execution confirmed - log file was recreated") 123 | 124 | finally: 125 | # Clean up: revert timer configuration and restore original state 126 | _run_systemctl_command(["systemctl", "revert", "insights-client.timer"], "timer revert") 127 | _run_systemctl_command(["systemctl", "daemon-reload"], "daemon reload after revert") 128 | 129 | # Ensure timer is stopped after revert 130 | _run_systemctl_command(["systemctl", "stop", "insights-client.timer"], "timer stop") 131 | 132 | # Ensure override file is removed 133 | override_path.unlink(missing_ok=True) 134 | logger.debug("Removed override configuration file") 135 | 136 | # Clean up log file if it exists 137 | log_file = Path(INSIGHTS_CLIENT_LOG_FILE) 138 | log_file.unlink(missing_ok=True) 139 | logger.debug("Cleaned up insights-client log file") 140 | -------------------------------------------------------------------------------- /integration-tests/test_motd.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import contextlib 10 | import os 11 | import subprocess 12 | import pytest 13 | from pytest_client_tools.util import loop_until 14 | 15 | 16 | DOT_REGISTERED_PATH = "/etc/insights-client/.registered" 17 | DOT_UNREGISTERED_PATH = "/etc/insights-client/.unregistered" 18 | 19 | MOTD_PATH = "/etc/motd.d/insights-client" 20 | MOTD_SRC = "/etc/insights-client/insights-client.motd" 21 | 22 | 23 | @pytest.fixture 24 | def delete_special_files(): 25 | """Clean special files before and after test. 26 | 27 | Since .registered and .unregistered files are usually ignored and 28 | do not affect the behavior of insights-client, the MOTD case is 29 | special and depends on them. 30 | 31 | To ensure the tests here are not affected by them, we remove them 32 | """ 33 | 34 | def delete_files(): 35 | with contextlib.suppress(FileNotFoundError): 36 | os.remove(DOT_REGISTERED_PATH) 37 | with contextlib.suppress(FileNotFoundError): 38 | os.remove(DOT_UNREGISTERED_PATH) 39 | with contextlib.suppress(FileNotFoundError): 40 | os.remove(MOTD_PATH) 41 | 42 | delete_files() 43 | yield 44 | delete_files() 45 | 46 | 47 | @pytest.mark.usefixtures("register_subman") 48 | @pytest.mark.usefixtures("delete_special_files") 49 | @pytest.mark.tier1 50 | def test_motd(insights_client): 51 | """ 52 | :id: a66a93bb-bbd2-4db0-a2aa-2bb184b11187 53 | :title: Test MOTD file presence based on registration status 54 | :description: 55 | This test verifies that the MOTD file exists on an unregistered 56 | system and that it is appropriately removed or not recreated upon 57 | registration and unregistration 58 | :tags: Tier 1 59 | :steps: 60 | 1. Verify that MOTD file is present on an unregistered system 61 | 2. Register the insights-client 62 | 3. Verify the MOTD file does not exists after registration 63 | 4. Unregister the insights-client 64 | 5. Verify the MOTD file still does not exist after unregistration 65 | :expectedresults: 66 | 1. The MOTD file is present 67 | 2. The client registers successfully 68 | 3. The MOTD file is removed 69 | 4. The client unregisters successfully 70 | 5. The MOTD file is still not present 71 | """ 72 | # If the system is not registered, the file should be present. 73 | insights_client.run("--status", check=False, selinux_context=None) 74 | assert os.path.exists(MOTD_PATH) 75 | 76 | # After registration, the file should not exist. 77 | insights_client.register() 78 | assert loop_until(lambda: insights_client.is_registered) 79 | assert not os.path.exists(MOTD_PATH) 80 | 81 | # The system was unregistered. Because .unregistered file exists, 82 | # the file should not be present. 83 | insights_client.unregister() 84 | assert not os.path.exists(MOTD_PATH) 85 | 86 | 87 | @pytest.mark.usefixtures("register_subman") 88 | @pytest.mark.usefixtures("delete_special_files") 89 | @pytest.mark.tier1 90 | def test_motd_dev_null(insights_client): 91 | """ 92 | :id: 7d48df16-e1af-4158-8a33-1d2cbb9ed22d 93 | :title: Test MOTD remains untouched when symlinked to /dev/null 94 | :description: 95 | This tst ensures that of the MOTD file is a symbolic link to 96 | /dev/null, it is not modified or removed during the client's registration 97 | and unregistration processes 98 | :tags: Tier 1 99 | :steps: 100 | 1. Create a symlink from MOTD_PATH to /dev/null 101 | 2. Run insights-client with --status option 102 | 3. Register the insights-client 103 | 4. Unregister the insights-client 104 | :expectedresults: 105 | 1. The symlink is created successfully 106 | 2. Command runs successfully and MOTD remains a symlink to /dev/null 107 | 3. The client is registered and MOTD stayed unchanged 108 | 4. the client is unregistered and MOTD stayed unchanged 109 | """ 110 | with contextlib.ExitStack() as stack: 111 | os.symlink(os.devnull, MOTD_PATH) 112 | stack.callback(os.unlink, MOTD_PATH) 113 | 114 | insights_client.run("--status", check=False, selinux_context=None) 115 | assert os.path.samefile(os.devnull, MOTD_PATH) 116 | 117 | insights_client.register() 118 | assert loop_until(lambda: insights_client.is_registered) 119 | assert os.path.samefile(os.devnull, MOTD_PATH) 120 | 121 | insights_client.unregister() 122 | assert os.path.samefile(os.devnull, MOTD_PATH) 123 | 124 | 125 | @pytest.mark.usefixtures("delete_special_files") 126 | @pytest.mark.tier1 127 | def test_motd_message(): 128 | """ 129 | :id: 56d12383-f7bb-4dbe-899c-a1cbd2172a30 130 | :title: Test MOTD message content for unregistered systems 131 | :description: 132 | This test ensures that on unregistered system, the MOTD provides users 133 | with complete instructions on how to register 134 | :reference: https://issues.redhat.com/browse/CCT-264 135 | :tags: Tier 1 136 | :steps: 137 | 1. Ensure the host is unregistered 138 | 2. Read the content of the MOTD file and verify that content matches 139 | the expected message 140 | :expectedresults: 141 | 1. The system is unregistered 142 | 2. The MOTD provides correct registration instructions 143 | """ 144 | cmd = ["cat", MOTD_SRC] 145 | output = subprocess.check_output(cmd, universal_newlines=True) 146 | motd_msg = "Register this system with Red Hat Lightspeed: rhc connect\n\n\ 147 | Example:\n\ 148 | # rhc connect --activation-key --organization \n\n\ 149 | The rhc client and Red Hat Lightspeed will enable analytics and additional\n\ 150 | management capabilities on your system.\n\ 151 | View your connected systems at https://console.redhat.com/insights\n\n\ 152 | You can learn more about how to register your system \n\ 153 | using rhc at https://red.ht/registration\n" 154 | assert output == motd_msg 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Red Hat Insights Client 2 | 3 | **insights-client** is a wrapper for Insights Core (the egg). 4 | 5 | ## Developer Setup 6 | 7 | Follow these instructions to prepare your system for development. 8 | 9 | 1. Fork both this and [insights-core](https://github.com/RedHatInsights/insights-core) repository. 10 | 11 | 2. Clone both of them to the same directory: 12 | 13 | ```shell 14 | $ git clone git@github.com:$YOU/insights-client.git 15 | $ git clone git@github.com:$YOU/insights-core.git 16 | ``` 17 | 18 | 3. Make sure your virtual environment uses system site packages. 19 | 20 | - For existing one, set `include-system-site-packages = true` in virtual environment's `pyvenv.cfg`. 21 | - For new one, create it with `--system-site-packages` flag. 22 | - Make sure both repositories share the virtual environment. 23 | 24 | 4. Install the `insights-core` as a Python package. 25 | 26 | First, make sure the following directories and files exist, otherwise the code will scream at you: 27 | 28 | ```shell 29 | $ sudo mkdir -p /etc/insights-client 30 | $ sudo ln -s `pwd`/data/redhattools.pub.gpg /etc/insights-client/ 31 | $ sudo ln -s `pwd`/data/cert-api.access.redhat.com.pem /etc/insights-client/ 32 | ``` 33 | 34 | Then you can install the package using pip: 35 | 36 | ```shell 37 | $ cd insights-client 38 | $ python3 -m pip install --upgrade pip wheel 39 | $ python3 -m pip install -e ../insights-core/.[client-develop] 40 | ``` 41 | 42 | 5. Run the client. 43 | 44 | ```shell 45 | $ sudo PYTHONPATH=./src:../insights-core python3 src/insights_client/__init__.py --help 46 | ``` 47 | 48 | 49 | ### Contributing 50 | 51 | - Every relevant change should have a test, if possible. 52 | - Every commit containing Python code should be formatted with `black`. 53 | - Every commit/PR will be checked for those in CI. 54 | - Read [TESTING.md](TESTING.md) for more information. 55 | 56 | 57 | ## Legacy Architecture Summary 58 | 59 | insights-client product consists of two parts: insights-client (wrapper) and insights-core. Historically, the Core (also named the Egg) has been distributed through CDN, with an older, frozen version packaged within insights-client itself. Later, the Core delivery model was changed to be RPM as well. 60 | 61 | Wrapper is the entry point providing CLI on $PATH, and ensures [**phases**](#phases) are run. 62 | 63 | Egg is a bundle that contains the `insights` package with all the main functionality. All flags and configurations are passed to the egg by the wrapper. 64 | 65 | ### Phases 66 | 67 | insights-client runs in four phases. 68 | 69 | 1. **pre-update**: Execute flags that exit immediately (`--version`, `--test-connection`, `--checkin`). 70 | 2. **update**: Download new Core from CDN. This phase is not used anymore. 71 | 3. **post-update**: Process flags (like registration options), and exit if the operation requires registration and the system is not registered. 72 | 4. **collect & upload**: Run data collection, compress the results and upload them to ConsoleDot. 73 | 74 | ## Configuration 75 | 76 | The configuration uses the values in the following hierarchy: 77 | 78 | 1. CLI flags 79 | 2. `/etc/insights-client/insights-client.conf` 80 | 3. environment variables (in a form of `INSIGHTS_xxxxxx`, where `xxxxxx` is the configuration variable name, in all caps) 81 | 82 | ### Directories 83 | The client utilizes several directories on the system for its operation: 84 | 85 | - `/etc/insights-client/` - The primary directory for configuration. It contains `insights-client.conf`, redaction files (`file-redaction.yaml`, `file-content-redaction.yaml`), and security certificates. 86 | - `/var/log/insights-client/` - The default directory for log files. 87 | - `/var/lib/insights/` - Stores information about the core module (egg), including `last_stable.egg`. 88 | - `/var/cache/insights-client/` - The default location where the archive is stored when the `--keep-archive`, `--no-upload` and `--offline` flag is used. 89 | - `/var/tmp/` - Used as a temporary location for building the archive before upload. 90 | 91 | ## Log Management 92 | The developer logs its activity to provide a record of its operations, which is essential for troubleshooting. 93 | All of the following switches were already explained in the **Configuration** section. 94 | 95 | ### Log File 96 | By default, all log output is sent to `/var/log/insights-client/insights-client.log`. 97 | 98 | ### Log Verbosity 99 | The level of detail in the logs can be controlled. 100 | 101 | 1. The `--verbose` flag increases the verbosity to `DEBUG` level for a single run. (More logs) 102 | 2. The `--quiet` flag decreases verbosity to only show `ERROR` messages. 103 | 104 | ## External Services 105 | When you run `insights-client`, it connects to several Red Hat services. Connectivity to these services is required for a standard, non-offline execution. 106 | 107 | ### Red Hat Insights API 108 | This are the primary services the client communicates with. 109 | 110 | Purpose: To upload the collected system data archive, download updated insights-core, and retrieve analysis results. It can also optionally connect to services for Compliance and Malware detection. 111 | 112 | Hostnames: The default hostname is `cert-api.access.redhat.com`. For newer infrastructure, `cert.cloud.redhat.com` is also used. 113 | 114 | ### Automatic Configuration via RHSM 115 | If the system is registered to RHSM, the insights-client may reconfigure itself to use Satellite hostname. 116 | 117 | If registered to a Red Hat Satellite server: 118 | The client will automatically reconfigure itself. It uses the Satellite's hostname as the destination for data uploads. 119 | 120 | If not registered to a Satellite server: 121 | The client operates in its default mode. It connects directly to the primary Red Hat Insights API, which is the standard behavior for systems registered with Red Hat. 122 | 123 | This entire auto-detection process is based on reading **local** config files only (managed by RHSM). 124 | 125 | examples what you could see in logs if `auto_config=True`: 126 | `DEBUG insights.client.auto_config:159 Connected to staging RHSM, using cert.cloud.stage.redhat.com` 127 | `DEBUG insights.client.auto_config:81 Not connected to Satellite, skipping branch_info` 128 | -------------------------------------------------------------------------------- /insights-client.spec: -------------------------------------------------------------------------------- 1 | %define _binaries_in_noarch_packages_terminate_build 0 2 | 3 | Name: insights-client 4 | Summary: Uploads Insights information to Red Hat on a periodic basis 5 | Version: 3.10.2 6 | Release: 0%{?dist} 7 | Source: {{{ git_dir_pack }}} 8 | License: GPL-2.0-or-later 9 | URL: https://console.redhat.com/insights 10 | Group: Applications/System 11 | Vendor: Red Hat, Inc. 12 | 13 | BuildArch: noarch 14 | 15 | Requires: tar 16 | Requires: gpg 17 | Requires: pciutils 18 | 19 | %{?__python3:Requires: %{__python3}} 20 | %{?systemd_requires} 21 | Requires: python3-requests >= 2.6 22 | Requires: python3-PyYAML 23 | Requires: python3-magic 24 | Requires: python3-six 25 | Requires: coreutils 26 | Requires: ((selinux-policy >= 42.1.8-1) if selinux-policy) 27 | Requires: (python3-libselinux if selinux-policy) 28 | 29 | Requires: insights-core >= 3.6.7 30 | Requires: subscription-manager 31 | 32 | BuildRequires: wget 33 | BuildRequires: binutils 34 | BuildRequires: python3-devel 35 | BuildRequires: systemd 36 | BuildRequires: pam 37 | BuildRequires: meson 38 | BuildRequires: python3-pytest 39 | BuildRequires: systemd-rpm-macros 40 | Requires(post): policycoreutils-python-utils 41 | 42 | 43 | %description 44 | Sends insightful information to Red Hat for automated analysis 45 | 46 | %package ros 47 | Requires: pcp-zeroconf 48 | Requires: insights-client 49 | 50 | Summary: The subpackage for Insights resource optimization service 51 | 52 | %description ros 53 | The ros subpackage add ros_collect configuration parameter to insights-client.conf file, 54 | the parameter is set to True by default. The system starts sending PCP archives to 55 | Resource Optimization service upon modifying ros_collect parameter to True. 56 | 57 | %prep 58 | {{{ git_dir_setup_macro }}} 59 | 60 | 61 | %build 62 | %{meson} \ 63 | -Dpython=%{__python3} -Dcore_selinux_policy=insights_core_t \ 64 | %if (0%{?rhel} && 0%{?rhel} < 10) 65 | -Dredhat_access_insights=true \ 66 | %endif 67 | %{nil} 68 | %{meson_build} 69 | 70 | 71 | %install 72 | %{meson_install} 73 | 74 | # Create different insights directories in /var 75 | mkdir -p %{buildroot}%{_localstatedir}/log/insights-client/ 76 | mkdir -p %{buildroot}%{_localstatedir}/lib/insights/ 77 | mkdir -p %{buildroot}%{_localstatedir}/cache/insights/ 78 | mkdir -p %{buildroot}%{_localstatedir}/cache/insights-client/ 79 | 80 | %post 81 | %systemd_post %{name}.timer 82 | %systemd_post %{name}-boot.service 83 | 84 | # Remove legacy egg files from previous installations 85 | rm -f %{_sysconfdir}/insights-client/rpm.egg 86 | rm -f %{_sysconfdir}/insights-client/rpm.egg.asc 87 | rm -f %{_localstatedir}/lib/insights/*.egg 88 | rm -f %{_localstatedir}/lib/insights/*.egg.asc 89 | 90 | # Symlink the message of the day if the system has not been registered with Insights 91 | _SHOULD_WRITE_MOTD=1 92 | # MOTD directory doesn't exist for some reason; don't even try 93 | if [ ! -d %{_sysconfdir}/motd.d ]; then _SHOULD_WRITE_MOTD=0; fi 94 | # Message shouldn't be displayed if the system has ever been registered 95 | if [ -e %{_sysconfdir}/insights-client/.registered ]; then _SHOULD_WRITE_MOTD=0; fi 96 | if [ -e %{_sysconfdir}/insights-client/.unregistered ]; then _SHOULD_WRITE_MOTD=0; fi 97 | # Message file is already in place (as a file, or as a symlink) 98 | if [ -e %{_sysconfdir}/motd.d/insights-client -o -L %{_sysconfdir}/motd.d/insights-client ]; then _SHOULD_WRITE_MOTD=0; fi 99 | if [ "$_SHOULD_WRITE_MOTD" -eq 1 ]; then 100 | ln -sn %{_sysconfdir}/insights-client/insights-client.motd %{_sysconfdir}/motd.d/insights-client 101 | fi 102 | 103 | if [ $1 -eq 2 ]; then 104 | /usr/sbin/semanage permissive --list | grep -q 'insights_client_t' 105 | if [ $? -eq 0 ]; then 106 | /usr/sbin/semanage permissive --delete insights_client_t &>/dev/null 107 | fi 108 | fi 109 | 110 | %post ros 111 | rm -f /var/lib/pcp/config/pmlogger/config.ros 112 | sed -i "/PCP_LOG_DIR\/pmlogger\/ros/d" /etc/pcp/pmlogger/control.d/local 113 | 114 | if ! grep -q "^ros_collect" %{_sysconfdir}/insights-client/insights-client.conf; then 115 | cat <> %{_sysconfdir}/insights-client/insights-client.conf 116 | ### Begin insights-client-ros ### 117 | ros_collect=True 118 | ### End insights-client-ros ### 119 | EOF 120 | fi 121 | 122 | %preun 123 | %systemd_preun %{name}.timer 124 | %systemd_preun %{name}.service 125 | %systemd_preun %{name}-boot.service 126 | 127 | %postun 128 | %systemd_postun %{name}.timer 129 | %systemd_postun %{name}.service 130 | %systemd_postun %{name}-boot.service 131 | 132 | # Clean up files created by insights-client that are unowned by the RPM 133 | if [ $1 -eq 0 ]; then 134 | rm -f %{_sysconfdir}/cron.daily/insights-client 135 | rm -f %{_sysconfdir}/ansible/facts.d/insights.fact 136 | rm -f %{_sysconfdir}/ansible/facts.d/insights_machine_id.fact 137 | rm -f %{_sysconfdir}/motd.d/insights-client 138 | rm -rf %{_localstatedir}/lib/insights 139 | rm -rf %{_localstatedir}/log/insights-client 140 | rm -f %{_sysconfdir}/insights-client/.*.etag 141 | rm -f %{_sysconfdir}/logrotate.d/insights-client 142 | fi 143 | 144 | %postun ros 145 | sed -i '/### Begin insights-client-ros ###/,/### End insights-client-ros ###/d;/ros_collect=True/d' %{_sysconfdir}/insights-client/insights-client.conf 146 | 147 | %clean 148 | rm -rf %{buildroot} 149 | 150 | 151 | %files 152 | %config(noreplace) %{_sysconfdir}/insights-client/*.conf 153 | %{_sysconfdir}/insights-client/insights-client.motd 154 | %{_bindir}/* 155 | %{_unitdir}/* 156 | %attr(444,root,root) %{_sysconfdir}/insights-client/*.pem 157 | %attr(444,root,root) %{_sysconfdir}/insights-client/redhattools.pub.gpg 158 | %{python3_sitelib}/insights_client/ 159 | %{_defaultdocdir}/%{name} 160 | %{_presetdir}/*.preset 161 | %attr(700,root,root) %dir %{_localstatedir}/log/insights-client/ 162 | %attr(700,root,root) %dir %{_localstatedir}/cache/insights-client/ 163 | %attr(750,root,root) %dir %{_localstatedir}/cache/insights/ 164 | %attr(750,root,root) %dir %{_localstatedir}/lib/insights/ 165 | %{_sysconfdir}/logrotate.d/insights-client 166 | %{_tmpfilesdir}/insights-client.conf 167 | 168 | %doc 169 | %defattr(-, root, root) 170 | %{_mandir}/man8/*.8.gz 171 | %{_mandir}/man5/*.5.gz 172 | 173 | %files ros 174 | 175 | %changelog 176 | {{{ git_dir_changelog }}} 177 | -------------------------------------------------------------------------------- /integration-tests/test_ros.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import conftest 10 | import pytest 11 | import subprocess 12 | import shutil 13 | from constants import CONFIG_FILE 14 | from pathlib import Path 15 | from pytest_client_tools.util import loop_until 16 | 17 | pytestmark = pytest.mark.usefixtures("register_subman") 18 | 19 | PACKAGE = "insights-client-ros" 20 | SERVICE = "pmlogger" 21 | 22 | 23 | @pytest.mark.tier1 24 | @pytest.mark.skipif(conftest.check_is_bootc_system(), reason="No dnf on bootc systems") 25 | def test_ros_install(): 26 | """ 27 | :id: 3fc19957-be97-429d-817a-610e6c69dd9d 28 | :title: Verify insights-client-ros can be installed 29 | :description: 30 | Ensure that the insights-client-ros can be successfully installed 31 | :tags: Tier 1 32 | :steps: 33 | 1. Try to install insights-client-ros 34 | 2. Check that ros_collect is set to true in insights-client.conf 35 | :expectedresults: 36 | 1. Subpackage insights-client-ros is installed 37 | 2. Field ros_collect is set to true in insights-client.conf 38 | """ 39 | install = subprocess.run( 40 | ["dnf", "install", "-y", "--disablerepo", "beaker-tasks", PACKAGE], 41 | capture_output=True, 42 | text=True, 43 | ) 44 | assert install.returncode == 0, f"{PACKAGE} was not installed" 45 | 46 | cat_result = subprocess.run(["cat", CONFIG_FILE], capture_output=True, text=True) 47 | assert "ros_collect=True" in cat_result.stdout 48 | 49 | 50 | @pytest.mark.tier1 51 | def test_pmlogger_running_and_metrics_exist(): 52 | """ 53 | :id: 8b37e4b2-d873-4ef5-87f0-ba51ba33e3af 54 | :title: Verify that pmlogger can be started and metrics exist 55 | :description: 56 | Ensure that the pmlogger service is active or can be activated 57 | and that PCP metrics archives are present for the current hostname 58 | :tags: Tier 1 59 | :steps: 60 | 1. Check pmlogger status using systemctl 61 | 2. Check that the directory /var/log/pcp/pmlogger/ exists 62 | :expectedresults: 63 | 1. Service is active. If not, it can be started 64 | 2. Directory exists with metrics archives ending in .0 65 | """ 66 | status_result = subprocess.run( 67 | ["systemctl", "is-active", SERVICE], capture_output=True, text=True 68 | ) 69 | if status_result.stdout.strip() != "active": 70 | subprocess.run(["systemctl", "start", SERVICE], check=True) 71 | status_result = subprocess.run( 72 | ["systemctl", "is-active", SERVICE], capture_output=True, text=True 73 | ) 74 | assert status_result.stdout.strip() == "active", f"{SERVICE} is not active" 75 | 76 | hostname = subprocess.run(["hostname"], capture_output=True, text=True).stdout.strip() 77 | metrics_dir = Path(f"/var/log/pcp/pmlogger/{hostname}") 78 | assert metrics_dir.exists(), f"{metrics_dir} does not exist" 79 | archives = list(metrics_dir.glob("*.0")) 80 | assert archives, f"No newest archives found in {metrics_dir}" 81 | 82 | 83 | @pytest.mark.tier1 84 | def test_register_with_ros(insights_client): 85 | """ 86 | :id: 66b43a68-218f-4988-9f91-ac228d6cc19b 87 | :title: Test client registration 88 | :description: 89 | This test verifies that the --register command successfully registers 90 | an unregistered client with insights-client-ros installed and active 91 | :tags: Tier 1 92 | :steps: 93 | 1. Run insights-client with --register option 94 | :expectedresults: 95 | 1. Verify the client successfully registered 96 | """ 97 | register_result = insights_client.run("--register") 98 | assert loop_until(lambda: insights_client.is_registered) 99 | assert register_result.returncode == 0 100 | 101 | 102 | @pytest.mark.tier2 103 | def test_upload_pre_collected_archive_with_ros(insights_client, tmp_path): 104 | """ 105 | :id: 0f316a85-2e2b-45a1-8440-672d95dce02a 106 | :title: Test Upload of Pre-Collected Archive 107 | :description: 108 | This test verifies that a pre-collect insights-archive 109 | can be uploaded using --payload operation even with 110 | insights-client-ros active. 111 | :tags: Tier 2 112 | :steps: 113 | 1. Register insights-client 114 | 2. Run insights-client in an offline mode to generate an archive 115 | and save it 116 | 3. Run the insights-client with the --payload option and valid --content-type 117 | 4. Verify the successful upload of the archive 118 | :expectedresults: 119 | 1. Insights-client is registered 120 | 2. The archive is successfully generated and saved 121 | 3. The upload process starts and the output message is as expected 122 | 4. The upload completes successfully with the message as expected 123 | """ 124 | archive_name = "archive.tar.gz" 125 | archive_location = tmp_path / archive_name 126 | 127 | # Registering the client because upload can happen on registered system 128 | insights_client.register() 129 | assert loop_until(lambda: insights_client.is_registered) 130 | 131 | # Running insights-client in offline mode to generate archive and save at tmp dir 132 | insights_client.run( 133 | f"--output-file={archive_location}", 134 | selinux_context=None, # using custom archive location, not a service action 135 | ) 136 | 137 | # Running insights-client --payload with --content-type to upload archive 138 | # collected in previous step 139 | upload_result = insights_client.run( 140 | f"--payload={archive_location}", 141 | "--content-type=gz", 142 | selinux_context=None, # using custom archive location, not a service action 143 | ) 144 | assert "Uploading Insights data." in upload_result.stdout 145 | assert "Successfully uploaded report" in upload_result.stdout 146 | 147 | 148 | @pytest.mark.tier1 149 | @pytest.mark.skipif(conftest.check_is_bootc_system(), reason="No dnf on bootc systems") 150 | def test_cleanup_pmlogger_and_ros(): 151 | """ 152 | :id: e9e150af-4fb7-42c2-b72b-5e1656959530 153 | :title: Verify insights-client-ros can be uninstalled 154 | and pmlogger can be stopped 155 | :description: 156 | This test verifies that the pmlogger service can be stopped and 157 | ROS can be uninstalled to clean up the system after testing ros 158 | :tags: Tier 1 159 | :steps: 160 | 1. Stop the pmlogger service using systemctl 161 | 2. Remove the ROS directory and uninstall insights-client-ros 162 | :expectedresults: 163 | 1. Pmlogger service is stopped 164 | 2. The directory is removed and insights-client-ros is uninstalled 165 | """ 166 | # Stop pmlogger service 167 | stop_result = subprocess.run(["systemctl", "stop", SERVICE], capture_output=True, text=True) 168 | assert stop_result.returncode == 0, f"Failed to stop {SERVICE}: {stop_result.stderr}" 169 | 170 | # Verify pmlogger is inactive 171 | status_result = subprocess.run( 172 | ["systemctl", "is-active", SERVICE], capture_output=True, text=True 173 | ) 174 | assert ( 175 | status_result.stdout.strip() != "active" 176 | ), f"{SERVICE} service is still active after stopping" 177 | 178 | # Remove the ROS directory 179 | ros_dir = Path("/var/log/pcp/pmlogger/ros") 180 | if ros_dir.exists(): 181 | shutil.rmtree(ros_dir, ignore_errors=True) 182 | assert not ros_dir.exists(), "ROS directory still exists after removal" 183 | 184 | # Uninstall insights-client-ros 185 | uninstall_result = subprocess.run( 186 | ["dnf", "remove", "-y", PACKAGE], capture_output=True, text=True 187 | ) 188 | assert ( 189 | uninstall_result.returncode == 0 190 | ), f"Failed to uninstall {PACKAGE}: {uninstall_result.stderr}" 191 | -------------------------------------------------------------------------------- /integration-tests/playbook_verifier/playbooks/bugs.yml: -------------------------------------------------------------------------------- 1 | # This testing file is used to identify, track, and fix issues. It allows adding 2 | # new issues as they are discovered and helps work through them step by step. 3 | - name: Set the LoginGraceTime with a regex and file mode to 0600 4 | hosts: localhost 5 | become: true 6 | vars: 7 | insights_signature_exclude: /hosts,/vars/insights_signature 8 | insights_signature: !!binary | 9 | TFMwdExTMUNSVWRKVGlCUVIxQWdVMGxIVGtGVVZWSkZMUzB0TFMwS1ZtVnljMmx2YmpvZ1IyNTFV 10 | RWNnZGpFS0NtbFJTV05DUVVGQ1EwRkJSMEpSU201b2NrOTNRVUZ2U2tWTmRuYzFPRVFyYWpWd1Rs 11 | SkJiMUF2TUhKVFowUmxWVU5DTDFoWFl6QlBkRW8xTTNabWJIb0tjWGhWWlRaNWRHcHZiVzkwZVhK 12 | alpHVjBhMmRyTjFseU9ITTJORVZKTnpsNVUxTndWVk40VTBncmIwWTJVMjkyU0RsSWFUQXJORWxK 13 | WlRWb1dsQjBRZ3BaUW05Q2FIUmpaRXRTWm1jMGVpOHJXRkYxT0ZOU1luVjZaak5FWTFRNWJVUjJk 14 | WGROTnprNGVHbFRjbTFZYTNGNVdUUmxkakZVYVZnM2RuRTNaREZGQ25GSGFtZGlOVTlDTUVJNE16 15 | bG5WM2xaYmtkV1NXNWtNV2xKUjB4UEwzSktaV3hqVHlzeWVFa3pVbGxQWnpRd1lVODRPR0pZZFZw 16 | cE1VeEllbXhJYm5FS2IydEZkWE5YU2l0VVEwbE9abUo2ZVN0QlIzZENWRXhCTWt4WVozSkZibmwz 17 | ZW5Fd2NteFNhMmhwTjI1c1luTTBkU3RoWlRGclNISnhPVTVLWkZGRFZncG1jRWxSU2k5eFdUSkxT 18 | MkYyWTJkUVEyWlNLMEZuZDJsalkwUmtlbEpDV2tWalEycHpWVzFDWlZNdlJrdFVSMFpHUlVneWJF 19 | SkRhRFpLWjNweFNsbHBDakZVU1c1cWF5dHlWamxuWkhkNk5uZGpWek5JZVhCdWVWZHFLMHBKTVda 20 | NWFUSnhaa0pKUVRoc1JWZHBiSFU1TTBWTVFXRXhhMHhpY1c5U2RWTmlUbThLWW1wM2RFOWpUSFZG 21 | VlZabllteHhlV2RpUldwRmNGRXJWMkpUZUdGMVptTnZVRzVWWTFGVWMzZFBZMmMwYVhCWlRUSTFN 22 | bWczZVZkSVRuaDBUa2hEYndwVVV6bE1NME5zVVVGcE9YTnRTMHRzWm1sVFNXc3JOMUJQZDJGT1dH 23 | SklObVZRTURobVltcFZWVkV6ZWpOQ1ZXa3lhMDFVZUdWQmVqWm9TbTE0UXk4ekNuQm1MMmRhYkVS 24 | eFprUk1jVXRHU0V4dVJra3lSa28yVGxGeVMwaFVOa2xVY21SelZ6WkxLM0ZIZFRWcmRWTmhlV04y 25 | YzBoeVMybEtPVmROWjNKSFRWSUtZVEpEZUdaQ1NHSllhbWhwZGtFNWVEbFZSMjE0ZGt0b1J5dG1j 26 | VVEyYmpCb1ozcElaM2swVWl0b01ESlRTbE40TkZwTldrNUZOVFZuVkhSdWExUnJNZ3BtT0Vsc1dH 27 | ZFZWbUZvV0VsS01HZ3pVa3QwYndvOVJXODBUd290TFMwdExVVk9SQ0JRUjFBZ1UwbEhUa0ZVVlZK 28 | RkxTMHRMUzBL 29 | tasks: 30 | - ticket: CCT-1065 31 | file: 32 | regexp: '(?i)^\s*LoginGraceTime' 33 | mode: 0600 34 | 35 | - name: Test string with quotes and special characters 36 | hosts: localhost 37 | become: true 38 | vars: 39 | insights_signature_exclude: /hosts,/vars/insights_signature 40 | insights_signature: !!binary | 41 | TFMwdExTMUNSVWRKVGlCUVIxQWdVMGxIVGtGVVZWSkZMUzB0TFMwS1ZtVnljMmx2YmpvZ1IyNTFV 42 | RWNnZGpFS0NtbFJTV05DUVVGQ1EwRkJSMEpSU201b2NsWlJRVUZ2U2tWTmRuYzFPRVFyYWpWd1Rp 43 | OW1iMUZCU25SWFZXRnFVRzlZTTFCQ1lubDJTMGxPSzFoV2EyNEtlQzlsUTJ0VWNHWlVWa2gwTTFa 44 | RVFubFVWVUZVUW01eU1sVTRNRVZxTHpSSUwxazJUemxXU25OVFZESmFTVUpxV1ZRNFdpOTVkSHBh 45 | TjBoSFMyWk9NZ3BzUzJ0TVJsSnpPRlIyVVdoaVFsRXdNbGt3TTNGa1lWWndaVU52ZGk5VGIwVXlh 46 | bnBoT0ZVMVkyZENNREZvTjJsYU4zWjNkVFZOUW5KcE9WTkhWRGQxQ25Od1VUZEpOQzlZUkRKRFQx 47 | Qk9WbVJ4WkNzNU5XUmlNSGxMV21kb2QySnphbnBLZURNM1JWbEhRa05XZUZOTmJuUnVTRzF6V0c1 48 | RVdsbHFlV0pIYmt3S1EwdFBkRkJCTDFoQ1NXOTVOMjF1VTI5aFVUUXdVVXg0WjJoU1RIaHRUMmxR 49 | WWxoRU1YUktRVGRGUzBaclZpODJPSGxIYW5FMWFHWnhNVVJEVDFjd013cFlOelJFWW14c1MwNU9T 50 | bEk1YUU4NGJEUk9TRVZDU210M2MwaHFUMHhhUjBwdVRYUjJSMmxRUmtFNE5HbExhV3g2T0RGbWVX 51 | cGFTbWd6UzJOTFRYVjRDbU41T0hZME9UWmxOaXRqV0ZWeGRGWkVPVWRPU2pacFFWWjBiSE4wTUc1 52 | VlpFSk1UM0pyVVhJMGVscDNNRnA1ZDJsblMyOWpSR2wyT1RocVptNTFWREVLYkhaNllYVXlZMnRO 53 | VTNSeFpYaEZaa1ZPU2psMWMyNXRhWGcwUVU1NVUxQlNlWGxvV0ZwdGVrWnZVMDV1YkhGbU5sRlhZ 54 | VVZ1YTIxaFJtNUtOVk5XS3dwTk1rSmlkSEV6ZHl0TlYyTmtaVWRvYVZkcGMyWXlNbXRwUkZKV01r 55 | ZFNSSEZ0UjBOTk1qUjBVRVZvVEhwNVNVeHhjbEpsZDJ4MVRVUm1Nbk0yUjA5TkNtMVNNRGxPT1Vs 56 | TFNYUm5ZMUpqUjBKaVdGTTJkMUp0TUdKa1UyaFNUMDV4TnpSUmMzZHJlRzAzYlcxd1JUaDZLMjVG 57 | UTNkWk1XNDBSbXRZYWxFM1lsVUtiVEE0VW1NMU1HczFRMWhCUTNkcVdqTlRXRzR3VWxaT1Qyc3JL 58 | M1pTU0VodlJFNTVkMUJpYUZkQlQzSndiak5CV1dweWFGWlpZbE4wWVU4clJDOTVjQXBvYUZsYWFV 59 | aHpOREZNWTBGSVNHZzBWRVowVVFvOVVqY3ZOQW90TFMwdExVVk9SQ0JRUjFBZ1UwbEhUa0ZVVlZK 60 | RkxTMHRMUzBL 61 | tasks: 62 | - ticket: CCT-1065 63 | value: > 64 | This_string_contains_special_characters, 65 | single'quote, double"quote, both'"quotes, 66 | backslashes\\need to be\\\\escaped!\\ 67 | Newlines?\nYep, they stay. 68 | Tabs\t too! 69 | ~End~ 70 | 71 | - name: Test a play with an empty map 72 | hosts: localhost 73 | become: true 74 | vars: 75 | insights_signature_exclude: /hosts,/vars/insights_signature 76 | insights_signature: !!binary | 77 | TFMwdExTMUNSVWRKVGlCUVIxQWdVMGxIVGtGVVZWSkZMUzB0TFMwS1ZtVnljMmx2YmpvZ1IyNTFV 78 | RWNnZGpFS0NtbFJTV05DUVVGQ1EwRkJSMEpSU201b2NsWldRVUZ2U2tWTmRuYzFPRVFyYWpWd1Ru 79 | cDJaMUZCU2xwR1p6VlBRVUZRU1haSU1XdG5jblJIWWtkM2VFb0taelJTWmk4NGVpODNhV1JOTHpJ 80 | MFREQTRla2RqVWxCMlVFeDBaRFYxYzI4NU5qaElTa3hEVlhoV2NVMTZaRUZEVUVkV2RqWlBWV0pC 81 | WWxCUFRsUlJjZ3B0WjNKUEwzTjRUblkzVUhKQ1IzWjRMMjAyYUhSVlprd3lVWFJCU1VaeUswOHpO 82 | Vk5CTVhaNGF5OUpNMlJJTlVGMU9XVXZNbFExWjBGWWVFdHZRWG96Q2tsd01GVkxOakZKV25WcGRu 83 | Z3JaV1oxVUhSR2JqQmlkQ3ROZDA5VlJXMVFkUzlsVTJWUVIycFZhSHBRTjJKU1JWaE9LMmR1TlRR 84 | elZHYzFRMEZqZFRRS09WbDBTV1UyVW5kTU5UaEpUR3hHWms5d01FUkJaMHRoYTFSSFNtYzBSWEIy 85 | YVRCVWVVbHViRTVYYTNoTVEyOWtMM0FyUVdOd01Wb3hUVTVhWlhWaEx3cEZZbVpsTUVwUU5qWTJi 86 | VU5SUjBWVFVHbHlUV2xTZW5GNU1sQXplVTh3ZG5ScGVteDNlRkp5UWtVNFIwOVZZMVpxYkhKUGMz 87 | bEdUM1ZqY0ZNM1IwZHRDbnBLUnpoRFRWbGFSM2hSYVc1VVFuUXZSRmRqU1VReGJrZGxhMGxFYkda 88 | VmNuSmxXV0ZzV2xCRlkwNDVlVkF6TTNWNEszbGtZVWg2VDNGSU9ITnNlbEFLZUUxRmRITnJVa3BM 89 | Um5WVFFYRkZiRGdyV0hGV1kyTnBaM2RGVm14VmEyWlFiR3RDUkZJNFdIRlJibHB6ZHpsSk1WcFdV 90 | MHg2T1V0UlFuVkJORTV6VHdwRVVXRlhNVXRTYTFsaWVHb3liRUp2YkZCeFJpOU5NRzVOVEVZeksx 91 | ZHdVRUpUUkRONVQzZzJLeko0VWpGd1JuSXdTVVpGTmxWMVUwbFhWMHMyVVVwQ0NqWTNhWE12TUNz 92 | eFZYcEpjMUY0U2tKbVZXTXdiVkprYzNSS1VFdG5WMHQzWjIxR1QyYzVWRXBFSzFkeVV6ZGtNV2M1 93 | U1RCWE1VSjRPV3hzVFhGbVVYY0tjekF6VW5RMGFsZHdaUzlPWTJ3NVNHZHJkMG95Y1M4MWVXaHpV 94 | bTk1ZFVOR1ZUaDZlbkJWVXpKaFYyUlRkR2h3WnpWMlZHUnRaeXRXYlZSNlpqVlNPUXBLYTJrek5I 95 | WlpTa1puZDIxbGNsZFphVlU0TlFvOWVteDFWd290TFMwdExVVk9SQ0JRUjFBZ1UwbEhUa0ZVVlZK 96 | RkxTMHRMUzBL 97 | tasks: 98 | - ticket: CCT-1101 99 | value: {} 100 | 101 | - name: Test a play with an empty value 102 | hosts: localhost 103 | become: true 104 | vars: 105 | insights_signature_exclude: /hosts,/vars/insights_signature 106 | insights_signature: !!binary | 107 | TFMwdExTMUNSVWRKVGlCUVIxQWdVMGxIVGtGVVZWSkZMUzB0TFMwS1ZtVnljMmx2YmpvZ1IyNTFV 108 | RWNnZGpFS0NtbFJTV05DUVVGQ1EwRkJSMEpSU201b2NsWmhRVUZ2U2tWTmRuYzFPRVFyYWpWd1Rs 109 | RnRhMUZCU1VGSFlqSmtSWE5NYkVKdlNrRm5Oa3hHUjB0VGRWWUtXRmhrSzBGcFJrYzBiSFJOV1cx 110 | RU5tc3dTRmxTUjJkUk9WRXpiREp2VUhoelRqRTFjVzl0TkV4SmJsazRZVkZxYjNGR1N6Vm9UV2N2 111 | WXpGcWQwdDFTZ3BsUkdFNGNYcFBURFZ4VjNFeVlXcGFlak0wU1d4aFRrMUVZM2xLTm00MVltbFhU 112 | bHBqZWs0eWEwcHNUaTlJWlV4cGJtSjFiSEJSWnpBMVpYSkxjWFJNQ2xWcFRGbDZkR0pGVGxsQ05r 113 | dFFWSGhJUjB0R1kzRkVka05hVjBaUWExRm1lVmhOZDNNeVFXeGpSbGRzYVhGcFdWWm9MelJRV1Vv 114 | NU1IaG1RbVpoWkdjS09EUlFiMFF5WmtkemRtNUdTVGgyTkhsWEwxbFRUV1p4VmpoMmVsUm9hV3RM 115 | VUdWb1kzcFZRazlNUVdKdVVWUmFObkZ4Y1dSU09YTlpNR2xtZHpSc1lRcGpjRWhrWTNnd1VsRkhM 116 | MDR3ZHpGUGVHbHphRkZKYXpSd1FYUk1NRXhZUkVwdk5IRklUWEptVTJsTmIwUjVNMVJxV1N0NlZX 117 | OTZPVUpHV1RWalJHZHJDblJtZVdSRFEyNVFRMnRVYjI1SGNFZDJla3BVTms5d1J6bHJkRTFaTjBj 118 | NFYxZHdOazh4ZGxwMldUUlRaVU5WVFdkNWJtWnlhbEo0VDBGcUsxZ3haV29LUzBsMlVXUXpaa2xM 119 | ZDJWSFNtWjNOamxsVTJWbWNGUkVlblIyUlhKM01IZEhNekUyUm1nMWNGRXdWSEJZYlhsV1ZVVmtN 120 | MlVyYTFGaE1tZzBPVWxvYkFwaWNXdGliV1JHYjFoSVIwSTNXWFE1VlVocVZrWmtabGxqV2k5MlpX 121 | TmFVR2wzV25aa1FYSnpTRTg0V0VweFNqUndUV2xpZEVsaE5GZEVOVnBKVldkMkNtWjFSbHAyZVRK 122 | NlJqWk5XWEJQY3pSRlpraGphbUo2TlRkWlVFVllUMGxpWkZVemQyeHdibEZxUVhVeVZXOXBVbFJP 123 | UnpabVdHMUxVbTlyVjFWMVJIZ0tjME52ZW05amVYbHJZMU5xTTJKNU9YWnhjVTVtUzNsVWRURjVR 124 | WFptYVdscWMzUnFia3hwV1RsM2JGYzRLMmRZWWxwdmNXeEtSRVEzWmsxeldFOHdOZ3BCYnpWV1ZF 125 | a3lNVWhhZUUxSVdtZHJLMUpyU1FvOWJHVk5RZ290TFMwdExVVk9SQ0JRUjFBZ1UwbEhUa0ZVVlZK 126 | RkxTMHRMUzBL 127 | tasks: 128 | - ticket: CCT-1102 129 | value: 130 | -------------------------------------------------------------------------------- /integration-tests/test_connection.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import configparser 10 | import contextlib 11 | import logging 12 | import os 13 | import typing 14 | 15 | import pytest 16 | 17 | if typing.TYPE_CHECKING: 18 | import pytest_client_tools.insights_client 19 | 20 | # the tests need a valid connection to insights so therefore the subman registration 21 | pytestmark = pytest.mark.usefixtures("register_subman") 22 | 23 | 24 | def _is_using_proxy( 25 | insights_config: "pytest_client_tools.insights_client.InsightsClientConfig", 26 | ) -> bool: 27 | for key, value in os.environ.items(): 28 | if key.lower() == "https_proxy": 29 | logging.debug(f"Proxy is set via environment variable: '{value}'.") 30 | return True 31 | 32 | with contextlib.suppress(KeyError): 33 | insights_proxy: str = insights_config.proxy 34 | if insights_proxy != "": 35 | logging.debug(f"Proxy is set via insights-client: '{insights_proxy}'.") 36 | return True 37 | 38 | # sub-man fixture doesn't currently support reading the configuration file, 39 | # let's parse it ourselves. 40 | with contextlib.suppress(Exception): 41 | rhsm_config = configparser.ConfigParser() 42 | rhsm_config.read("/etc/rhsm/rhsm.conf") 43 | rhsm_proxy: str = rhsm_config.get("server", "proxy_hostname", fallback="") 44 | if rhsm_proxy != "": 45 | logging.debug(f"Proxy is set via subscription-manager: '{rhsm_proxy}'.") 46 | return True 47 | 48 | logging.debug("Proxy is not set.") 49 | return False 50 | 51 | 52 | @pytest.mark.tier1 53 | def test_connection_ok(insights_client): 54 | """ 55 | :id: ff674d37-0ccc-481c-9f04-91237b8c50d0 56 | :title: Test connection 57 | :description: 58 | This test verifies that the --test-connection option works 59 | properly, confirming successful connection 60 | :tags: Tier 1 61 | :steps: 62 | 1. Run insights-client with --test-connection option 63 | 2. Verify that the connection to the upload URl is successful 64 | 3. Verify that the connection to the API URL is successful 65 | :expectedresults: 66 | 1. The command executes successfully 67 | 2. The output contains the expected output about upload connection 68 | 3. The output contains expected message about API URL connection 69 | """ 70 | url_test = "End Upload URL Connection Test: SUCCESS" 71 | api_test = "End API URL Connection Test: SUCCESS" 72 | 73 | test_connection = insights_client.run("--test-connection", selinux_context=None) 74 | assert url_test in test_connection.stdout 75 | assert api_test in test_connection.stdout 76 | 77 | 78 | @pytest.mark.tier1 79 | def test_http_timeout(insights_client): 80 | """ 81 | :id: 46c5fe2a-1553-4f2e-802d-fa10080c72df 82 | :title: Test HTTP timeout configuration 83 | :description: 84 | Verifies that setting a very low http_timeout value causes 85 | the connection to time out 86 | :tags: Tier 1 87 | :steps: 88 | 1. Set the http_timeout option to a very low value and save 89 | 2. Run insights-client with the --test-conection option 90 | 3. Verify that the command fails due to the low timeout setting 91 | 4. Check that the timeout value is in the output 92 | :expectedresults: 93 | 1. The http_timeout configuration is set and saved 94 | 2. The insights-client command is run 95 | 3. The command fails with a return code of 1 96 | 4. The output mentions timeout value 97 | """ 98 | insights_client.config.http_timeout = 0.001 99 | insights_client.config.save() 100 | 101 | output = insights_client.run("--test-connection", check=False, selinux_context=None) 102 | assert output.returncode == 1 103 | 104 | if _is_using_proxy(insights_client.config): 105 | assert "timeout('timed out')" in output.stdout 106 | else: 107 | assert "Read timed out. (read timeout=0.001)" 108 | assert "Traceback" not in output.stdout 109 | 110 | 111 | @pytest.mark.tier1 112 | def test_noauth_proxy_connection(insights_client, test_config): 113 | """ 114 | :id: a4bcb7e6-c04f-49d2-8362-525124dc61d9 115 | :title: Test no-auth proxy connection 116 | :description: 117 | Verifies that the insights-client can successfully connect 118 | through a no-auth proxy when using the --test-connection option 119 | :tags: Tier 1 120 | :steps: 121 | 1. Configure insights-client to use no-auth proxy and save 122 | 2. Run insights-client with the --test-connection option 123 | 3. Verify the connection to the upload URL is successful 124 | 4. Verify that the connection to the API URL is successful 125 | :expectedresults: 126 | 1. Insights-client is configured to use no-auth proxy 127 | 2. The command is executed successfully 128 | 3. The output mentions that the upload URL connection was suuccessful 129 | 4. The output mentions that the API URL connection test was successful 130 | """ 131 | url_test = "End Upload URL Connection Test: SUCCESS" 132 | api_test = "End API URL Connection Test: SUCCESS" 133 | 134 | no_auth_proxy: str = ( 135 | "http://" 136 | + test_config.get("noauth_proxy", "host") 137 | + ":" 138 | + str(test_config.get("noauth_proxy", "port")) 139 | ) 140 | insights_client.config.proxy = no_auth_proxy 141 | insights_client.config.save() 142 | 143 | test_connection = insights_client.run("--test-connection", selinux_context=None) 144 | assert url_test in test_connection.stdout 145 | assert api_test in test_connection.stdout 146 | 147 | 148 | @pytest.mark.tier1 149 | def test_auth_proxy_connection(insights_client, test_config): 150 | """ 151 | :id: 0b3e91d6-3b8b-42c7-8f3b-f7ee1728c311 152 | :title: Test auth-proxy connection 153 | :description: 154 | Verifies that the insights-client can successfully connect 155 | through an authenticated proxy 156 | :tags: Tier 1 157 | :steps: 158 | 1. Configure insights-client to use auth proxy and save 159 | 2. Run the insights-client with --test-connection option 160 | 3. Verify the connection to the upload URL is successful 161 | 4. Verify that the connection to the API URL is successful 162 | :expectedresults: 163 | 1. Insights-client is configured to use auth proxy 164 | 2. The command is executed successfully 165 | 3. The output mentions that the upload URL connection was suuccessful 166 | 4. The output mentions that the API URL connection test was successful 167 | """ 168 | url_test = "End Upload URL Connection Test: SUCCESS" 169 | api_test = "End API URL Connection Test: SUCCESS" 170 | 171 | auth_proxy: str = ( 172 | "http://" 173 | + test_config.get("auth_proxy", "username") 174 | + ":" 175 | + test_config.get("auth_proxy", "password") 176 | + "@" 177 | + test_config.get("auth_proxy", "host") 178 | + ":" 179 | + str(test_config.get("auth_proxy", "port")) 180 | ) 181 | insights_client.config.proxy = auth_proxy 182 | insights_client.config.save() 183 | test_connection = insights_client.run("--test-connection", selinux_context=None) 184 | assert url_test in test_connection.stdout 185 | assert api_test in test_connection.stdout 186 | 187 | 188 | @pytest.mark.tier1 189 | def test_wrong_url_connection(insights_client): 190 | """ 191 | :id: aa411b34-9af2-4759-ae05-756e9019c85e 192 | :title: Test wrong URL connection 193 | :description: 194 | Verifies that the insights-client fails to connect when an 195 | incorrect URL is configured 196 | :tags: Tier 1 197 | :steps: 198 | 1. Disable auto-configuration and auto-updates 199 | 2. Set an incorrect base URL in the configuration 200 | 3. Run insights-client with the --test-connection option 201 | 4. Verify the command fails due to the incorrect URL 202 | 5. Check that the output contains the appropriate failure message 203 | :expectedresults: 204 | 1. Auto-configuration and auto-updates are disabled 205 | 2. Incorrect base URL is set 206 | 3. Insights-client command is executed successfully 207 | 4. The command failed with a return code of 1 208 | 5. The message includes expected mentions of the failure 209 | """ 210 | insights_client.config.auto_config = False 211 | insights_client.config.auto_update = False 212 | insights_client.config.base_url = "no-such-insights-url.example.com/something" 213 | insights_client.config.authmethod = "CERT" 214 | insights_client.config.save() 215 | 216 | test_connection = insights_client.run("--test-connection", check=False, selinux_context=None) 217 | assert test_connection.returncode == 1 218 | 219 | if _is_using_proxy(insights_client.config): 220 | assert "Cannot connect to proxy." in test_connection.stdout 221 | else: 222 | assert "Failed to establish a new connection" in test_connection.stdout 223 | assert "Connectivity test failed!" in test_connection.stdout 224 | -------------------------------------------------------------------------------- /integration-tests/test_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import contextlib 10 | import glob 11 | import os 12 | import subprocess 13 | import pytest 14 | from pytest_client_tools.util import Version, loop_until 15 | 16 | 17 | @pytest.mark.usefixtures("register_subman") 18 | @pytest.mark.tier1 19 | def test_client_files_permission(insights_client): 20 | """ 21 | :id: e793cf5e-1c25-4e31-93c9-6f0465f50cae 22 | :title: Verify client files permission 23 | :description: 24 | Verify that the permission for the last upload file 25 | /etc/insights-client/.lastupload is set to 0644 26 | :reference: https://bugzilla.redhat.com/show_bug.cgi?id=1924990 27 | :tags: Tier 1 28 | :steps: 29 | 1. Remove /etc/insights-client/.lastupload if it exists 30 | 2. Register insights-client 31 | 3. Verify the file permissions 32 | :expectedresults: 33 | 1. The file /etc/insights-client/.lastupload does not exist 34 | 2. The insights-client is registered 35 | 3. The permission of /etc/insights-client/.lastupload is set to 0644 36 | """ 37 | file_last_upload = "/etc/insights-client/.lastupload" 38 | with contextlib.suppress(FileNotFoundError): 39 | os.remove(file_last_upload) # performing a cleanup before test 40 | insights_client.register() 41 | assert loop_until(lambda: insights_client.is_registered) 42 | assert oct(os.stat(file_last_upload).st_mode & 0o777) == "0o644" 43 | 44 | 45 | @pytest.fixture() 46 | def rpm_ql_insights_client(): 47 | cmd = ["rpm", "-ql", "insights-client"] 48 | list_file = subprocess.check_output(cmd, universal_newlines=True) 49 | return list_file.split("\n") 50 | 51 | 52 | @pytest.mark.parametrize( 53 | "filename", 54 | [ 55 | "/etc/insights-client/insights-client.motd", 56 | "/etc/insights-client/insights-client.conf", 57 | "/usr/lib/systemd/system/insights-client-boot.service", 58 | "/usr/lib/systemd/system/insights-client-results.path", 59 | "/usr/lib/systemd/system/insights-client-results.service", 60 | "/usr/lib/systemd/system/insights-client.service", 61 | "/usr/lib/systemd/system/insights-client.timer", 62 | "/usr/share/doc/insights-client/file-content-redaction.yaml.example", 63 | "/usr/share/doc/insights-client/file-redaction.yaml.example", 64 | "/usr/share/man/man5/insights-client.conf.5.gz", 65 | "/usr/share/man/man8/insights-client.8.gz", 66 | "/etc/logrotate.d/insights-client", 67 | ], 68 | ) 69 | @pytest.mark.tier1 70 | def test_client_rpm_mandatory_files(filename, rpm_ql_insights_client): 71 | """ 72 | :id: c7d2edbe-ae78-47e0-9b3d-ae1634c0ac79 73 | :title: Verify mandatory files for RPM 74 | :parametrized: yes 75 | :description: Verify the existence of mandatory files for the insights-client RPM 76 | :tags: Tier 1 77 | :steps: 78 | 1. List all files in the insights-client RPM package 79 | 2. Check if each mandatory file exists in the package 80 | :expectedresults: 81 | 1. A list of files is generated 82 | 2. All of the mandatory files are present in the RPM 83 | """ 84 | assert filename in rpm_ql_insights_client, f"{filename} is not in insights-client package" 85 | 86 | 87 | @pytest.mark.usefixtures("register_subman") 88 | @pytest.mark.tier1 89 | def test_client_logfiles_mask(insights_client): 90 | """ 91 | :id: 8f24500c-d0ff-4ab1-a2ae-3b99cbf68e36 92 | :title: Verify client logfiles permissions 93 | :description: 94 | Verify that the log files in 95 | /var/log/insights-client have the correct mode 0600 96 | :reference: https://bugzilla.redhat.com/show_bug.cgi?id=1955724 97 | :tags: Tier 1 98 | :steps: 99 | 1. Register insights-client 100 | 2. Check the file permission of each log file generated 101 | :expectedresults: 102 | 1. Insights-client is registered 103 | 2. The file permissions for all log files are 0600 104 | """ 105 | # It is necessary to perform some command using insights-client 106 | # to populate logs 107 | insights_client.register() 108 | logfiles = glob.glob("/var/log/insights-client/*.log*") 109 | for logfile in logfiles: 110 | assert oct(os.stat(logfile).st_mode & 0o777) == "0o600" 111 | 112 | 113 | @pytest.mark.tier1 114 | def test_client_logdir_permissions(): 115 | """ 116 | :id: 204b1d54-6d8f-4d87-9227-cf3924cc5bb8 117 | :title: Verify log directory permissions 118 | :description: 119 | Verify that the permissions on the directory 120 | /var/log/insights-client are set to 0700 121 | :tags: Tier 1 122 | :steps: Check the directory permissions of /var/log/insights-client 123 | :expectedresults: The directory permissions are set to 0700 124 | """ 125 | logdir_name = "/var/log/insights-client" 126 | assert oct(os.stat(logdir_name).st_mode & 0o777) == "0o700" 127 | 128 | 129 | @pytest.mark.usefixtures("register_subman") 130 | @pytest.mark.tier1 131 | def test_verify_logrotate_feature(insights_client): 132 | """ 133 | :id: 5442729f-c6bc-4322-aa17-facd538e9fc3 134 | :title: Verify Logrotate feature 135 | :description: Verify that the logrotate works properly for insights-client 136 | :reference: https://bugzilla.redhat.com/show_bug.cgi?id=1940267 137 | :tags: Tier 1 138 | :steps: 139 | 1. Ensure the logrotate configuration file exists 140 | 2. Register insights-client 141 | 3. Run the logrotate command 142 | 4. Verify that 2 new log files were created 143 | 5. Verify the size of insights-client.log 144 | 6. Verify the size of insights-client-payload.log 145 | :expectedresults: 146 | 1. The logrotate config file exists 147 | 2. The insights-client is registered 148 | 3. The logrotate command is executed successfully 149 | 4. Two new log files were created 150 | 5. The size of insights-client.log is 0B 151 | 6. The size of insights-client-payload.log is 0B 152 | """ 153 | 154 | logrotate_conf_file_path = "/etc/logrotate.d/insights-client" 155 | logdir = "/var/log/insights-client/" 156 | logfile_insights = f"{logdir}/insights-client.log" 157 | logfile_payload = f"{logdir}/insights-client-payload.log" 158 | 159 | assert os.path.exists(logrotate_conf_file_path), "logrotate is not configured" 160 | """ 161 | It is necessary to perform some command using insights-client 162 | to populate logs. 163 | Save the archive, to be used while register operation using --keep-archive. 164 | for example- 165 | [root@test ~]# insights-client --register --keep-archive 166 | Automatic scheduling for Insights has been enabled. 167 | Starting to collect Insights data for test 168 | Writing RHSM facts to /etc/rhsm/facts/insights-client.facts ... 169 | Uploading Insights data. 170 | Successfully uploaded report for test. 171 | View the Red Hat Insights console at https://console.redhat.com/insights/ 172 | Copying archive from /var/tmp/insights-client-qxl3vdqy/insights-test-date.tar.gz 173 | to /var/cache/insights-client/insights-test-date.tar.gz 174 | Insights archive retained in /var/cache/insights-client/insights-test-date.tar.gz 175 | """ 176 | reg_result = insights_client.run("--register", "--keep-archive") 177 | assert loop_until(lambda: insights_client.is_registered) 178 | 179 | archive_name = reg_result.stdout.split()[-1] 180 | insights_client.run( 181 | f"--payload={archive_name}", 182 | "--content-type=gz", 183 | ) 184 | number_of_log_files = len(os.listdir(logdir)) # count of files before rotation 185 | 186 | subprocess.check_call(["logrotate", "-vf", logrotate_conf_file_path]) 187 | assert os.path.getsize(logfile_insights) == 0 188 | assert os.path.getsize(logfile_payload) == 0 189 | number_of_files_after_logrotate = len(os.listdir(logdir)) 190 | assert number_of_files_after_logrotate == (number_of_log_files + 2) 191 | 192 | 193 | @pytest.mark.usefixtures("register_subman") 194 | @pytest.mark.tier1 195 | def test_insights_details_file_exists(insights_client): 196 | """ 197 | :id: 2ccc8e00-0e76-47fd-bdb2-27998c0094ab 198 | :title: Verify insights-client details file exists 199 | :description: 200 | Verify that the file /var/lib/insights/insights-client.json 201 | will not get recreated 202 | :reference: https://issues.redhat.com/browse/CCT-1082 203 | :tags: Tier 1 204 | :steps: 205 | 1. Register insights-client 206 | 2. Delete /var/lib/insights/insights-client.json if it exists 207 | 3. Run the --check-results command 208 | 4. Verify /var/lib/insights/insights-client.json is still not present 209 | :expectedresults: 210 | 1. Insights-client is registered 211 | 2. The file /var/lib/insights/insights-client.json does not exist 212 | 3. The --check-results command is executed successfully 213 | 4. The file /var/lib/insights/insights-client.json does not exists 214 | """ 215 | output_file = "/var/lib/insights/insights-details.json" 216 | insights_client.register() 217 | assert loop_until(lambda: insights_client.is_registered) 218 | 219 | # Deleting file manually 220 | with contextlib.suppress(FileNotFoundError): 221 | os.remove(output_file) 222 | insights_client.run("--check-results") 223 | # Verify that insights-details.json is not remade on egg >= 3.6.2 224 | if insights_client.core_version < Version(3, 6, 2): 225 | assert os.path.isfile(output_file) 226 | else: 227 | assert not os.path.isfile(output_file) 228 | -------------------------------------------------------------------------------- /src/insights_client/__init__.py: -------------------------------------------------------------------------------- 1 | """Gather and upload Insights data for Red Hat Insights""" 2 | 3 | import logging 4 | import os 5 | import subprocess 6 | import sys 7 | 8 | from insights.client import InsightsClient 9 | from insights.client.phase.v2 import get_phases 10 | from insights.client.config import InsightsConfig 11 | 12 | try: 13 | from .constants import InsightsConstants 14 | from .constants import CORE_SELINUX_POLICY 15 | 16 | # if there is a policy for insights-core, unconditionally try to interface 17 | # with SELinux: insights-client was built with a policy for insights-core, 18 | # so not being able to apply that is an hard failure 19 | if CORE_SELINUX_POLICY != "": 20 | import selinux 21 | 22 | SWITCH_CORE_SELINUX_POLICY = selinux.is_selinux_enabled() 23 | else: 24 | SWITCH_CORE_SELINUX_POLICY = False 25 | except ImportError: 26 | # The source file is build from 'constants.py.in' and is not 27 | # available during development 28 | class InsightsConstants(object): 29 | version = "development" 30 | 31 | CORE_SELINUX_POLICY = "" 32 | SWITCH_CORE_SELINUX_POLICY = False 33 | 34 | LOG_FORMAT = "%(asctime)s %(levelname)8s %(name)s:%(lineno)s %(message)s" 35 | NO_COLOR = os.environ.get("NO_COLOR") is not None 36 | 37 | MOTD_SRC = "/etc/insights-client/insights-client.motd" 38 | MOTD_FILE = "/etc/motd.d/insights-client" 39 | REGISTERED_FILE = "/etc/insights-client/.registered" 40 | UNREGISTERED_FILE = "/etc/insights-client/.unregistered" 41 | 42 | 43 | logger = logging.getLogger(__name__) 44 | 45 | 46 | def get_logging_config(): 47 | config = {} 48 | 49 | for arg in ["silent", "verbose"]: 50 | environ_variable = f"INSIGHTS_{arg.upper()}" 51 | environ_value = os.environ.get(environ_variable, "") 52 | 53 | if environ_value.lower() == "true": 54 | config[arg] = True 55 | else: 56 | cli_flag = f"--{arg}" 57 | config[arg] = cli_flag in sys.argv 58 | 59 | return config 60 | 61 | 62 | def set_up_logging(logging_config): 63 | if logging_config["silent"]: 64 | logger.setLevel(logging.FATAL) 65 | return 66 | elif not logging_config["verbose"]: 67 | return 68 | 69 | logger.setLevel(logging.DEBUG) 70 | 71 | handler = logging.StreamHandler(sys.stdout) 72 | formatter = logging.Formatter(LOG_FORMAT) 73 | handler.setFormatter(formatter) 74 | logger.addHandler(handler) 75 | 76 | 77 | def tear_down_logging(): 78 | for handler in logger.handlers: 79 | logger.removeHandler(handler) 80 | 81 | 82 | def debug_environ(environ): 83 | items = map(lambda item: f"{item[0]}={item[1]}", environ.items()) 84 | return " ".join(items) 85 | 86 | 87 | def debug_command(command, environ=None): 88 | if environ: 89 | full_command = [debug_environ(environ)] + command 90 | else: 91 | full_command = command 92 | # Please note that neither spaces nor any other special characters are quoted. 93 | return " ".join(full_command) 94 | 95 | 96 | def run_phase(phase): 97 | """Call the run script for the given phase.""" 98 | insights_command = [ 99 | sys.executable, 100 | os.path.join(os.path.dirname(__file__), "run.py"), 101 | ] + sys.argv[1:] 102 | 103 | logger.debug(f"Running phase '{phase['name']}'") 104 | 105 | insights_env = { 106 | "INSIGHTS_PHASE": str(phase["name"]), 107 | } 108 | env = os.environ 109 | env.update(insights_env) 110 | 111 | if SWITCH_CORE_SELINUX_POLICY: 112 | # SELinux context switch into insights-core is allowed and preferred 113 | context = selinux.context_new(selinux.getcon()[1]) 114 | source_type = selinux.context_type_get(context) 115 | 116 | if source_type in ("unconfined_t", "sysadm_t", "unconfined_service_t"): 117 | # Do not transition into insights-core context if we're running 118 | # in privileged context already. 119 | logger.debug(f"Staying in SELinux context {source_type}") 120 | else: 121 | # Do transition insights-core context if we're running in 122 | # other (unknown), confined context. 123 | logger.debug(f"Switching SELinux context from {source_type} to {CORE_SELINUX_POLICY}") 124 | selinux.context_type_set(context, CORE_SELINUX_POLICY) 125 | new_core_context = selinux.context_str(context) 126 | selinux.setexeccon(new_core_context) 127 | selinux.context_free(context) 128 | 129 | process = subprocess.Popen(insights_command, env=env) 130 | process.communicate() 131 | 132 | if SWITCH_CORE_SELINUX_POLICY: 133 | # setexeccon() in theory ought to reset the context for the next 134 | # execv*() after that execution; it does not seem to happen though, 135 | # so for now manually reset it 136 | selinux.setexeccon(None) 137 | logger.debug("Switched to the original SELinux context") 138 | 139 | if process.returncode == 0: 140 | logger.debug("phase '%s' successful", phase["name"]) 141 | update_motd_message() 142 | return 143 | 144 | if process.returncode not in [0, 100]: 145 | logger.debug( 146 | "phase '%s' failed with return code %d", 147 | phase["name"], 148 | process.returncode, 149 | ) 150 | 151 | if process.returncode >= 100: 152 | # 100 and 101 are unrecoverable, like post-unregistration, or 153 | # a machine not being registered yet, or simply a 'dump & die' 154 | # CLI option 155 | # * 100: Success, exit 156 | # * 101: Failure, exit 157 | sys.exit(process.returncode % 100) 158 | 159 | # Phase failed 160 | sys.exit(1) 161 | 162 | 163 | def update_motd_message(): 164 | """Update MOTD (after a phase was run). 165 | 166 | MOTD displays a message about system not being registered. Once a 167 | registration stamp file exists, we make that message go away by pointing 168 | /etc/motd.d/insights-client at an empty file. 169 | 170 | It is intentional that the message does not reappear if a system is then 171 | unregistered. Only if both the unregistered and the registered stamp files 172 | do not exist is a motd symlink created. 173 | 174 | The motd message could also be deliberately disabled by the users before 175 | registration, simply because they don't want to use insights-client, by 176 | pointing /etc/motd.d/insights-client at an empty file. 177 | """ 178 | if not os.path.exists(os.path.dirname(MOTD_FILE)): 179 | logger.debug( 180 | "directory '%s' does not exist, ignoring MOTD update request", 181 | os.path.dirname(MOTD_FILE), 182 | ) 183 | return 184 | 185 | if os.path.exists(MOTD_FILE) and os.path.samefile(os.devnull, MOTD_FILE): 186 | logger.debug("MOTD file points at /dev/null, ignoring MOTD update request") 187 | return 188 | 189 | motd_should_exist = not os.path.exists(REGISTERED_FILE) and not os.path.exists( 190 | UNREGISTERED_FILE 191 | ) 192 | 193 | if motd_should_exist: 194 | # .registered & .unregistered do not exist, MOTD should be displayed 195 | if not os.path.lexists(MOTD_FILE): 196 | logger.debug( 197 | ".registered and .unregistered do not exist; pointing the MOTD file '%s' to '%s'", 198 | MOTD_SRC, 199 | MOTD_FILE, 200 | ) 201 | try: 202 | os.symlink(MOTD_SRC, MOTD_FILE) 203 | except OSError as exc: 204 | logger.debug( 205 | "could not point the MOTD file '%s' to '%s': %s", 206 | MOTD_SRC, 207 | MOTD_FILE, 208 | exc, 209 | ) 210 | else: 211 | logger.debug( 212 | ".registered and .unregistered do not exist; file '%s' correctly points to '%s'", 213 | MOTD_SRC, 214 | MOTD_FILE, 215 | ) 216 | 217 | else: 218 | # .registered or .unregistered exist, MOTD should not be displayed 219 | if os.path.lexists(MOTD_FILE): 220 | logger.debug( 221 | ".registered or .unregistered exist; removing the MOTD file '%s'", 222 | MOTD_FILE, 223 | ) 224 | try: 225 | os.remove(MOTD_FILE) 226 | except OSError as exc: 227 | logger.debug("could not remove the MOTD file '%s': %s", MOTD_FILE, exc) 228 | else: 229 | logger.debug( 230 | ".registered or .unregistered exist; file '%s' correctly does not exist", 231 | MOTD_FILE, 232 | ) 233 | 234 | 235 | def _main(): 236 | """Initialize and run insights client.""" 237 | logging_config = get_logging_config() 238 | set_up_logging(logging_config) 239 | 240 | if SWITCH_CORE_SELINUX_POLICY: 241 | logger.debug("Running with SELinux") 242 | else: 243 | logger.debug("Running without SELinux") 244 | 245 | try: 246 | try: 247 | config = InsightsConfig(_print_errors=True, **logging_config).load_all() 248 | except ValueError as e: 249 | sys.stderr.write("ERROR: " + str(e) + "\n") 250 | sys.exit("Unable to load Insights Config") 251 | 252 | if config["version"]: 253 | print("Client: %s" % InsightsConstants.version) 254 | print("Core: %s" % InsightsClient().version()) 255 | return 256 | 257 | if os.getuid() != 0: 258 | sys.exit("Insights client must be run as root.") 259 | 260 | client = InsightsClient(config, False) # read config, but dont setup logging 261 | logger.debug( 262 | "Initialized. Client: %s, Core: %s", 263 | InsightsConstants.version, 264 | client.version(), 265 | ) 266 | 267 | # we now have access to the clients logging mechanism 268 | tear_down_logging() 269 | client.set_up_logging() 270 | 271 | for p in get_phases(): 272 | run_phase(p) 273 | except KeyboardInterrupt: 274 | sys.exit("Aborting.") 275 | 276 | 277 | if __name__ == "__main__": 278 | _main() 279 | -------------------------------------------------------------------------------- /integration-tests/test_upload.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import os 10 | import pytest 11 | from pytest_client_tools.util import loop_until 12 | 13 | pytestmark = pytest.mark.usefixtures("register_subman") 14 | 15 | 16 | @pytest.mark.tier1 17 | def test_upload_pre_collected_archive(insights_client, tmp_path): 18 | """ 19 | :id: 9eba5a67-d013-4d43-98c7-c41ed38bcede 20 | :title: Test Upload of Pre-Collected Archive 21 | :description: 22 | This test verifies that a pre-collect insights-archive 23 | can be uploaded using --payload operation. 24 | :tags: Tier 1 25 | :steps: 26 | 1. Register insights-client 27 | 2. Run insights-client in an offline mode to generate an archive 28 | and save it 29 | 3. Run the insights-client with the --payload option and valid --content-type 30 | 4. Verify the successful upload of the archive 31 | :expectedresults: 32 | 1. Insights-client is registered 33 | 2. The archive is successfully generated and saved 34 | 3. The upload process starts and the output message is as expected 35 | 4. The upload completes successfully with the message as expected 36 | """ 37 | archive_name = "archive.tar.gz" 38 | archive_location = tmp_path / archive_name 39 | 40 | # Registering the client because upload can happen on registered system 41 | insights_client.register() 42 | assert loop_until(lambda: insights_client.is_registered) 43 | 44 | # Running insights-client in offline mode to generate archive and save at tmp dir 45 | insights_client.run( 46 | f"--output-file={archive_location}", 47 | selinux_context=None, # using custom archive location, not a service action 48 | ) 49 | 50 | # Running insights-client --payload with --content-type to upload archive 51 | # collected in previous step 52 | upload_result = insights_client.run( 53 | f"--payload={archive_location}", 54 | "--content-type=gz", 55 | selinux_context=None, # using custom archive location, not a service action 56 | ) 57 | assert "Uploading Insights data." in upload_result.stdout 58 | assert "Successfully uploaded report" in upload_result.stdout 59 | 60 | 61 | @pytest.mark.tier1 62 | def test_upload_wrong_content_type(insights_client, tmp_path): 63 | """ 64 | :id: bb9ee84a-d262-4c42-ae16-9b45bf5a385c 65 | :title: Test Upload with Wrong Content Type 66 | :description: 67 | This test verifies that uploading an archive with wrong content 68 | type throws appropriate error message. Generate an archive and upload using 69 | --payload but wrong --content-type 70 | :tags: Tier 1 71 | :steps: 72 | 1. Register insights-client 73 | 2. Run the insights-client in offline mode to generate an archive and save it 74 | 3. Run the insights-client with --payload option and invalid --content-type 75 | 4. Run the insigts-client with a valid --content-type but different from 76 | compressor used 77 | :expectedresults: 78 | 1. Insights-client is registered 79 | 2. The archive is generated and saved 80 | 3. The upload process fails with the appropriate message 81 | 4. The upload process fails with the appropriate message 82 | """ 83 | archive_name = "archive.tar.gz" 84 | archive_location = tmp_path / archive_name 85 | 86 | # Registering the client because upload can happen on registered system 87 | insights_client.register() 88 | assert loop_until(lambda: insights_client.is_registered) 89 | 90 | # Running insights-client in offline mode to generate archive and save at tmp dir 91 | insights_client.run( 92 | f"--output-file={archive_location}", 93 | selinux_context=None, # using custom archive location, not a service action 94 | ) 95 | 96 | # Running insights-client --payload with invalid --content-type to upload archive 97 | # collected in previous step 98 | upload_result = insights_client.run( 99 | f"--payload={archive_location}", 100 | "--content-type=bzip", 101 | check=False, 102 | selinux_context=None, # using custom archive location, not a service action 103 | ) 104 | assert "Invalid content-type." in upload_result.stdout 105 | 106 | # trying to upload with a valid content type but different from compressor 107 | upload_result = insights_client.run( 108 | f"--payload={archive_location}", 109 | "--content-type=xz", 110 | check=False, 111 | selinux_context=None, # using custom archive location, not a service action 112 | ) 113 | assert "Content type different from compression" in upload_result.stdout 114 | 115 | 116 | @pytest.mark.tier1 117 | def test_upload_too_large_archive(insights_client, tmp_path): 118 | """ 119 | :id: a71c2b00-a472-4070-8dc8-f66dc9467c10 120 | :title: Test Upload of Too Large Archive 121 | :description: 122 | This test verifies that an attempt to upload too large archive 123 | results in failure 124 | :tags: Tier 1 125 | :steps: 126 | 1. Register insights-client 127 | 2. Create a large archive file in the temporary directory 128 | 3. Run insights-client with --payload option and --content-type 129 | pointing to the archive 130 | :expectedresults: 131 | 1. Insights-client is registered 132 | 2. A large archive is created in the temporary directory 133 | 3. The upload process fails with an appropriate message 134 | """ 135 | insights_client.register() 136 | assert loop_until(lambda: insights_client.is_registered) 137 | 138 | file_path = tmp_path / "large_file.tar.gz" 139 | file_size = 100 * 1024 * 1024 # 100mb 140 | with open(file_path, "wb") as f: 141 | f.seek(int(file_size) + 1) 142 | f.write(b"\0") 143 | 144 | upload_result = insights_client.run(f"--payload={file_path}", "--content-type=gz", check=False) 145 | 146 | assert "Archive is too large to upload" in upload_result.stdout 147 | assert "Upload failed." in upload_result.stdout 148 | 149 | 150 | @pytest.mark.parametrize( 151 | "compressor,expected_extension", 152 | [ 153 | ("gz", ".gz"), 154 | ("bz2", ".bz2"), 155 | ("xz", ".xz"), 156 | ], 157 | ) 158 | @pytest.mark.tier1 159 | def test_upload_compressor_options( 160 | insights_client, 161 | compressor, 162 | expected_extension, 163 | ): 164 | """ 165 | :id: 69a06826-6093-46de-a7a6-9726ae141820 166 | :title: Test upload with Different Compressor Options 167 | :parametrized: yes 168 | :description: 169 | This test verifies that valid compression types can be used 170 | with --compressor to create archives and upload data using --payload 171 | :tags: Tier 1 172 | :steps: 173 | 1. Register insights-client 174 | 2. Run insights-client with --compressor option to generate an archive 175 | with specified type 176 | 3. Verify the archive has the correct file extension based on the compression 177 | type 178 | :expectedresults: 179 | 1. Insights-client is registered 180 | 2. The archive is successfully generated 181 | 3. The file has expected file extension 182 | """ 183 | insights_client.register() 184 | assert loop_until(lambda: insights_client.is_registered) 185 | 186 | # using --compressor option to generate and save archive 187 | command_result = insights_client.run(f"--compressor={compressor}", "--no-upload") 188 | archive_name = command_result.stdout.split()[-1] 189 | 190 | # Verifying that archive is created with expected extension 191 | assert os.path.splitext(archive_name)[1] == expected_extension 192 | assert (os.path.splitext(archive_name)[0]).endswith(".tar") 193 | 194 | # Now try to upload the pre-collected archive 195 | upload_result = insights_client.run(f"--payload={archive_name}", f"--content-type={compressor}") 196 | assert "Uploading Insights data." in upload_result.stdout 197 | assert "Successfully uploaded report" in upload_result.stdout 198 | 199 | 200 | @pytest.mark.tier1 201 | def test_retries(insights_client): 202 | """ 203 | :id: dafeb86e-463e-42fd-88e5-4551f1ba8f66 204 | :title: Test Retries on Upload Failure 205 | :description: 206 | This test verifies that client tries to upload archive if upload 207 | fails. Setting retries to 2 only because between each attempt the wait time is 208 | 180 sec. Set wrong base_url in insights-client.config to fail upload operation 209 | :tags: Tier 1 210 | :steps: 211 | 1. Register insights-client 212 | 2. Save the archive 213 | 3. Modify the configuration to use a non-existent base URL 214 | 4. Run insights-client with --payload option specifying --retry=2 215 | to attempt upload twice 216 | 5. verify the retry mechanism 217 | 6. verify the final failure message 218 | :expectedresults: 219 | 1. Insights-client is registered 220 | 2. Archive is saved 221 | 3. The configuration is modified and saved 222 | 4. The command is run 223 | 5. Each of the retry failed with expected error message 224 | 6. the final error message is as expected 225 | """ 226 | reg_result = insights_client.run("--register", "--keep-archive") 227 | assert loop_until(lambda: insights_client.is_registered) 228 | 229 | # Save the archive, to be used while upload operation 230 | archive_name = reg_result.stdout.split()[-1] 231 | 232 | # Modifying config to break connection 233 | insights_client.config.auto_config = False 234 | insights_client.config.base_url = "non-existent-url.redhat.com" 235 | insights_client.config.save() 236 | 237 | # Now try to upload the pre-collected archive with retry=2 , default content=type gz 238 | upload_result = insights_client.run( 239 | f"--payload={archive_name}", "--content-type={gz}", "--retry=2", check=False 240 | ) 241 | 242 | assert "Upload attempt 1 of 2 failed" in upload_result.stdout 243 | assert "Upload attempt 2 of 2 failed" in upload_result.stdout 244 | assert "Waiting 180 seconds then retrying" in upload_result.stdout 245 | assert "All attempts to upload have failed!" in upload_result.stdout 246 | 247 | 248 | @pytest.mark.tier1 249 | def test_retries_not_happening_on_unrecoverable_errors(insights_client): 250 | """ 251 | :id: 1d740d1c-e98b-4571-86ac-10a233ff65ce 252 | :title: Test No Retries on Uncoverable Errors 253 | :description: 254 | This test verifies that client retries won't happen during 255 | unrecoverable errors. The client should try to upload just once and then fail. 256 | :tags: Tier 1 257 | :steps: 258 | 1. Register insights-client 259 | 2. Save the archive 260 | 3. Run insights-client with --payload option specifying invalid --content-type 261 | 4. Verify the output of the command 262 | 5. Verify no-retries occur 263 | :expectedresults: 264 | 1. Insights-client is registered 265 | 2. Archive is saved 266 | 3. The command is run 267 | 4. The process fails with an appropriate message 268 | 5. No retries occurred 269 | """ 270 | reg_result = insights_client.run("--register", "--keep-archive") 271 | assert loop_until(lambda: insights_client.is_registered) 272 | 273 | # Save the archive, to be used while upload operation 274 | archive_name = reg_result.stdout.split()[-1] 275 | 276 | # Pass invalid content type to mock unrecoverable errors 277 | upload_result = insights_client.run( 278 | f"--payload={archive_name}", 279 | "--content-type=invalid-type", 280 | "--retry=2", 281 | check=False, 282 | ) 283 | assert "Upload failed." in upload_result.stdout 284 | assert "Upload attempt 1 of 2 failed" not in upload_result.stdout 285 | -------------------------------------------------------------------------------- /integration-tests/test_display_name_option.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import string 10 | import pytest 11 | import uuid 12 | import logging 13 | import json 14 | import random 15 | import subprocess 16 | from pytest_client_tools.util import loop_until 17 | 18 | from constants import HOST_DETAILS 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | pytestmark = pytest.mark.usefixtures("register_subman") 23 | 24 | 25 | def read_host_details(): 26 | with open(HOST_DETAILS, "r") as data_file: 27 | return json.load(data_file) 28 | 29 | 30 | def generate_unique_hostname(): 31 | return f"test-qa.{uuid.uuid4()}.csi-client-tools.example.com" 32 | 33 | 34 | def create_random_string(n: int): 35 | return "".join(random.choices(string.ascii_letters, k=n)) 36 | 37 | 38 | @pytest.mark.tier1 39 | def test_display_name(insights_client, test_config): 40 | """ 41 | :id: 4758cb21-03b4-4334-852c-791b7c82b50a 42 | :title: Test updating display name via '--display-name' 43 | :description: 44 | This test verifies that a registered host's display name can be 45 | updated using the --display-name option. 46 | :tags: Tier 1 47 | :steps: 48 | 1. Generate a unique hostname and register the insights-client 49 | 2. Update the display name using --display-name 50 | 3. Verify the display name has been updated in the host details 51 | :expectedresults: 52 | 1. A unique hostname is generated and insights-client is registered 53 | 2. The command outputs 'Display name updated to ' 54 | 3. The display_name in host detailes matches the new hostname 55 | """ 56 | if "satellite" in test_config.environment: 57 | pytest.skip(reason="Test is not applicable to Satellite") 58 | new_hostname = generate_unique_hostname() 59 | insights_client.run("--register") 60 | assert loop_until(lambda: insights_client.is_registered) 61 | 62 | response = insights_client.run("--display-name", new_hostname) 63 | logger.debug(f"response from console {response}") 64 | 65 | assert f"Display name updated to {new_hostname}" in response.stdout 66 | 67 | def display_name_changed(): 68 | insights_client.run("--check-results") 69 | host_details = read_host_details() 70 | logger.debug(f"host details {host_details}") 71 | record = host_details["results"][0] 72 | return new_hostname == record["display_name"] 73 | 74 | assert loop_until(display_name_changed) 75 | 76 | 77 | @pytest.mark.tier1 78 | def test_register_with_display_name(insights_client): 79 | """ 80 | :id: d127b2bf-2f6d-4b02-bb8e-99036bfc4291 81 | :title: Test registration with custom display name 82 | :description: 83 | This test ensures that registering the insights-client with a custom 84 | display name sets the display name correctly in host details 85 | :tags: Tier 1 86 | :steps: 87 | 1. Generate a unique hostname 88 | 2. Register the insights-client using '--register --display-name' 89 | 3. Verify the display_name in host details 90 | :expectedresults: 91 | 1. Unique hostname is generated 92 | 2. The client registers and successfully outputs the unique hostname 93 | 3. The display_name in host details matches the unique hostname 94 | """ 95 | unique_hostname = generate_unique_hostname() 96 | 97 | status = insights_client.run("--register", "--display-name", unique_hostname) 98 | assert loop_until(lambda: insights_client.is_registered) 99 | assert unique_hostname in status.stdout 100 | insights_client.run("--check-results") 101 | host_details = read_host_details() 102 | logger.debug(f"content of host-details.json {host_details}") 103 | 104 | record = host_details["results"][0] 105 | assert "display_name" in record.keys() 106 | assert unique_hostname == record["display_name"] 107 | 108 | 109 | @pytest.mark.tier1 110 | def test_register_twice_with_different_display_name(insights_client, test_config, subtests): 111 | """ 112 | :id: 3d28562d-16c4-4fb1-b9b1-f39044e05ef5 113 | :title: Test re-registration with different display names 114 | :description: 115 | This test checks that registering the insights-client twice with different 116 | display names does not change the insights_id and display_name is updated 117 | :tags: Tier 1 118 | :steps: 119 | 1. Generate a unique hostname 120 | 2. Register the insights-client using '--register --display-name' 121 | 3. Record the machine ID and display_name 122 | 4. Generate another unique hostname 123 | 5. Register the insights-client using '--register --display_name' 124 | 6. Compare the old display_name and insights_id with those updated 125 | :expectedresults: 126 | 1. Unique hostname is generated 127 | 2. The client registers and successfully outputs the unique hostname 128 | 3. Information are successfully retrieved and stored 129 | 4. Unique hostname is generated 130 | 5. The output indicates that the host is already registered but updates the 131 | display_name to the new one 132 | 6. Insights_id stayed unchanged while display_name changed to the latest one 133 | """ 134 | insights_id = None 135 | unique_hostname = generate_unique_hostname() 136 | unique_hostname_02 = generate_unique_hostname() 137 | 138 | with subtests.test(msg="the first registration"): 139 | status = insights_client.run("--register", "--display-name", unique_hostname) 140 | assert unique_hostname in status.stdout 141 | 142 | assert loop_until(lambda: insights_client.is_registered) 143 | insights_client.run("--check-results") 144 | host_details = read_host_details() 145 | record = host_details["results"][0] 146 | assert "display_name" in record.keys() 147 | assert unique_hostname == record["display_name"] 148 | insights_id = record["insights_id"] 149 | 150 | (status, host_details, record) = (None, None, None) 151 | with subtests.test(msg="The second registration"): 152 | status = insights_client.run("--register", "--display-name", unique_hostname_02) 153 | registration_message = "This host has already been registered" 154 | assert registration_message in status.stdout 155 | 156 | assert loop_until(lambda: insights_client.is_registered) 157 | insights_client.run("--check-results") 158 | host_details = read_host_details() 159 | logger.debug(f"content of host-details.json: {host_details}") 160 | record = host_details["results"][0] 161 | assert "display_name" in record.keys() 162 | assert unique_hostname_02 == record["display_name"] 163 | assert ( 164 | insights_id == record["insights_id"] 165 | ), "machine-id should remain the same even display-name has been changed" 166 | 167 | 168 | @pytest.mark.parametrize("invalid_display_name", [create_random_string(201), ""]) 169 | @pytest.mark.tier1 170 | def test_invalid_display_name(invalid_display_name, insights_client): 171 | """ 172 | :id: 9cbdd1a6-9ee3-4799-baaf-15c3894ca55b 173 | :title: Test handling of invalid display names 174 | :parametrized: yes 175 | :description: 176 | This test verifies that attempting to set an invalid display_name is rejected 177 | and does not alter the current display_name value 178 | :tags: Tier 1 179 | :steps: 180 | 1. Register the insights-client 181 | 2. Record the original display_name value 182 | 3. Attempt to update the display_name using an invalid value 183 | 4. Verify that display_name stayed unchanged 184 | :expectedresults: 185 | 1. Insights-client is registered 186 | 2. Original display_name value is saved 187 | 3. The command fails with an error code 1 and a message 188 | 'Could not update display name' 189 | 4. The display_name in host details matches the saved original 190 | """ 191 | insights_client.run("--register") 192 | assert loop_until(lambda: insights_client.is_registered) 193 | 194 | insights_client.run("--check-results") 195 | host_details = read_host_details() 196 | origin_display_name = host_details["results"][0]["display_name"] 197 | 198 | response = insights_client.run("--display-name", invalid_display_name, check=False) 199 | assert response.returncode == 1 200 | assert "Could not update display name" in response.stdout 201 | 202 | insights_client.run("--check-results") 203 | host_details = read_host_details() 204 | logger.debug(f"content of host-details.json {host_details}") 205 | 206 | record = host_details["results"][0] 207 | assert ( 208 | origin_display_name == record["display_name"] 209 | ), "display-name should remain unchanged when new display-name is rejected" 210 | 211 | 212 | @pytest.mark.tier2 213 | def test_display_name_disable_autoconfig_and_autoupdate(insights_client, test_config): 214 | """ 215 | :id: 8cdbc0ff-42ba-41e8-bd3f-31550ccac081 216 | :title: Test registration with display_name when auto-config and auto-update 217 | are disabled 218 | :description: 219 | This test verifies that the insights-client can be registered with a 220 | display_name name even when auto_config and auto_update are set to 221 | False and host appears on cloud with the correct display name 222 | :reference: https://issues.redhat.com/browse/RHEL-19435 223 | :tags: Tier 2 224 | :steps: 225 | 1. Configure insights-client.conf with auto_config and auto_update set to False 226 | and display_name set 227 | 2. Register the insights-client 228 | 3. Verify the host is visible in cloud.redhat.com with the correct display_name 229 | set in the configuration file 230 | :expectedresults: 231 | 1. Configuration is set and successfully saved 232 | 2. Insights-client is registered 233 | 3. Host appears in inventory with the display name matching the one that was set 234 | """ 235 | # configuration on insights-client.conf 236 | insights_client.config.legacy_upload = False 237 | insights_client.config.cert_verify = True 238 | insights_client.config.auto_update = False 239 | insights_client.config.auto_config = False 240 | insights_client.config.authmethod = "CERT" 241 | unique_hostname = generate_unique_hostname() 242 | insights_client.config.display_name = unique_hostname 243 | if "satellite" in test_config.environment: 244 | satellite_hostname = test_config.get("candlepin", "host") 245 | satellite_port = test_config.get("candlepin", "port") 246 | insights_client.config.base_url = ( 247 | satellite_hostname + ":" + str(satellite_port) + "/redhat_access/r/insights" 248 | ) 249 | insights_client.config.cert_verify = "/etc/rhsm/ca/katello-server-ca.pem" 250 | elif "prod" in test_config.environment: 251 | insights_client.config.base_url = "cert.cloud.redhat.com/api" 252 | elif "stage" in test_config.environment: 253 | insights_client.config.base_url = "cert.cloud.stage.redhat.com/api" 254 | insights_client.config.save() 255 | 256 | # register insights 257 | try: 258 | status = insights_client.run("--register") 259 | except subprocess.CalledProcessError as e: 260 | if "certificate verify failed" in e.stdout.lower() or "certificate verify failed" in str(e): 261 | pytest.skip("Skipping test due to SSL certificate verification failure") 262 | raise 263 | assert loop_until(lambda: insights_client.is_registered) 264 | assert unique_hostname in status.stdout 265 | 266 | # check the display name on CRC 267 | insights_client.run("--check-results") 268 | host_details = read_host_details() 269 | logger.debug(f"content of host-details.json {host_details}") 270 | record = host_details["results"][0] 271 | assert unique_hostname == record["display_name"] 272 | -------------------------------------------------------------------------------- /integration-tests/test_collection.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import contextlib 10 | import os 11 | import tarfile 12 | import pytest 13 | from pytest_client_tools.util import loop_until 14 | 15 | 16 | @pytest.mark.tier1 17 | def test_output_file_valid_parameters(insights_client, tmp_path): 18 | """ 19 | :id: 011e38c7-c6dc-4c17-add4-d67f0775f5cd 20 | :title: Test --output-file with valid parameters 21 | :description: 22 | This test verifies that the --output-file option correctly 23 | creates a new archive file when provided with a valid path 24 | :tags: Tier 1 25 | :steps: 26 | 1. Define the archive name path 27 | 2. Run insights-client in an offline mode with --output-file 28 | pointing to the archive name 29 | 3. Verify that a new archive file is created 30 | 4. Verify that the command output confirms the collected 31 | data was copied to the new archive 32 | :expectedresults: 33 | 1. The archive name path is set correctly 34 | 2. The insights-client runs successfully with the specified output file path 35 | 3. A new archive file are created at the specified location 36 | 4. The command output confirms that the collected data was copied to the new 37 | archive 38 | """ 39 | archive_name = tmp_path / "archive" 40 | 41 | # Running insights-client in offline mode to generate archive 42 | cmd_result = insights_client.run( 43 | f"--output-file={archive_name}", 44 | selinux_context=None, # using custom archive location, not a service related option 45 | ) 46 | assert os.path.isfile(f"{archive_name}.tar.gz") 47 | assert f"Collected data copied to {archive_name}.tar.gz" in cmd_result.stdout 48 | 49 | 50 | @pytest.mark.tier1 51 | def test_output_file_non_existing_path(insights_client): 52 | """ 53 | :id: 003faa12-8daa-417b-83bb-71aaa80209b6 54 | :title: Test --output-file with non-existing path 55 | :description: 56 | Checks that the --output-file option fails with an appropriate error 57 | message when provided with a non-existent path 58 | :tags: Tier 1 59 | :steps: 60 | 1. Define an archive file path in a non-existing directory 61 | and define the parent directory path 62 | 2. Run insights-client with the --output-file option pointing 63 | to an non-existing path 64 | 3. Verify that the command output fails with a return code of 1 65 | 4. Verify that the error message indicates the parent directory 66 | does not exist 67 | :expectedresults: 68 | 1. The archive path is set to a non-existent directory 69 | and the parent directory path is correctly derived 70 | 2. The insights-client runs with the specified output file path 71 | 3. The command returns code of 1 72 | 4. The error message contains the expected message 73 | """ 74 | archive_location = "/not-existing-dir/archive_file.tar.gz" 75 | parent_dir = os.path.dirname(archive_location) 76 | cmd_result = insights_client.run(f"--output-file={archive_location}", check=False) 77 | assert cmd_result.returncode == 1 78 | assert ( 79 | f"Cannot write to {archive_location}. Parent" 80 | f" directory {parent_dir} does not exist." in cmd_result.stderr 81 | ) 82 | 83 | 84 | @pytest.mark.tier1 85 | def test_output_dir_without_specifying_a_path(insights_client): 86 | """ 87 | :id: 63eb1ba0-b9dd-4185-b422-18325c12b503 88 | :title: Test --output-dir without specifying a path 89 | :description: 90 | Verifies that the --output-dir option fails with an appropriate 91 | error message when no path is specified 92 | :tags: Tier 1 93 | :steps: 94 | 1. Run insights-client with an empty --output-file option 95 | 2. Verify that the command fails with a return code of 1 96 | 3. Verify that the error message indicates that --output-file cannot be empty 97 | :expectedresults: 98 | 1. The insights-client runs with an empty option 99 | 2. The command fails with a return code of 1 100 | 3. The error message is as expected 101 | """ 102 | cmd_result = insights_client.run("--output-file=", check=False) 103 | assert cmd_result.returncode == 1 104 | assert "ERROR: --output-file cannot be empty" in cmd_result.stderr 105 | 106 | 107 | @pytest.mark.tier1 108 | def test_output_specifying_both_dir_and_file(insights_client, tmp_path): 109 | """ 110 | :id: a572654d-904c-410e-ba00-f7b63f910e53 111 | :title: Test --output-file specifying both directory and file 112 | :description: 113 | Verifies that specifying both output file and output directory 114 | option together fails with an appropriate error message 115 | :tags: Tier 1 116 | :steps: 117 | 1. Define both output file and directory paths 118 | 2. Run insights-client with --output-file and --output-dir 119 | 3. Verify the command fails with an error code of 1 120 | 4. Verify the error message says they can't be used together 121 | :expectedresults: 122 | 1. the output file and directory paths are set correctly 123 | 2. The insights-client runs with specified options 124 | 3. The command fails with a return code of 1 125 | 4. The error message is as expected 126 | """ 127 | # Parent directory does not exist 128 | output_file = tmp_path / "output_file.tar.gz" 129 | output_dir = tmp_path 130 | cmd_result = insights_client.run( 131 | f"--output-file={output_file}", f"--output-dir={output_dir}", check=False 132 | ) 133 | assert cmd_result.returncode == 1 134 | assert "Specify only one: --output-dir or --output-file." in cmd_result.stderr 135 | 136 | 137 | @pytest.mark.tier1 138 | def test_output_file_with_relative_path(insights_client): 139 | """ 140 | :id: e1236f11-46c3-452c-81b8-3c61506e1841 141 | :title: Test --output-file with relative path 142 | :description: 143 | Verifies that using a relative path that points to a directory 144 | with the --output-file option fails with an appropriate error message 145 | :tags: Tier 1 146 | :steps: 147 | 1. Define a relative path pointing to a directory 148 | 2. Run insights-client with the --output file option pointing to 149 | this relative path 150 | 3. Verify that the command fails with a return code of 1 151 | 4. Verify the error message says that --output 152 | cannot be used with a directory 153 | :expectedresults: 154 | 1. The relative path is correctly 155 | 2. The insights-client runs with the specified option 156 | 3. The command fails with an exit code of 1 157 | 4. The error message is as expected 158 | """ 159 | relative_path = os.path.realpath("") 160 | cmd_result = insights_client.run(f"--output-file={relative_path}", check=False) 161 | assert cmd_result.returncode == 1 162 | assert f"{relative_path} is a directory." in cmd_result.stderr 163 | 164 | 165 | @pytest.mark.tier1 166 | def test_output_dir_with_not_empty_directory(insights_client): 167 | """ 168 | :id: 30384d37-92b4-4196-8423-0355b0cbef30 169 | :title: Test --output-dir with non-empty directory 170 | :description: 171 | Verify that when the --output-dir option is used with an existing 172 | non-empty directory, the command fails with an appropriate error message 173 | :tags: Tier 1 174 | :steps: 175 | 1. Define a path to an existing non-empty directory 176 | 2. Run insights-client with --output-dir option specified 177 | 3. Verify the command fails with a return code of 1 178 | 4. Verify the error message is as expected 179 | :expectedresults: 180 | 1. The path to the non-empty directory is set correctly 181 | 3. The insights-client is run 182 | 4. The command fails with a return code of 1 183 | 5. The error message is as expected 184 | """ 185 | relative_path = os.path.realpath("") 186 | cmd_result = insights_client.run( 187 | f"--output-dir={relative_path}", 188 | check=False, 189 | selinux_context=None, # using custom archive location, not a service related option 190 | ) 191 | assert cmd_result.returncode == 1 192 | assert f"Directory {relative_path} already exists and is not empty." in cmd_result.stderr 193 | 194 | 195 | @pytest.mark.tier1 196 | def test_output_dir_creates_archive_for_directory(insights_client, tmp_path): 197 | """ 198 | :id: 6a966179-3b61-4a74-8af4-68fa0c2c237e 199 | :title: Test --output-file creates archive for directory 200 | :description: 201 | Checks that when the --output-file option is used with a path 202 | that is an existing directory, the insights-client correctly generates 203 | an archive with the directory name appended with .tar.gz 204 | :tags: Tier 1 205 | :steps: 206 | 1. Define a directory path 207 | 2. Run insights-client with the --output-file option pointing to that directory 208 | 3. Verify that the command creates an archive file with the directory name 209 | and extension 210 | 4. Clean up by removing the generated archive file 211 | :expectedresults: 212 | 1. The directory is set correctly 213 | 2. The insights-client runs with the specified dir path 214 | 3. An archive file with the directory name is created 215 | 4. The generated archive file is successfully removed 216 | """ 217 | directory = "/tmp/directory/" 218 | try: 219 | cmd_result = insights_client.run(f"--output-file={directory}", check=False) 220 | assert os.path.abspath(directory[0:-1] + ".tar.gz") in cmd_result.stdout 221 | finally: 222 | os.remove(os.path.abspath(directory[0:-1] + ".tar.gz")) 223 | 224 | 225 | @pytest.mark.tier1 226 | def test_output_file_already_exists(insights_client, tmp_path): 227 | """ 228 | :id: 36456352-da31-4633-872a-061c2045176a 229 | :title: Test --output-file already exists 230 | :description: 231 | Verify that when the --output-file option is used with a path 232 | that points to an existing archive file, the command fails with an 233 | appropriate error message 234 | :tags: Tier 1 235 | :steps: 236 | 1. Create an archive file at a specified location 237 | 2. Run insights-client with the --output-file option to this existing archive 238 | 3. Verify the command fails with a return code of 1 239 | 4. Verify that the error message indicates that the file already exists 240 | :expectedresults: 241 | 1. The archive file is created at the specified location 242 | 2. The insights-client is run with the specified output file path 243 | 3. The command fails with a return code of 1 244 | 4. The error message indicates that the file already exists 245 | """ 246 | # File should already exist 247 | output_file = tmp_path / "output_file.tar.gz" 248 | with contextlib.suppress(Exception): 249 | with tarfile.open(output_file, "w:gz"): 250 | pass 251 | 252 | cmd_result = insights_client.run(f"--output-file={output_file}", check=False) 253 | assert cmd_result.returncode == 1 254 | assert f"ERROR: File {output_file} already exists." in cmd_result.stderr 255 | 256 | 257 | @pytest.mark.usefixtures("register_subman") 258 | @pytest.mark.tier2 259 | def test_cmd_timeout(insights_client): 260 | """ 261 | :id: 0a55318c-28e0-4ca7-bdbf-3eb8c0d689ea 262 | :title: Test cmd_timeout configuration 263 | :description: 264 | Verify that the cmd_timeout configuration correctly limits the 265 | execution time of commands, killing them if they exceed said limit 266 | :tags: Tier 2 267 | :steps: 268 | 1. Register insights-client 269 | 2. Set the cmd_timeout option to 10 seconds 270 | 3. Run insights-client with a command that would take longer than the 271 | the timeout limit 272 | 4. Verify the command is terminated after 10 seconds 273 | 5. Verify that the output includes the expected message 274 | :expectedresults: 275 | 1. The insights-client is registered 276 | 2. The cmd_timeout option is set to 10 seconds 277 | 3. The command is run with the specified timeout 278 | 4. The command is terminated after 10 seconds 279 | 5. The output contains "Executing: [['timeout', '-s', '9', '10'" 280 | """ 281 | cmd_output_message = "Executing: [['timeout', '-s', '9', '10'" 282 | 283 | insights_client.register() 284 | assert loop_until(lambda: insights_client.is_registered) 285 | 286 | insights_client.config.cmd_timeout = 10 287 | insights_client.config.save() 288 | 289 | timeout_output = insights_client.run("--verbose", check=False, selinux_context=None) 290 | assert cmd_output_message in timeout_output.stdout 291 | -------------------------------------------------------------------------------- /integration-tests/test_registration.py: -------------------------------------------------------------------------------- 1 | """ 2 | :casecomponent: insights-client 3 | :requirement: RHSS-291297 4 | :subsystemteam: rhel-sst-csi-client-tools 5 | :caseautomation: Automated 6 | :upstream: Yes 7 | """ 8 | 9 | import os 10 | import pytest 11 | import contextlib 12 | from pytest_client_tools.util import Version, loop_until 13 | from constants import MACHINE_ID_FILE 14 | 15 | pytestmark = pytest.mark.usefixtures("register_subman") 16 | 17 | 18 | @pytest.mark.tier1 19 | def test_register(insights_client): 20 | """ 21 | :id: 5371018d-7b4a-4535-9bf9-7a7e60a9ee4a 22 | :title: Test client registration 23 | :description: 24 | This test verifies that the --register command successfully registers 25 | an unregistered client 26 | :tags: Tier 1 27 | :steps: 28 | 1. Run insights-client with --register option 29 | 2. Verify the client successfully registered 30 | 3. Verify the report is successfully uploaded 31 | :expectedresults: 32 | 1. The client attempts to register 33 | 2. The client is confirmed as registered and the output includes 34 | "Starting to collect Insights data." 35 | 3. The output includes "Successfully uploaded report" 36 | """ 37 | register_result = insights_client.run("--register") 38 | assert loop_until(lambda: insights_client.is_registered) 39 | 40 | assert "Starting to collect Insights data" in register_result.stdout 41 | assert "Successfully uploaded report" in register_result.stdout 42 | 43 | 44 | @pytest.mark.tier1 45 | def test_register_auth_proxy(insights_client, test_config): 46 | """ 47 | :id: 1387745b-59a1-4a90-8f6d-dee2afa4723c 48 | :title: Test registration with authenticated proxy 49 | :description: 50 | This test verifies that the --register command successfully registers the 51 | host when an authentication proxy is configured 52 | :tags: Tier 1 53 | :steps: 54 | 1. Set the proxy configuration in the insights-client.conf file 55 | 2. Run the insights-client with --register option and verbose output 56 | 3. Verify the client is successfully registered 57 | :expectedresults: 58 | 1. The proxy details are saved to the client config file 59 | 2. The client attempts to register, using the configured proxy 60 | 3. The client is confirmed as registered and the output includes 61 | the proxy details (host,user) and 'Proxy Scheme' 62 | """ 63 | try: 64 | proxy_host = test_config.get("auth_proxy", "host") 65 | proxy_user = test_config.get("auth_proxy", "username") 66 | proxy_pass = test_config.get("auth_proxy", "password") 67 | proxy_port = str(test_config.get("auth_proxy", "port")) 68 | except KeyError: 69 | pytest.skip("Skipping because this test needs proxy settings to be configured") 70 | 71 | auth_proxy = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}" 72 | 73 | # save proxy information in insights-client.conf 74 | insights_client.config.proxy = auth_proxy 75 | insights_client.config.save() 76 | 77 | register_result = insights_client.run("--register", "--verbose") 78 | assert loop_until(lambda: insights_client.is_registered) 79 | assert "Proxy Scheme: http://" in register_result.stdout 80 | assert f"Proxy Location: {proxy_host}" in register_result.stdout 81 | assert f"Proxy User: {proxy_user}" in register_result.stdout 82 | 83 | 84 | @pytest.mark.tier1 85 | def test_register_noauth_proxy(insights_client, test_config): 86 | """ 87 | :id: cbde1ce7-97fc-48d4-85bb-955ca45c8862 88 | :title: Test registration with unauthenticated proxy 89 | :description: 90 | This test verifies that the --register command successfully registers the 91 | host when a unauthenticated proxy is configured 92 | :tags: Tier 1 93 | :steps: 94 | 1. Set the proxy configuration in the insights-client.conf file 95 | 2. Run the insights-client with --register option and verbose output 96 | 3. Verify the client is successfully registered 97 | :expectedresults: 98 | 1. The proxy details are saved to the client config file 99 | 2. The client attempts to register, using the configured proxy 100 | 3. The client is confirmed as registered and the output includes 101 | 'CONF Proxy' and the unauthenticated proxy details 102 | """ 103 | try: 104 | proxy_host = test_config.get("noauth_proxy", "host") 105 | proxy_port = str(test_config.get("noauth_proxy", "port")) 106 | except KeyError: 107 | pytest.skip("Skipping because this test needs proxy settings to be configured") 108 | no_auth_proxy = f"http://{proxy_host}:{proxy_port}" 109 | insights_client.config.proxy = no_auth_proxy 110 | insights_client.config.save() 111 | 112 | register_result = insights_client.run("--register", "--verbose") 113 | assert loop_until(lambda: insights_client.is_registered) 114 | assert f"CONF Proxy: {no_auth_proxy}" in register_result.stdout 115 | 116 | 117 | @pytest.mark.tier1 118 | def test_machineid_exists_only_when_registered(insights_client): 119 | """ 120 | :id: 27440051-e0d3-452e-b052-070cddf65aa1 121 | :title: Test that machine ID file exists only when registered 122 | :description: 123 | This test verifies that the machine ID file is created only when the client 124 | is registered 125 | :tags: Tier 1 126 | :steps: 127 | 1. Verify the client is not registered and machine ID does not exist 128 | 2. Run the insights-client without registration 129 | 3. Register insights-client and check machine ID 130 | 4. Unregister insights-client and confirm machine ID is removed 131 | :expectedresults: 132 | 1. The client is not registered and machine ID does not exist 133 | 2. The command fails with instructions to register in output and 134 | machine ID still does not exist 135 | 3. Client is successfully registered and machine ID is present on the system 136 | 4. The client is successfully unregistered and machine ID file is removed 137 | """ 138 | assert loop_until(lambda: not insights_client.is_registered) 139 | assert not os.path.exists(MACHINE_ID_FILE) 140 | 141 | res = insights_client.run(check=False) 142 | assert ( 143 | "This host is unregistered. Use --register to register this host" in res.stdout 144 | or "This host has not been registered. Use --register to register this host" in res.stdout 145 | ) 146 | assert res.returncode != 0 147 | assert not os.path.exists(MACHINE_ID_FILE) 148 | 149 | insights_client.register() 150 | assert loop_until(lambda: insights_client.is_registered) 151 | assert os.path.exists(MACHINE_ID_FILE) 152 | 153 | insights_client.unregister() 154 | assert not os.path.exists(MACHINE_ID_FILE) 155 | 156 | 157 | @pytest.mark.tier1 158 | def test_machineid_changes_on_new_registration(insights_client): 159 | """ 160 | :id: ada04c6f-c351-4018-92f0-f3f21b7d645a 161 | :title: Test machine ID file changes on new registration 162 | :description: 163 | This test verifies that the machine ID file content changes when 164 | the client is unregistered and then registered again 165 | :tags: Tier 1 166 | :steps: 167 | 1. Register insights-client and store current machine ID 168 | 2. Unregister the client 169 | 3. Register client again and check the machine ID 170 | :expectedresults: 171 | 1. Client is registered and machine ID stored 172 | 2. Client successfully unregisters and machine is removed 173 | 3. After the registration on systems with version 3.3.16 and higher 174 | the machine ID should stay the same, on lower versions the number 175 | should change 176 | """ 177 | insights_client.register() 178 | with open(MACHINE_ID_FILE, "r") as f: 179 | machine_id_old = f.read() 180 | 181 | insights_client.unregister() 182 | assert not os.path.exists(MACHINE_ID_FILE) 183 | 184 | insights_client.register() 185 | with open(MACHINE_ID_FILE, "r") as f: 186 | machine_id_new = f.read() 187 | 188 | if insights_client.core_version >= Version(3, 3, 16): 189 | """after the new changes to CCT-161 machine-id stays the same""" 190 | assert machine_id_new == machine_id_old 191 | else: 192 | assert machine_id_new != machine_id_old 193 | 194 | 195 | @pytest.mark.tier1 196 | def test_double_registration(insights_client): 197 | """ 198 | :id: b1cf2516-aab9-438d-b4c0-42182c84fde9 199 | :title: Test double registration 200 | :description: 201 | This test verifies that the --register flag can be passed multiple 202 | times on a system that is already registered without causing errors 203 | :tags: Tier 1 204 | :steps: 205 | 1. Register insights-client and store its machine ID 206 | 2. Run the --register command again on the registered system 207 | 3. Verify the machine ID remains unchanged 208 | :expectedresults: 209 | 1. System is registered and machine ID is present 210 | 2. The command does not fail and shows a message 211 | 'This host has already been registered' 212 | 3. The machine ID stayed unchanged 213 | """ 214 | assert loop_until(lambda: not insights_client.is_registered) 215 | 216 | insights_client.register() 217 | assert os.path.exists(MACHINE_ID_FILE) 218 | with open(MACHINE_ID_FILE, "r") as f: 219 | machine_id_old = f.read() 220 | 221 | res = insights_client.register() 222 | assert "This host has already been registered" in res.stdout 223 | assert os.path.exists(MACHINE_ID_FILE) 224 | with open(MACHINE_ID_FILE, "r") as f: 225 | machine_id_new = f.read() 226 | 227 | assert machine_id_new == machine_id_old 228 | 229 | 230 | @pytest.mark.parametrize( 231 | "legacy_upload_value", 232 | [ 233 | pytest.param(True, marks=pytest.mark.xfail), 234 | pytest.param(False), 235 | ], 236 | ) 237 | @pytest.mark.tier1 238 | def test_register_group_option(insights_client, legacy_upload_value): 239 | """ 240 | :id: 5213a950-e66f-4749-8a76-66b6d4ed9aa5 241 | :title: Test register with --group option 242 | :parametrized: yes 243 | :description: 244 | This test verifies that the --register command works as expected when 245 | --group option is used 246 | :reference: https://issues.redhat.com/browse/RHINENG-7567 247 | :tags: Tier 1 248 | :steps: 249 | 1. Unregister the client if registered 250 | 2. Set the legacy_upload value and save the configuration 251 | 3. Run insights-client with --register and --group=tag options 252 | :expectedresults: 253 | 1. Client is unregistered successfully 254 | 2. The configuration is updated successfully 255 | 3. The client is registered with the specified group and the return 256 | code is 0 257 | """ 258 | # make sure the system is not registered to insights 259 | with contextlib.suppress(Exception): 260 | insights_client.unregister() 261 | assert loop_until(lambda: not insights_client.is_registered) 262 | insights_client.config.legacy_upload = legacy_upload_value 263 | insights_client.config.save() 264 | register_group_option = insights_client.run( 265 | "--register", 266 | "--group=tag", 267 | check=False, 268 | selinux_context=None, # using --group, not a service related option 269 | ) 270 | assert register_group_option.returncode == 0 271 | 272 | 273 | @pytest.mark.tier1 274 | def test_registered_and_unregistered_files_are_created_and_deleted(insights_client): 275 | """ 276 | :id: 6e692793-f9ae-4ccb-a9d6-813b6d9aa7c3 277 | :title: Test files creation and deletion while registering and unregistering 278 | :description: 279 | This test verifies that the .registered file is created when the client 280 | is registered and the .unregistered file is created when the client is 281 | unregistered 282 | :tags: Tier 1 283 | :steps: 284 | 1. Verify that the client is not registered and .registered file does not exist 285 | 2. Register the client and verify that the .registered file was created 286 | and .unregistered does not appear 287 | 3. Unregister the client and verify that .registered file was removed and 288 | .unregistered file was created 289 | :expectedresults: 290 | 1. Client is not registered and .registered file does not exist 291 | 2. The client registers and .registered file is created, .unregistered 292 | does not exist 293 | 3. The client is unregistered, .registered file was removed and .unregistered 294 | appears 295 | """ 296 | assert loop_until(lambda: not insights_client.is_registered) 297 | assert not os.path.exists("/etc/insights-client/.registered") 298 | 299 | insights_client.register() 300 | assert loop_until(lambda: insights_client.is_registered) 301 | assert os.path.exists("/etc/insights-client/.registered") 302 | assert not os.path.exists("/etc/insights-client/.unregistered") 303 | 304 | insights_client.unregister() 305 | assert os.path.exists("/etc/insights-client/.unregistered") 306 | assert not os.path.exists("/etc/insights-client/.registered") 307 | --------------------------------------------------------------------------------