use crate::commands::tracker::StatusContext; use crate::commands::validate::{OutputFormatType, Reporter}; use crate::rules::eval_context::{ simplifed_json_from_root, BinaryComparison, ClauseReport, EventRecord, FileReport, InComparison, RuleReport, UnaryComparison, }; use crate::rules::path_value::traversal::{Node, Traversal, TraversalResult}; use crate::rules::Status; use fancy_regex::Regex; use lazy_static::lazy_static; use std::collections::{BTreeSet, HashMap, HashSet}; use std::io::Write; use std::rc::Rc; #[derive(Debug)] pub(crate) struct TfAware<'reporter> { next: Option<&'reporter dyn Reporter>, } impl<'reporter> TfAware<'reporter> { pub(crate) fn new_with(next: &'reporter dyn Reporter) -> TfAware { TfAware { next: Some(next) } } } impl<'reporter> Reporter for TfAware<'reporter> { fn report( &self, _writer: &mut dyn Write, _status: Option, _failed_rules: &[&StatusContext], _passed_or_skipped: &[&StatusContext], _longest_rule_name: usize, _rules_file: &str, _data_file: &str, _data: &Traversal<'_>, _output_type: OutputFormatType, ) -> crate::rules::Result<()> { Ok(()) } fn report_eval<'value>( &self, write: &mut dyn Write, status: Status, root_record: &EventRecord<'value>, rules_file: &str, data_file: &str, data_file_bytes: &str, data: &Traversal<'value>, output_type: OutputFormatType, ) -> crate::rules::Result<()> { let root = data.root().unwrap(); let is_tf_plan = match data.at("/resource_changes", root) { Ok(_resource_changes) => matches!(data.at("/terraform_version", root), Ok(_)), _ => false, }; if is_tf_plan { let failure_report = simplifed_json_from_root(root_record)?; match output_type { OutputFormatType::YAML => serde_yaml::to_writer(write, &failure_report)?, OutputFormatType::JSON => serde_json::to_writer_pretty(write, &failure_report)?, OutputFormatType::SingleLineSummary => { single_line(write, data_file, rules_file, data, root, failure_report)? } }; Ok(()) } else { self.next.map_or(Ok(()), |next| { next.report_eval( write, status, root_record, rules_file, data_file, data_file_bytes, data, output_type, ) }) } } } lazy_static! { static ref RESOURCE_CHANGE_EXTRACTION: Regex = Regex::new( "/resource_changes/(?P[^/]+)/change/after/(?P.*)?" ) .ok() .unwrap(); } use super::common::{ populate_hierarchy_path_trees, IdentityHash, LocalResourceAggr, PathTree, RuleHierarchy, }; use crate::rules::display::ValueOnlyDisplay; use crate::rules::errors::Error; use crate::rules::path_value::PathAwareValue; use colored::*; use nom::Slice; fn single_line( writer: &mut dyn Write, data_file: &str, rules_file: &str, data: &Traversal<'_>, root: &Node<'_>, failure_report: FileReport<'_>, ) -> crate::rules::Result<()> { if failure_report.not_compliant.is_empty() { return Ok(()); } let mut path_tree = PathTree::new(); let mut hierarchy = RuleHierarchy::new(); let root_node = std::rc::Rc::new(String::from("")); for each_rule in &failure_report.not_compliant { populate_hierarchy_path_trees(each_rule, root_node.clone(), &mut path_tree, &mut hierarchy); } let mut by_resources = HashMap::new(); for (key, value) in path_tree.range(String::from("/resource_changes/")..) { let resource_ptr = match RESOURCE_CHANGE_EXTRACTION.captures(key) { Ok(Some(cap)) => cap.name("index_or_name").unwrap().as_str(), Ok(None) => unreachable!(), Err(e) => return Err(Error::from(e)), }; let address = format!("/resource_changes/{}", resource_ptr); let resource = match data.at(&address, root)? { TraversalResult::Value(n) => n, _ => unreachable!(), }; let addr = match data.at("0/address", resource)? { TraversalResult::Value(n) => match n.value() { PathAwareValue::String((_, rt)) => rt.as_str(), _ => unreachable!(), }, _ => unreachable!(), }; let dot_sep = addr.find('.').unwrap(); let (resource_type, resource_name) = (addr.slice(0..dot_sep), addr.slice(dot_sep + 1..)); let resource_aggr = by_resources .entry(resource_name) .or_insert(LocalResourceAggr { name: String::from(resource_name), resource_type, cdk_path: None, clauses: HashSet::new(), paths: BTreeSet::new(), }); for node in value.iter() { resource_aggr .clauses .insert(IdentityHash { key: node.clause }); resource_aggr.paths.insert(node.path.as_ref().clone()); } } writeln!( writer, "Evaluating data {} against rules {}", data_file, rules_file )?; let num_of_resources = format!("{}", by_resources.len()).bold(); writeln!( writer, "Number of non-compliant resources {}", num_of_resources )?; for (_, resource) in by_resources { writeln!(writer, "Resource = {} {{", resource.name.yellow().bold())?; let prefix = String::from(" "); writeln!( writer, "{prefix}{0: format!("/{}", name), _ => unreachable!(), }; let range = resource .paths .range(rule_name.clone()..) .take_while(|p| p.starts_with(&rule_name)) .count(); if range > 0 { struct ErrWriter {} impl super::common::ComparisonErrorWriter for ErrWriter { fn binary_error_msg( &mut self, writer: &mut dyn Write, _cr: &ClauseReport<'_>, bc: &BinaryComparison, prefix: &str, ) -> crate::rules::Result { let width = "PropertyPath".len() + 4; let from = &bc.from.self_path().0; let to = &bc.to.self_path().0; let resource_based = if from.starts_with("/resource_changes") { from.as_str() } else { to.as_str() }; let (_res, property) = match resource_based.find("change/after/") { Some(idx) => resource_based.split_at(idx), None => (resource_based, ""), }; let property = property.slice("change/after/".len()..).replace('/', "."); writeln!( writer, "{prefix}{pp:, _: &InComparison, _: &str, ) -> crate::rules::Result { todo!() } fn unary_error_msg( &mut self, writer: &mut dyn Write, _cr: &ClauseReport<'_>, re: &UnaryComparison, prefix: &str, ) -> crate::rules::Result { let resource_based = re.value.self_path().0.as_str(); let (_res, property) = match resource_based.find("changes/after/") { Some(idx) => resource_based.split_at(idx), None => (resource_based, ""), }; let property = property.replace('/', "."); let width = "PropertyPath".len() + 4; writeln!( writer, "{prefix}{pp: