1use log::{self, debug, error, info, trace};
5use serde_json::Value;
6use std::collections::HashMap;
7use std::collections::HashSet;
8use std::error::Error;
9use std::fs;
10
11pub fn run(template_file: &str) -> Result<Vec<String>, Box<dyn Error>> {
12 let template_contents = fs::read_to_string(template_file)?;
13
14 trace!(
15 "Template file is '{}' and its contents are:\n'{}'",
16 template_file,
17 template_contents
18 );
19
20 Ok(run_gen(&template_contents))
21}
22
23pub fn run_gen(template_file_contents: &str) -> Vec<String> {
24 info!("Loading CloudFormation Template");
25 debug!("Entered run_gen");
26
27 debug!("Deserializing CloudFormation template");
28 let cfn_template: HashMap<String, Value> = match serde_json::from_str(template_file_contents) {
29 Ok(s) => s,
30 Err(_) => match serde_yaml::from_str(template_file_contents) {
31 Ok(y) => y,
32 Err(e) => {
33 let msg_string =
34 format!("Template file format was unreadable as json or yaml: {}", e);
35 error!("{}", &msg_string);
36 return vec![msg_string];
37 }
38 },
39 };
40 trace!("CFN Template is {:#?}", &cfn_template);
41 let cfn_resources_clone = match cfn_template.get("Resources") {
42 Some(y) => y.clone(),
43 None => {
44 let msg_string = format!("Template lacks a Resources section");
45 error!("{}", &msg_string);
46 return vec![msg_string];
47 }
48 };
49 let cfn_resources: HashMap<String, Value> = match serde_json::from_value(cfn_resources_clone) {
50 Ok(y) => y,
51 Err(e) => {
52 let msg_string = format!("Template Resources section has an invalid structure: {}", e);
53 error!("{}", &msg_string);
54 return vec![msg_string];
55 }
56 };
57 trace!("CFN resources are: {:?}", cfn_resources);
58 gen_rules(cfn_resources)
59}
60
61fn gen_rules(cfn_resources: HashMap<String, Value>) -> Vec<String> {
62 let mut rule_set: HashSet<String> = HashSet::new();
63 let mut rule_map: HashMap<String, HashSet<String>> = HashMap::new();
64 for (name, cfn_resource) in cfn_resources {
65 trace!("{} is {:?}", name, &cfn_resource);
66 let props: HashMap<String, Value> =
67 match serde_json::from_value(cfn_resource["Properties"].clone()) {
68 Ok(s) => s,
69 Err(_) => continue,
70 };
71 for (prop_name, prop_val) in props {
72 let stripped_val = match prop_val.as_str() {
73 Some(v) => String::from(v),
74 None => prop_val.to_string(),
75 };
76 let no_newline_stripped_val = stripped_val.trim().replace("\n", "");
77 let key_name = format!("{} {}", &cfn_resource["Type"].as_str().unwrap(), prop_name);
78 if !rule_map.contains_key(&key_name) {
80 let value_set: HashSet<String> =
81 vec![no_newline_stripped_val].into_iter().collect();
82 rule_map.insert(key_name, value_set);
83 } else {
84 let value_set = rule_map.get_mut(&key_name).unwrap();
86 value_set.insert(no_newline_stripped_val);
87 };
88 }
89 }
90 for (key, val_set) in rule_map {
91 let mut rule_string: String = String::from("");
92 let mut count = 0;
93 let mut sorted_val_set: Vec<String> = val_set.into_iter().collect::<Vec<String>>();
94 sorted_val_set.sort();
95 for r in sorted_val_set {
96 let temp_rule_string = format!("{} == {}", key, r);
97 if count > 0 {
98 rule_string = format!("{} |OR| {}", rule_string, temp_rule_string);
99 } else {
100 rule_string = temp_rule_string;
101 }
102 count += 1;
103 }
104 rule_set.insert(rule_string);
105 }
106 rule_set.into_iter().collect()
107}