Skip to content
Open
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ coverage.xml
*.cover
*.py,cover
.hypothesis/

# Terraform
terraform.tfvars
terraform.tfstate*
.terraform/

.pytest_cache/
cover/

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review:

  1. Correctness:

    • The added Terraform-related entries seem appropriate for ignoring sensitive Terraform files (terraform.tfvars, terraform.tfstate*, .terraform/).
    • No obvious bugs in handling the new additions.
  2. Improvements:

    • Consider adding comments explaining each ignore pattern to enhance readability.
    • It's a good practice to sort the patterns alphabetically; it helps maintain a consistent style and makes it easier to locate specific entries.
    • Ensure that these patterns accurately reflect what should be ignored by version control.
  3. Risk Assessment:

    • Depending on the project, ensure that the .tfstate is managed securely as it can contain sensitive information.
    • Regularly review and update these ignore patterns as your project evolves to ensure that nothing essential is being inadvertently omitted.

Overall:

The changes made appear to address the necessary additions for Terraform file management. Adding comments and sorting the entries would be helpful for code maintenance. Regularly reviewing these ignore lists is important to maintain security and prevent accidental exposure of sensitive information.

Expand Down
145 changes: 145 additions & 0 deletions backend/terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# GPTeasers Terraform Infrastructure

This directory contains the Infrastructure as Code (IaC) configuration for deploying the GPTeasers quiz application to Azure Container Apps.

## 📁 Directory Contents

- `main.tf` - Main infrastructure configuration (Resource Group, Container App Environment, Container App)
- `variables.tf` - Input variables for the infrastructure
- `outputs.tf` - Output values from the deployed infrastructure
- `providers.tf` - Terraform provider configurations
- `terraform.tfvars` - Actual values for sensitive variables (not committed to git)
- `terraform.tfvars.example` - Example file showing required variable structure
- `terraform.tfstate` - Current state of deployed infrastructure (not committed to git)
- `graph.dot` - Infrastructure dependency graph in DOT format
- `infrastructure.png` & `infrastructure.svg` - Visual representations of the infrastructure

## 🚀 Deploying the Infrastructure

### Prerequisites

1. **Azure CLI installed and authenticated**:
```bash
az login
```

2. **Terraform installed** (v1.14.3+ recommended)

3. **Configure your secrets**:
```bash
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your actual API keys and tokens
```

### Deployment Commands

1. **Initialize Terraform** (first time only):
```bash
cd backend/terraform
terraform init
```

2. **Review the planned changes**:
```bash
cd backend/terraform
terraform plan
```

3. **Deploy the infrastructure**:
```bash
cd backend/terraform
terraform apply
```

4. **Get the deployed app URL**:
```bash
cd backend/terraform
terraform output container_app_url
```

### Cleanup

To destroy all resources:
```bash
cd backend/terraform
terraform destroy
```

## ⚠️ Important Notes

### Hardcoded Container Image Concern

**I'm a bit confused and concerned that the container image is hardcoded in `main.tf`**:

```terraform
image = "ghcr.io/djsaunders1997/gpteasers:7aa9ecacb7f655e25c149d704108690263ec26b4"
```

This means:
- The infrastructure is tied to a specific image version/tag
- Updates require manual Terraform changes
- CI/CD pipelines can't automatically update the infrastructure
- Risk of the image being deleted or becoming unavailable

**Future Enhancement Idea**: This would be a cool project to parameterize the image reference:
- Add an `image_tag` variable to `variables.tf`
- Make the image reference dynamic: `"ghcr.io/djsaunders1997/gpteasers:${var.image_tag}"`
- Allow CI/CD to pass the latest image tag during deployment
- Enable blue/green deployments with different image versions

For now, the infrastructure works as-is, but image updates require manual intervention. This could be a fun Terraform parameterization project for the future! 🎯

## 🔧 Configuration Details

### Resources Created

- **Resource Group**: `ContainerApps` (UK West region)
- **Container App Environment**: `container-app-environment`
- **Container App**: `gpteasers` with:
- 0.25 CPU cores, 0.5Gi memory
- External ingress on port 8000
- GitHub Container Registry integration
- Environment variables for all AI provider API keys

### Secrets Management

The following secrets are managed via Terraform:
- OpenAI API Key
- Azure AI API Key & Base URL
- Gemini API Key
- DeepSeek API Key
- GitHub Container Registry Token

**Security Note**: Secrets are defined in `terraform.tfvars` (gitignored) and managed through Terraform's lifecycle rules to prevent accidental overwrites during CI/CD deployments.

### Network Configuration

- **Ingress**: External enabled, auto transport
- **Traffic**: 100% to latest revision
- **Scaling**: 0-1 replicas (manual scaling)

## 📊 Infrastructure Visualization

View the infrastructure dependencies:
```bash
# Generate DOT graph
cd backend/terraform
terraform graph > graph.dot

# Convert to PNG (requires GraphViz)
dot -Tpng graph.dot -o infrastructure.png
```

## 🔍 Troubleshooting

- **State issues**: `terraform refresh` to sync with Azure
- **Import existing resources**: `terraform import <resource_type>.<name> <azure_resource_id>`
- **Debug mode**: `TF_LOG=DEBUG terraform apply`

## 📝 Development Workflow

1. Make changes to `.tf` files
2. Run `terraform plan` to preview
3. Run `terraform apply` to deploy
4. Use `terraform output` to get URLs/endpoints
5. Commit changes (excluding `.tfstate` and `.tfvars`)
108 changes: 108 additions & 0 deletions backend/terraform/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Resource Group
resource "azurerm_resource_group" "gpteasers" {
name = var.resource_group_name
location = var.location
}

# Container App Environment
resource "azurerm_container_app_environment" "gpteasers" {
name = var.container_app_environment_name
location = azurerm_resource_group.gpteasers.location
resource_group_name = azurerm_resource_group.gpteasers.name
# Remove log_analytics_workspace_id - the existing environment doesn't have one
}

# Container App
resource "azurerm_container_app" "gpteasers" {
name = var.container_app_name
container_app_environment_id = azurerm_container_app_environment.gpteasers.id
resource_group_name = azurerm_resource_group.gpteasers.name
revision_mode = "Single"

template {
container {
name = "gpteasers"
image = "ghcr.io/djsaunders1997/gpteasers:7aa9ecacb7f655e25c149d704108690263ec26b4"
cpu = 0.25
memory = "0.5Gi"

env {
name = "OPENAI_API_KEY"
secret_name = "openai-api-key-secret"
}
env {
name = "AZURE_AI_API_KEY"
secret_name = "azure-ai-api-key-secret"
}
env {
name = "GEMINI_API_KEY"
secret_name = "gemini-api-key-secret"
}
env {
name = "DEEPSEEK_API_KEY"
secret_name = "deepseek-api-key-secret"
}
env {
name = "AZURE_AI_API_BASE"
secret_name = "azure-ai-api-base-secret"
}
}

min_replicas = 0
max_replicas = 1
}

ingress {
external_enabled = true
target_port = 8000
transport = "auto" # Changed from "Auto" to "auto" to match existing
traffic_weight {
latest_revision = true
percentage = 100
}
}

registry {
server = "ghcr.io"
username = "DJSaunders1997"
password_secret_name = "ghcrio-djsaunders1997"
}

# Secrets are managed externally - these placeholders prevent Terraform from removing them
secret {
name = "openai-api-key-secret"
value = var.openai_api_key
}

secret {
name = "azure-ai-api-key-secret"
value = var.azure_ai_api_key
}

secret {
name = "gemini-api-key-secret"
value = var.gemini_api_key
}

secret {
name = "deepseek-api-key-secret"
value = var.deepseek_api_key
}

secret {
name = "azure-ai-api-base-secret"
value = var.azure_ai_api_base
}

secret {
name = "ghcrio-djsaunders1997"
value = var.ghcr_token
}

lifecycle {
ignore_changes = [
secret, # Don't update secrets - they're managed by CI/CD or manually
template[0].container[0].image, # Allow image updates from CI/CD
]
}
}
14 changes: 14 additions & 0 deletions backend/terraform/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output "container_app_url" {
description = "URL of the deployed container app"
value = azurerm_container_app.gpteasers.latest_revision_fqdn
}

output "resource_group_name" {
description = "Name of the resource group"
value = azurerm_resource_group.gpteasers.name
}

output "container_app_environment_name" {
description = "Name of the container app environment"
value = azurerm_container_app_environment.gpteasers.name
}
12 changes: 12 additions & 0 deletions backend/terraform/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}

provider "azurerm" {
features {}
}
12 changes: 12 additions & 0 deletions backend/terraform/terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copy this file to terraform.tfvars and fill in your actual values
# DO NOT commit terraform.tfvars to git - it contains sensitive information

# API Keys
openai_api_key = "your-openai-api-key-here"
gemini_api_key = "your-gemini-api-key-here"
azure_ai_api_key = "your-azure-ai-api-key-here"
azure_ai_api_base = "your-azure-ai-api-base-url-here"
deepseek_api_key = "your-deepseek-api-key-here"

# GitHub Container Registry
ghcr_token = "your-github-personal-access-token-here"
60 changes: 60 additions & 0 deletions backend/terraform/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
variable "resource_group_name" {
description = "Name of the resource group"
type = string
default = "ContainerApps"
}

variable "location" {
description = "Azure region"
type = string
default = "UK West"
}

variable "container_app_name" {
description = "Name of the container app"
type = string
default = "gpteasers"
}

variable "container_app_environment_name" {
description = "Name of the container app environment"
type = string
default = "container-app-environment"
}

# Secret variables - values loaded from terraform.tfvars (not committed)
variable "openai_api_key" {
description = "OpenAI API key"
type = string
sensitive = true
}

variable "gemini_api_key" {
description = "Gemini API key"
type = string
sensitive = true
}

variable "azure_ai_api_key" {
description = "Azure AI API key"
type = string
sensitive = true
}

variable "azure_ai_api_base" {
description = "Azure AI API base URL"
type = string
sensitive = true
}

variable "deepseek_api_key" {
description = "DeepSeek API key"
type = string
sensitive = true
}

variable "ghcr_token" {
description = "GitHub Container Registry token"
type = string
sensitive = true
}