Skip to content

Commit

Permalink
Add JSON & PDF reports - #4 & #8
Browse files Browse the repository at this point in the history
  • Loading branch information
bencehezso committed Dec 12, 2024
1 parent d28dbc8 commit 7b49932
Show file tree
Hide file tree
Showing 10 changed files with 1,083 additions and 178 deletions.
Binary file added assets/icons/severity/high.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/severity/low.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/icons/severity/medium.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/logo/report_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion assets/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ <h4>
<img src="assets/img/logo/logo.png" width="30" alt="EscapeCloud" />
EscapeCloud Community Edition - Cloud Exit Assessment Report
</h4>
<ul class="main-row">
<li class="main-col"><a class="btn1" href="report.pdf" target="_blank">Executive Summary (PDF)</a></li>
</ul>
</div>
</header>
<div class="main-wrpper">
Expand Down Expand Up @@ -84,7 +87,7 @@ <h3>
<div>
<h4>TimeStamp</h4>
<h3>
{{ assessment_ts }}
{{ timestamp }}
</h3>
</div>
</li>
Expand Down
98 changes: 28 additions & 70 deletions core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
from azure.mgmt.costmanagement import CostManagementClient
from azure.mgmt.costmanagement.models import QueryDefinition, TimeframeType

from .utils import copy_assets, get_cost_summary, get_risk_summary, prepare_alternative_technologies
from .utils import copy_assets
from .utils_aws import build_aws_resource_inventory, build_aws_cost_inventory
from .utils_azure import build_azure_resource_inventory, build_azure_cost_inventory
from .utils_db import connect, load_data
from .utils_report import generate_html_report, generate_pdf_report, generate_json_report

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

# Stage 6
def generate_report(cloud_service_provider, exit_strategy, assessment_type, report_path):
def generate_report(provider_details, cloud_service_provider, exit_strategy, assessment_type, report_path, raw_data_path):
try:
db_path = os.path.join(report_path, "data", "assessment.db")

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

# Create resource_inventory_dict with names and icons
resource_inventory_dict = {
str(item["resource_type"]): {
**item,
"name": resource_type_mapping.get(str(item["resource_type"]), {}).get("name", "Unknown Resource"),
"icon": resource_type_mapping.get(str(item["resource_type"]), {}).get("icon", "assets/icons/default.png")
}
for item in resource_inventory
# Timestamp
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")

metadata = {
"cloud_service_provider": cloud_service_provider,
"exit_strategy": exit_strategy,
"assessment_type": assessment_type,
"timestamp": timestamp,
}

# Prepare risk data
risks, severity_counts = get_risk_summary(risk_data, risk_definitions, resource_inventory_dict)

# Prepare cost data
months, cost_values, total_cost, currency_symbol = get_cost_summary(cost_data)

# Prepare resource data with names and icons
resource_counts = []
for resource_type, resource in resource_inventory_dict.items():
count = resource.get("count", 0)
resource_info = resource_type_mapping.get(str(resource_type), {})
name = resource_info.get("name", "Unknown Resource")
icon = resource_info.get("icon", "assets/icons/default.png").lstrip('/')

resource_counts.append({
"resource_type": resource_type,
"name": name,
"icon": icon,
"count": count
})

# Get the total count of all resources
total_resources = sum(item["count"] for item in resource_counts)

# Prepare alternative technologies using the helper function
alternative_technologies_data = prepare_alternative_technologies(
resource_inventory,
alternatives,
alternative_technologies,
exit_strategy
# Generate Outputs
reports = {}

# Generate HTML report
reports["HTML"] = generate_html_report(
report_path, metadata, resource_type_mapping, resource_inventory,
cost_data, risk_data, risk_definitions, alternatives, alternative_technologies, exit_strategy
)

# Render the HTML template
assessment_ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
assessment_type = assessment_type or "Not Specified"

template_path = os.path.join("assets", "template", "index.html")
with open(template_path, 'r') as file:
template_content = file.read()

template = Template(template_content)
html_content = template.render(
cloud_service_provider=cloud_service_provider,
exit_strategy=exit_strategy,
assessment_type=assessment_type,
assessment_ts=assessment_ts,
risks=risks,
high_risk_count=severity_counts['high'],
medium_risk_count=severity_counts['medium'],
low_risk_count=severity_counts['low'],
total_cost=total_cost,
months_json=json.dumps(months),
costs_json=json.dumps(cost_values),
currency_symbol=currency_symbol,
total_resources=total_resources,
resource_inventory=resource_counts,
alternative_technologies=alternative_technologies_data,
# Generate PDF report
reports["PDF"] = generate_pdf_report(
provider_details, report_path, metadata, resource_type_mapping, resource_inventory,
cost_data, risk_data, risk_definitions, alternatives, alternative_technologies, exit_strategy
)

# Save the generated report to a file
report_file_path = os.path.join(report_path, "index.html")
with open(report_file_path, 'w') as report_file:
report_file.write(html_content)
# Generate JSON report
reports["JSON"] = generate_json_report(
raw_data_path, metadata, resource_type_mapping, resource_inventory,
cost_data, risk_data, risk_definitions, alternatives, alternative_technologies, exit_strategy
)

return {"success": True, "reports": {"HTML": report_file_path}}
return {"success": True, "reports": reports}

except Exception as e:
return {"success": False, "logs": f"Error generating report: {str(e)}"}
105 changes: 0 additions & 105 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,108 +33,3 @@ def copy_assets(report_path):
# Only copy if the destination doesn't already exist
if not os.path.exists(db_dest_path):
shutil.copyfile(db_src_path, db_dest_path)


def get_cost_summary(cost_data):
months = []
cost_values = []
total_cost = 0

# Map currency codes to their respective symbols
currency_symbols = {
"USD": "$",
"GBP": "£",
"EUR": "€"
}

# Convert list to dictionary if necessary
if isinstance(cost_data, list):
cost_data = {
item["month"]: {"cost": item["cost"], "currency": item["currency"]}
for item in cost_data
}

# Extract currency from the first entry, assuming all costs use the same currency
first_entry = next(iter(cost_data.values()), None)
currency_code = first_entry.get("currency", "USD") if first_entry else "USD"
currency_symbol = currency_symbols.get(currency_code, currency_code) # Default to currency_code if no symbol exists

# Iterate over the cost data, expecting 6 months
for month, details in sorted(cost_data.items()):
months.append(datetime.strptime(month, "%Y-%m-%d").strftime('%b'))
cost_values.append(details["cost"])
total_cost += details["cost"]

total_cost = round(total_cost, 2)
return months, cost_values, total_cost, currency_symbol

def get_risk_summary(risk_data, risk_definitions, resource_inventory):
severity_order = {'high': 1, 'medium': 2, 'low': 3}
severity_counts = {'high': 0, 'medium': 0, 'low': 0}
sorted_risks = []

# Map resource IDs to resource names for quick lookup
resource_name_map = {str(key): value['name'] for key, value in resource_inventory.items()}

# Group risks by their risk code and impacted resources
risk_map = defaultdict(lambda: {"impacted_resources": set(), "count": 0})
for risk_entry in risk_data:
risk_code = risk_entry['risk']
resource_type = str(risk_entry['resource_type']) if risk_entry['resource_type'] != "null" else None

if resource_type:
# Handle risks with associated resource types
resource_name = resource_name_map.get(resource_type, "Unknown Resource")
risk_map[risk_code]["impacted_resources"].add(resource_name)
risk_map[risk_code]["count"] += 1
else:
# Handle overall risks with no specific resource type
risk_map[risk_code]["impacted_resources"] = []
risk_map[risk_code]["count"] = None

# Process risk definitions
for risk_code, risk_info in risk_map.items():
risk_definition = next((rd for rd in risk_definitions if rd["id"] == risk_code), None)
if not risk_definition:
continue

severity = risk_definition['severity']
severity_counts[severity] += 1

sorted_risks.append({
'name': risk_definition['name'],
'description': risk_definition['description'],
'impacted_resources': list(risk_info["impacted_resources"]),
'impacted_resources_count': risk_info["count"],
'severity': severity
})

# Sort risks by severity
sorted_risks.sort(key=lambda x: severity_order.get(x['severity'], 4))

return sorted_risks, severity_counts

def prepare_alternative_technologies(resource_inventory, alternatives, alternative_technologies, exit_strategy):
alt_tech_data = []
for resource in resource_inventory:
resource_type = resource.get("resource_type")
relevant_alternatives = [
alt for alt in alternatives
if str(alt["resource_type"]) == str(resource_type) and str(alt["strategy_type"]) == str(exit_strategy)
]
for alt in relevant_alternatives:
tech = next(
(t for t in alternative_technologies if t["id"] == alt["alternative_technology"] and t["status"] == "t"),
None
)
if tech:
alt_tech_data.append({
"resource_type_id": resource_type,
"product_name": tech.get("product_name"),
"product_description": tech.get("product_description"),
"product_url": tech.get("product_url"),
"open_source": tech.get("open_source") == "t",
"support_plan": tech.get("support_plan") == "t",
"status": tech.get("status") == "t",
})
return alt_tech_data
Loading

0 comments on commit 7b49932

Please sign in to comment.