Skip to content

Commit b204621

Browse files
added ec2-cleaner.py file and made chanfes to zap_cli.py file
1 parent 5e5bd82 commit b204621

File tree

3 files changed

+185
-17
lines changed

3 files changed

+185
-17
lines changed

commands/devops/ec2_cleaner.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import click
2+
import boto3
3+
from botocore.exceptions import NoCredentialsError, PartialCredentialsError
4+
from pathlib import Path
5+
import sys
6+
7+
# Get current script directory
8+
currentdir = Path(__file__).resolve().parent
9+
# Get parent directory
10+
parentdir = currentdir.parent
11+
# Add parent directory to sys.path
12+
sys.path.insert(0, str(parentdir))
13+
14+
# import logger after specifying root/parent path
15+
import logger
16+
17+
# ========== INITIALIZE AWS EC2 CLIENT
18+
19+
def get_ec2_client(region=None):
20+
"""
21+
Returns a Boto3 EC2 client using the default profile.
22+
"""
23+
try:
24+
return boto3.client("ec2", region_name=region)
25+
except (NoCredentialsError, PartialCredentialsError) as e:
26+
logger.error(f"Error: {str(e)}")
27+
exit(1)
28+
29+
# ========== EC2 INSTANCE CLEANER
30+
31+
def clean_unused_instances(ec2_client):
32+
"""
33+
Find and terminate unused EC2 instances.
34+
"""
35+
logger.info("\n==================== EC2 INSTANCE CLEANUP")
36+
instances = ec2_client.describe_instances()
37+
terminated_instances = []
38+
39+
total_instances = 0
40+
for reservation in instances["Reservations"]:
41+
for instance in reservation["Instances"]:
42+
instance_id = instance["InstanceId"]
43+
state = instance["State"]["Name"]
44+
launch_time = instance["LaunchTime"]
45+
46+
total_instances += 1
47+
# Display instance details
48+
click.echo(f"\n===== INSTANCE: {total_instances}")
49+
click.echo(f"Instance ID: {instance_id}")
50+
click.echo(f"State : {state}")
51+
click.echo(f"Launch time: {launch_time.strftime('%Y-%m-%d %H:%M:%S')}")
52+
53+
if state == "stopped":
54+
click.echo(f"Status: {state} - Terminating instance...")
55+
ec2_client.terminate_instances(InstanceIds=[instance_id])
56+
terminated_instances.append(instance_id)
57+
else:
58+
click.echo(f"Status: {state} - No action needed.")
59+
60+
if terminated_instances:
61+
logger.info(f"\nTerminated instances: {', '.join(terminated_instances)}")
62+
else:
63+
logger.info("\nNo stopped instances to terminate.")
64+
65+
# ========== CLEAN UNUSED VOLUMES
66+
67+
def clean_unused_volumes(ec2_client):
68+
"""
69+
Find and delete unused EC2 volumes.
70+
"""
71+
click.echo("\n==================== EC2 VOLUME CLEANUP")
72+
volumes = ec2_client.describe_volumes()
73+
deleted_volumes = []
74+
75+
total_volumes = 0
76+
for volume in volumes['Volumes']:
77+
volume_id = volume['VolumeId']
78+
state = volume['State']
79+
attachment_state = volume.get('Attachments', [])
80+
creation_time = volume['CreateTime']
81+
82+
total_volumes += 1
83+
# Display instance details
84+
click.echo(f"\n===== INSTANCE: {total_volumes}")
85+
# Display volume details
86+
click.echo(f"Volume ID : {volume_id}")
87+
click.echo(f"State : {state}")
88+
click.echo(f"Created on : {creation_time.strftime('%Y-%m-%d %H:%M:%S')}")
89+
if attachment_state:
90+
click.echo(f"Attached to : {', '.join([attachment['InstanceId'] for attachment in attachment_state])}")
91+
else:
92+
click.echo("Attached to : None (Unattached)")
93+
94+
if state == 'available' and not attachment_state:
95+
click.echo(f"Status: Unattached — Deleting this volume...")
96+
ec2_client.delete_volume(VolumeId=volume_id)
97+
deleted_volumes.append(volume_id)
98+
else:
99+
click.echo(f"Status: In Use — No action needed.")
100+
101+
if deleted_volumes:
102+
click.echo(f"\nDeleted volumes: {', '.join(deleted_volumes)}\n")
103+
else:
104+
click.echo("\nNo unattached volumes to delete.\n")
105+
106+
# ========== CLICK GROUP
107+
108+
@click.group(help="A group of commands for EC2 instance cleanup")
109+
def ec2_cleaner():
110+
"""
111+
A group of commands for terminating EC2 instances & volumes.
112+
"""
113+
pass
114+
115+
116+
@ec2_cleaner.command()
117+
@click.option('--region', default=None, help='AWS region to target (default is configured region)')
118+
def clean_instances(region):
119+
"""
120+
Find and terminate unused EC2 instances.
121+
"""
122+
ec2_client = get_ec2_client(region)
123+
clean_unused_instances(ec2_client)
124+
125+
126+
@ec2_cleaner.command()
127+
@click.option('--region', default=None, help='AWS region to target (default is configured region)')
128+
def clean_volumes(region):
129+
"""
130+
Find and delete unused EC2 volumes.
131+
"""
132+
ec2_client = get_ec2_client(region)
133+
clean_unused_volumes(ec2_client)
134+
135+
136+
@ec2_cleaner.command()
137+
@click.option('--region', default=None, help='AWS region to target (default is configured region)')
138+
def clean_all(region):
139+
"""
140+
Clean both unused EC2 instances and volumes.
141+
"""
142+
ec2_client = get_ec2_client(region)
143+
clean_unused_instances(ec2_client)
144+
clean_unused_volumes(ec2_client)
145+
146+
147+
# if this script is run directly, invoke the 'ec2_cleaner' group
148+
if __name__ == "__main__":
149+
ec2_cleaner()

commands/personal/gen_pass.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import click
22
import secrets
33
import string
4-
from commands import logger
4+
import sys
5+
from pathlib import Path
6+
7+
# Get current script directory
8+
currentdir = Path(__file__).resolve().parent
9+
# Get parent directory
10+
parentdir = currentdir.parent
11+
# Add parent directory to sys.path
12+
sys.path.insert(0, str(parentdir))
13+
14+
import logger
515

616
# ====================
717

zap_cli/zap_cli.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from commands.personal import image_processor
2626

2727
# import modules from devops
28-
from commands.devops import *
28+
from commands.devops import ec2_cleaner
2929

3030
# define main command group for the CLI Tool
3131
@click.group(help="ZAP CLI tool:A command-line interface for various utilities.")
@@ -35,25 +35,34 @@ def cli():
3535
# is REQUIRED for defining the command group
3636
pass
3737

38-
"""
39-
Adds command groups to the `cli` group.
40-
Each command group is a separate subcommand namespace.
41-
The first part is the module and the second part specifies the command to be added.
42-
"""
38+
39+
# add devops command group
40+
@click.group(help="DevOps related commands.")
41+
def devops():
42+
pass
43+
44+
# add personal command group
45+
@click.group(help="Personal related commands.")
46+
def personal():
47+
pass
4348

4449
# ========== ADD DEVOPS GROUP TO CLI
45-
# CODE HERE
50+
devops.add_command(ec2_cleaner.ec2_cleaner)
4651

4752
# ========== ADD PERSONAL GROUP TO CLI
48-
cli.add_command(calculator.calculator)
49-
cli.add_command(note_app.note_app)
50-
cli.add_command(yt_dl.yt_dl)
51-
cli.add_command(gen_pass.gen_pass)
52-
cli.add_command(unit.unit)
53-
cli.add_command(expense.expense)
54-
cli.add_command(sysinfo.sysinfo)
55-
cli.add_command(cache.cache)
56-
cli.add_command(image_processor.image_processor)
53+
personal.add_command(calculator.calculator)
54+
personal.add_command(note_app.note_app)
55+
personal.add_command(yt_dl.yt_dl)
56+
personal.add_command(gen_pass.gen_pass)
57+
personal.add_command(unit.unit)
58+
personal.add_command(expense.expense)
59+
personal.add_command(sysinfo.sysinfo)
60+
personal.add_command(cache.cache)
61+
personal.add_command(image_processor.image_processor)
62+
63+
# Add 'devops' and 'personal' as subcommands to the main 'cli' group
64+
cli.add_command(devops)
65+
cli.add_command(personal)
5766

5867
# Entry point of the script.
5968
# Calls the CLI tool if the script is executed.

0 commit comments

Comments
 (0)