Student: Shrirangesh Vedanarayanan
NEU ID: 002597004
- Ubuntu 24.04 LTS
- Python 3.12+
- PostgreSQL 16+
- HashiCorp Packer
- Terraform
- AWS CLI & GCP gcloud CLI
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv postgresql postgresql-contribsudo -u postgres psql
CREATE DATABASE webapp_db;
CREATE USER webapp_user WITH PASSWORD 'your_secure_password';
GRANT ALL PRIVILEGES ON DATABASE webapp_db TO webapp_user;
GRANT ALL ON SCHEMA public TO webapp_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO webapp_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO webapp_user;
\qcp .env.example .env
# Edit .env with your database credentials
nano .envpython3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtuvicorn app.main:app --host 0.0.0.0 --port 8080Application will be available at: http://localhost:8080
GET /healthz- Returns 200 OK if healthy, 503 if database is down- Only GET method allowed (405 for others)
- No payload allowed (400 if payload present)
POST /v1/user- Create new user (201 Created)GET /v1/user/self- Get current user info (requires Basic Auth)PUT /v1/user/self- Update user info (requires Basic Auth, returns 204)
GET /v1/metadata- Returns cloud platform metadata (public, no auth required)- Automatically detects AWS or GCP at runtime
- Returns instance ID, region, machine type, and network interfaces
- Returns 503 when not running on a supported cloud platform
- Only GET method allowed (405 for others)
- No payload or query parameters allowed (400 if present)
- Response includes
Cache-Control: no-cache, no-store, must-revalidateandPragma: no-cacheheaders
source venv/bin/activate
pytest tests/ -vcurl -v http://localhost:8080/healthzcurl -X POST http://localhost:8080/v1/user \
-H "Content-Type: application/json" \
-d '{"username":"test@example.com","password":"Pass123","first_name":"John","last_name":"Doe"}'curl -X GET http://localhost:8080/v1/user/self \
-u "test@example.com:Pass123"curl -X PUT http://localhost:8080/v1/user/self \
-u "test@example.com:Pass123" \
-H "Content-Type: application/json" \
-d '{"first_name":"Jane","last_name":"Smith"}'curl -v http://localhost:8080/v1/metadataCustom machine images are built for both AWS (AMI) and GCP (Image) using HashiCorp Packer. Images include the application, all dependencies, and PostgreSQL installed locally.
- Source image: Ubuntu 24.04 LTS (both AWS and GCP)
- Application user:
csye6225(no-login shell,/usr/sbin/nologin) - Application directory:
/opt/webapp - All artifacts owned by
csye6225:csye6225 - Systemd service configured for automatic startup
- Database (PostgreSQL) installed locally in the image
# Initialize Packer plugins
packer init packer/machine-image.pkr.hcl
# Validate template
packer validate packer/machine-image.pkr.hcl
# Build AWS AMI
packer build -only=amazon-ebs.webapp packer/machine-image.pkr.hcl
# Build GCP Image
packer build -only=googlecompute.webapp packer/machine-image.pkr.hcl- Location:
packer/machine-image.pkr.hcl - Provisioning script:
packer/setup.sh - Systemd service file:
packer/webapp.service
- Runs
packer fmt -checkto ensure template is properly formatted - Runs
packer validateto ensure template is syntactically valid - Failing checks prevent the pull request from being merged
- Runs integration tests
- Builds the application artifact on the GitHub Actions runner
- Builds custom images for both AWS and GCP
- No image is built if any preceding step fails
AWS_ACCESS_KEY_ID— AWS IAM access key for Packer buildsAWS_SECRET_ACCESS_KEY— AWS IAM secret key for Packer buildsGCP_SA_KEY— GCP service account key (JSON) for Packer builds
Infrastructure is managed in a separate tf-infra repository using Terraform.
- VPC with public and private subnets across 3 availability zones
- Internet gateway and route tables
- Application security group (ports 22, 80, 443, 8080)
- EC2 instance (t2.micro, 25GB GP2, custom AMI)
- VPC network with public and private subnets
- Firewall rules (ports 22, 80, 443, 8080) with target tag
webapp - Compute Engine instance (e2-medium, 25GB pd-balanced, custom image)
- Language: Python 3.12
- Framework: FastAPI
- Database: PostgreSQL 16
- ORM: SQLAlchemy
- Authentication: Basic Auth with BCrypt password hashing
- Image Building: HashiCorp Packer
- Infrastructure as Code: Terraform
- CI/CD: GitHub Actions
- Cloud Providers: AWS, GCP
Webapp/
├── app/
│ ├── config/
│ │ └── database.py # Database configuration
│ ├── models/
│ │ ├── user.py # User model
│ │ └── health_check.py # Health check model
│ ├── routes/
│ │ ├── health.py # Health check endpoint
│ │ ├── user.py # User management endpoints
│ │ └── metadata.py # Instance metadata endpoint
│ ├── utils/
│ │ ├── auth.py # Authentication utilities
│ │ └── validators.py # Input validators
│ └── main.py # Application entry point
├── tests/
│ ├── conftest.py # Test configuration and fixtures
│ ├── test_health.py # Health check tests
│ ├── test_user.py # User endpoint tests
│ └── test_metadata.py # Metadata endpoint tests
├── packer/
│ ├── machine-image.pkr.hcl # Packer template (AWS + GCP)
│ ├── setup.sh # Image provisioning script
│ └── webapp.service # Systemd service file
├── .github/
│ └── workflows/
│ ├── ci.yml # Integration tests (on PR)
│ ├── packer-validate.yml # Packer validation (on PR)
│ └── packer-build.yml # Packer build (on merge)
├── scripts/
│ └── setup.sh # Server setup script
├── requirements.txt # Python dependencies
├── .env.example # Environment template
├── .gitignore # Git ignore file
└── README.md # This file
- BCrypt password hashing with salt
- Basic Authentication for protected endpoints
- No passwords returned in API responses
- Input validation with Pydantic
- SQL injection protection via SQLAlchemy ORM
- Least-privilege service accounts for CI/CD
- Dedicated application user (
csye6225) with no login shell - Environment-based configuration (no hardcoded credentials)
- id (UUID, primary key)
- username (VARCHAR, unique, not null)
- password (VARCHAR, BCrypt hashed, not null)
- first_name (VARCHAR, not null)
- last_name (VARCHAR, not null)
- account_created (TIMESTAMP WITH TIMEZONE)
- account_updated (TIMESTAMP WITH TIMEZONE)
- check_id (BIGINT, primary key, auto-increment)
- check_datetime (TIMESTAMP WITH TIMEZONE)
- All timestamps stored in UTC
- Database schema created automatically on startup
- Application follows cloud-native principles (stateless, external config)
- Platform detection uses short timeouts (2s) to avoid delays when running locally
- Platform detection is cached for the lifetime of the process