DEV Community

Cover image for Build Your Own AWS DevOps CLI with Python & Boto3

Build Your Own AWS DevOps CLI with Python & Boto3

Automate S3, EC2, and Lambda like a pro β€” with just a terminal command.

🐍 Introduction β€” Python Meets DevOps Automation

Welcome Devs to the world of code and automation πŸ‘¨β€πŸ’»

Being around the DevOps landscape for more than two years, I’ve often come across one of the biggest myths in the field β€” β€œDevOps Engineers don’t code.”

Well… we do code, and quite a lot of it. From automating repetitive tasks, writing IAM policies, and managing infrastructure as code in HCL, to gluing together entire cloud workflows β€” code is very much a part of a DevOps engineer’s daily toolkit.

It’s been a while since I last got my hands dirty with Python, so I decided to change that. In today’s blog, we’re kicking off another project in the Python for DevOps series.

We’ll be building a custom CLI tool using Python3 and Boto3 (AWS SDK for Python) to manage EC2 instances, S3 buckets, and Lambda functions β€” all through the command line.

This project is a great way to:

  • Strengthen your Python skills 🧠

  • Get hands-on with Boto3 πŸ’»

  • Automate AWS tasks like a pro ⚑

So without further ado, let’s dive right in and start building something practical.


πŸ“½οΈ Youtube Demonstration


🧰 Prerequisites

Before we start building our custom AWS CLI, let’s make sure your system is ready to roll.

Here’s what you’ll need:

  • πŸͺ„ AWS CLI Installed & Configured

    You should have the AWS CLI installed and configured with an IAM user that has full access to:

    • EC2
    • Lambda
    • IAM
    • S3

    If you haven’t set this up yet, I’ve got you covered β€” check out the Step 1 section of my previous blog here:

    πŸ‘‰ Learn how to deploy a Three-Tier Application on AWS EKS using Terraform

  • 🐍 Python 3

    Make sure Python 3 is installed on your system.

    You can verify it by running:

    python3 --version
    

Once both are ready, we can move on to setting up our project structure and start coding. ⚑


🧠 Writing the Core Python Scripts β€” EC2, S3 & Lambda Automation

The full source code for this project is available on my GitHub repo:

πŸ‘‰ Pravesh-Sudha/python-for-devops

Navigate inside the automate-ec2-s3-and-lambda directory, and you’ll find three main files:

  • ec2_manager.py

  • s3_manager.py

  • lambda_manager.py

These files contain the logic to interact with AWS using Boto3, the official AWS SDK for Python.

πŸ–₯️ 1. EC2 Manager β€” ec2_manager.py

This file handles everything related to EC2 instances: creating, listing, and terminating them.

import boto3
from botocore.exceptions import ClientError

class EC2Manager:
    def __init__(self, region_name="us-east-1"):
        self.ec2 = boto3.client("ec2", region_name=region_name)

    def create_instance(self, image_id="ami-0360c520857e3138f", instance_type="t3.micro", key_name=None):
        try:
            print("Creating a Ec2 Instance....")
            params = {
                "ImageId": image_id,
                "InstanceType": instance_type,
                "MinCount": 1,
                "MaxCount": 1
            }
            if key_name:
                params["KeyName"] = key_name

            response = self.ec2.run_instances(**params)
            instance_id = response["Instances"][0]["InstanceId"]
            print(f"EC2 instance Created successfully with id: {instance_id}")
            return instance_id
        except ClientError as e:
            print(f"Ec2 creation failed. Error occured: {e}")
            return None

    def list_instances(self):
        try:
            response = self.ec2.describe_instances()
            instances = []
            for reservation in response["Reservations"]:
                for instance in reservation["Instances"]:
                    instance_info = {
                        "InstanceId": instance["InstanceId"],
                        "State": instance["State"]["Name"],
                        "Type": instance["InstanceType"],
                        "PublicIP": instance.get("PublicIpAddress")
                    }
                    instances.append(instance_info)
            return instances
        except ClientError as e:
            print(f"Failed to get list of Ec2 instances. Error Occured: {e}")
            return []

    def terminate_instance(self, instance_id):
        try:
            self.ec2.terminate_instances(InstanceIds=[instance_id])
            print(f"Instance with id {instance_id} terminated successfully")
        except ClientError as e:
            print(f"Failed to terminate instance {instance_id}. Error Occured: {e}")
Enter fullscreen mode Exit fullscreen mode

πŸ“ Quick Breakdown:

  • We initialize a Boto3 EC2 client inside the class constructor.

  • create_instance() launches a new EC2 instance using a given AMI ID and instance type.

  • list_instances() fetches and displays all existing instances with their state and public IP.

  • terminate_instance() shuts down an instance by its ID.

  • The try-except block ensures clean error handling and avoids breaking the script when AWS throws an exception.

πŸͺ£ 2. S3 Manager β€” s3_manager.py

This file deals with creating, listing, and deleting S3 buckets.

import boto3
from botocore.exceptions import ClientError

class S3Manager:
    def __init__(self, region_name="us-east-1"):
        self.s3 = boto3.client("s3", region_name=region_name)

    def create_bucket(self, bucket_name):
        try:
            print(f"Creating a new Bucket name: {bucket_name}")
            self.s3.create_bucket(Bucket=bucket_name)
            print(f"Bucket Created Successfully.")
        except ClientError as e:
            print(f"Failed to create Bucket. Error: {e}")

    def list_buckets(self):
        try:
            response = self.s3.list_buckets()
            buckets = [b["Name"] for b in response["Buckets"]]
            return buckets
        except ClientError as e:
            print(f"Failed to list the bucket. Error: {e}")
            return []

    def delete_bucket(self, bucket_name):
        try:
            print(f"Deleting Bucket: {bucket_name}")
            self.s3.delete_bucket(Bucket=bucket_name)
            print(f"Successfully deleted the bucket: {bucket_name}")
        except ClientError as e:
            print(f"Failed to delete the Bucket. Error: {e}")
Enter fullscreen mode Exit fullscreen mode

πŸ“ Quick Breakdown:

  • Boto3’s S3 client allows easy bucket operations.

  • We can create new buckets, fetch all bucket names, and delete existing ones.

  • Again, try-except helps us handle permission issues or invalid operations gracefully.

🧬 3. Lambda Manager β€” lambda_manager.py

This one is a bit more advanced because Lambda also requires an IAM role to execute.

import boto3
from botocore.exceptions import ClientError
import zipfile
import os
import time, json

class LambdaManager:
    def __init__(self, region_name="us-east-1"):
        self.lambda_client = boto3.client("lambda", region_name = region_name)
        self.iam_client = boto3.client("iam", region_name = region_name)

    def _create_deployment_package(self, code_path, zip_name="lambda_function.zip"):
        with zipfile.ZipFile(zip_name, "w") as zf:
            zf.write(code_path, os.path.basename(code_path))
            print(f"Deployment package built Successfully")
        return zip_name

    def _get_or_create_role(self, role_name="LambdaBasicExecutionRole"):
        assume_role_policy = {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {"Service": "lambda.amazonaws.com"},
                    "Action": "sts:AssumeRole"
                }
            ]
        }
        try:
            role = self.iam_client.get_role(RoleName = role_name)
            print(f"Getting the IAM role for Lambda....")
            return role["Role"]["Arn"]
        except self.iam_client.exceptions.NoSuchEntityException as e:
            print(f"Creating the IAM role : {role_name}....")
            role = self.iam_client.create_role(
                RoleName = role_name,
                AssumeRolePolicyDocument = json.dumps(assume_role_policy) 
            )
            self.iam_client.attach_role_policy(
                RoleName = role_name,
                PolicyArn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
            )
            print(f"Waiting for the IAM role Propagation...")
            time.sleep(10)
            return role["Role"]["Arn"]

    def create_lambda(self, function_name, code_path, handler_name="lambda_function.lambda_handler", runtime = "python3.9"):        
        try:
            zip_file = self._create_deployment_package(code_path)
            role_arn = self._get_or_create_role()

            with open(zip_file, "rb") as f:
                zip_bytes = f.read()

            response = self.lambda_client.create_function(
                FunctionName = function_name,
                Code = {"ZipFile": zip_bytes},
                Runtime = runtime,
                Role = role_arn,
                Handler = handler_name,
                Timeout = 15,
                MemorySize = 128, 
            )
            print(f"Lambda function {function_name} created Successfully.")
            return response["FunctionArn"]
        except ClientError as e:
            print(f"Failed to create the lambda function. Error: {e}")

    def list_lambdas(self):
        try:
            response = self.lambda_client.list_functions()
            lambdas = response.get("Functions",[])
            return [
                {
                "Name": fn["FunctionName"],
                "Runtime": fn["Runtime"],
                "Arn": fn["FunctionArn"]
                }
                for fn in lambdas
            ]
        except ClientError as e:
            print(f"Failed to list Lambda Functions. Error: {e}")
            return []

    def delete_lambda(self, function_name):
        try:
            self.lambda_client.delete_function(FunctionName = function_name)
            print(f"Lambda function: {function_name} deleted Successfully.")
        except ClientError as e:
            print(f"Failed to delete Lambda Function. Error: {e}")
Enter fullscreen mode Exit fullscreen mode

πŸ“ Quick Breakdown:

  • _create_deployment_package() zips your function code for Lambda deployment.

  • _get_or_create_role() ensures an IAM role exists to let Lambda run. If not, it creates one and attaches the basic execution policy.

  • create_lambda() uploads the zipped code and creates a new Lambda function.

  • list_lambdas() and delete_lambda() handle listing and cleanup.

And here’s our simple lambda_function.py file that acts as the Lambda code:

import json

def lambda_handler(event, context):
    print("Hello from Pravesh - Your AWS Community Builder!") 
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Pravesh - Your AWS Community Builder!')
    }
Enter fullscreen mode Exit fullscreen mode

🧭 4. Connecting Everything With main.py (CLI)

To make this project actually feel like a real CLI tool, we’ll use Python’s built-in argparse module.

This allows us to run commands like:

python main.py ec2 create
python main.py s3 list
python main.py lambda delete <function-name>
Enter fullscreen mode Exit fullscreen mode

Here’s the full CLI script πŸ‘‡

import argparse
from ec2_manager import EC2Manager
from s3_manager import S3Manager
from lambda_manager import LambdaManager

def main():
    parser = argparse.ArgumentParser(
        description="AWS Automation CLI using Boto3 (EC2 & S3)"
    )

    subparsers = parser.add_subparsers(dest="service", help="Choose AWS service")

    # EC2 Commands
    ec2_parser = subparsers.add_parser("ec2", help="Manage EC2 instances")
    ec2_subparsers = ec2_parser.add_subparsers(dest="action", help="EC2 actions")

    ec2_create = ec2_subparsers.add_parser("create", help="Create EC2 instance")
    ec2_create.add_argument("--image-id", default="ami-0c94855ba95c71c99", help="AMI ID")
    ec2_create.add_argument("--instance-type", default="t2.micro", help="Instance type")
    ec2_create.add_argument("--key-name", help="Optional key pair name")

    ec2_subparsers.add_parser("list", help="List EC2 instances")
    ec2_terminate = ec2_subparsers.add_parser("terminate", help="Terminate EC2 instance")
    ec2_terminate.add_argument("instance_id", help="Instance ID to terminate")

    # S3 Commands
    s3_parser = subparsers.add_parser("s3", help="Manage S3 buckets")
    s3_subparsers = s3_parser.add_subparsers(dest="action", help="S3 actions")
    s3_subparsers.add_parser("list", help="List S3 buckets")
    s3_create = s3_subparsers.add_parser("create", help="Create S3 bucket")
    s3_create.add_argument("bucket_name", help="Bucket name to create")
    s3_delete = s3_subparsers.add_parser("delete", help="Delete S3 bucket")
    s3_delete.add_argument("bucket_name", help="Bucket name to delete")

    # Lambda Commands
    lambda_parser = subparsers.add_parser("lambda", help="Manage AWS Lambda functions")
    lambda_subparsers = lambda_parser.add_subparsers(dest="action", help="Lambda actions")
    lambda_create = lambda_subparsers.add_parser("create", help="Create Lambda function")
    lambda_create.add_argument("function_name", help="Lambda function name")
    lambda_create.add_argument("code_path", help="Path to Python file (e.g. lambda_function.py)")
    lambda_create.add_argument("--handler", default="lambda_function.lambda_handler", help="Handler name")
    lambda_create.add_argument("--runtime", default="python3.9", help="Runtime version")
    lambda_subparsers.add_parser("list", help="List Lambda functions")
    lambda_delete = lambda_subparsers.add_parser("delete", help="Delete Lambda function")
    lambda_delete.add_argument("function_name", help="Lambda function name to delete")

    args = parser.parse_args()

    # Service Handling
    if args.service == "ec2":
        ec2 = EC2Manager()
        if args.action == "create":
            ec2.create_instance(image_id=args.image_id, instance_type=args.instance_type, key_name=args.key_name)
        elif args.action == "list":
            instances = ec2.list_instances()
            if not instances:
                print("No EC2 instances found.")
            else:
                print("\nEC2 Instances:")
                for i in instances:
                    print(f"{i['InstanceId']} | {i['State']} | {i['Type']} | {i['PublicIP']}")
        elif args.action == "terminate":
            ec2.terminate_instance(args.instance_id)

    elif args.service == "s3":
        s3 = S3Manager()
        if args.action == "create":
            s3.create_bucket(args.bucket_name)
        elif args.action == "list":
            buckets = s3.list_buckets()
            if not buckets:
                print("No buckets found.")
            else:
                print("\nS3 Buckets:")
                for b in buckets:
                    print(f"- {b}")
        elif args.action == "delete":
            s3.delete_bucket(args.bucket_name)

    elif args.service == "lambda":
        lm = LambdaManager()
        if args.action == "create":
            lm.create_lambda(args.function_name, args.code_path, handler_name=args.handler, runtime=args.runtime)
        elif args.action == "list":
            functions = lm.list_lambdas()
            if not functions:
                print("No Lambda functions found.")
            else:
                print("\nLambda Functions:")
                for f in functions:
                    print(f"{f['Name']} | {f['Runtime']} | {f['Arn']}")
        elif args.action == "delete":
            lm.delete_lambda(args.function_name)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

This is where everything comes together. Each manager class is imported, and argparse routes the commands to the right function.

🧠 Pro Tip β€” Write the Code Yourself

Now comes the most important part β€” don’t just copy the code.

Write the ec2_manager.py file line by line in your own workspace. Understand:

  • Why we use try/except

  • How boto3 clients are created

  • How API calls are structured and handled

Then move on to S3 and Lambda.

If you get stuck β€” look back at the repo for hints, but type it yourself. This will build muscle memory and confidence, which is crucial in the age of AI-assisted development.

Even though tools can generate code for you, knowing how to build it yourself gives you the real power ⚑

πŸ› οΈ Setting Up the Virtual Environment

Once your files are ready, set up the virtual environment and install dependencies:

python3 -m venv venv
source venv/bin/activate
pip3 install boto3 botocore
Enter fullscreen mode Exit fullscreen mode

πŸš€ Running the CLI Commands

EC2

python main.py ec2 create
python main.py ec2 list
python main.py ec2 terminate <instance-id>
Enter fullscreen mode Exit fullscreen mode

S3

python main.py s3 create <bucket-name>
python main.py s3 list
python main.py s3 delete <bucket-name>
Enter fullscreen mode Exit fullscreen mode

Lambda

python main.py lambda create <function-name> lambda_function.py
python main.py lambda list
python main.py lambda delete <function-name>
Enter fullscreen mode Exit fullscreen mode

Before deleting your Lambda function, open the AWS Lambda Dashboard, find your function, and give it a test.

You’ll see your lambda_function.py code running and the expected output returned in the console βœ…


🏁 Conclusion

And that’s a wrap, folks! πŸŽ‰

We just built a fully functional Python CLI tool that can manage your AWS EC2, S3, and Lambda resources β€” all from the command line. This might look simple at first, but projects like this help you truly understand how DevOps engineers use code to automate and simplify daily cloud operations.

This is just the beginning. You can extend this CLI to include:

  • More AWS services like RDS, DynamoDB, and CloudWatch πŸ“ˆ

  • Advanced features like error handling and logging 🧭

  • Role-based actions with IAM policies πŸ”

I’ve pushed the entire project to GitHub.

πŸ‘‰ Make sure to fork and star the repo if you found it useful β€” it really motivates me to create more such DevOps + Python projects.

If you build your own version, I’d love to see it! Share it with me or tag me on socials:

Until next time β€” keep automating and keep building. πŸ’ͺ⚑

Top comments (0)