Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 55 additions & 30 deletions src/module_utils/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from enum import Enum
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime


Expand Down Expand Up @@ -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
Expand All @@ -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:
"""
Expand Down Expand Up @@ -202,7 +201,6 @@ def is_applicable(self, context_value: Any) -> bool:
return context_value == self.value


@dataclass
class Check:
"""
Represents a configuration check
Expand All @@ -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
Expand All @@ -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:
"""
Expand All @@ -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
Expand All @@ -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
35 changes: 30 additions & 5 deletions src/modules/configuration_check_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 <CONTEXT.property> 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 = {
Expand All @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions src/modules/get_package_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
---
Expand Down
Loading