Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create html based report for dry runs for cloud providers. #138

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions assets/css/reporting.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* CSS for html report generated during dry run. */
body{
font-family: sans-serif;
}
h1,h3{
margin-left: 15px;
}
table, th, td {
border-collapse: collapse;
margin-left: 0;
}

#cloud_table {
border-collapse: collapse;
margin: 25px 0;
font: 0.9em sans-serif;
min-width: 800px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
border-radius: 5px 5px 0 0;
overflow: hidden;
}

#cloud_table thead tr {
background-color: #009879;
color: #ffffff;
text-align: center;
font-weight: bold;
}

#cloud_table th, #cloud_table td {
padding: 12px 15px;
}

#cloud_table th:not(:last-child), #cloud_table td:not(:last-child) {
border-right: 0.1px solid black;
}

#cloud_table tbody tr {
border-bottom: 1px solid #dddddd;
color: #488b8b;
font-weight: bold;
}

#cloud_table tbody tr:nth-of-type(odd) {
background-color: #f3f3f3;
}

#cloud_table tbody tr:last-of-type {
border-bottom: 2px solid #009879;
}

#cloud_table tbody td {
text-align: left;
}

ul {
margin: 0;
padding: 0 0 0 20px;
list-style-type: circle;
}

ul li {
margin-top: 10px;
margin-bottom: 10px;
}
#cloud_table td {
font-size: 0.8em;
}
1 change: 1 addition & 0 deletions cloudwash/providers/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

def cleanup(**kwargs):
is_dry_run = kwargs.get("dry_run", False)
dry_data['PROVIDER'] = "AWS"
regions = settings.aws.auth.regions
if "all" in regions:
with compute_client("aws", aws_region="us-west-2") as client:
Expand Down
3 changes: 1 addition & 2 deletions cloudwash/providers/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

def cleanup(**kwargs):
is_dry_run = kwargs["dry_run"]

dry_data['PROVIDER'] = "AZURE"
regions = settings.azure.auth.regions
groups = settings.azure.auth.resource_groups

Expand All @@ -19,7 +19,6 @@ def cleanup(**kwargs):
# as it's never accessed and is only stored within wrapper
with compute_client("azure", azure_region="us-west", resource_group="foo") as azure_client:
regions = list(zip(*azure_client.list_region()))[0]

for region in regions:
if "all" in groups:
# non-existent RG can be chosen for query
Expand Down
2 changes: 2 additions & 0 deletions cloudwash/providers/gce.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@

def cleanup(**kwargs):
is_dry_run = kwargs.get("dry_run", False)
dry_data['PROVIDER'] = "GCE"
zones = settings.gce.auth.get('zones', ['all'])
if "all" in zones:
zones = gce_zones()
if kwargs["nics"] or kwargs["_all"]:
logger.warning("Cloudwash does not supports NICs operation for GCE yet!")
if kwargs["discs"] or kwargs["_all"]:
logger.warning("Cloudwash does not supports DISCs operation for GCE yet!")

with compute_client("gce") as gce_client:
for zone in zones:
for items in data:
Expand Down
112 changes: 72 additions & 40 deletions cloudwash/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,24 @@
from collections import namedtuple
from datetime import datetime

import dominate
import pytz
from dominate.tags import div
from dominate.tags import h1
from dominate.tags import h3
from dominate.tags import li
from dominate.tags import style
from dominate.tags import table
from dominate.tags import tbody
from dominate.tags import td
from dominate.tags import th
from dominate.tags import thead
from dominate.tags import tr
from dominate.tags import ul

from cloudwash.logger import logger


_vms_dict = {"VMS": {"delete": [], "stop": [], "skip": []}}
dry_data = {
"NICS": {"delete": []},
Expand All @@ -14,6 +28,7 @@
"RESOURCES": {"delete": []},
"STACKS": {"delete": []},
"IMAGES": {"delete": []},
"PROVIDER": "",
}
dry_data.update(_vms_dict)

Expand All @@ -24,47 +39,64 @@ def echo_dry(dry_data=None) -> None:
:param dict dry_data: The deletable resources dry data of a Compute Resource,
it follows the format of module scoped `dry_data` variable in this module
"""

logger.info("\n=========== DRY SUMMARY ============\n")
deletable_vms = dry_data["VMS"]["delete"]
stopable_vms = dry_data["VMS"]["stop"]
skipped_vms = dry_data["VMS"]["skip"]
deletable_discs = dry_data["DISCS"]["delete"]
deletable_nics = dry_data["NICS"]["delete"]
deletable_images = dry_data["IMAGES"]["delete"]
deletable_pips = dry_data["PIPS"]["delete"] if "PIPS" in dry_data else None
deletable_resources = dry_data["RESOURCES"]["delete"]
deletable_stacks = dry_data["STACKS"]["delete"] if "STACKS" in dry_data else None
if deletable_vms or stopable_vms or skipped_vms:
logger.info(
f"VMs:\n\tDeletable: {deletable_vms}\n\tStoppable: {stopable_vms}\n\t"
f"Skip: {skipped_vms}"
)
if deletable_discs:
logger.info(f"DISCs:\n\tDeletable: {deletable_discs}")
if deletable_nics:
logger.info(f"NICs:\n\tDeletable: {deletable_nics}")
if deletable_images:
logger.info(f"IMAGES:\n\tDeletable: {deletable_images}")
if deletable_pips:
logger.info(f"PIPs:\n\tDeletable: {deletable_pips}")
if deletable_resources:
logger.info(f"RESOURCEs:\n\tDeletable: {deletable_resources}")
if deletable_stacks:
logger.info(f"STACKs:\n\tDeletable: {deletable_stacks}")
if not any(
[
deletable_vms,
stopable_vms,
deletable_discs,
deletable_nics,
deletable_pips,
deletable_resources,
deletable_stacks,
deletable_images,
]
):
logger.info("\nNo resources are eligible for cleanup!")
logger.info("\n====================================\n")
resource_data = {
"provider": dry_data.get('PROVIDER'),
"deletable_vms": dry_data["VMS"]["delete"],
"stopable_vms": dry_data["VMS"]["stop"],
"skipped_vms": dry_data["VMS"]["skip"],
"deletable_discs": dry_data["DISCS"]["delete"],
"deletable_nics": dry_data["NICS"]["delete"],
"deletable_images": dry_data["IMAGES"]["delete"],
"deletable_pips": dry_data["PIPS"]["delete"] if "PIPS" in dry_data else None,
"deletable_resources": dry_data["RESOURCES"]["delete"],
"deletable_stacks": dry_data["STACKS"]["delete"] if "STACKS" in dry_data else None,
}
if any(value for key, value in resource_data.items() if key != 'provider'):
logger.info("Resources eligible for cleanup:")
jyejare marked this conversation as resolved.
Show resolved Hide resolved
for key, value in resource_data.items():
if value and key != "provider":
logger.info(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeating Deletable VMs with VMs (or any resource type), So its good to keep the old format of:

=========== DRY SUMMARY ============

VMs:
	Deletable: ['test-bvhoduliam']
	Stoppable: ['foremanqe-nightly2']

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, refactored the code to print the same way.

f"{key.replace('_', ' ').title()}:\n\t{key.split('_')[0].title()}: {value}"
)

logger.info("\n====================================\n")

create_html(**resource_data)
else:
logger.info("\nNo resources are eligible for cleanup!\n")


def create_html(**kwargs):
'''Creates a html based report file with deletable resources.'''
doc = dominate.document(title="Cloud resources page")

with doc.head:
with open('assets/css/reporting.css', 'r') as css:
style(css.read())

with doc:
with div(cls='cloud_box'):
h1('CLOUDWASH REPORT')
h3(f"{kwargs.get('provider')} RESOURCES")
with table(id='cloud_table'):
with thead():
with tr():
for table_head in kwargs.keys():
if kwargs[table_head] and table_head != "provider":
th(table_head.replace("_", " ").title())
with tbody():
for key, values in kwargs.items():
if key != "provider" and values:
if isinstance(values, list):
with td():
with ul():
[li(resource_name) for resource_name in values]
else:
td(values)
with open('cleanup_resource.html', 'w') as file:
file.write(doc.render())


def total_running_time(vm_obj) -> namedtuple:
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ install_requires =
dynaconf
click
wget
dominate
packages = find:

[options.extras_require]
Expand Down
Loading