diff --git a/ansible_risk_insight/finder.py b/ansible_risk_insight/finder.py index 19b13dd1..8c8236c6 100644 --- a/ansible_risk_insight/finder.py +++ b/ansible_risk_insight/finder.py @@ -103,6 +103,15 @@ def find_module_name(data_block): continue if module_name_re.match(k): return k + if "local_action" in keys: + local_action_value = data_block["local_action"] + module_name = "" + if isinstance(local_action_value, str): + module_name = local_action_value.split(" ")[0] + elif isinstance(local_action_value, dict): + module_name = local_action_value.get("module", "") + if module_name: + return module_name return "" diff --git a/ansible_risk_insight/model_loader.py b/ansible_risk_insight/model_loader.py index 7404758c..6c79d651 100644 --- a/ansible_risk_insight/model_loader.py +++ b/ansible_risk_insight/model_loader.py @@ -1489,6 +1489,16 @@ def load_task( task_name = v if k == module_name: module_options = v + elif k == "local_action": + _opt = data_block[k] + if isinstance(_opt, str): + module_options = _opt.lstrip(module_name).lstrip(" ") + elif isinstance(_opt, dict): + for mk, mv in _opt.items(): + if mk == "module": + continue + module_options[mk] = mv + task_options.update({k: v}) else: task_options.update({k: v}) diff --git a/ansible_risk_insight/models.py b/ansible_risk_insight/models.py index f2886766..ef38679c 100644 --- a/ansible_risk_insight/models.py +++ b/ansible_risk_insight/models.py @@ -1301,33 +1301,36 @@ def yaml(self, original_module="", use_yaml_lines=True): task_data_wrapper = [] task_data = {} + is_local_action = "local_action" in self.options + # task name if self.name: task_data["name"] = self.name elif "name" in task_data: task_data.pop("name") - # module name - if original_module: - mo = deepcopy(task_data[original_module]) - task_data[self.module] = mo - elif self.module and self.module not in task_data: - task_data[self.module] = self.module_options - - # module options - if isinstance(self.module_options, dict): - current_mo = task_data[self.module] - # if the module options was an old style inline parameter in YAML, - # we can ignore them here because it is parsed as self.module_options - if not isinstance(current_mo, dict): - current_mo = {} - old_keys = list(current_mo.keys()) - new_keys = list(self.module_options.keys()) - for old_key in old_keys: - if old_key not in new_keys: - current_mo.pop(old_key) - recursive_copy_dict(self.module_options, current_mo) - task_data[self.module] = current_mo + if not is_local_action: + # module name + if original_module: + mo = deepcopy(task_data[original_module]) + task_data[self.module] = mo + elif self.module and self.module not in task_data: + task_data[self.module] = self.module_options + + # module options + if isinstance(self.module_options, dict): + current_mo = task_data[self.module] + # if the module options was an old style inline parameter in YAML, + # we can ignore them here because it is parsed as self.module_options + if not isinstance(current_mo, dict): + current_mo = {} + old_keys = list(current_mo.keys()) + new_keys = list(self.module_options.keys()) + for old_key in old_keys: + if old_key not in new_keys: + current_mo.pop(old_key) + recursive_copy_dict(self.module_options, current_mo) + task_data[self.module] = current_mo # task options if isinstance(self.options, dict): @@ -1340,6 +1343,11 @@ def yaml(self, original_module="", use_yaml_lines=True): if old_key not in new_keys: current_to.pop(old_key) options_without_name = {k: v for k, v in self.options.items() if k != "name"} + if is_local_action: + new_la_opt = {} + new_la_opt["module"] = self.module + recursive_copy_dict(self.module_options, new_la_opt) + options_without_name["local_action"] = new_la_opt recursive_copy_dict(options_without_name, current_to) if len(task_data_wrapper) == 0: task_data_wrapper.append(current_to) diff --git a/ansible_risk_insight/risk_detector.py b/ansible_risk_insight/risk_detector.py index 26fae444..878d409d 100644 --- a/ansible_risk_insight/risk_detector.py +++ b/ansible_risk_insight/risk_detector.py @@ -183,6 +183,7 @@ def detect(contexts: List[AnsibleRunContext], rules_dir: str = "", rules: list = for rule in loaded_rules: if not rule.enabled: continue + rule_id = getattr(rule, "rule_id") start_time = time.time() r_result = RuleResult(file=t.file_info(), rule=rule.get_metadata()) detail = {} @@ -198,7 +199,7 @@ def detect(contexts: List[AnsibleRunContext], rules_dir: str = "", rules: list = fatal = detail.get("fatal", False) if detail else False if fatal: error = r_result.error or "unknown error" - error = f"ARI rule evaluation threw fatal exception: {error}" + error = f"ARI rule evaluation threw fatal exception: RuleID={rule_id}, error={error}" raise FatalRuleResultError(error) if rule.spec_mutation: if isinstance(detail, dict):