Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: AWS DevOps CI/CD Pipeline

on:
push:
branches: [main, master]
branches: main
pull_request:
branches: [main, master]
branches: main
workflow_dispatch:

jobs:
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Detailed documentation for AWS resources setup is available in [docs/task1_aws.m
- S3 event-triggered function
- CloudWatch logging integration
- Screenshot:
![Lambda Function Configuration](screenshots/lambda-function.png)
![Lambda Function Configuration](screenshots/lambda-function.png)

## Task 2: Scripting

Expand All @@ -53,12 +53,16 @@ Python scripts that utilize AWS SDK (boto3) to:
- Lists all S3 buckets in your AWS account
- Displays object count in a specified bucket
- Usage: `python scripts/list_s3_buckets.py --bucket <bucket-name>`
- Screenshot:
![Lambda Function Configuration](screenshots/S3Bucket.png)

2. **csv_analyzer.py**

- Analyzes a CSV file (name, age, grade)
- Prints students with grades above a threshold
- Usage: `python scripts/csv_analyzer.py scripts/sample_students.csv --threshold 80`
- Screenshot:
![Lambda Function Configuration](screenshots/Csv.png)

3. **SDK Documentation References**
- See [docs/sdk_documentation.md](docs/sdk_documentation.md) for links to the AWS SDK documentation
Expand Down
8 changes: 4 additions & 4 deletions lambda/s3_event_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
def lambda_handler(event, _):
# Log the full event
logger.info('Received S3 event: ' + json.dumps(event))
logger.info('Received S3 event: %s', json.dumps(event))

# Extract and log key details
for record in event.get('Records', []):
bucket = record.get('s3', {}).get('bucket', {}).get('name', 'unknown')
key = record.get('s3', {}).get('object', {}).get('key', 'unknown')
event_name = record.get('eventName', 'unknown')

logger.info(f'Event: {event_name}, Bucket: {bucket}, Key: {key}')
logger.info('Event: %s, Bucket: %s, Key: %s', event_name, bucket, key)

return {
'statusCode': 200,
'body': json.dumps('Event processed successfully!')
}
}
18 changes: 11 additions & 7 deletions lambda/test_s3_event_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

import json
import unittest
import logging
from unittest.mock import patch, MagicMock
from unittest.mock import patch
from s3_event_logger import lambda_handler

class TestS3EventLogger(unittest.TestCase):
Expand Down Expand Up @@ -64,8 +63,13 @@ def test_lambda_handler_logs_event(self, mock_logger):
result = lambda_handler(self.event, self.context)

# Check that logger.info was called with the expected arguments
mock_logger.info.assert_any_call('Received S3 event: ' + json.dumps(self.event))
mock_logger.info.assert_any_call('Event: ObjectCreated:Put, Bucket: test-bucket, Key: test-object.txt')
mock_logger.info.assert_any_call('Received S3 event: %s', json.dumps(self.event))
mock_logger.info.assert_any_call(
'Event: %s, Bucket: %s, Key: %s',
'ObjectCreated:Put',
'test-bucket',
'test-object.txt'
)

# Verify the return structure
self.assertEqual(result['statusCode'], 200)
Expand All @@ -80,7 +84,7 @@ def test_lambda_handler_handles_empty_event(self, mock_logger):
result = lambda_handler(empty_event, self.context)

# Check logger.info was called with the event but not with details
mock_logger.info.assert_called_with('Received S3 event: ' + json.dumps(empty_event))
mock_logger.info.assert_called_with('Received S3 event: %s', json.dumps(empty_event))

# Verify the function completed successfully
self.assertEqual(result['statusCode'], 200)
Expand All @@ -105,10 +109,10 @@ def test_lambda_handler_handles_missing_fields(self, mock_logger):
result = lambda_handler(event_missing_fields, self.context)

# Check that logger.info was called with "unknown" for missing fields
mock_logger.info.assert_any_call('Event: ObjectCreated:Put, Bucket: unknown, Key: unknown')
mock_logger.info.assert_any_call('Event: %s, Bucket: %s, Key: %s', 'ObjectCreated:Put', 'unknown', 'unknown')

# Verify the function completed successfully
self.assertEqual(result['statusCode'], 200)

if __name__ == '__main__':
unittest.main()
unittest.main()
Binary file added screenshots/Csv.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 screenshots/S3Bucket.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 screenshots/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 19 additions & 7 deletions scripts/csv_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def analyze_csv(file_path, threshold):

# Read the CSV file
students = []
with open(file_path, 'r', newline='') as csvfile:
with open(file_path, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
# Skip header if it exists (we'll check first row)
header = next(reader, None)
Expand All @@ -48,14 +48,14 @@ def analyze_csv(file_path, threshold):
above_threshold = [student for student in students if student['grade'] > threshold]

# Print results
print(f"\nCSV Analysis Results:")
print(f"=====================")
print("\nCSV Analysis Results:")
print("=====================")
print(f"File: {file_path}")
print(f"Total students: {len(students)}")
print(f"Average grade: {avg_grade:.2f}")
print(f"Threshold: {threshold}")
print(f"\nStudents with grade above {threshold}:")
print(f"--------------------------------------")
print("--------------------------------------")

if above_threshold:
# Sort by grade in descending order
Expand All @@ -65,8 +65,20 @@ def analyze_csv(file_path, threshold):
else:
print("No students found with grade above the threshold.")

except Exception as e:
print(f"Error analyzing CSV: {e}")
except FileNotFoundError as e:
print(f"File not found error: {e}")
sys.exit(1)
except csv.Error as e:
print(f"CSV parsing error: {e}")
sys.exit(1)
except ValueError as e:
print(f"Value error: {e}")
sys.exit(1)
except IOError as e:
print(f"I/O error: {e}")
sys.exit(1)
except Exception as e: # pylint: disable=W0718
print(f"Unexpected error analyzing CSV: {e}")
sys.exit(1)

def process_row(row):
Expand Down Expand Up @@ -113,4 +125,4 @@ def main():
analyze_csv(args.file, args.threshold)

if __name__ == "__main__":
main()
main()
98 changes: 0 additions & 98 deletions scripts/list_s3_buckets.py
Original file line number Diff line number Diff line change
@@ -1,98 +0,0 @@
#!/usr/bin/env python3
"""
Script to interact with AWS S3 using boto3:
- Lists all S3 buckets in your AWS account
- Displays the total number of objects in a specified S3 bucket
"""

import boto3
import sys
import argparse

def list_all_buckets():
"""List all S3 buckets in the AWS account"""
try:
# Create an S3 client
s3 = boto3.client('s3')

# Get all buckets
response = s3.list_buckets()

# Print bucket info
print("S3 Buckets in your AWS account:")
print("-" * 40)
if not response['Buckets']:
print("No buckets found.")
return

for i, bucket in enumerate(response['Buckets'], 1):
print(f"{i}. {bucket['Name']} (Created: {bucket['CreationDate']})")

print(f"\nTotal buckets: {len(response['Buckets'])}")

except Exception as e:
print(f"Error listing buckets: {e}")
sys.exit(1)

def count_objects_in_bucket(bucket_name):
"""Count objects in a specified S3 bucket"""
try:
# Create an S3 client
s3 = boto3.client('s3')

# Check if bucket exists
try:
s3.head_bucket(Bucket=bucket_name)
except Exception as e:
print(f"Error: Bucket '{bucket_name}' does not exist or you don't have access to it.")
return

# List all objects in the bucket
paginator = s3.get_paginator('list_objects_v2')
total_objects = 0
total_size = 0

print(f"\nObjects in bucket '{bucket_name}':")
print("-" * 40)

for page in paginator.paginate(Bucket=bucket_name):
if 'Contents' in page:
total_objects += len(page['Contents'])
# Sum up the size of all objects
for obj in page['Contents']:
total_size += obj['Size']

# Convert size to a readable format
size_str = format_size(total_size)

print(f"Total objects: {total_objects}")
print(f"Total size: {size_str}")

except Exception as e:
print(f"Error counting objects: {e}")
sys.exit(1)

def format_size(size_bytes):
"""Format bytes to a human-readable size"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_bytes < 1024.0:
return f"{size_bytes:.2f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:.2f} PB"

def main():
# Set up argument parser
parser = argparse.ArgumentParser(description='AWS S3 Bucket and Object Management')
parser.add_argument('--bucket', '-b', type=str, help='Specify a bucket name to count objects')

args = parser.parse_args()

# List all buckets
list_all_buckets()

# If a bucket name is provided, count objects in that bucket
if args.bucket:
count_objects_in_bucket(args.bucket)

if __name__ == "__main__":
main()
18 changes: 8 additions & 10 deletions scripts/test_csv_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@

import os
import sys
import tempfile
from pathlib import Path

# Add the parent directory to sys.path so we can import the module
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from scripts.csv_analyzer import process_row, is_numeric
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from csv_analyzer import process_row, is_numeric # pylint: disable=C0413

def test_is_numeric():
"""Test the is_numeric function"""
assert is_numeric("123") == True
assert is_numeric("123.45") == True
assert is_numeric("-123.45") == True
assert is_numeric("abc") == False
assert is_numeric("") == False
assert is_numeric("123") is True
assert is_numeric("123.45") is True
assert is_numeric("-123.45") is True
assert is_numeric("abc") is False
assert is_numeric("") is False

def test_process_row():
"""Test the process_row function"""
Expand Down Expand Up @@ -50,4 +48,4 @@ def test_process_row_with_invalid_data():
result = process_row(row)
assert result["name"] == "Bob Smith"
assert result["age"] == 20
assert result["grade"] == 0.0 # Should default to 0.0
assert result["grade"] == 0.0 # Should default to 0.0