Skip to content

Commit 0a37342

Browse files
authored
Merge pull request #10 from escapecloud/task/json_and_pdf_reports
Issue #4 & 8 - Add JSON & PDF reports
2 parents d28dbc8 + 7b49932 commit 0a37342

10 files changed

Lines changed: 1083 additions & 178 deletions

File tree

assets/icons/severity/high.png

882 Bytes
Loading

assets/icons/severity/low.png

895 Bytes
Loading

assets/icons/severity/medium.png

1.18 KB
Loading

assets/img/logo/report_logo.png

31.6 KB
Loading

assets/template/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ <h4>
2626
<img src="assets/img/logo/logo.png" width="30" alt="EscapeCloud" />
2727
EscapeCloud Community Edition - Cloud Exit Assessment Report
2828
</h4>
29+
<ul class="main-row">
30+
<li class="main-col"><a class="btn1" href="report.pdf" target="_blank">Executive Summary (PDF)</a></li>
31+
</ul>
2932
</div>
3033
</header>
3134
<div class="main-wrpper">
@@ -84,7 +87,7 @@ <h3>
8487
<div>
8588
<h4>TimeStamp</h4>
8689
<h3>
87-
{{ assessment_ts }}
90+
{{ timestamp }}
8891
</h3>
8992
</div>
9093
</li>

core/engine.py

Lines changed: 28 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
from azure.mgmt.costmanagement import CostManagementClient
1717
from azure.mgmt.costmanagement.models import QueryDefinition, TimeframeType
1818

19-
from .utils import copy_assets, get_cost_summary, get_risk_summary, prepare_alternative_technologies
19+
from .utils import copy_assets
2020
from .utils_aws import build_aws_resource_inventory, build_aws_cost_inventory
2121
from .utils_azure import build_azure_resource_inventory, build_azure_cost_inventory
2222
from .utils_db import connect, load_data
23+
from .utils_report import generate_html_report, generate_pdf_report, generate_json_report
2324

2425
# Configure the logger
2526
logger = logging.getLogger("core.engine")
@@ -271,7 +272,7 @@ def perform_risk_assessment(exit_strategy, report_path):
271272
return {"success": False, "logs": str(e)}
272273

273274
# Stage 6
274-
def generate_report(cloud_service_provider, exit_strategy, assessment_type, report_path):
275+
def generate_report(provider_details, cloud_service_provider, exit_strategy, assessment_type, report_path, raw_data_path):
275276
try:
276277
db_path = os.path.join(report_path, "data", "assessment.db")
277278

@@ -286,81 +287,38 @@ def generate_report(cloud_service_provider, exit_strategy, assessment_type, repo
286287
cost_data = load_data("cost_inventory", db_path=db_path)
287288
risk_data = load_data("risk_inventory", db_path=db_path)
288289

289-
# Create resource_inventory_dict with names and icons
290-
resource_inventory_dict = {
291-
str(item["resource_type"]): {
292-
**item,
293-
"name": resource_type_mapping.get(str(item["resource_type"]), {}).get("name", "Unknown Resource"),
294-
"icon": resource_type_mapping.get(str(item["resource_type"]), {}).get("icon", "assets/icons/default.png")
295-
}
296-
for item in resource_inventory
290+
# Timestamp
291+
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
292+
293+
metadata = {
294+
"cloud_service_provider": cloud_service_provider,
295+
"exit_strategy": exit_strategy,
296+
"assessment_type": assessment_type,
297+
"timestamp": timestamp,
297298
}
298299

299-
# Prepare risk data
300-
risks, severity_counts = get_risk_summary(risk_data, risk_definitions, resource_inventory_dict)
301-
302-
# Prepare cost data
303-
months, cost_values, total_cost, currency_symbol = get_cost_summary(cost_data)
304-
305-
# Prepare resource data with names and icons
306-
resource_counts = []
307-
for resource_type, resource in resource_inventory_dict.items():
308-
count = resource.get("count", 0)
309-
resource_info = resource_type_mapping.get(str(resource_type), {})
310-
name = resource_info.get("name", "Unknown Resource")
311-
icon = resource_info.get("icon", "assets/icons/default.png").lstrip('/')
312-
313-
resource_counts.append({
314-
"resource_type": resource_type,
315-
"name": name,
316-
"icon": icon,
317-
"count": count
318-
})
319-
320-
# Get the total count of all resources
321-
total_resources = sum(item["count"] for item in resource_counts)
322-
323-
# Prepare alternative technologies using the helper function
324-
alternative_technologies_data = prepare_alternative_technologies(
325-
resource_inventory,
326-
alternatives,
327-
alternative_technologies,
328-
exit_strategy
300+
# Generate Outputs
301+
reports = {}
302+
303+
# Generate HTML report
304+
reports["HTML"] = generate_html_report(
305+
report_path, metadata, resource_type_mapping, resource_inventory,
306+
cost_data, risk_data, risk_definitions, alternatives, alternative_technologies, exit_strategy
329307
)
330308

331-
# Render the HTML template
332-
assessment_ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
333-
assessment_type = assessment_type or "Not Specified"
334-
335-
template_path = os.path.join("assets", "template", "index.html")
336-
with open(template_path, 'r') as file:
337-
template_content = file.read()
338-
339-
template = Template(template_content)
340-
html_content = template.render(
341-
cloud_service_provider=cloud_service_provider,
342-
exit_strategy=exit_strategy,
343-
assessment_type=assessment_type,
344-
assessment_ts=assessment_ts,
345-
risks=risks,
346-
high_risk_count=severity_counts['high'],
347-
medium_risk_count=severity_counts['medium'],
348-
low_risk_count=severity_counts['low'],
349-
total_cost=total_cost,
350-
months_json=json.dumps(months),
351-
costs_json=json.dumps(cost_values),
352-
currency_symbol=currency_symbol,
353-
total_resources=total_resources,
354-
resource_inventory=resource_counts,
355-
alternative_technologies=alternative_technologies_data,
309+
# Generate PDF report
310+
reports["PDF"] = generate_pdf_report(
311+
provider_details, report_path, metadata, resource_type_mapping, resource_inventory,
312+
cost_data, risk_data, risk_definitions, alternatives, alternative_technologies, exit_strategy
356313
)
357314

358-
# Save the generated report to a file
359-
report_file_path = os.path.join(report_path, "index.html")
360-
with open(report_file_path, 'w') as report_file:
361-
report_file.write(html_content)
315+
# Generate JSON report
316+
reports["JSON"] = generate_json_report(
317+
raw_data_path, metadata, resource_type_mapping, resource_inventory,
318+
cost_data, risk_data, risk_definitions, alternatives, alternative_technologies, exit_strategy
319+
)
362320

363-
return {"success": True, "reports": {"HTML": report_file_path}}
321+
return {"success": True, "reports": reports}
364322

365323
except Exception as e:
366324
return {"success": False, "logs": f"Error generating report: {str(e)}"}

core/utils.py

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -33,108 +33,3 @@ def copy_assets(report_path):
3333
# Only copy if the destination doesn't already exist
3434
if not os.path.exists(db_dest_path):
3535
shutil.copyfile(db_src_path, db_dest_path)
36-
37-
38-
def get_cost_summary(cost_data):
39-
months = []
40-
cost_values = []
41-
total_cost = 0
42-
43-
# Map currency codes to their respective symbols
44-
currency_symbols = {
45-
"USD": "$",
46-
"GBP": "£",
47-
"EUR": "€"
48-
}
49-
50-
# Convert list to dictionary if necessary
51-
if isinstance(cost_data, list):
52-
cost_data = {
53-
item["month"]: {"cost": item["cost"], "currency": item["currency"]}
54-
for item in cost_data
55-
}
56-
57-
# Extract currency from the first entry, assuming all costs use the same currency
58-
first_entry = next(iter(cost_data.values()), None)
59-
currency_code = first_entry.get("currency", "USD") if first_entry else "USD"
60-
currency_symbol = currency_symbols.get(currency_code, currency_code) # Default to currency_code if no symbol exists
61-
62-
# Iterate over the cost data, expecting 6 months
63-
for month, details in sorted(cost_data.items()):
64-
months.append(datetime.strptime(month, "%Y-%m-%d").strftime('%b'))
65-
cost_values.append(details["cost"])
66-
total_cost += details["cost"]
67-
68-
total_cost = round(total_cost, 2)
69-
return months, cost_values, total_cost, currency_symbol
70-
71-
def get_risk_summary(risk_data, risk_definitions, resource_inventory):
72-
severity_order = {'high': 1, 'medium': 2, 'low': 3}
73-
severity_counts = {'high': 0, 'medium': 0, 'low': 0}
74-
sorted_risks = []
75-
76-
# Map resource IDs to resource names for quick lookup
77-
resource_name_map = {str(key): value['name'] for key, value in resource_inventory.items()}
78-
79-
# Group risks by their risk code and impacted resources
80-
risk_map = defaultdict(lambda: {"impacted_resources": set(), "count": 0})
81-
for risk_entry in risk_data:
82-
risk_code = risk_entry['risk']
83-
resource_type = str(risk_entry['resource_type']) if risk_entry['resource_type'] != "null" else None
84-
85-
if resource_type:
86-
# Handle risks with associated resource types
87-
resource_name = resource_name_map.get(resource_type, "Unknown Resource")
88-
risk_map[risk_code]["impacted_resources"].add(resource_name)
89-
risk_map[risk_code]["count"] += 1
90-
else:
91-
# Handle overall risks with no specific resource type
92-
risk_map[risk_code]["impacted_resources"] = []
93-
risk_map[risk_code]["count"] = None
94-
95-
# Process risk definitions
96-
for risk_code, risk_info in risk_map.items():
97-
risk_definition = next((rd for rd in risk_definitions if rd["id"] == risk_code), None)
98-
if not risk_definition:
99-
continue
100-
101-
severity = risk_definition['severity']
102-
severity_counts[severity] += 1
103-
104-
sorted_risks.append({
105-
'name': risk_definition['name'],
106-
'description': risk_definition['description'],
107-
'impacted_resources': list(risk_info["impacted_resources"]),
108-
'impacted_resources_count': risk_info["count"],
109-
'severity': severity
110-
})
111-
112-
# Sort risks by severity
113-
sorted_risks.sort(key=lambda x: severity_order.get(x['severity'], 4))
114-
115-
return sorted_risks, severity_counts
116-
117-
def prepare_alternative_technologies(resource_inventory, alternatives, alternative_technologies, exit_strategy):
118-
alt_tech_data = []
119-
for resource in resource_inventory:
120-
resource_type = resource.get("resource_type")
121-
relevant_alternatives = [
122-
alt for alt in alternatives
123-
if str(alt["resource_type"]) == str(resource_type) and str(alt["strategy_type"]) == str(exit_strategy)
124-
]
125-
for alt in relevant_alternatives:
126-
tech = next(
127-
(t for t in alternative_technologies if t["id"] == alt["alternative_technology"] and t["status"] == "t"),
128-
None
129-
)
130-
if tech:
131-
alt_tech_data.append({
132-
"resource_type_id": resource_type,
133-
"product_name": tech.get("product_name"),
134-
"product_description": tech.get("product_description"),
135-
"product_url": tech.get("product_url"),
136-
"open_source": tech.get("open_source") == "t",
137-
"support_plan": tech.get("support_plan") == "t",
138-
"status": tech.get("status") == "t",
139-
})
140-
return alt_tech_data

0 commit comments

Comments
 (0)