diff --git a/cmk/active_checks/check_cmk_inv.py b/cmk/active_checks/check_cmk_inv.py index 919e158d14b..e06afd413d3 100644 --- a/cmk/active_checks/check_cmk_inv.py +++ b/cmk/active_checks/check_cmk_inv.py @@ -74,9 +74,9 @@ def main( completed_process = subprocess.run(cmd, capture_output=True, encoding="utf8", check=False) if completed_process.stderr: - print(completed_process.stderr, file=sys.stderr) + sys.stderr.write(completed_process.stderr + "\n") if completed_process.stdout: - print(completed_process.stdout) + sys.stdout.write(completed_process.stdout + "\n") return completed_process.returncode diff --git a/cmk/base/automations/__init__.py b/cmk/base/automations/__init__.py index 3903d185e80..a945b8d08a4 100644 --- a/cmk/base/automations/__init__.py +++ b/cmk/base/automations/__init__.py @@ -103,11 +103,10 @@ def execute(self, cmd: str, args: list[str]) -> AutomationExitCode: profiling.output_profile() with suppress(IOError): - print( - result.serialize(cmk_version.Version.from_str(cmk_version.__version__)), - flush=True, - file=sys.stdout, + sys.stdout.write( + result.serialize(cmk_version.Version.from_str(cmk_version.__version__)) + "\n" ) + sys.stdout.flush() return AutomationExitCode.SUCCESS diff --git a/cmk/base/core.py b/cmk/base/core.py index 949f715ece1..a12072b39f1 100644 --- a/cmk/base/core.py +++ b/cmk/base/core.py @@ -138,7 +138,8 @@ def activation_lock(mode: Literal["abort", "wait"] | None) -> Iterator[None]: def print_(txt: str) -> None: with suppress(IOError): - print(txt, end="", flush=True, file=sys.stdout) + sys.stdout.write(txt) + sys.stdout.flush() def do_core_action( diff --git a/cmk/base/core_config.py b/cmk/base/core_config.py index 14a665e95c0..90e1ff92ba0 100644 --- a/cmk/base/core_config.py +++ b/cmk/base/core_config.py @@ -278,12 +278,10 @@ def do_create_config( and available for starting the monitoring. """ with suppress(IOError): - print( + sys.stdout.write( "Generating configuration for core (type %s)...\n" % core.name(), - end="", - flush=True, - file=sys.stdout, ) + sys.stdout.flush() try: with tracer.start_as_current_span( diff --git a/cmk/base/core_nagios/_create_config.py b/cmk/base/core_nagios/_create_config.py index aa4b910535d..f6a4acff38d 100644 --- a/cmk/base/core_nagios/_create_config.py +++ b/cmk/base/core_nagios/_create_config.py @@ -125,7 +125,8 @@ def _precompile_hostchecks( precompile_mode: PrecompileMode, ) -> None: with suppress(IOError): - print("Precompiling host checks...", end="", flush=True, file=sys.stdout) + sys.stdout.write("Precompiling host checks...") + sys.stdout.flush() precompile_hostchecks( config_path, self._config_cache, @@ -133,7 +134,8 @@ def _precompile_hostchecks( precompile_mode=precompile_mode, ) with suppress(IOError): - print(tty.ok + "\n", end="", flush=True, file=sys.stdout) + sys.stdout.write(tty.ok + "\n") + sys.stdout.flush() # .--Create config-------------------------------------------------------. diff --git a/cmk/base/core_nagios/_host_check_template.py b/cmk/base/core_nagios/_host_check_template.py index 7df9c7fdd79..1df4996a24f 100644 --- a/cmk/base/core_nagios/_host_check_template.py +++ b/cmk/base/core_nagios/_host_check_template.py @@ -102,7 +102,8 @@ def main() -> int: ) except KeyboardInterrupt: with suppress(IOError): - print("\n", end="", flush=True, file=sys.stderr) + sys.stderr.write("\n") + sys.stderr.flush() return 1 except Exception as e: import traceback diff --git a/cmk/base/dump_host.py b/cmk/base/dump_host.py index 8ea09a1bd03..7e42a60ffdf 100644 --- a/cmk/base/dump_host.py +++ b/cmk/base/dump_host.py @@ -121,7 +121,8 @@ def _agent_description(cds: ComputedDataSources) -> str: def print_(txt: str) -> None: with suppress(IOError): - print(txt, end="", flush=True, file=sys.stdout) + sys.stdout.write(txt) + sys.stdout.flush() def dump_host( diff --git a/cmk/base/nagios_utils.py b/cmk/base/nagios_utils.py index 54131194dfb..23383353789 100644 --- a/cmk/base/nagios_utils.py +++ b/cmk/base/nagios_utils.py @@ -14,7 +14,8 @@ def print_(txt: str) -> None: with suppress(IOError): - print(txt, end="", flush=True, file=sys.stdout) + sys.stdout.write(txt) + sys.stdout.flush() def do_check_nagiosconfig() -> bool: @@ -37,5 +38,6 @@ def do_check_nagiosconfig() -> bool: print_("ERROR:\n") with suppress(IOError): - print(completed_process.stdout, end="", flush=True, file=sys.stderr) + sys.stderr.write(completed_process.stdout) + sys.stdout.flush() return False diff --git a/cmk/base/notify.py b/cmk/base/notify.py index eb1d05c6c25..9608bd6d5d9 100644 --- a/cmk/base/notify.py +++ b/cmk/base/notify.py @@ -1816,7 +1816,8 @@ def plugin_log(s: str) -> None: output_lines.append(output) if _log_to_stdout: with suppress(IOError): - print(line, end="", flush=True, file=sys.stdout) + sys.stdout.write(line) + sys.stdout.flush() except MKTimeout: plugin_log( "Notification plug-in did not finish within %d seconds. Terminating." diff --git a/cmk/base/parent_scan.py b/cmk/base/parent_scan.py index 337134d30ee..a551c63f07a 100644 --- a/cmk/base/parent_scan.py +++ b/cmk/base/parent_scan.py @@ -124,7 +124,8 @@ def scan_parents_of( def dot(color: str, dot: str = "o") -> None: if not silent: with suppress(IOError): - print(tty.bold + color + dot + tty.normal, end="", flush=True, file=sys.stdout) + sys.stdout.write(tty.bold + color + dot + tty.normal) + sys.stdout.flush() # Now all run and we begin to read the answers. For each host # we add a triple to gateways: the gateway, a scan state and a diagnostic output diff --git a/cmk/base/profiling.py b/cmk/base/profiling.py index 08b03821381..95d14eed5c6 100644 --- a/cmk/base/profiling.py +++ b/cmk/base/profiling.py @@ -49,9 +49,5 @@ def output_profile() -> None: show_profile.chmod(0o755) with suppress(IOError): - print( - f"Profile '{_profile_path}' written. Please run {show_profile}.\n", - end="", - flush=True, - file=sys.stderr, - ) + sys.stderr.write(f"Profile '{_profile_path}' written. Please run {show_profile}.\n") + sys.stderr.flush() diff --git a/cmk/cmkpasswd.py b/cmk/cmkpasswd.py index 6f909c7d9f5..1ba01154a73 100644 --- a/cmk/cmkpasswd.py +++ b/cmk/cmkpasswd.py @@ -104,7 +104,7 @@ def _run_cmkpasswd( if dst_file is not None: Htpasswd(dst_file).save(user_id, pw_hash) else: - print(Htpasswd.serialize_entries([(user_id, pw_hash)])) + sys.stdout.write(Htpasswd.serialize_entries([(user_id, pw_hash)]) + "\n") def main(args: Sequence[str]) -> int: @@ -123,7 +123,7 @@ def main(args: Sequence[str]) -> int: InvalidPasswordError, InvalidUsernameError, ) as e: - print("cmk-passwd:", e, file=sys.stderr) + sys.stderr.write(f"cmk-passwd: {e}\n") return 1 return 0 diff --git a/omd/packages/omd/omdlib/dialog.py b/omd/packages/omd/omdlib/dialog.py index 03f40726a76..85d9aa9305c 100644 --- a/omd/packages/omd/omdlib/dialog.py +++ b/omd/packages/omd/omdlib/dialog.py @@ -133,7 +133,7 @@ def _run_dialog(args: list[str]) -> DialogResult: # dialog returns 1 on the nolabel answer. But a return code of 1 is # used for errors. So we need to check the output. if completed_process.returncode != 0 and completed_process.stderr != "": - print(completed_process.stderr, file=sys.stderr) + sys.stderr.write(completed_process.stderr + "\n") return completed_process.returncode == 0, completed_process.stderr diff --git a/packages/cmk-frontend-vue/scripts/test_license_headers.py b/packages/cmk-frontend-vue/scripts/test_license_headers.py index e7551b8e502..075059c0b57 100644 --- a/packages/cmk-frontend-vue/scripts/test_license_headers.py +++ b/packages/cmk-frontend-vue/scripts/test_license_headers.py @@ -125,7 +125,7 @@ def check(suffix: str, path: Path) -> bool: def main() -> int: - print("Checking license headers...") + sys.stdout.write("Checking license headers...\n") problems = [] for root, _dirs, files in Path(".").walk(): if root.parts and root.parts[0] in ROOT_FOLDERS_IGNORED: @@ -149,10 +149,10 @@ def main() -> int: problems.append(path) if problems: - print("Please check the license header for the following files:") - print("\n".join(str(p) for p in problems)) + sys.stdout.write("Please check the license header for the following files:\n") + sys.stdout.write("\n".join(str(p) for p in problems) + "\n") return 1 - print("Done!") + sys.stdout.write("Done!\n") return 0 diff --git a/packages/cmk-frontend/scripts/test_scss_content.py b/packages/cmk-frontend/scripts/test_scss_content.py index 4e1b73ae8c2..eee1232040e 100644 --- a/packages/cmk-frontend/scripts/test_scss_content.py +++ b/packages/cmk-frontend/scripts/test_scss_content.py @@ -4,6 +4,7 @@ # conditions defined in the file COPYING, which is part of this source code package. import re +import sys import traceback from collections.abc import Callable, Iterable from pathlib import Path @@ -96,14 +97,14 @@ def test_hex_color_codes() -> None: def test(function: Callable) -> None: - print(function.__name__) + sys.stdout.write(function.__name__ + "\n") try: function() except Exception: - print(indent(traceback.format_exc().rstrip("\n"), " ")) - print(" FAIL") + sys.stdout.write(indent(traceback.format_exc().rstrip("\n"), " ") + "\n") + sys.stdout.write(" FAIL\n") else: - print(" OK") + sys.stdout.write(" OK\n") if __name__ == "__main__": diff --git a/packages/cmk-livestatus-client/example_multisite.py b/packages/cmk-livestatus-client/example_multisite.py index ca4089065fb..2ac7fbbe77f 100755 --- a/packages/cmk-livestatus-client/example_multisite.py +++ b/packages/cmk-livestatus-client/example_multisite.py @@ -40,12 +40,12 @@ c = livestatus.MultiSiteConnection(sites) # type: ignore[arg-type] c.set_prepend_site(True) -print(c.query("GET hosts\nColumns: name state\n")) +sys.stdout.write("%s\n" % c.query("GET hosts\nColumns: name state\n")) c.set_prepend_site(False) -print(c.query("GET hosts\nColumns: name state\n")) +sys.stdout.write("%s\n" % c.query("GET hosts\nColumns: name state\n")) # Beware: When doing stats, you need to aggregate yourself: -print(sum(c.query_column("GET hosts\nStats: state >= 0\n"))) +sys.stdout.write("%d\n" % sum(c.query_column("GET hosts\nStats: state >= 0\n"))) # Detect errors: sites = { @@ -70,7 +70,7 @@ c = livestatus.MultiSiteConnection(sites) # type: ignore[arg-type] for name, state in c.query("GET hosts\nColumns: name state\n"): - print("%-15s: %d" % (name, state)) -print("Dead sites:") + sys.stdout.write("%-15s: %d\n" % (name, state)) +sys.stdout.write("Dead sites:\n") for sitename, info in c.dead_sites().items(): - print("{}: {}".format(sitename, info["exception"])) + sys.stdout.write("{}: {}\n".format(sitename, info["exception"])) diff --git a/packages/cmk-livestatus-client/example_singlesite.py b/packages/cmk-livestatus-client/example_singlesite.py index c5b77d85b08..f99a216b55f 100755 --- a/packages/cmk-livestatus-client/example_singlesite.py +++ b/packages/cmk-livestatus-client/example_singlesite.py @@ -20,22 +20,22 @@ try: # Make a single connection for each query - print("\nPerformance:") + sys.stdout.write("\nPerformance:\n") for key, value in ( livestatus.SingleSiteConnection(socket_path).query_row_assoc("GET status").items() ): - print("%-30s: %s" % (key, value)) - print("\nHosts:") + sys.stdout.write("%-30s: %s\n" % (key, value)) + sys.stdout.write("\nHosts:\n") hosts = livestatus.SingleSiteConnection(socket_path).query_table( "GET hosts\nColumns: name alias address" ) for name, alias, address in hosts: - print("%-16s %-16s %s" % (name, address, alias)) + sys.stdout.write("%-16s %-16s %s\n" % (name, address, alias)) # Do several queries in one connection conn = livestatus.SingleSiteConnection(socket_path) num_up = conn.query_value("GET hosts\nStats: hard_state = 0") - print("\nHosts up: %d" % num_up) + sys.stdout.write("\nHosts up: %d\n" % num_up) stats = conn.query_row( "GET services\n" @@ -44,12 +44,14 @@ "Stats: state = 2\n" "Stats: state = 3\n" ) - print("Service stats: %d/%d/%d/%d" % tuple(stats)) + sys.stdout.write("Service stats: %d/%d/%d/%d\n" % tuple(stats)) - print("List of commands: %s" % ", ".join(conn.query_column("GET commands\nColumns: name"))) + sys.stdout.write( + "List of commands: %s\n" % ", ".join(conn.query_column("GET commands\nColumns: name")) + ) - print("Query error:") + sys.stdout.write("Query error:\n") conn.query_value("GET hosts\nColumns: hirni") except Exception as e: # livestatus.MKLivestatusException, e: - print("Livestatus error: %s" % str(e)) + sys.stdout.write("Livestatus error: %s\n" % str(e)) diff --git a/packages/cmk-werks/cmk/werks/cli.py b/packages/cmk-werks/cmk/werks/cli.py index a1032234c8e..a0560ce83c5 100644 --- a/packages/cmk-werks/cmk/werks/cli.py +++ b/packages/cmk-werks/cmk/werks/cli.py @@ -871,14 +871,12 @@ def edit_werk(werk_path: Path, custom_files: list[str] | None = None, commit: bo cmk_werks_load_werk(file_content=werk.path.read_text(), file_name=werk.path.name) break except Exception: # pylint: disable=broad-exception-caught - print(initial_werk_text) - print() - print(traceback.format_exc()) - print() - print( + sys.stdout.write(initial_werk_text + "\n\n") + sys.stdout.write(traceback.format_exc() + "\n\n") + sys.stdout.write( "Could not load the werk, see exception above.\n" "You may copy the initial werk text above the exception to fix your werk.\n" - "Will reopen the editor, after you acknowledged with enter" + "Will reopen the editor, after you acknowledged with enter\n" ) input() @@ -1071,7 +1069,7 @@ def meta_data() -> Iterator[str]: yield f"
{item}
{getattr(werk, item)}
" definition_list = "\n".join(meta_data()) - print( + sys.stdout.write( f'' "" f"Preview of werk {args.id}" @@ -1081,7 +1079,7 @@ def meta_data() -> Iterator[str]: f'
{werk.description}
' f"
{definition_list}
" "" - "" + "\n" ) diff --git a/packages/cmk-werks/cmk/werks/collect.py b/packages/cmk-werks/cmk/werks/collect.py index 71f32fbfbc8..e5c1afaa4d6 100644 --- a/packages/cmk-werks/cmk/werks/collect.py +++ b/packages/cmk-werks/cmk/werks/collect.py @@ -6,6 +6,7 @@ import json import logging import re +import sys from collections import defaultdict from collections.abc import Iterable, Mapping from pathlib import Path @@ -158,4 +159,4 @@ def main(config: Config, repo_path: Path, branches: Mapping[str, str]) -> None: **werk_dict, # type: ignore[arg-type] ).model_dump(by_alias=True, mode="json") - print(json.dumps(all_werks_by_id)) + sys.stdout.write(json.dumps(all_werks_by_id) + "\n") diff --git a/packages/cmk-werks/cmk/werks/validate.py b/packages/cmk-werks/cmk/werks/validate.py index c18b608a249..0def79a58cf 100644 --- a/packages/cmk-werks/cmk/werks/validate.py +++ b/packages/cmk-werks/cmk/werks/validate.py @@ -6,6 +6,7 @@ import argparse import os import re +import sys from pathlib import Path from . import load_werk @@ -49,7 +50,7 @@ def main( except Exception as e: raise RuntimeError(f"Error while loading werk {werk_path}\n\n{werk_content}") from e - print(f"Successfully validated {len(werks_to_check)} werks") + sys.stdout.write(f"Successfully validated {len(werks_to_check)} werks\n") def parse_args() -> argparse.Namespace: diff --git a/tests/code_quality/file_content/test_find_debug_code_web.py b/tests/code_quality/file_content/test_find_debug_code_web.py deleted file mode 100644 index 2f861095d69..00000000000 --- a/tests/code_quality/file_content/test_find_debug_code_web.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 -# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and -# conditions defined in the file COPYING, which is part of this source code package. - -from collections.abc import Iterable -from itertools import chain -from pathlib import Path - -from tests.testlib.repo import repo_path - -from ..conftest import ChangedFiles - - -def test_find_debug_code(changed_files: ChangedFiles) -> None: - to_scan = _files_to_scan(changed_files) - - for path in to_scan: - with path.open(encoding="utf-8") as f: - for line in f: - l = line.lstrip() - assert not l.startswith("html.debug("), 'Found "html.debug(...)" call' - - -def _files_to_scan(changed_files: ChangedFiles) -> Iterable[Path]: - base_dir = repo_path() / "cmk" / "gui" - to_scan = [] - for matched_file in chain(base_dir.glob("**/*.py"), base_dir.glob("**/*.wsgi")): - if matched_file.is_file() and changed_files.is_changed(matched_file): - to_scan.append(matched_file) - return to_scan diff --git a/tests/code_quality/file_content/test_find_debug_print.py b/tests/code_quality/file_content/test_find_debug_print.py deleted file mode 100644 index 894f34f059b..00000000000 --- a/tests/code_quality/file_content/test_find_debug_print.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2 -# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and -# conditions defined in the file COPYING, which is part of this source code package. - -import logging -import os -import re -from pathlib import Path - -import pytest - -from tests.testlib.repo import repo_path - -from ..conftest import ChangedFiles - -LOGGER = logging.getLogger() - -check_paths = [ - "bin", - # TODO: Why don't we check the whole cmk module? - "cmk/base", - "cmk/base/cee", - "cmk/base/cme", - "cmk/base/modes", - "cmk/base/default_config", - "lib", - "checks", - "inventory", - "notifications", - "active_checks", - # TODO: Update all agent plugins to use sys.stdout.write instead of print - "agents/plugins", -] - -exclude_folders = ["plugins/build", "plugins/build_32", "chroot"] -exclude_files = ["bin/mkeventd_open514", "bin/mkevent"] - - -def find_debugs(line: str) -> re.Match[str] | None: - return re.match(r"(?m)^(:?pprint\.)?pp?rint[( ](?!.*\b(:?file=\w+)).*[\)\"']$", line.lstrip()) - - -@pytest.mark.parametrize( - "line", [' print "hello Word"', 'print("variable")', " pprint(dict)", " pprint.pprint(list)"] -) -def test_find_debugs(changed_files: ChangedFiles, line: str) -> None: - assert find_debugs(line) - - -@pytest.mark.parametrize( - "line", ['sys.stdout.write("message")', "# print(variable)", 'print("hello", file=sys.stdout)'] -) -def test_find_debugs_false(changed_files: ChangedFiles, line: str) -> None: - assert find_debugs(line) is None - - -@pytest.mark.parametrize( - "path", - [p for dir_path in check_paths for p in [repo_path() / dir_path] if p.exists()], -) -def test_find_debug_code(changed_files: ChangedFiles, path: str) -> None: - scanned = 0 - - for dirpath, _, filenames in os.walk(path): - scanned += 1 - for filename in filenames: - file_path = f"{dirpath}/{filename}" - - if not changed_files.is_changed(Path(file_path)): - continue - - if [folder for folder in exclude_folders if folder in file_path]: - continue - - if file_path.endswith((".pyc", ".whl", ".tar.gz", ".swp")): - continue - - if os.path.relpath(file_path, repo_path()) in exclude_files: - continue - - try: - with open(file_path) as file: - for nr, line in enumerate(file.readlines()): - if nr == 0 and ("bash" in line or "php" in line): - break # skip non python files - - assert not find_debugs(line), 'Found "print(...)" call in %s:%d' % ( - file_path, - nr + 1, - ) - except UnicodeDecodeError: - LOGGER.warning("Could not read %r due to UnicodeDecodeError", file_path) - - assert scanned > 0 diff --git a/tests/semgrep/rules/find_debug_print.yaml b/tests/semgrep/rules/find_debug_print.yaml new file mode 100644 index 00000000000..762223d5a96 --- /dev/null +++ b/tests/semgrep/rules/find_debug_print.yaml @@ -0,0 +1,22 @@ +--- +rules: + - id: disallow-print + pattern-either: + - pattern: print(...) + - pattern: pprint(...) + message: Found call to print, which may be some left over debug output. + languages: [python] + severity: ERROR + paths: + # TODO: Make this an exclude list + include: + - "bin" + # TODO: Why don't we check the whole cmk module? + - "cmk/base" + - "packages" + - "notifications" + - "active_checks" + # TODO: Update all agent plugins to use sys.stdout.write instead of print + - "agents/plugins" + exclude: + - "doc/treasures" diff --git a/tests/semgrep/rules/gui_html_debug.yaml b/tests/semgrep/rules/gui_html_debug.yaml new file mode 100644 index 00000000000..ecfba390b1a --- /dev/null +++ b/tests/semgrep/rules/gui_html_debug.yaml @@ -0,0 +1,7 @@ +--- +rules: + - id: disallow-html-debug + pattern: html.debug(...) + message: Found html.debug call + languages: [python] + severity: ERROR