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
35 changes: 4 additions & 31 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,10 @@ jobs:
verify-ssm-parameters:
name: Verify SSM Parameters
needs: determine-environment
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ needs.determine-environment.outputs.environment_name == 'dev' && secrets.DEV_AWS_ACCESS_KEY_ID || secrets.PROD_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ needs.determine-environment.outputs.environment_name == 'dev' && secrets.DEV_AWS_SECRET_ACCESS_KEY || secrets.PROD_AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2

- name: Check required SSM parameters
run: |
REQUIRED_PARAMS=(
"/jaildata/alert-email"
)

MISSING_PARAMS=0

for param in "${REQUIRED_PARAMS[@]}"; do
echo "Checking SSM parameter: $param"
if ! aws ssm get-parameter --name "$param" --with-decryption 2>/dev/null; then
echo "::error::Missing required SSM parameter: $param"
MISSING_PARAMS=1
fi
done

if [ $MISSING_PARAMS -ne 0 ]; then
echo "::error::One or more required SSM parameters are missing"
exit 1
fi

echo "All required SSM parameters are present"
uses: ./.github/workflows/verify-ssm-parameters.yml
with:
environment: ${{ needs.determine-environment.outputs.environment_name }}
secrets: inherit

terraform-apply:
name: Terraform Apply
Expand Down
35 changes: 4 additions & 31 deletions .github/workflows/manual-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,10 @@ on:
jobs:
verify-ssm-parameters:
name: Verify SSM Parameters
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ github.event.inputs.environment == 'dev' && secrets.DEV_AWS_ACCESS_KEY_ID || secrets.PROD_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ github.event.inputs.environment == 'dev' && secrets.DEV_AWS_SECRET_ACCESS_KEY || secrets.PROD_AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2

- name: Check required SSM parameters
run: |
REQUIRED_PARAMS=(
"/jaildata/alert-email"
)

MISSING_PARAMS=0

for param in "${REQUIRED_PARAMS[@]}"; do
echo "Checking SSM parameter: $param"
if ! aws ssm get-parameter --name "$param" --with-decryption 2>/dev/null; then
echo "::error::Missing required SSM parameter: $param"
MISSING_PARAMS=1
fi
done

if [ $MISSING_PARAMS -ne 0 ]; then
echo "::error::One or more required SSM parameters are missing"
exit 1
fi

echo "All required SSM parameters are present"
uses: ./.github/workflows/verify-ssm-parameters.yml
with:
environment: ${{ github.event.inputs.environment }}
secrets: inherit

terraform-apply:
name: Terraform Apply
Expand Down
106 changes: 104 additions & 2 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,27 @@ jobs:
- name: Check TypeScript build
run: npx tsc --noEmit

verify-ssm-parameters-dev:
name: Verify SSM Parameters (Dev)
if: github.base_ref == 'main'
uses: ./.github/workflows/verify-ssm-parameters.yml
with:
environment: dev
secrets: inherit

verify-ssm-parameters-prod:
name: Verify SSM Parameters (Prod)
if: github.base_ref == 'live'
uses: ./.github/workflows/verify-ssm-parameters.yml
with:
environment: prod
secrets: inherit

terraform-plan-dev:
name: Terraform Plan (Dev)
if: github.base_ref == 'main'
runs-on: ubuntu-latest
needs: verify-ssm-parameters-dev
defaults:
run:
working-directory: ./infra/terraform
Expand Down Expand Up @@ -67,8 +84,39 @@ jobs:

- name: Terraform Plan
working-directory: ./infra/terraform/dev
timeout-minutes: 5
run: |
terraform plan -no-color > plan_full.txt || { exit_code=$?; cat plan_full.txt; echo "plan<<EOF" >> $GITHUB_OUTPUT; cat plan_full.txt >> $GITHUB_OUTPUT; echo "EOF" >> $GITHUB_OUTPUT; echo "has_changes=true" >> $GITHUB_OUTPUT; exit $exit_code; }
echo "Starting Terraform plan at $(date)"
echo "Current working directory: $(pwd)"
echo "Terraform version: $(terraform version)"
echo "AWS region: $AWS_REGION"
echo "Checking state lock status..."

# Add debugging for state backend
terraform show -json 2>/dev/null | jq -r '.backend // "No backend info"' || echo "Could not query backend info"

# Enable verbose logging
export TF_LOG=DEBUG
export TF_LOG_PATH=/tmp/terraform.log

echo "Running terraform plan with timeout and debugging..."
timeout 240 terraform plan -no-color -lock-timeout=60s > plan_full.txt 2>&1 || {
exit_code=$?
echo "Terraform plan failed with exit code: $exit_code"
echo "=== Plan output ==="
cat plan_full.txt
echo "=== Terraform debug logs (last 50 lines) ==="
tail -n 50 /tmp/terraform.log 2>/dev/null || echo "No debug logs available"
echo "=== Current processes ==="
ps aux | grep -E "(terraform|aws)" | grep -v grep || echo "No terraform/aws processes found"
echo "plan<<EOF" >> $GITHUB_OUTPUT
cat plan_full.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "has_changes=true" >> $GITHUB_OUTPUT
exit $exit_code
}

echo "Terraform plan completed successfully at $(date)"
cat plan_full.txt

# Check if there are any changes planned
Expand Down Expand Up @@ -108,6 +156,7 @@ jobs:
name: Terraform Plan (Prod)
if: github.base_ref == 'live'
runs-on: ubuntu-latest
needs: verify-ssm-parameters-prod
environment: prod
defaults:
run:
Expand All @@ -133,14 +182,67 @@ jobs:
working-directory: ./infra/terraform/prod
run: terraform init

- name: Check and Clear State Locks
working-directory: ./infra/terraform/prod
run: |
echo "Checking for existing state locks in prod account..."
# Try to acquire a lock briefly to see if one exists
if ! timeout 10 terraform plan -detailed-exitcode >/dev/null 2>&1; then
echo "Terraform operation failed, checking if it's a lock issue..."

# Check the state file directly for lock info
if aws s3 cp s3://jaildata-tf-state/terraform.tfstate - 2>/dev/null | jq -e '.lineage' >/dev/null 2>&1; then
echo "State file exists and is readable"
else
echo "State file may have issues"
fi

# Try to force unlock any existing locks
echo "Attempting to clear any existing locks..."
terraform force-unlock -force $(aws s3 cp s3://jaildata-tf-state/.terraform/terraform.tfstate.lock.info - 2>/dev/null | jq -r '.ID // empty') 2>/dev/null || echo "No lock file found or unable to unlock"
else
echo "No lock detected"
fi

- name: Set Terraform environment variables
run: |
echo "TF_VAR_alert_email=${{ vars.ALERT_EMAIL }}" >> $GITHUB_ENV

- name: Terraform Plan
working-directory: ./infra/terraform/prod
timeout-minutes: 5
run: |
terraform plan -no-color > plan_full.txt || { exit_code=$?; cat plan_full.txt; echo "plan<<EOF" >> $GITHUB_OUTPUT; cat plan_full.txt >> $GITHUB_OUTPUT; echo "EOF" >> $GITHUB_OUTPUT; echo "has_changes=true" >> $GITHUB_OUTPUT; exit $exit_code; }
echo "Starting Terraform plan at $(date)"
echo "Current working directory: $(pwd)"
echo "Terraform version: $(terraform version)"
echo "AWS region: $AWS_REGION"
echo "Checking state lock status..."

# Add debugging for state backend
terraform show -json 2>/dev/null | jq -r '.backend // "No backend info"' || echo "Could not query backend info"

# Enable verbose logging
export TF_LOG=DEBUG
export TF_LOG_PATH=/tmp/terraform.log

echo "Running terraform plan with timeout and debugging..."
timeout 240 terraform plan -no-color -lock-timeout=60s > plan_full.txt 2>&1 || {
exit_code=$?
echo "Terraform plan failed with exit code: $exit_code"
echo "=== Plan output ==="
cat plan_full.txt
echo "=== Terraform debug logs (last 50 lines) ==="
tail -n 50 /tmp/terraform.log 2>/dev/null || echo "No debug logs available"
echo "=== Current processes ==="
ps aux | grep -E "(terraform|aws)" | grep -v grep || echo "No terraform/aws processes found"
echo "plan<<EOF" >> $GITHUB_OUTPUT
cat plan_full.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "has_changes=true" >> $GITHUB_OUTPUT
exit $exit_code
}

echo "Terraform plan completed successfully at $(date)"
cat plan_full.txt

# Check if there are any changes planned
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/verify-ssm-parameters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Verify SSM Parameters

on:
workflow_call:
inputs:
environment:
description: "Environment to verify (dev/prod)"
required: true
type: string
aws-region:
description: "AWS Region"
required: false
type: string
default: "us-east-2"
secrets:
DEV_AWS_ACCESS_KEY_ID:
description: "AWS Access Key ID for dev environment"
required: false
DEV_AWS_SECRET_ACCESS_KEY:
description: "AWS Secret Access Key for dev environment"
required: false
PROD_AWS_ACCESS_KEY_ID:
description: "AWS Access Key ID for prod environment"
required: false
PROD_AWS_SECRET_ACCESS_KEY:
description: "AWS Secret Access Key for prod environment"
required: false

jobs:
verify-ssm-parameters:
name: Verify SSM Parameters (${{ inputs.environment }})
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ inputs.environment == 'dev' && secrets.DEV_AWS_ACCESS_KEY_ID || secrets.PROD_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ inputs.environment == 'dev' && secrets.DEV_AWS_SECRET_ACCESS_KEY || secrets.PROD_AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ inputs.aws-region }}

- name: Check required SSM parameters
run: |
REQUIRED_PARAMS=(
"/jaildata/base-url"
"/jaildata/alert-email"
"/jaildata/alert-topic-arn"
"/jaildata/facilities/buncombe/api-id"
)

MISSING_PARAMS=0

echo "🔍 Verifying SSM parameters for ${{ inputs.environment }} environment..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

for param in "${REQUIRED_PARAMS[@]}"; do
echo "Checking SSM parameter: $param"
if ! PARAM_VALUE=$(aws ssm get-parameter --name "$param" --with-decryption --query 'Parameter.Value' --output text 2>/dev/null); then
echo "::error::Missing required SSM parameter: $param"
MISSING_PARAMS=1
elif [ "$PARAM_VALUE" = "CHANGE_ME" ] || [ -z "$PARAM_VALUE" ]; then
echo "::error::SSM parameter $param has not been configured (value is empty or placeholder)"
MISSING_PARAMS=1
else
echo "✅ Parameter $param is properly configured"
fi
done

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

if [ $MISSING_PARAMS -ne 0 ]; then
echo "::error::❌ One or more required SSM parameters are missing or misconfigured"
echo ""
echo "💡 To fix this:"
echo " 1. Ensure Terraform has been applied to create the parameter structure"
echo " 2. Manually set the actual values in AWS Systems Manager Parameter Store"
echo " 3. Replace any 'CHANGE_ME' placeholder values with real configuration"
exit 1
fi

echo "🎉 All required SSM parameters are properly configured for ${{ inputs.environment }}!"
10 changes: 5 additions & 5 deletions infra/terraform/dev/dev.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module "main" {
source = "../main"
environment = var.environment
region = var.region
domain = var.domain
alert_email = var.alert_email
source = "../main"
environment = var.environment
region = var.region
domain = var.domain
alert_email = var.alert_email
}
22 changes: 22 additions & 0 deletions infra/terraform/main/parameters.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
resource "aws_ssm_parameter" "jail_data_base_url" {
name = "/jaildata/base-url"
type = "String"
value = "CHANGE_ME"
description = "Base URL for external jail data API endpoints (set manually after deployment)"

lifecycle {
ignore_changes = [value]
}
}

resource "aws_ssm_parameter" "buncombe_api_id" {
name = "/jaildata/facilities/buncombe/api-id"
type = "String"
value = "CHANGE_ME"
description = "API ID for Buncombe County jail data system (set manually after deployment)"

lifecycle {
ignore_changes = [value]
}
}

# API Gateway domain outputs
output "ApiDomain" {
description = "API Gateway custom domain name"
Expand Down
18 changes: 9 additions & 9 deletions serverless/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ This is the backend API for the Detention Data project, built with the Serverles

## Architecture

- **AWS Lambda**: Serverless functions for API endpoints and scheduled data collection
- **Amazon DynamoDB**: NoSQL database for storing detention data
- **Amazon API Gateway**: REST API with API key authentication
- **AWS EventBridge**: Scheduled triggers for data collection
- **AWS CloudWatch**: Logging and monitoring
- **AWS Lambda**: Serverless functions for API endpoints and scheduled data collection
- **Amazon DynamoDB**: NoSQL database for storing detention data
- **Amazon API Gateway**: REST API with API key authentication
- **AWS EventBridge**: Scheduled triggers for data collection
- **AWS CloudWatch**: Logging and monitoring

## Project Structure

Expand All @@ -27,13 +27,13 @@ serverless/

## API Endpoints

- `GET /status` - Health check
- `GET /detainee/{detaineeId}` - Get detainee records
- `GET /detainees/active` - List active detainees
- `GET /status` - Health check
- `GET /detainee/{detaineeId}` - Get detainee records
- `GET /detainees/active` - List active detainees

## Scheduled Functions

- **Data Collection**: Runs daily at 10 AM UTC to collect detention data from configured county sources
- **Data Collection**: Runs daily at 10 AM UTC to collect detention data from configured county sources

## Development

Expand Down
Loading