# terraform-internal-spa Module Usage Instructions

## Purpose

This Terraform module deploys infrastructure for hosting Single Page Applications (SPAs) on AWS S3 with private access through an Application Load Balancer and VPC endpoints. The module creates an internal ALB that routes HTTPS traffic to an S3 bucket via VPC endpoints, providing secure, private access to SPAs within a VPC.

## Prerequisites

Before using this module, ensure you have:

1. **AWS Account Access**: Valid AWS credentials with permissions to create:
   - S3 buckets
   - Application Load Balancers and target groups
   - Security groups
   - VPC endpoint associations
   - Route53 records (if DNS enabled)
   - WAF associations

2. **Existing AWS Resources**:
   - VPC with ID available
   - At least 2 subnets in different availability zones for ALB deployment
   - S3 interface VPC endpoint already created in the VPC
   - ACM certificate for HTTPS (matching `certificate_domain` pattern)
   - WAF web ACL (or use default naming convention)

3. **Terraform Version**: >= 1.2.0

4. **AWS Provider Version**: >= 5.0

## Module Source

Reference this module from Git:

```hcl
source = "git@github.com:theorchard/terraform-internal-spa.git?ref=<VERSION>"
```

Replace `<VERSION>` with a specific version tag (e.g., `1.0.0`) or branch name.

## Required Providers

The module requires two AWS provider configurations:

1. **Default provider**: For creating most resources (ALB, S3, security groups, etc.)
2. **DNS provider** (aliased as `aws.dns`): For creating Route53 records

### Provider Configuration Examples

**Option 1: DNS records in networking account**
```hcl
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  region  = "us-east-1"
  alias   = "dns"
  profile = "networking"
}

module "internal_spa" {
  source = "git@github.com:theorchard/terraform-internal-spa.git?ref=1.0.0"
  
  providers = {
    aws.dns = aws.dns
  }
  
  # ... module variables
}
```

**Option 2: DNS records in same account**
```hcl
provider "aws" {
  region = "us-east-1"
}

module "internal_spa" {
  source = "git@github.com:theorchard/terraform-internal-spa.git?ref=1.0.0"
  
  providers = {
    aws.dns = aws
  }
  
  # ... module variables
}
```

## Required Variables

The following variables **must** be provided:

| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `environment` | `string` | Environment name | `"dev"`, `"qa"`, `"prod"` |
| `service_name` | `string` | Service name without environment prefix | `"customer-portal"` |
| `application_family` | `string` | Application family grouping | `"web"`, `"internal-tools"` |
| `vpc_id` | `string` | VPC ID where resources will be created | `"vpc-12345678"` |
| `load_balancer_subnet_ids` | `list(string)` | Subnet IDs for ALB (min 2, different AZs) | `["subnet-11111111", "subnet-22222222"]` |
| `fully_qualified_domain_name` | `string` | FQDN for the SPA (used as S3 bucket name) | `"portal.dev.theorchard.io"` |
| `certificate_domain` | `string` | ACM certificate domain pattern | `"*.dev.theorchard.io"` |

## Access Control Variables

At least one ingress access control method **must** be configured:

| Variable | Type | Description |
|----------|------|-------------|
| `ingress_cidr_blocks` | `list(string)` | CIDR blocks allowed to access ALB |
| `ingress_prefix_list_names` | `list(string)` | Managed prefix list names for ALB access |
| `allow_vpc_private_subnets` | `bool` | Allow traffic from VPC private subnets prefix list |

## Commonly Used Optional Variables

| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `route53_record_enabled` | `bool` | `false` | Create Route53 DNS record |
| `route53_zone_name` | `string` | `null` | Route53 zone name (required if DNS enabled) |
| `route53_is_private_zone` | `bool` | `false` | Whether Route53 zone is private |
| `vpc_endpoint_name` | `string` | `""` | Name tag filter for S3 VPC endpoint (if multiple exist) |
| `custom_waf_name` | `string` | `""` | Custom WAF name (defaults to `{environment}-orcd-waf-block`) |
| `custom_static_path_patterns` | `list(string)` | `["/frontend-${var.service_name}/frontend-${var.service_name}-*", "/frontend-${var.service_name}/prs/*", "/frontend-${var.service_name}/assets/*", "${local.spa_rewrite_target}"]` | Paths forwarded without rewriting |
| `spa_rewrite_path_patterns` | `list(string)` | `["/*"]` | Paths to apply URL rewriting |
| `additional_tags` | `map(string)` | `{}` | Additional tags for resources |

## Basic Usage Example

```hcl
# Configure providers
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  region  = "us-east-1"
  alias   = "dns"
  profile = "networking"
}

# Deploy the module
module "customer_portal" {
  source = "git@github.com:theorchard/terraform-internal-spa.git?ref=1.0.0"
  
  providers = {
    aws.dns = aws.dns
  }
  
  # Required variables
  environment                 = "dev"
  service_name                = "customer-portal"
  application_family          = "web"
  vpc_id                      = "vpc-12345678"
  load_balancer_subnet_ids    = ["subnet-11111111", "subnet-22222222"]
  fully_qualified_domain_name = "customer-portal.dev.theorchard.io"
  certificate_domain          = "*.dev.theorchard.io"
  
  # Access control
  ingress_prefix_list_names = ["vpn-ny-users"]
  
  # Optional: DNS record
  route53_record_enabled = true
  route53_zone_name      = "dev.theorchard.io"
  
  # Optional: Custom tags
  additional_tags = {
    team  = "platform"
    owner = "devops"
  }
}
```

## Advanced Usage Example

```hcl
module "admin_dashboard" {
  source = "git@github.com:theorchard/terraform-internal-spa.git?ref=1.0.0"
  
  providers = {
    aws.dns = aws
  }
  
  # Required variables
  environment                 = "prod"
  service_name                = "admin-dashboard"
  application_family          = "internal-tools"
  vpc_id                      = "vpc-87654321"
  load_balancer_subnet_ids    = ["subnet-33333333", "subnet-44444444"]
  fully_qualified_domain_name = "admin.theorchard.io"
  certificate_domain          = "*.theorchard.io"
  
  # Access control - multiple methods
  ingress_cidr_blocks       = ["10.0.0.0/8"]
  ingress_prefix_list_names = ["vpn-users", "office-network"]
  allow_vpc_private_subnets = true
  
  # VPC endpoint configuration
  vpc_endpoint_name = "primary-s3-endpoint"
  
  # Custom WAF
  custom_waf_name = "prod-admin-waf-strict"
  
  # Custom URL rewriting
  custom_static_path_patterns = ["/frontend-admin-dashboard/assets/*", "/frontend-admin-dashboard/api/*", "/favicon.ico"]
  spa_rewrite_path_patterns   = ["/admin/*"]
  spa_rewrite_regex           = "^/admin/.*$"
  spa_rewrite_target          = "index-somebrand.html"
  
  # Health check customization
  health_check_path                = "/frontend-admin-dashboard/index.html"
  health_check_interval            = 30
  health_check_healthy_threshold   = 5
  health_check_unhealthy_threshold = 2
  
  # Load balancer customization
  load_balancer_idle_timeout              = 120
  load_balancer_enable_deletion_protection = true
  
  # S3 bucket configuration
  s3_read_only_policy = true
  s3_bucket_cors_rule = [
    {
      allowed_headers = ["*"]
      allowed_methods = ["GET", "HEAD"]
      allowed_origins = ["https://admin.theorchard.io"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3600
    }
  ]
  
  # Route53
  route53_record_enabled = true
  route53_zone_name      = "theorchard.io"
}
```

## Outputs

After applying the module, the following outputs are available:

| Output | Description |
|--------|-------------|
| `load_balancer_arn` | ARN of the Application Load Balancer |
| `load_balancer_dns_name` | DNS name of the ALB |
| `load_balancer_id` | ID of the ALB |
| `s3_bucket_name` | Name of the S3 bucket |
| `s3_bucket_arn` | ARN of the S3 bucket |
| `route53_record_fqdn` | FQDN of Route53 record (if created) |
| `route53_record_name` | Name of Route53 record (if created) |
| `target_group_arn` | ARN of the target group |
| `https_listener_arn` | ARN of the HTTPS listener |
| `load_balancer_security_group_id` | ID of the load balancer security group |

### Using Outputs

```hcl
# Reference outputs from the module
output "app_url" {
  value = module.customer_portal.route53_record_fqdn != null ? 
          "https://${module.customer_portal.route53_record_fqdn}" :
          "https://${module.customer_portal.load_balancer_dns_name}"
}

output "s3_bucket" {
  value = module.customer_portal.s3_bucket_name
}
```

## Post-Deployment Steps

After successfully applying the module:

1. **Deploy SPA files to S3 (in Dev)**:
   ```bash
   # Build your SPA
   npm run build
   
   # Sync to the S3 bucket
   aws s3 sync ./dist s3://$(terraform output -raw s3_bucket_name)/frontend-app-name/ --delete
   ```

2. **Verify ALB health checks**:
   ```bash
   # Check target health
   aws elbv2 describe-target-health \
     --target-group-arn $(terraform output -raw target_group_arn)
   ```

3. **Test access**:
   ```bash
   # Test via ALB DNS name
   curl -k https://$(terraform output -raw load_balancer_dns_name)
   
   # Test via Route53 record (if configured)
   curl https://$(terraform output -raw route53_record_fqdn)
   ```

## Common Issues and Solutions

### Issue: Certificate not found
**Error**: `No certificate found matching domain pattern`

**Solution**: Ensure an ACM certificate exists matching the `certificate_domain` pattern, is in `ISSUED` status, and matches the domain used by `fully_qualified_domain_name`

### Issue: Health checks failing
**Error**: Target group shows unhealthy targets

**Solutions**:
- Verify the S3 bucket contains an `index.html` file at the `/frontend-app-name/index.html` location
- Check `health_check_path` matches an existing file in S3
- Verify VPC endpoint has network connectivity to S3

### Issue: Multiple VPC endpoints found
**Error**: Module discovers wrong S3 VPC endpoint

**Solution**: Set `vpc_endpoint_name` to filter by the Name tag of the desired endpoint.

### Issue: Access denied
**Error**: 403 Forbidden when accessing the application

**Solutions**:
- Check WAF is not blocking requests
- Ensure bucket policy allows VPC endpoint access

### Issue: Connection hanging
**Error**: Connection times out

**Solutions**:
- Verify security group rules allow traffic from your source
- Verify prefix lists are correctly configured

## Validation Checklist

Before applying the module, verify:

- [ ] VPC and subnets exist and are in the correct account/region
- [ ] S3 interface VPC endpoint exists in the VPC
- [ ] ACM certificate exists and matches `certificate_domain`
- [ ] ACM certificate domain matches domain used by `fully_qualified_domain_name`
- [ ] WAF web ACL exists (or default naming convention is correct)
- [ ] At least one access control method is configured
- [ ] `fully_qualified_domain_name` follows S3 bucket naming rules
- [ ] If Route53 enabled: zone name and permissions are correct
- [ ] Provider aliases are correctly configured

## Testing

The module includes automated tests:

```bash
# Run all tests
terraform test

# Run only unit tests (fast, uses mocks)
terraform test -filter=tests/unit.tftest.hcl

# Run integration tests (slow, creates real resources)
terraform test -filter=tests/integration.tftest.hcl
```

## Troubleshooting

Enable Terraform debugging:
```bash
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform-debug.log
terraform apply
```

Check AWS resources:
```bash
# List ALB details
aws elbv2 describe-load-balancers --names ${environment}-${service_name}

# Check S3 bucket
aws s3 ls s3://${fully_qualified_domain_name}/

# Verify VPC endpoint
aws ec2 describe-vpc-endpoints --filters "Name=vpc-id,Values=${vpc_id}"
```

## Support

For issues or questions:
- Review the [README.md](../README.md) for detailed documentation
- Check existing GitHub issues
- Contact the DevOps team
