diff --git a/src/module_utils/enums.py b/src/module_utils/enums.py index de331c6a..ae2cd74d 100644 --- a/src/module_utils/enums.py +++ b/src/module_utils/enums.py @@ -4,7 +4,6 @@ from enum import Enum from typing import Dict, Any, List, Optional -from dataclasses import dataclass, field from datetime import datetime @@ -154,7 +153,6 @@ def to_dict(self) -> Dict[str, Any]: } -@dataclass class ApplicabilityRule: """ Represents a rule to determine if a check is applicable based on context properties @@ -165,8 +163,9 @@ class ApplicabilityRule: :type value: Any """ - property: str - value: Any + def __init__(self, property: str, value: Any): + self.property = property + self.value = value def is_applicable(self, context_value: Any) -> bool: """ @@ -202,7 +201,6 @@ def is_applicable(self, context_value: Any) -> bool: return context_value == self.value -@dataclass class Check: """ Represents a configuration check @@ -217,8 +215,8 @@ class Check: :type category: str :param workload: Workload type (e.g., SAP, Non-SAP) :type workload: str - :param TestSeverity: TestSeverity level of the check - :type TestSeverity: TestSeverity + :param severity: Severity level of the check + :type severity: TestSeverity :param collector_type: Type of collector to use (e.g., command, azure) :type collector_type: str :param collector_args: Arguments for the collector @@ -237,20 +235,37 @@ class Check: :type report: Optional[str] """ - id: str - name: str - description: str - category: str - workload: str - severity: TestSeverity = TestSeverity.WARNING - collector_type: str = "command" - collector_args: Dict[str, Any] = field(default_factory=dict) - validator_type: str = "string" - validator_args: Dict[str, Any] = field(default_factory=dict) - tags: List[str] = field(default_factory=list) - applicability: List[ApplicabilityRule] = field(default_factory=list) - references: Dict[str, str] = field(default_factory=dict) - report: Optional[str] = "check" + def __init__( + self, + id: str, + name: str, + description: str, + category: str, + workload: str, + severity: TestSeverity = TestSeverity.WARNING, + collector_type: str = "command", + collector_args: Optional[Dict[str, Any]] = None, + validator_type: str = "string", + validator_args: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, + applicability: Optional[List[ApplicabilityRule]] = None, + references: Optional[Dict[str, str]] = None, + report: Optional[str] = "check", + ): + self.id = id + self.name = name + self.description = description + self.category = category + self.workload = workload + self.severity = severity + self.collector_type = collector_type + self.collector_args = collector_args if collector_args is not None else {} + self.validator_type = validator_type + self.validator_args = validator_args if validator_args is not None else {} + self.tags = tags if tags is not None else [] + self.applicability = applicability if applicability is not None else [] + self.references = references if references is not None else {} + self.report = report def is_applicable(self, context: Dict[str, Any]) -> bool: """ @@ -270,7 +285,6 @@ def is_applicable(self, context: Dict[str, Any]) -> bool: return True -@dataclass class CheckResult: """ Represents the result of a check execution @@ -293,11 +307,22 @@ class CheckResult: :type details: Optional[str] """ - check: Check - status: TestStatus - hostname: str - expected_value: Any - actual_value: Any - execution_time: float - timestamp: datetime = field(default_factory=datetime.now) - details: Optional[str] = None + def __init__( + self, + check: Check, + status: TestStatus, + hostname: str, + expected_value: Any, + actual_value: Any, + execution_time: float, + timestamp: Optional[datetime] = None, + details: Optional[str] = None, + ): + self.check = check + self.status = status + self.hostname = hostname + self.expected_value = expected_value + self.actual_value = actual_value + self.execution_time = execution_time + self.timestamp = timestamp if timestamp is not None else datetime.now() + self.details = details diff --git a/src/modules/configuration_check_module.py b/src/modules/configuration_check_module.py index fc482c81..f6b074f8 100644 --- a/src/modules/configuration_check_module.py +++ b/src/modules/configuration_check_module.py @@ -438,8 +438,8 @@ def validate_string(self, check: Check, collected_data: str) -> Dict[str, Any]: if check.validator_args.get("strip_whitespace", True): expected = str(expected).strip() - expected = re.sub(r'\s+', ' ', expected) - collected = re.sub(r'\s+', ' ', collected) + expected = re.sub(r"\s+", " ", expected) + collected = re.sub(r"\s+", " ", collected) if check.validator_args.get("case_insensitive", False): expected = expected.lower() @@ -794,8 +794,33 @@ def get_results_summary(self) -> Dict[str, int]: def format_results_for_html_report(self): """ - Reformat results for HTML report + Reformat results for HTML report. + Removes CONTEXT template placeholders to prevent Ansible template evaluation errors. """ + + def remove_context_templates(value): + """ + Recursively remove or neutralize CONTEXT template placeholders. + Replaces {{ CONTEXT.* }} with a safe placeholder to prevent Ansible templating issues. + + :param value: Value to process (str, dict, list, or other) + :type value: Any + :return: Value with CONTEXT templates removed + :rtype: Any + """ + if isinstance(value, str): + # Replace {{ CONTEXT.property }} with to neutralize templates + return re.sub( + r"\{\{\s*CONTEXT\.[^}]+\s*\}\}", + lambda m: m.group(0).replace("{{", "<").replace("}}", ">"), + value, + ) + if isinstance(value, dict): + return {k: remove_context_templates(v) for k, v in value.items()} + if isinstance(value, list): + return [remove_context_templates(item) for item in value] + return value + serialized_results = [] for check_result in self.result["check_results"]: result_dict = { @@ -811,9 +836,9 @@ def format_results_for_html_report(self): else str(check_result.check.severity) ), "collector_type": check_result.check.collector_type, - "collector_args": check_result.check.collector_args, + "collector_args": remove_context_templates(check_result.check.collector_args), "validator_type": check_result.check.validator_type, - "validator_args": check_result.check.validator_args, + "validator_args": remove_context_templates(check_result.check.validator_args), "tags": check_result.check.tags, "references": check_result.check.references, "report": check_result.check.report, diff --git a/src/modules/get_package_list.py b/src/modules/get_package_list.py index e1ce85ec..baac7cd9 100644 --- a/src/modules/get_package_list.py +++ b/src/modules/get_package_list.py @@ -9,9 +9,11 @@ from ansible.module_utils.basic import AnsibleModule try: - from ansible.module_utils.sap_automation_qa import SapAutomationQA, TestStatus + from ansible.module_utils.sap_automation_qa import SapAutomationQA + from ansible.module_utils.enums import TestStatus except ImportError: - from src.module_utils.sap_automation_qa import SapAutomationQA, TestStatus + from src.module_utils.sap_automation_qa import SapAutomationQA + from src.module_utils.enums import TestStatus DOCUMENTATION = r""" ---