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 all 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
53 changes: 53 additions & 0 deletions assets/css/reporting.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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;
table-layout: fixed; /* Ensure the first column has a fixed width */
width: 100%; /* Set the table width */
}
/* Styling the first column */
#cloud_table tbody td:first-child {
background-color: #009879;
color: #ffffff;
text-align: center;
font-weight: bold;
width: 250px; /* Set a fixed width for the first column */
white-space: nowrap; /* Prevent text wrapping in the first column */
}
#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;
}
#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
3 changes: 2 additions & 1 deletion cloudwash/providers/podman.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Azure CR Cleanup Utilities"""
"""PODMAN CR Cleanup Utilities"""
from cloudwash.client import compute_client
from cloudwash.constants import container_data as data
from cloudwash.entities.providers import PodmanCleanup
Expand All @@ -8,6 +8,7 @@

def cleanup(**kwargs):
is_dry_run = kwargs.get("dry_run", False)
dry_data['PROVIDER'] = "PODMAN"
for items in data:
dry_data[items]['delete'] = []
with compute_client("podman") as podman_client:
Expand Down
131 changes: 79 additions & 52 deletions cloudwash/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@
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 style
from dominate.tags import table
from dominate.tags import tbody
from dominate.tags import td
from dominate.tags import tr
from dominate.util import raw

from cloudwash.logger import logger


_vms_dict = {"VMS": {"delete": [], "stop": [], "skip": []}}
_containers_dict = {"CONTAINERS": {"delete": [], "stop": [], "skip": []}}

Expand All @@ -16,6 +27,7 @@
"RESOURCES": {"delete": []},
"STACKS": {"delete": []},
"IMAGES": {"delete": []},
"PROVIDER": "",
}
dry_data.update(_vms_dict)
dry_data.update(_containers_dict)
Expand All @@ -28,58 +40,73 @@ def echo_dry(dry_data=None) -> None:
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_containers = dry_data["CONTAINERS"]["delete"]
stopable_containers = dry_data["CONTAINERS"]["stop"]
skipped_containers = dry_data["CONTAINERS"]["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_containers or stopable_containers or skipped_containers:
logger.info(
f"Containers:\n\tDeletable: {deletable_containers}\n\t"
f"Stoppable: {stopable_containers}\n\t"
f"Skip: {skipped_containers}"
)
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,
deletable_containers,
stopable_containers,
skipped_containers,
]
):
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_containers": dry_data["CONTAINERS"]["delete"],
"stopable_containers": dry_data["CONTAINERS"]["stop"],
"skipped_containers": dry_data["CONTAINERS"]["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,
}

# Group the same resource type under the same section for logging
grouped_resources = {}
for key, value in resource_data.items():
if key != 'provider' and value:
suffix = key.split('_')[1].upper()
action = key.split('_')[0].title()

if suffix not in grouped_resources.keys():
grouped_resources[suffix] = {}
grouped_resources[suffix][action] = value

if any(value for key, value in resource_data.items() if key != 'provider'):
for suffix, actions in grouped_resources.items():
logger.info(f"{suffix}:")
for action, value in actions.items():
logger.info(f"\t{action}: {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 tbody():
for table_head in kwargs.keys():
if kwargs[table_head] and table_head != "provider":
with tr():
td(table_head.replace("_", " ").title())
bullet = '•'
if isinstance(kwargs[table_head], list):
component = ''
for resource_name in kwargs[table_head]:
component += bullet + ' ' + resource_name + ' '
td(raw(component))
else:
td(raw(bullet + ' ' + kwargs[table_head]))
with open('cleanup_resource_{}.html'.format(kwargs.get('provider')), '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