From bd083a5a4e317ca3b25f0fa51fe9b03828f266c8 Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Wed, 14 Jan 2026 22:58:31 -0500 Subject: [PATCH 1/7] feat(packer): add Ubuntu 24.04 AMI builder for P4 Code Review (Swarm) Add Packer template and configuration scripts for building P4 Code Review (Helix Swarm) AMI on Ubuntu 24.04 LTS. Key components: - p4_code_review_x86.pkr.hcl: Packer template for x86_64 AMI - swarm_setup.sh: Initial Swarm package installation - swarm_configure.sh: Runtime configuration script for: - P4 server connection with SSL trust and authentication - Redis cache configuration - SSO/SAML setup - Queue worker configuration - Swarm extension Swarm-Secure setting --- .../packer/perforce/p4-code-review/README.md | 365 ++++++++++++++++++ .../p4-code-review/example.pkrvars.hcl | 17 + .../p4-code-review/p4_code_review_x86.pkr.hcl | 111 ++++++ .../p4-code-review/swarm_instance_init.sh | 338 ++++++++++++++++ .../perforce/p4-code-review/swarm_setup.sh | 132 +++++++ mkdocs.yml | 1 + 6 files changed, 964 insertions(+) create mode 100644 assets/packer/perforce/p4-code-review/README.md create mode 100644 assets/packer/perforce/p4-code-review/example.pkrvars.hcl create mode 100644 assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl create mode 100644 assets/packer/perforce/p4-code-review/swarm_instance_init.sh create mode 100644 assets/packer/perforce/p4-code-review/swarm_setup.sh diff --git a/assets/packer/perforce/p4-code-review/README.md b/assets/packer/perforce/p4-code-review/README.md new file mode 100644 index 00000000..e24311ef --- /dev/null +++ b/assets/packer/perforce/p4-code-review/README.md @@ -0,0 +1,365 @@ +# P4 Code Review Packer Template + +This Packer template creates an Amazon Machine Image (AMI) for P4 Code Review (Helix Swarm) on Ubuntu 24.04 LTS. The AMI includes all necessary software pre-installed, with runtime configuration handled automatically during instance launch. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Quick Start](#quick-start) +- [What Gets Installed](#what-gets-installed) +- [Building the AMI](#building-the-ami) +- [Finding Your AMI](#finding-your-ami) +- [Next Steps](#next-steps) +- [Troubleshooting](#troubleshooting) + +## Prerequisites + +Before building the AMI, ensure you have: + +1. **AWS CLI** configured with valid credentials: + + ```bash + aws configure + # Verify access + aws sts get-caller-identity + ``` + +2. **Packer** installed (version >= 1.8.0): + + ```bash + packer version + ``` + + If not installed, download from + +3. **VPC Access**: + - Default VPC in your region (Default behaviour) + - OR custom VPC with a public subnet (Can be configured by passing the VPC id through the `vpc_id` variable) + +4. **IAM Permissions**: Your AWS credentials need permissions to: + - Launch EC2 instances + - Create AMIs + - Create/delete security groups + - Create/delete key pairs + +## Quick Start + +From the **repository root**, run: + +```bash +# 1. Navigate to Packer template directory +cd assets/packer/perforce/p4-code-review + +# 2. Initialize Packer (downloads required plugins) +packer init p4_code_review_x86.pkr.hcl + +# 3. Validate the template +packer validate p4_code_review_x86.pkr.hcl + +# 4. Build the AMI (takes ~10-15 minutes) +packer build p4_code_review_x86.pkr.hcl +``` + +At the end of the build, Packer will output the AMI ID: + +```text +==> amazon-ebs.ubuntu2404: AMI: ami-0abc123def456789 +``` + +**Save this AMI ID** - you'll need it for Terraform deployment. + +## What Gets Installed + +The AMI includes a complete P4 Code Review installation: + +### Software Components + +1. **Perforce Repository**: Official Perforce package repository (Ubuntu jammy/22.04 compatible) +2. **PHP 8.x**: PHP runtime with all required extensions: + 1. Core: curl, mbstring, xml, intl, ldap, bcmath + 2. Database: mysql + 3. Graphics: gd + 4. Archive: zip + 5. PECL: igbinary, msgpack, redis +3. **Helix Swarm**: Native DEB installation via `helix-swarm` package +4. **Apache2**: Web server with mod_php and required modules (rewrite, proxy, proxy_fcgi) +5. **PHP-FPM**: FastCGI Process Manager for PHP +6. **helix-swarm-optional** (optional, installed by default): LibreOffice for document preview (.docx, .xlsx, .pptx) and ImageMagick for image preview (.png, .jpg, .tiff, etc.) (~500MB) +7. **AWS CLI v2**: Required for Secrets Manager access and EBS volume operations at runtime +8. **Configuration Script**: `/home/ubuntu/swarm_scripts/swarm_instance_init.sh` for runtime setup (see Runtime Configuration Details below) + +### System Configuration + +- **AppArmor**: Ubuntu's security module (less restrictive by default for `/opt`) +- **Services**: Apache2 and PHP-FPM enabled for automatic startup +- **User**: `swarm` system user created with proper permissions +- **Directories**: `/opt/perforce/swarm` prepared with correct ownership + +### What's NOT Configured Yet + +The following are configured at **deployment** when you launch an instance: + +- P4 Server connection details +- P4 user credentials (fetched from AWS Secrets Manager) +- Redis cache connection +- External hostname/URL +- SSO settings +- EBS volume mounting for persistent data +- Queue worker configuration (cron job and endpoint) +- File permissions for worker processes +- P4 Server extension installation (Swarm triggers) + +### Runtime Configuration Details + +When an EC2 instance launches, the user-data script performs the following steps: + +1. **EBS Volume Attachment**: Finds and attaches the persistent data volume by tags +2. **Filesystem Setup**: Creates ext4 filesystem (first launch) or mounts existing one +3. **Swarm Configuration**: Executes `/home/ubuntu/swarm_scripts/swarm_instance_init.sh` which: + - Retrieves P4 credentials from AWS Secrets Manager + - Runs Perforce's official `configure-swarm.sh` to: + - Connect to P4 Server and validate credentials + - Install Swarm extension on P4 Server (enables event triggers) + - Create initial configuration file + - Set up Apache VirtualHost + - Create cron job for queue workers + - Configures file permissions for queue worker functionality + - Updates configuration with Redis connection details + - Configures queue workers to use localhost endpoint + - Starts Apache and PHP-FPM services + +**Queue Workers**: P4 Code Review requires background workers to process events, send notifications, and index files. These are spawned by a cron job (created by `configure-swarm.sh`) that runs every minute. The runtime configuration ensures workers have proper permissions and connect to the correct endpoint. + +## Building the AMI + +### Option 1: Using Default VPC (Recommended) + +If your AWS region has a default VPC: + +```bash +cd assets/packer/perforce/p4-code-review +packer init p4_code_review_x86.pkr.hcl +packer build p4_code_review_x86.pkr.hcl +``` + +### Option 2: Using Custom VPC + +If you don't have a default VPC, specify your own: + +```bash +packer build \ + -var="region=us-west-2" \ + -var="vpc_id=vpc-xxxxx" \ + -var="subnet_id=subnet-xxxxx" \ + -var="associate_public_ip_address=true" \ + -var="ssh_interface=public_ip" \ + p4_code_review_x86.pkr.hcl +``` + +**Requirements for custom VPC**: + +- Subnet must be in a **public** subnet (has route to Internet Gateway) +- `associate_public_ip_address=true` if subnet doesn't auto-assign public IPs +- Security group allows outbound internet access (for package downloads) + +### Option 3: Using Variables File + +Create a `my-vars.pkrvars.hcl`: + +```hcl +region = "us-west-2" +vpc_id = "vpc-xxxxx" +subnet_id = "subnet-xxxxx" +associate_public_ip_address = true +ssh_interface = "public_ip" +``` + +Then build: + +```bash +packer build -var-file="my-vars.pkrvars.hcl" p4_code_review_x86.pkr.hcl +``` + +### Build Output + +Successful build output looks like: + +```text +==> amazon-ebs.ubuntu2404: Stopping the source instance... +==> amazon-ebs.ubuntu2404: Waiting for the instance to stop... +==> amazon-ebs.ubuntu2404: Creating AMI p4_code_review_ubuntu-20231209123456 from instance i-xxxxx +==> amazon-ebs.ubuntu2404: AMI: ami-0abc123def456789 +==> amazon-ebs.ubuntu2404: Waiting for AMI to become ready... +==> amazon-ebs.ubuntu2404: Terminating the source AWS instance... +Build 'amazon-ebs.ubuntu2404' finished after 12 minutes 34 seconds. + +==> Wait completed after 12 minutes 34 seconds + +==> Builds finished. The artifacts of successful builds are: +--> amazon-ebs.ubuntu2404: AMIs were created: +us-west-2: ami-0abc123def456789 +``` + +**Copy the AMI ID** (e.g., `ami-0abc123def456789`) - you'll need this for Terraform. + +## Finding Your AMI + +### List All P4 Code Review AMIs + +```bash +aws ec2 describe-images \ + --owners self \ + --filters "Name=name,Values=p4_code_review_ubuntu-*" \ + --query 'Images[*].[ImageId,Name,CreationDate]' \ + --output table +``` + +Output: + +```text ++-----------------------------------------------------------------------+ +| DescribeImages | ++----------------------+---------------------------------------+--------+ +| ami-0abc123def456 | p4_code_review_ubuntu-20231209 | 2023...| +| ami-0def456abc789 | p4_code_review_ubuntu-20231208 | 2023...| ++----------------------+---------------------------------------+--------+ +``` + +### Get the Latest AMI + +```bash +aws ec2 describe-images \ + --owners self \ + --filters "Name=name,Values=p4_code_review_ubuntu-*" \ + --query 'Images | sort_by(@, &CreationDate) | [-1].[ImageId,Name,CreationDate]' \ + --output table +``` + +### Get Details About a Specific AMI + +```bash +aws ec2 describe-images --image-ids ami-0abc123def456789 +``` + +## Next Steps + +Now that you have an AMI, proceed to deploy P4 Code Review infrastructure: + +1. **Read the [P4 Code Review Module Documentation](../../../../modules/perforce/modules/p4-code-review/README.md)** + +2. **Follow the deployment guide** in the module README, which covers: + - Creating AWS Secrets Manager secrets for P4 credentials + - Writing Terraform configuration + - Deploying the infrastructure + - Accessing the P4 Code Review web console + +## Troubleshooting + +### "No default VPC available" + +**Error**: Packer fails with "No default VPC for this user" + +**Solution**: Use Option 2 or 3 above to specify your VPC and subnet: + +```bash +packer build \ + -var="vpc_id=vpc-xxxxx" \ + -var="subnet_id=subnet-xxxxx" \ + p4_code_review_x86.pkr.hcl +``` + +### "Unable to connect to instance" + +**Error**: Packer times out connecting to the instance + +**Possible causes**: + +1. Subnet is not public (no route to Internet Gateway) +2. Security group blocks SSH (port 22) +3. No public IP assigned to instance + +**Solution**: Verify your subnet has: + +```bash +# Check if subnet has route to IGW +aws ec2 describe-route-tables \ + --filters "Name=association.subnet-id,Values=subnet-xxxxx" \ + --query 'RouteTables[*].Routes[?GatewayId!=`local`]' +``` + +### "Package installation failed" + +**Error**: APT/DEB errors during build + +**Possible causes**: + +1. No internet access from instance +2. Perforce repository temporarily unavailable +3. Package version conflicts + +**Solution**: + +- Check build instance has outbound internet access +- Try rebuilding (temporary outages resolve themselves) +- Review `/var/log/swarm_setup.log` on build instance + +### "AMI already exists with that name" + +**Error**: "AMI name 'p4_code_review_ubuntu-TIMESTAMP' already exists" + +**This shouldn't happen** (timestamp should be unique), but if it does: + +```bash +# List your AMIs +aws ec2 describe-images --owners self \ + --filters "Name=name,Values=p4_code_review_ubuntu-*" + +# Deregister old AMI if no longer needed +aws ec2 deregister-image --image-id ami-xxxxx +``` + +### Build is slow + +**Normal build time**: 10-15 minutes + +**If taking longer**: + +- Package downloads can be slow depending on region +- Perforce repository might be experiencing high load +- This is normal - be patient + +### Need to debug the build? + +**Enable debug mode to step through each provisioner**: + +```bash +packer build -debug p4_code_review_x86.pkr.hcl +``` + +This will pause before each provisioner step, allowing you to: + +- SSH into the build instance +- Inspect the current state +- Verify installation progress +- Press Enter to continue to the next step + +**Enable detailed logging**: + +```bash +PACKER_LOG=1 packer build p4_code_review_x86.pkr.hcl +``` + +## Additional Resources + +- [Packer Documentation](https://www.packer.io/docs) +- [Perforce Helix Swarm Admin Guide](https://www.perforce.com/manuals/swarm/Content/Swarm/Home-swarm.html) +- [Ubuntu 24.04 LTS Documentation](https://ubuntu.com/server/docs) + +## Questions or Issues? + +If you encounter problems: + +1. Check the troubleshooting section above +2. Review Packer logs with `PACKER_LOG=1` +3. Use `packer build -debug` to step through the build process +4. Verify AWS credentials and permissions diff --git a/assets/packer/perforce/p4-code-review/example.pkrvars.hcl b/assets/packer/perforce/p4-code-review/example.pkrvars.hcl new file mode 100644 index 00000000..d1f165ff --- /dev/null +++ b/assets/packer/perforce/p4-code-review/example.pkrvars.hcl @@ -0,0 +1,17 @@ +# Region where the Packer builder instance will run +region = "us-east-1" + +# VPC for the Packer builder instance (leave commented out to use default VPC) +vpc_id = "vpc-xxxxx" + +# Public subnet for the Packer builder instance (must have internet access for package downloads) +subnet_id = "subnet-xxxxx" + +# Optional: Associate public IP to builder instance (required if subnet doesn't auto-assign public IPs) +# associate_public_ip_address = true + +# Optional: SSH interface for Packer to connect (use "public_ip" for public subnets) +# ssh_interface = "public_ip" + +# Optional: Install helix-swarm-optional package (LibreOffice, ImageMagick for previews, adds ~500MB to AMI) +# install_swarm_optional = true diff --git a/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl b/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl new file mode 100644 index 00000000..0a273ee0 --- /dev/null +++ b/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl @@ -0,0 +1,111 @@ +packer { + required_plugins { + amazon = { + version = ">= 0.0.2" + source = "github.com/hashicorp/amazon" + } + } +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") + ami_prefix = "p4_code_review_ubuntu" +} + +data "amazon-ami" "ubuntu" { + filters = { + name = "ubuntu/images/hvm-ssd-gp3/ubuntu-*-amd64-server-*" + architecture = "x86_64" + root-device-type = "ebs" + virtualization-type = "hvm" + } + most_recent = true + owners = ["099720109477"] # Canonical + region = var.region +} + +variable "region" { + type = string + default = null +} + +variable "vpc_id" { + type = string + default = null +} + +variable "subnet_id" { + type = string + default = null +} + +variable "associate_public_ip_address" { + type = bool + default = true +} + +variable "ssh_interface" { + type = string + default = "public_ip" +} + +variable "install_swarm_optional" { + type = bool + default = true + description = "Install helix-swarm-optional package (includes LibreOffice for document previews and ImageMagick for image previews). Adds ~500MB to AMI size." +} + +source "amazon-ebs" "ubuntu" { + region = var.region + ami_name = "${local.ami_prefix}-${local.timestamp}" + instance_type = "t3.medium" + + vpc_id = var.vpc_id + subnet_id = var.subnet_id + + associate_public_ip_address = var.associate_public_ip_address + ssh_interface = var.ssh_interface + + source_ami = data.amazon-ami.ubuntu.id + + ssh_username = "ubuntu" +} + +build { + name = "P4_CODE_REVIEW_AWS" + sources = [ + "source.amazon-ebs.ubuntu" + ] + + provisioner "shell" { + inline = [ + "cloud-init status --wait", + "sudo apt-get update", + "sudo apt-get install -y git unzip curl" + ] + } + + provisioner "shell" { + script = "${path.root}/swarm_setup.sh" + execute_command = "sudo sh {{.Path}}" + environment_vars = [ + "INSTALL_SWARM_OPTIONAL=${var.install_swarm_optional}" + ] + } + + provisioner "file" { + source = "${path.root}/swarm_instance_init.sh" + destination = "/tmp/swarm_instance_init.sh" + } + + provisioner "shell" { + inline = ["mkdir -p /home/ubuntu/swarm_scripts", + "sudo mv /tmp/swarm_instance_init.sh /home/ubuntu/swarm_scripts" + ] + } + + provisioner "shell" { + inline = ["sudo chmod +x /home/ubuntu/swarm_scripts/swarm_instance_init.sh"] + } + +} diff --git a/assets/packer/perforce/p4-code-review/swarm_instance_init.sh b/assets/packer/perforce/p4-code-review/swarm_instance_init.sh new file mode 100644 index 00000000..b7a6c08b --- /dev/null +++ b/assets/packer/perforce/p4-code-review/swarm_instance_init.sh @@ -0,0 +1,338 @@ +#!/bin/bash + +# P4 Code Review Runtime Configuration Script +# Configures P4 Code Review with P4 Server connection details, Redis cache, and other runtime settings +# This script is called by user-data at instance launch time + +LOG_FILE="/var/log/swarm_instance_init.log" + +log_message() { + echo "$(date) - $1" | tee -a $LOG_FILE +} + +ROOT_UID=0 +if [ "$UID" -ne "$ROOT_UID" ]; then + echo "Must be root to run this script." + exit 1 +fi + +log_message "=========================================" +log_message "Starting P4 Code Review runtime configuration" +log_message "=========================================" + +# Parse command line arguments +P4D_PORT="" +P4CHARSET="none" +SWARM_HOST="" +SWARM_REDIS="" +SWARM_REDIS_PORT="6379" +SWARM_FORCE_EXT="y" +CUSTOM_CONFIG_FILE="" + +# Secret ARNs for fetching credentials from AWS Secrets Manager +P4D_SUPER_SECRET_ARN="" +P4D_SUPER_PASSWD_SECRET_ARN="" +SWARM_USER_SECRET_ARN="" +SWARM_PASSWD_SECRET_ARN="" + +while [[ $# -gt 0 ]]; do + case $1 in + --p4d-port) + P4D_PORT="$2" + shift 2 + ;; + --p4charset) + P4CHARSET="$2" + shift 2 + ;; + --swarm-host) + SWARM_HOST="$2" + shift 2 + ;; + --swarm-redis) + SWARM_REDIS="$2" + shift 2 + ;; + --swarm-redis-port) + SWARM_REDIS_PORT="$2" + shift 2 + ;; + --swarm-force-ext) + SWARM_FORCE_EXT="$2" + shift 2 + ;; + --custom-config-file) + CUSTOM_CONFIG_FILE="$2" + shift 2 + ;; + --p4d-super-secret-arn) + P4D_SUPER_SECRET_ARN="$2" + shift 2 + ;; + --p4d-super-passwd-secret-arn) + P4D_SUPER_PASSWD_SECRET_ARN="$2" + shift 2 + ;; + --swarm-user-secret-arn) + SWARM_USER_SECRET_ARN="$2" + shift 2 + ;; + --swarm-passwd-secret-arn) + SWARM_PASSWD_SECRET_ARN="$2" + shift 2 + ;; + *) + log_message "Unknown parameter: $1" + shift + ;; + esac +done + +log_message "Configuration parameters:" +log_message "P4D_PORT: $P4D_PORT" +log_message "P4CHARSET: $P4CHARSET" +log_message "SWARM_HOST: $SWARM_HOST" +log_message "SWARM_REDIS: $SWARM_REDIS" +log_message "SWARM_REDIS_PORT: $SWARM_REDIS_PORT" +log_message "SWARM_FORCE_EXT: $SWARM_FORCE_EXT" +log_message "CUSTOM_CONFIG_FILE: $CUSTOM_CONFIG_FILE" + +# Extract hostname from full URL for configure-swarm.sh +# configure-swarm.sh expects just the hostname (it constructs URLs internally) +# SWARM_HOST may contain https://hostname or just hostname +SWARM_HOSTNAME="${SWARM_HOST#https://}" +SWARM_HOSTNAME="${SWARM_HOSTNAME#http://}" +log_message "SWARM_HOSTNAME (for configure-swarm.sh): $SWARM_HOSTNAME" + +# Retrieve credentials from AWS Secrets Manager +log_message "Fetching secrets from AWS Secrets Manager" +P4D_SUPER=$(aws secretsmanager get-secret-value --secret-id "$P4D_SUPER_SECRET_ARN" --query SecretString --output text) +P4D_SUPER_PASSWD=$(aws secretsmanager get-secret-value --secret-id "$P4D_SUPER_PASSWD_SECRET_ARN" --query SecretString --output text) +SWARM_USER=$(aws secretsmanager get-secret-value --secret-id "$SWARM_USER_SECRET_ARN" --query SecretString --output text) +SWARM_PASSWD=$(aws secretsmanager get-secret-value --secret-id "$SWARM_PASSWD_SECRET_ARN" --query SecretString --output text) + +if [ -z "$P4D_SUPER" ] || [ -z "$P4D_SUPER_PASSWD" ] || [ -z "$SWARM_USER" ] || [ -z "$SWARM_PASSWD" ]; then + log_message "ERROR: Failed to fetch secrets from AWS Secrets Manager" + exit 1 +fi + +log_message "Successfully fetched secrets" + +# P4 Code Review data directory - stores application data and configuration +SWARM_DATA_PATH="/opt/perforce/swarm/data" +SWARM_CONFIG="${SWARM_DATA_PATH}/config.php" + +# Ensure data directory exists with proper ownership +# Note: configure-swarm.sh will change these, we'll fix them again afterwards +mkdir -p "$SWARM_DATA_PATH" +chown -R swarm:www-data "$SWARM_DATA_PATH" +chmod 775 "$SWARM_DATA_PATH" + +# Run the official P4 Code Review configuration script +# This handles initial setup and P4 Server extension installation +log_message "Running configure-swarm.sh with super user credentials" + +/opt/perforce/swarm/sbin/configure-swarm.sh \ + -n \ + -p "$P4D_PORT" \ + -u "$SWARM_USER" \ + -w "$SWARM_PASSWD" \ + -H "$SWARM_HOSTNAME" \ + -e localhost \ + -X \ + -U "$P4D_SUPER" \ + -W "$P4D_SUPER_PASSWD" || { + log_message "ERROR: configure-swarm.sh failed with exit code $?" + log_message "This likely means P4 server connectivity or permissions issue" + exit 1 + } + +# Note: Swarm extension configuration is handled by configure-swarm.sh above +# The extension is configured with: +# - Swarm-URL: https:// (passed via -H parameter) +# - Swarm-Secure: true (default, enables SSL certificate validation) + +# Configure initial permissions for Swarm data directory +# Note: Queue-specific permissions are set after Apache starts (see below) +log_message "Configuring initial permissions for Swarm data directory" +chown -R swarm:www-data "$SWARM_DATA_PATH" +chmod 775 "$SWARM_DATA_PATH" + +# Ensure p4trust file is readable by Apache worker processes +chmod 644 "$SWARM_DATA_PATH/p4trust" 2>/dev/null || true + +# Swarm application log must be a regular file with group write permissions +if [ -e "$SWARM_DATA_PATH/log" ] && [ ! -f "$SWARM_DATA_PATH/log" ]; then + log_message "Correcting log path to be a regular file" + rm -rf "$SWARM_DATA_PATH/log" +fi +if [ ! -f "$SWARM_DATA_PATH/log" ]; then + touch "$SWARM_DATA_PATH/log" + chown swarm:www-data "$SWARM_DATA_PATH/log" + chmod 664 "$SWARM_DATA_PATH/log" +fi + +# Add swarm-cron user to www-data group for queue worker file access +log_message "Adding swarm-cron user to www-data group" +usermod -aG www-data swarm-cron + +# Update configuration file with runtime settings +log_message "Updating P4 Code Review configuration" + +if [ -f "$SWARM_CONFIG" ]; then + # Backup existing configuration + cp "$SWARM_CONFIG" "${SWARM_CONFIG}.backup.$(date +%s)" + + log_message "Adding Redis configuration to config.php" + + # Use PHP to properly modify the configuration file + php -r " + \$config = include '$SWARM_CONFIG'; + + // Configure Redis connection for session storage and caching + if (!isset(\$config['redis'])) { + \$config['redis'] = array(); + } + \$config['redis']['options'] = array( + 'server' => array( + 'host' => '$SWARM_REDIS', + 'port' => $SWARM_REDIS_PORT, + ), + ); + + // Set external URL for generating links in notifications and emails + if (!isset(\$config['environment'])) { + \$config['environment'] = array(); + } + \$config['environment']['hostname'] = '$SWARM_HOST'; + + // Write back the configuration + file_put_contents('$SWARM_CONFIG', '/dev/null || true + else + log_message "No custom configuration file provided or file is empty" + fi + + chown swarm:www-data "$SWARM_CONFIG" + chmod 664 "$SWARM_CONFIG" + + log_message "Configuration file updated successfully" +else + log_message "ERROR: Config file not found at $SWARM_CONFIG after running configure-swarm.sh" + exit 1 +fi + +# Disable default Apache site so Swarm becomes the default (important for health checks) +log_message "Disabling default Apache site" +a2dissite 000-default || log_message "Default site already disabled" + +# Start Apache web server +log_message "Starting Apache service" +systemctl enable apache2 +systemctl restart apache2 +systemctl status apache2 --no-pager + +# Start PHP-FPM for PHP request handling +if systemctl list-unit-files | grep -q php-fpm; then + log_message "Starting PHP-FPM service" + systemctl enable php-fpm + systemctl start php-fpm + systemctl status php-fpm --no-pager +fi + +# Configure permissions for queue workers and caching +# This must run AFTER Apache starts because Swarm may create directories with restrictive permissions +log_message "Configuring permissions for queue worker functionality" + +# Create queue directories if they don't exist +mkdir -p "$SWARM_DATA_PATH/queue/workers" +mkdir -p "$SWARM_DATA_PATH/queue/tokens" +mkdir -p "$SWARM_DATA_PATH/cache" + +# Set ownership and permissions for queue-related directories +# Workers run as swarm-cron (in www-data group) and need write access +chown -R www-data:www-data "$SWARM_DATA_PATH/queue" +chmod 770 "$SWARM_DATA_PATH/queue" +chmod 770 "$SWARM_DATA_PATH/queue/workers" +chmod 770 "$SWARM_DATA_PATH/queue/tokens" +chown -R www-data:www-data "$SWARM_DATA_PATH/cache" +chmod 775 "$SWARM_DATA_PATH/cache" + +# Configure P4 Code Review background workers for async tasks +# Workers are spawned by cron job created by configure-swarm.sh at /etc/cron.d/helix-swarm +# Update the default worker configuration to use localhost for optimal performance +log_message "Configuring P4 Code Review queue workers" + +SWARM_CRON_CONFIG="/opt/perforce/etc/swarm-cron-hosts.conf" +log_message "Updating worker configuration at $SWARM_CRON_CONFIG" + +# Workers should connect to localhost to avoid routing through load balancer +echo "http://localhost" > "$SWARM_CRON_CONFIG" +chown swarm-cron:swarm-cron "$SWARM_CRON_CONFIG" +chmod 644 "$SWARM_CRON_CONFIG" + +log_message "Queue workers configured to use localhost endpoint" + +# Ensure worker token is properly initialized +# The token file may exist but be empty after configure-swarm.sh runs +log_message "Initializing queue worker authentication token" + +TOKEN_DIR="${SWARM_DATA_PATH}/queue/tokens" + +# Find existing token file +TOKEN_FILE=$(find "$TOKEN_DIR" -type f 2>/dev/null | head -1) + +if [ -n "$TOKEN_FILE" ] && [ -f "$TOKEN_FILE" ]; then + # Check if token file is empty + if [ ! -s "$TOKEN_FILE" ]; then + log_message "Token file exists but is empty, generating new token" + TOKEN_CONTENT=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "$(date +%s)-$(hostname)") + echo "$TOKEN_CONTENT" > "$TOKEN_FILE" + chown www-data:www-data "$TOKEN_FILE" + chmod 644 "$TOKEN_FILE" + log_message "Worker token initialized: $(basename "$TOKEN_FILE")" + else + log_message "Worker token already exists and is valid" + fi +else + log_message "No token file found, creating new one" + TOKEN_NAME=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "swarm-token-$(date +%s)") + TOKEN_FILE="$TOKEN_DIR/$TOKEN_NAME" + TOKEN_CONTENT=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "$(date +%s)-$(hostname)") + echo "$TOKEN_CONTENT" > "$TOKEN_FILE" + chown www-data:www-data "$TOKEN_FILE" + chmod 644 "$TOKEN_FILE" + log_message "Worker token created: $TOKEN_NAME" +fi + +log_message "=========================================" +log_message "P4 Code Review configuration completed" +log_message "P4 Code Review should be accessible at: $SWARM_HOST" +log_message "Data path: $SWARM_DATA_PATH" +log_message "=========================================" diff --git a/assets/packer/perforce/p4-code-review/swarm_setup.sh b/assets/packer/perforce/p4-code-review/swarm_setup.sh new file mode 100644 index 00000000..e1498d5f --- /dev/null +++ b/assets/packer/perforce/p4-code-review/swarm_setup.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +# Log file location +LOG_FILE="/var/log/swarm_setup.log" + +# Function to log messages +log_message() { + echo "$(date) - $1" | tee -a $LOG_FILE +} + +# Constants +ROOT_UID=0 + +# Check if script is run as root +if [ "$UID" -ne "$ROOT_UID" ]; then + echo "Must be root to run this script." + log_message "Script not run as root." + exit 1 +fi + +log_message "Starting P4 Code Review (Swarm) installation" + +# Update package lists +log_message "Updating package lists" +apt-get update + +# Install required dependencies +log_message "Installing required dependencies" +apt-get install -y software-properties-common gnupg2 wget apt-transport-https ca-certificates unzip curl + +# Install AWS CLI v2 +log_message "Installing AWS CLI v2" +( + cd /tmp || exit 1 + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip -q awscliv2.zip + ./aws/install + rm -rf aws awscliv2.zip +) + +# Add Perforce repository +log_message "Adding Perforce repository" +wget -qO - https://package.perforce.com/perforce.pubkey | gpg --dearmor | tee /usr/share/keyrings/perforce-archive-keyring.gpg > /dev/null +echo "deb [signed-by=/usr/share/keyrings/perforce-archive-keyring.gpg] http://package.perforce.com/apt/ubuntu noble release" | tee /etc/apt/sources.list.d/perforce.list + +# Update package lists with new repository +log_message "Updating package lists with Perforce repository" +apt-get update + +# Check if PHP 8.x is available natively +log_message "Checking for PHP 8.x availability" +if apt-cache show php8.3 &>/dev/null || apt-cache show php8.1 &>/dev/null; then + log_message "PHP 8.x available natively, using system packages" +else + log_message "PHP 8.x not available natively, adding ondrej/php PPA" + add-apt-repository -y ppa:ondrej/php + apt-get update +fi + +# Determine which PHP 8.x version to install +if apt-cache show php8.3 &>/dev/null; then + PHP_VERSION="8.3" +elif apt-cache show php8.1 &>/dev/null; then + PHP_VERSION="8.1" +else + log_message "ERROR: No PHP 8.x version available" + exit 1 +fi + +log_message "Installing Apache2 and PHP ${PHP_VERSION} with required extensions" +apt-get install -y apache2 \ + php${PHP_VERSION} php${PHP_VERSION}-fpm php${PHP_VERSION}-cli php${PHP_VERSION}-common \ + php${PHP_VERSION}-curl php${PHP_VERSION}-gd php${PHP_VERSION}-intl php${PHP_VERSION}-ldap php${PHP_VERSION}-mbstring \ + php${PHP_VERSION}-mysql php${PHP_VERSION}-xml php${PHP_VERSION}-zip php${PHP_VERSION}-bcmath \ + libapache2-mod-php${PHP_VERSION} + +# Install PHP PECL extensions +log_message "Installing PHP PECL extensions" +apt-get install -y php${PHP_VERSION}-igbinary php${PHP_VERSION}-msgpack php${PHP_VERSION}-redis + +# Install Helix Swarm +log_message "Installing Helix Swarm" +apt-get install -y helix-swarm + +# Install helix-swarm-optional package (LibreOffice, ImageMagick) +if [ "${INSTALL_SWARM_OPTIONAL:-true}" = "true" ]; then + log_message "Installing helix-swarm-optional package" + apt-get install -y helix-swarm-optional || log_message "helix-swarm-optional package not available, skipping" +else + log_message "Skipping helix-swarm-optional installation" +fi + +# Enable required Apache modules +log_message "Enabling required Apache modules" +a2enmod rewrite +a2enmod proxy +a2enmod proxy_fcgi +a2enmod setenvif + +# Enable PHP-FPM configuration for Apache +log_message "Configuring PHP-FPM for Apache" +a2enconf php*-fpm + +# Enable and configure Apache +log_message "Enabling Apache service" +systemctl enable apache2 + +# Enable and configure PHP-FPM +log_message "Enabling PHP-FPM service" +systemctl enable php${PHP_VERSION}-fpm + +# Create swarm user if it doesn't exist (package may have already created it) +if ! id -u swarm > /dev/null 2>&1; then + log_message "Creating swarm user" + useradd -r -s /bin/bash swarm +fi + +# Set proper ownership on Swarm directories +log_message "Setting ownership on Swarm directories" +chown -R swarm:swarm /opt/perforce/swarm || log_message "Swarm directory ownership already set" + +# Configure AppArmor for Swarm (Ubuntu uses AppArmor instead of SELinux) +if command -v aa-status > /dev/null 2>&1; then + log_message "AppArmor is active" + # AppArmor is less restrictive by default for /opt + # Additional configuration can be added here if needed +else + log_message "AppArmor not found, skipping AppArmor configuration" +fi + +log_message "P4 Code Review (Swarm) installation completed successfully" +log_message "Configuration will be done at runtime via swarm_instance_init.sh" diff --git a/mkdocs.yml b/mkdocs.yml index 92f85dac..1d96d0d1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -102,6 +102,7 @@ nav: - Windows Build Agent: - assets/packer/build-agents/windows/README.md - P4 Server: assets/packer/perforce/p4-server/README.md + - P4 Code Review: assets/packer/perforce/p4-code-review/README.md - Virtual Workstations: assets/packer/virtual-workstations/README.md - Jenkins Pipelines: assets/jenkins-pipelines/README.md - Ansible Playbooks: assets/ansible-playbooks/perforce/p4-server/README.md From 8a062053f290f18c7fb0a3808af83c8c926dd062 Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Wed, 14 Jan 2026 23:04:00 -0500 Subject: [PATCH 2/7] feat(perforce/p4-code-review): refactor module from ECS to EC2 Auto Scaling Refactor P4 Code Review (Helix Swarm) deployment from ECS Fargate to native EC2 with Auto Scaling Group for improved performance and simpler operations. Key changes: - Replace ECS task definition with EC2 launch template - Add Auto Scaling Group (min=1, max=1) for automatic instance recovery - Add persistent EBS volume for Swarm data directory - Add user-data script for volume attachment and Swarm configuration - Update security groups for EC2-based deployment - Add ALB target group for health checks - Support for custom config.php injection via Secrets Manager --- modules/perforce/README.md | 29 +- .../diagrams/p4-code-review-architecture.png | Bin 297671 -> 117038 bytes modules/perforce/main.tf | 33 +-- .../perforce/modules/p4-code-review/README.md | 210 +++++++++++--- .../perforce/modules/p4-code-review/alb.tf | 14 +- .../perforce/modules/p4-code-review/data.tf | 33 ++- .../perforce/modules/p4-code-review/ec2.tf | 162 +++++++++++ .../modules/p4-code-review/elasticache.tf | 1 + .../perforce/modules/p4-code-review/iam.tf | 177 +++++------ .../perforce/modules/p4-code-review/locals.tf | 18 +- .../perforce/modules/p4-code-review/main.tf | 274 +----------------- .../modules/p4-code-review/outputs.tf | 35 ++- modules/perforce/modules/p4-code-review/sg.tf | 100 +++++-- .../modules/p4-code-review/user-data.sh.tpl | 255 ++++++++++++++++ .../modules/p4-code-review/variables.tf | 109 ++++--- modules/perforce/outputs.tf | 18 +- modules/perforce/sg.tf | 26 +- modules/perforce/variables.tf | 59 ++-- 18 files changed, 941 insertions(+), 612 deletions(-) create mode 100644 modules/perforce/modules/p4-code-review/ec2.tf create mode 100644 modules/perforce/modules/p4-code-review/user-data.sh.tpl diff --git a/modules/perforce/README.md b/modules/perforce/README.md index d4104678..b92279a0 100644 --- a/modules/perforce/README.md +++ b/modules/perforce/README.md @@ -47,15 +47,19 @@ For a video walkthrough demonstrating how to use this module, see this YouTube V certificate, the module allows you to import this into ACM to be used for the other components that will be deployed (such as the internal ALB). You may also use Email validation to validate DNS ownership. -- **Existing Perforce Amazon Machine Image (AMI)** - - As mentioned in the architecture, an Amazon EC2 instance is used for the P4 Server, and this instance must be be - provisioned using an AMI that is configured for Perforce. To expedite this process, we have +- **Existing Perforce Amazon Machine Images (AMIs)** + - **P4 Server AMI**: As mentioned in the architecture, an Amazon EC2 instance is used for the P4 Server, and this + instance must be provisioned using an AMI that is configured for Perforce. To expedite this process, we have sample [HashiCorp Packer](https://www.packer.io/) templates provided in the [AWS Cloud Game Development Toolkit repository](https://github.com/aws-games/cloud-game-development-toolkit/tree/main/assets/packer/perforce/p4-server) that you can use to create a Perforce AMI in your AWS Account. **Note:** You must also reference the `p4_configure.sh` and `p4_setup.sh` files that are in this directory, as these are used to configure the P4 Commit Server. These are already referenced in the `perforce_arm64.pkr.hcl` and `perforce_x86.pkr.hcl` packer templates that are available for use. + - **P4 Code Review AMI**: If deploying P4 Code Review (Helix Swarm), you must also build an AMI using the Packer + template at [assets/packer/perforce/p4-code-review](../../assets/packer/perforce/p4-code-review). + See the [P4 Code Review Packer README](../../assets/packer/perforce/p4-code-review/README.md) + for detailed build instructions. ## Examples @@ -63,10 +67,13 @@ For example configurations, please see the [examples](https://github.com/aws-gam ## Deployment Instructions -1. Create the Perforce AMI in your AWS account using one of the supplied Packer templates. Ensure you use the Packer - template that aligns with the architecture type (e.g. arm64) of the EC2 instance you wish to create. On the Terraform - side, you may also set this using the `instance_architecture` variable. Ensure your `instance_type` is supported for - your desired `instance_architecture`. For a full list of this mapping, see +1. Create the required AMIs in your AWS account using the supplied Packer templates: + - **P4 Server AMI** (required): Use the templates in `assets/packer/perforce/p4-server/`. Choose the template that + aligns with the architecture type (e.g. arm64) of the EC2 instance you wish to create. + - **P4 Code Review AMI** (if deploying P4 Code Review): Use the template in `assets/packer/perforce/p4-code-review/`. + + On the Terraform side, you may set the architecture using the `instance_architecture` variable. Ensure your + `instance_type` is supported for your desired `instance_architecture`. For a full list of this mapping, see the [AWS Docs for EC2 Naming Conventions](https://docs.aws.amazon.com/ec2/latest/instancetypes/instance-type-names.html). You can also use the interactive chart on Instances by [Vantage](https://instances.vantage.sh/). @@ -191,6 +198,7 @@ packer build perforce_x86.pkr.hcl | [aws_security_group.perforce_network_load_balancer](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group.perforce_web_services_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_vpc_security_group_egress_rule.p4_code_review_outbound_to_p4_server](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_egress_rule.p4_server_outbound_to_perforce_web_services_alb_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | | [aws_vpc_security_group_egress_rule.perforce_alb_outbound_to_p4_auth](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | | [aws_vpc_security_group_egress_rule.perforce_alb_outbound_to_p4_code_review](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | | [aws_vpc_security_group_egress_rule.perforce_nlb_outbound_to_perforce_web_services_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | @@ -219,7 +227,7 @@ packer build perforce_x86.pkr.hcl | [existing\_ecs\_cluster\_name](#input\_existing\_ecs\_cluster\_name) | The name of an existing ECS cluster to use for the Perforce server. If omitted a new cluster will be created. | `string` | `null` | no | | [existing\_security\_groups](#input\_existing\_security\_groups) | A list of existing security group IDs to attach to the shared network load balancer. | `list(string)` | `[]` | no | | [p4\_auth\_config](#input\_p4\_auth\_config) | # General
name: "The string including in the naming of resources related to P4Auth. Default is 'p4-auth'."

project\_prefix : "The project prefix for the P4Auth service. Default is 'cgd'."

environment : "The environment where the P4Auth service will be deployed. Default is 'dev'."

enable\_web\_based\_administration: "Whether to de enable web based administration. Default is 'true'."

debug : "Whether to enable debug mode for the P4Auth service. Default is 'false'."

fully\_qualified\_domain\_name : "The FQDN for the P4Auth Service. This is used for the P4Auth's Perforce configuration."


# Compute
cluster\_name : "The name of the ECS cluster where the P4Auth service will be deployed. Cluster is not created if this variable is null."

container\_name : "The name of the P4Auth service container. Default is 'p4-auth-container'."

container\_port : "The port on which the P4Auth service will be listening. Default is '3000'."

container\_cpu : "The number of CPU units to reserve for the P4Auth service container. Default is '1024'."

container\_memory : "The number of CPU units to reserve for the P4Auth service container. Default is '4096'."

pd4\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4Auth service logs in CloudWatch. Default is 365 days."


# Networking
create\_defaults\_sgs : "Whether to create default security groups for the P4Auth service."

internal : "Set this flag to true if you do not want the P4Auth service to have a public IP."

create\_default\_role : "Whether to create the P4Auth default IAM Role. Default is set to true."

custom\_role : "ARN of a custom IAM Role you wish to use with P4Auth."

admin\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4Auth Administrator username."

admin\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4Auth Administrator password."


# - SCIM -
p4d\_super\_user\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the super user username for p4d."

p4d\_super\_user\_password\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the super user password for p4d."

scim\_bearer\_token\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the bearer token."

extra\_env : "Extra configuration environment variables to set on the p4 auth svc container." |
object({
# - General -
name = optional(string, "p4-auth")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
enable_web_based_administration = optional(bool, true)
debug = optional(bool, false)
fully_qualified_domain_name = string

# - Compute -
container_name = optional(string, "p4-auth-container")
container_port = optional(number, 3000)
container_cpu = optional(number, 1024)
container_memory = optional(number, 4096)
p4d_port = optional(string, null)

# - Storage & Logging -
cloudwatch_log_retention_in_days = optional(number, 365)

# - Networking & Security -
service_subnets = optional(list(string), null)
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

certificate_arn = optional(string, null)
create_default_role = optional(bool, true)
custom_role = optional(string, null)
admin_username_secret_arn = optional(string, null)
admin_password_secret_arn = optional(string, null)

# SCIM
p4d_super_user_arn = optional(string, null)
p4d_super_user_password_arn = optional(string, null)
scim_bearer_token_arn = optional(string, null)
extra_env = optional(map(string), null)
})
| `null` | no | -| [p4\_code\_review\_config](#input\_p4\_code\_review\_config) | # General
name: "The string including in the naming of resources related to P4 Code Review. Default is 'p4-code-review'."

project\_prefix : "The project prefix for the P4 Code Review service. Default is 'cgd'."

environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'."

debug : "Whether to enable debug mode for the P4 Code Review service. Default is 'false'."

fully\_qualified\_domain\_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration."


# Compute
container\_name : "The name of the P4 Code Review service container. Default is 'p4-code-review-container'."

container\_port : "The port on which the P4 Code Review service will be listening. Default is '3000'."

container\_cpu : "The number of CPU units to reserve for the P4 Code Review service container. Default is '1024'."

container\_memory : "The number of CPU units to reserve for the P4 Code Review service container. Default is '4096'."

pd4\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."

p4charset : "The P4CHARSET environment variable to set in the P4 Code Review container."

existing\_redis\_connection : "The existing Redis connection for the P4 Code Review service."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days."


# Networking & Security
create\_default\_sgs : "Whether to create default security groups for the P4 Code Review service."

internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP."

create\_default\_role : "Whether to create the P4 Code Review default IAM Role. Default is set to true."

custom\_role : "ARN of a custom IAM Role you wish to use with P4 Code Review."

super\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review Administrator username."

super\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review Administrator password."

p4d\_p4\_code\_review\_user\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username."

p4d\_p4\_code\_review\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password."

p4d\_p4\_code\_review\_user\_password\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password."

enable\_sso : "Whether to enable SSO for the P4 Code Review service. Default is set to false."

config\_php\_source : "Used as the ValueFrom for P4CR's config.php. Contents should be base64 encoded, and will be combined with the generated config.php via array\_replace\_recursive."


# Caching
elasticache\_node\_count : "The number of Elasticache nodes to create for the P4 Code Review service. Default is '1'."

elasticache\_node\_type : "The type of Elasticache node to create for the P4 Code Review service. Default is 'cache.t4g.micro'." |
object({
# General
name = optional(string, "p4-code-review")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
debug = optional(bool, false)
fully_qualified_domain_name = string

# Compute
container_name = optional(string, "p4-code-review-container")
container_port = optional(number, 80)
container_cpu = optional(number, 1024)
container_memory = optional(number, 4096)
p4d_port = optional(string, null)
p4charset = optional(string, null)
existing_redis_connection = optional(object({
host = string
port = number
}), null)

# Storage & Logging
cloudwatch_log_retention_in_days = optional(number, 365)

# Networking & Security
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)
service_subnets = optional(list(string), null)

create_default_role = optional(bool, true)
custom_role = optional(string, null)

super_user_password_secret_arn = optional(string, null)
super_user_username_secret_arn = optional(string, null)
p4_code_review_user_password_secret_arn = optional(string, null)
p4_code_review_user_username_secret_arn = optional(string, null)
enable_sso = optional(string, false)
config_php_source = optional(string, null)

# Caching
elasticache_node_count = optional(number, 1)
elasticache_node_type = optional(string, "cache.t4g.micro")
})
| `null` | no | +| [p4\_code\_review\_config](#input\_p4\_code\_review\_config) | # General
name: "The string including in the naming of resources related to P4 Code Review. Default is 'p4-code-review'."

project\_prefix : "The project prefix for the P4 Code Review service. Default is 'cgd'."

environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'."

fully\_qualified\_domain\_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration."


# Compute
application\_port : "The port on which the P4 Code Review service will be listening. Default is '80'."

instance\_type : "EC2 instance type for running P4 Code Review. Default is 'm5.large'."

ami\_id : "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI."

p4d\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."

p4charset : "The P4CHARSET environment variable to set for the P4 Code Review instance."

existing\_redis\_connection : "The existing Redis connection for the P4 Code Review service."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days."

ebs\_volume\_size : "Size in GB for the EBS volume that stores P4 Code Review data. Default is '20'."

ebs\_volume\_type : "EBS volume type for P4 Code Review data storage. Default is 'gp3'."

ebs\_volume\_encrypted : "Enable encryption for the EBS volume storing P4 Code Review data. Default is 'true'."

ebs\_availability\_zone : "Availability zone for the EBS volume. Must match the EC2 instance AZ."


# Networking & Security
create\_default\_sgs : "Whether to create default security groups for the P4 Code Review service."

internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP."

instance\_subnet\_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security."

super\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password."

super\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user username."

p4\_code\_review\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password."

p4\_code\_review\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username."

custom\_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc."


# Caching
elasticache\_node\_count : "The number of Elasticache nodes to create for the P4 Code Review service. Default is '1'."

elasticache\_node\_type : "The type of Elasticache node to create for the P4 Code Review service. Default is 'cache.t4g.micro'." |
object({
# General
name = optional(string, "p4-code-review")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
fully_qualified_domain_name = string

# Compute
application_port = optional(number, 80)
instance_type = optional(string, "m5.large")
ami_id = optional(string, null)
p4d_port = optional(string, null)
p4charset = optional(string, null)
existing_redis_connection = optional(object({
host = string
port = number
}), null)

# Storage & Logging
cloudwatch_log_retention_in_days = optional(number, 365)
ebs_volume_size = optional(number, 20)
ebs_volume_type = optional(string, "gp3")
ebs_volume_encrypted = optional(bool, true)
ebs_availability_zone = optional(string, null)

# Networking & Security
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)
service_subnets = optional(list(string), null)
instance_subnet_id = string

super_user_password_secret_arn = optional(string, null)
super_user_username_secret_arn = optional(string, null)
p4_code_review_user_password_secret_arn = optional(string, null)
p4_code_review_user_username_secret_arn = optional(string, null)
custom_config = optional(string, null)

# Caching
elasticache_node_count = optional(number, 1)
elasticache_node_type = optional(string, "cache.t4g.micro")
})
| `null` | no | | [p4\_server\_config](#input\_p4\_server\_config) | # - General -
name: "The string including in the naming of resources related to P4 Server. Default is 'p4-server'"

project\_prefix: "The project prefix for this workload. This is appended to the beginning of most resource names."

environment: "The current environment (e.g. dev, prod, etc.)"

auth\_service\_url: "The URL for the P4Auth Service."

fully\_qualified\_domain\_name = "The FQDN for the P4 Server. This is used for the P4 Server's Perforce configuration."


# - Compute -
lookup\_existing\_ami : "Whether to lookup the existing Perforce P4 Server AMI."

ami\_prefix: "The AMI prefix to use for the AMI that will be created for P4 Server."

instance\_type: "The instance type for Perforce P4 Server. Defaults to c6g.large."

instance\_architecture: "The architecture of the P4 Server instance. Allowed values are 'arm64' or 'x86\_64'."

IMPORTANT: "Ensure the instance family of the instance type you select supports the instance\_architecture you select. For example, 'c6in' instance family only works for 'x86\_64' architecture, not 'arm64'. For a full list of this mapping, see the AWS Docs for EC2 Naming Conventions: https://docs.aws.amazon.com/ec2/latest/instancetypes/instance-type-names.html"

p4\_server\_type: "The Perforce P4 Server server type. Valid values are 'p4d\_commit' or 'p4d\_replica'."

unicode: "Whether to enable Unicode configuration for P4 Server the -xi flag for p4d. Set to true to enable Unicode support."

selinux: "Whether to apply SELinux label updates for P4 Server. Don't enable this if SELinux is disabled on your target operating system."

case\_sensitive: "Whether or not the server should be case insensitive (Server will run '-C1' mode), or if the server will run with case sensitivity default of the underlying platform. False enables '-C1' mode. Default is set to true."

plaintext: "Whether to enable plaintext authentication for P4 Server. This is not recommended for production environments unless you are using a load balancer for TLS termination. Default is set to false."


# - Storage -
storage\_type: "The type of backing store. Valid values are either 'EBS' or 'FSxN'"

depot\_volume\_size: "The size of the depot volume in GiB. Defaults to 128 GiB."

metadata\_volume\_size: "The size of the metadata volume in GiB. Defaults to 32 GiB."

logs\_volume\_size: "The size of the logs volume in GiB. Defaults to 32 GiB."


# - Networking & Security -
instance\_subnet\_id: "The subnet where the P4 Server instance will be deployed."

instance\_private\_ip: "The private IP address to assign to the P4 Server."

create\_default\_sg : "Whether to create a default security group for the P4 Server instance."

existing\_security\_groups: "A list of existing security group IDs to attach to the P4 Server load balancer."

internal: "Set this flag to true if you do not want the P4 Server instance to have a public IP."

super\_user\_password\_secret\_arn: "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's username here. Otherwise, the default of 'perforce' will be used."

super\_user\_username\_secret\_arn: "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's password here."

create\_default\_role: "Optional creation of P4 Server default IAM Role with SSM managed instance core policy attached. Default is set to true."

custom\_role: "ARN of a custom IAM Role you wish to use with P4 Server." |
object({
# General
name = optional(string, "p4-server")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
auth_service_url = optional(string, null)
fully_qualified_domain_name = string

# Compute
lookup_existing_ami = optional(bool, true)
ami_prefix = optional(string, "p4_al2023")

instance_type = optional(string, "c6i.large")
instance_architecture = optional(string, "x86_64")
p4_server_type = optional(string, null)

unicode = optional(bool, false)
selinux = optional(bool, false)
case_sensitive = optional(bool, true)
plaintext = optional(bool, false)

# Storage
storage_type = optional(string, "EBS")
depot_volume_size = optional(number, 128)
metadata_volume_size = optional(number, 32)
logs_volume_size = optional(number, 32)

# Networking & Security
instance_subnet_id = optional(string, null)
instance_private_ip = optional(string, null)
create_default_sg = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

super_user_password_secret_arn = optional(string, null)
super_user_username_secret_arn = optional(string, null)

create_default_role = optional(bool, true)
custom_role = optional(string, null)

# FSxN
fsxn_password = optional(string, null)
fsxn_filesystem_security_group_id = optional(string, null)
protocol = optional(string, null)
fsxn_region = optional(string, null)
fsxn_management_ip = optional(string, null)
fsxn_svm_name = optional(string, null)
amazon_fsxn_svm_id = optional(string, null)
fsxn_aws_profile = optional(string, null)
})
| `null` | no | | [project\_prefix](#input\_project\_prefix) | The project prefix for this workload. This is appended to the beginning of most resource names. | `string` | `"cgd"` | no | | [route53\_private\_hosted\_zone\_name](#input\_route53\_private\_hosted\_zone\_name) | The name of the private Route53 Hosted Zone for the Perforce resources. | `string` | `null` | no | @@ -247,10 +255,7 @@ packer build perforce_x86.pkr.hcl | [p4\_code\_review\_alb\_dns\_name](#output\_p4\_code\_review\_alb\_dns\_name) | The DNS name of the P4 Code Review ALB. | | [p4\_code\_review\_alb\_security\_group\_id](#output\_p4\_code\_review\_alb\_security\_group\_id) | Security group associated with the P4 Code Review load balancer. | | [p4\_code\_review\_alb\_zone\_id](#output\_p4\_code\_review\_alb\_zone\_id) | The hosted zone ID of the P4 Code Review ALB. | -| [p4\_code\_review\_default\_role\_id](#output\_p4\_code\_review\_default\_role\_id) | The default role for the P4 Code Review service task | -| [p4\_code\_review\_execution\_role\_id](#output\_p4\_code\_review\_execution\_role\_id) | The default role for the P4 Code Review service task | -| [p4\_code\_review\_perforce\_cluster\_name](#output\_p4\_code\_review\_perforce\_cluster\_name) | Name of the ECS cluster hosting P4 Code Review. | -| [p4\_code\_review\_service\_security\_group\_id](#output\_p4\_code\_review\_service\_security\_group\_id) | Security group associated with the ECS service running P4 Code Review. | +| [p4\_code\_review\_service\_security\_group\_id](#output\_p4\_code\_review\_service\_security\_group\_id) | Security group associated with P4 Code Review application. | | [p4\_code\_review\_target\_group\_arn](#output\_p4\_code\_review\_target\_group\_arn) | The service target group for the P4 Code Review. | | [p4\_server\_eip\_id](#output\_p4\_server\_eip\_id) | The ID of the Elastic IP associated with your P4 Server instance. | | [p4\_server\_eip\_public\_ip](#output\_p4\_server\_eip\_public\_ip) | The public IP of your P4 Server instance. | diff --git a/modules/perforce/assets/media/diagrams/p4-code-review-architecture.png b/modules/perforce/assets/media/diagrams/p4-code-review-architecture.png index 329d09ed2111842f2ca11b0a206841a281744b67..546e43f87a9b5cd765d1f8299cb5355b832ed5d7 100644 GIT binary patch literal 117038 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

X1a%Mr#$g2OMipW5t;If^4LVs%FlBrXk1>fJq_ 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 Gzx_X7J>Sv* diff --git a/modules/perforce/main.tf b/modules/perforce/main.tf index f499c696..85180d63 100644 --- a/modules/perforce/main.tf +++ b/modules/perforce/main.tf @@ -125,18 +125,12 @@ module "p4_code_review" { # General name = var.p4_code_review_config.name project_prefix = var.p4_code_review_config.project_prefix - debug = var.p4_code_review_config.debug fully_qualified_domain_name = var.p4_code_review_config.fully_qualified_domain_name - cluster_name = ( - var.existing_ecs_cluster_name != null ? - var.existing_ecs_cluster_name : - aws_ecs_cluster.perforce_web_services_cluster[0].name - ) - container_name = var.p4_code_review_config.container_name - container_port = var.p4_code_review_config.container_port - container_cpu = var.p4_code_review_config.container_cpu - container_memory = var.p4_code_review_config.container_memory + # Compute + application_port = var.p4_code_review_config.application_port + instance_type = var.p4_code_review_config.instance_type + ami_id = var.p4_code_review_config.ami_id p4d_port = var.p4_code_review_config.p4d_port != null ? var.p4_code_review_config.p4d_port : local.p4_port p4charset = var.p4_code_review_config.p4charset != null ? var.p4_code_review_config.p4charset : ( var.p4_server_config != null ? ( @@ -146,28 +140,33 @@ module "p4_code_review" { existing_redis_connection = var.p4_code_review_config.existing_redis_connection # Storage & Logging + ebs_volume_size = var.p4_code_review_config.ebs_volume_size + ebs_volume_type = var.p4_code_review_config.ebs_volume_type + ebs_volume_encrypted = var.p4_code_review_config.ebs_volume_encrypted + ebs_availability_zone = var.p4_code_review_config.ebs_availability_zone enable_alb_access_logs = false cloudwatch_log_retention_in_days = var.p4_code_review_config.cloudwatch_log_retention_in_days # Networking & Security - vpc_id = var.vpc_id - subnets = var.p4_code_review_config.service_subnets + vpc_id = var.vpc_id + subnets = var.p4_code_review_config.service_subnets + instance_subnet_id = var.p4_code_review_config.instance_subnet_id create_application_load_balancer = false internal = var.p4_code_review_config.internal - create_default_role = var.p4_code_review_config.create_default_role - custom_role = var.p4_code_review_config.custom_role + # ElastiCache Redis + elasticache_node_count = var.p4_code_review_config.elasticache_node_count + elasticache_node_type = var.p4_code_review_config.elasticache_node_type super_user_password_secret_arn = module.p4_server[0].super_user_password_secret_arn super_user_username_secret_arn = module.p4_server[0].super_user_username_secret_arn p4_code_review_user_password_secret_arn = module.p4_server[0].super_user_password_secret_arn p4_code_review_user_username_secret_arn = module.p4_server[0].super_user_username_secret_arn - enable_sso = var.p4_code_review_config.enable_sso - config_php_source = var.p4_code_review_config.config_php_source + custom_config = var.p4_code_review_config.custom_config - depends_on = [aws_ecs_cluster.perforce_web_services_cluster[0]] + depends_on = [module.p4_server] } ################################################# diff --git a/modules/perforce/modules/p4-code-review/README.md b/modules/perforce/modules/p4-code-review/README.md index 3d11d59b..5f7afee3 100644 --- a/modules/perforce/modules/p4-code-review/README.md +++ b/modules/perforce/modules/p4-code-review/README.md @@ -1,24 +1,24 @@ # P4 Code Review Submodule -[P4 Code Review](https://www.perforce.com/products/helix-swarm) is a free code review tool for projects hosted in [P4 Server](https://www.perforce.com/products/helix-core/aws). This module deploys P4 Code Review as a service on AWS Elastic Container Service using the [publicly available image from Dockerhub](https://hub.docker.com/r/perforce/helix-swarm). +[P4 Code Review](https://www.perforce.com/products/helix-swarm) is a free code review tool for projects hosted in [P4 Server](https://www.perforce.com/products/helix-core/aws). This module deploys P4 Code Review on an EC2 Auto Scaling Group using a custom AMI built with [Packer](../../../../assets/packer/perforce/p4-code-review/README.md). P4 Code Review also relies on a Redis cache. The module provisions a single node AWS Elasticache Redis OSS cluster and configures connectivity for the P4 Code Review service. This module deploys the following resources: -- An Elastic Container Service (ECS) cluster backed by AWS Fargate. This can also be created externally and passed in via the `cluster_name` variable. -- An ECS service running the latest P4 Code Review container ([perforce/helix-swarm](https://hub.docker.com/r/perforce/helix-swarm)) available. +- An EC2 Auto Scaling Group running the P4 Code Review AMI (built using the [Packer template](../../../../assets/packer/perforce/p4-code-review/README.md)). +- A persistent EBS volume for P4 Code Review data that survives instance replacement. - An Application Load Balancer for TLS termination of the P4 Code Review service. - A single node [AWS Elasticache Redis OSS](https://aws.amazon.com/elasticache/redis/) cluster. -- Supporting resources such as Cloudwatch log groups, IAM roles, and security groups. +- Supporting resources such as CloudWatch log groups, IAM roles, and security groups. ## Architecture -![P4 Code Review Submodule Architecture](../../assets/media/diagrams/p4-code-review-architecture.png) +![P4 Code Review Architecture](../../assets/media/diagrams/p4-code-review-architecture.png) ## Prerequisites -P4 Code Review needs to be able to connect to a P4 Server. P4 Code Review leverages the same authentication mechanism as P4 Server, and needs to install required plugins on the upstream P4 Server instance during setup. This happens automatically, but P4 Code Review requires an administrative user's credentials to be able to initially connect. These credentials are provided to the module through variables specifying AWS Secrets Manager secrets, and then pulled into the P4 Code Review container during startup. See the `p4d_super_user_arn`, `p4d_super_user_password_arn`, `p4d_swarm_user_arn`, and `p4d_swarm_password_arn` variables below for more details. +P4 Code Review needs to be able to connect to a P4 Server. P4 Code Review leverages the same authentication mechanism as P4 Server, and needs to install required plugins on the upstream P4 Server instance during setup. This happens automatically, but P4 Code Review requires an administrative user's credentials to be able to initially connect. These credentials are provided to the module through variables specifying AWS Secrets Manager secrets, and then pulled into the P4 Code Review instance during startup. See the `p4d_super_user_arn`, `p4d_super_user_password_arn`, `p4d_swarm_user_arn`, and `p4d_swarm_password_arn` variables below for more details. The [P4 Server submodule](../p4-server/README.md) creates an administrative user on initial deployment, and stores the credentials in AWS Secrets manager. The ARN of the credentials secret is then made available as a Terraform output from the module, and can be referenced elsewhere. The is done by default by the parent Perforce module. @@ -50,6 +50,123 @@ If you're running into issues with P4 Code Review, here are some common log file - `/opt/perforce/swarm/data/configure-swarm.log`: errors coming from p4cr configuration - `/opt/perforce/swarm/data/log`: errors from the p4cr runtime +## Custom Configuration + +The `custom_config` variable allows you to pass additional configuration to P4 Code Review as a JSON string. This configuration is merged with the generated `config.php` using PHP's `array_replace_recursive` function at instance startup. + +This can be used to configure: + +- SSO/SAML authentication +- Email notifications +- Jira integration +- Project settings +- And any other [Swarm configuration option](https://www.perforce.com/manuals/swarm/Content/Swarm/admin.configuration.html) + +### Example: SSO/SAML with Auth0 + +SSO/SAML configuration requires two parts: + +1. **`p4.sso`** - Enables the SSO login option. Values: + - `"disabled"` - No SSO, only password login (default) + - `"optional"` - Both SSO and password login available + - `"enabled"` - SSO only, no password login + +2. **`saml`** - The SAML technical configuration (IdP/SP settings, certificates) + +```hcl +module "p4_code_review" { + source = "modules/perforce/modules/p4-code-review" + # ... other required variables ... + + custom_config = jsonencode({ + # Enable SSO login option + p4 = { + sso = "optional" + } + # SAML configuration + saml = { + header = "Log in with SSO" + sp = { + entityId = "https://swarm.example.com" + assertionConsumerService = { + url = "https://swarm.example.com/saml/acs" + } + singleLogoutService = { + url = "https://swarm.example.com/saml/sls" + } + NameIDFormat = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + } + idp = { + entityId = "urn:your-auth0-domain" + singleSignOnService = { + url = "https://your-auth0-domain/samlp/YOUR_CLIENT_ID" + } + singleLogoutService = { + url = "https://your-auth0-domain/samlp/YOUR_CLIENT_ID/logout" + } + x509cert = "YOUR_IDP_CERTIFICATE_HERE" + } + } + }) +} +``` + +### Example: Email Notifications + +```hcl +module "p4_code_review" { + source = "modules/perforce/modules/p4-code-review" + # ... other required variables ... + + custom_config = jsonencode({ + mail = { + transport = { + host = "smtp.example.com" + port = 587 + security = "tls" + } + sender = "swarm@example.com" + } + }) +} +``` + +### Example: Jira Integration + +```hcl +module "p4_code_review" { + source = "modules/perforce/modules/p4-code-review" + # ... other required variables ... + + custom_config = jsonencode({ + jira = { + host = "https://your-company.atlassian.net" + user = "jira-user@example.com" + password = "your-api-token" + job_field = "customfield_10001" + } + }) +} +``` + +### Combining Multiple Configurations + +You can combine multiple configuration sections in a single `custom_config`: + +```hcl +custom_config = jsonencode({ + saml = { + # SSO configuration... + } + mail = { + # Email configuration... + } + jira = { + # Jira configuration... + } +}) +``` + ## Requirements @@ -75,21 +192,20 @@ No modules. | Name | Type | |------|------| -| [aws_cloudwatch_log_group.log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_autoscaling_group.swarm_asg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource | +| [aws_cloudwatch_log_group.application_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_cloudwatch_log_group.redis_service_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | -| [aws_ecs_cluster.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | -| [aws_ecs_cluster_capacity_providers.cluster_fargate_providers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | -| [aws_ecs_service.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | -| [aws_ecs_task_definition.task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ebs_volume.swarm_data](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume) | resource | | [aws_elasticache_cluster.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_cluster) | resource | | [aws_elasticache_subnet_group.subnet_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_subnet_group) | resource | -| [aws_iam_policy.default_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_instance_profile.ec2_instance_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_policy.ebs_attachment_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.secrets_manager_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | -| [aws_iam_role.default_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role.task_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy_attachment.default_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.p4_auth_task_execution_role_ecs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.p4_auth_task_execution_role_secrets_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role.ec2_instance_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.ec2_instance_role_ebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ec2_instance_role_secrets_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ec2_instance_role_ssm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_launch_template.swarm_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | | [aws_lb.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource | | [aws_lb_listener.alb_https_listener](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | | [aws_lb_target_group.alb_target_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource | @@ -98,62 +214,67 @@ No modules. | [aws_s3_bucket_policy.alb_access_logs_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.access_logs_bucket_public_block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_security_group.alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | -| [aws_security_group.ecs_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.application](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.ec2_instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group.elasticache](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | -| [aws_vpc_security_group_egress_rule.alb_outbound_to_ecs_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | -| [aws_vpc_security_group_egress_rule.ecs_service_outbound_to_internet_ipv4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | -| [aws_vpc_security_group_egress_rule.ecs_service_outbound_to_internet_ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | -| [aws_vpc_security_group_ingress_rule.ecs_service_inbound_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | -| [aws_vpc_security_group_ingress_rule.elasticache_inbound_from_ecs_service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [aws_vpc_security_group_egress_rule.alb_outbound_to_application](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_egress_rule.application_outbound_to_internet_ipv4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_egress_rule.application_outbound_to_internet_ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_egress_rule.ec2_instance_outbound_to_internet_ipv4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_egress_rule.ec2_instance_outbound_to_internet_ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.alb_inbound_from_application](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.application_inbound_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.elasticache_inbound_from_application](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | | [random_string.alb_access_logs_bucket_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | | [random_string.p4_code_review](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | -| [aws_ecs_cluster.cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_cluster) | data source | +| [aws_ami.p4_code_review](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_elb_service_account.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account) | data source | | [aws_iam_policy_document.access_logs_bucket_alb_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.default_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.ecs_tasks_trust_relationship](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ebs_attachment_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ec2_instance_trust_relationship](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.secrets_manager_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_subnet.instance_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [instance\_subnet\_id](#input\_instance\_subnet\_id) | The subnet ID where the EC2 instance will be launched. Should be a private subnet for security. | `string` | n/a | yes | | [p4\_code\_review\_user\_password\_secret\_arn](#input\_p4\_code\_review\_user\_password\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review password. | `string` | n/a | yes | | [p4\_code\_review\_user\_username\_secret\_arn](#input\_p4\_code\_review\_user\_username\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review username. | `string` | n/a | yes | -| [subnets](#input\_subnets) | A list of subnets to deploy the P4 Code Review ECS Service into. Private subnets are recommended. | `list(string)` | n/a | yes | +| [subnets](#input\_subnets) | A list of subnets for ElastiCache Redis deployment. Private subnets are recommended. | `list(string)` | n/a | yes | | [super\_user\_password\_secret\_arn](#input\_super\_user\_password\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user password. | `string` | n/a | yes | | [super\_user\_username\_secret\_arn](#input\_super\_user\_username\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user username. | `string` | n/a | yes | | [vpc\_id](#input\_vpc\_id) | The ID of the existing VPC you would like to deploy P4 Code Review into. | `string` | n/a | yes | | [alb\_access\_logs\_bucket](#input\_alb\_access\_logs\_bucket) | ID of the S3 bucket for P4 Code Review ALB access log storage. If access logging is enabled and this is null the module creates a bucket. | `string` | `null` | no | | [alb\_access\_logs\_prefix](#input\_alb\_access\_logs\_prefix) | Log prefix for P4 Code Review ALB access logs. If null the project prefix and module name are used. | `string` | `null` | no | | [alb\_subnets](#input\_alb\_subnets) | A list of subnets to deploy the load balancer into. Public subnets are recommended. | `list(string)` | `[]` | no | +| [ami\_id](#input\_ami\_id) | Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI with name pattern 'p4\_code\_review\_ubuntu-*'. | `string` | `null` | no | | [application\_load\_balancer\_name](#input\_application\_load\_balancer\_name) | The name of the P4 Code Review ALB. Defaults to the project prefix and module name. | `string` | `null` | no | +| [application\_port](#input\_application\_port) | The port that P4 Code Review listens on. Used for ALB target group configuration. | `number` | `80` | no | | [certificate\_arn](#input\_certificate\_arn) | The TLS certificate ARN for the P4 Code Review service load balancer. | `string` | `null` | no | | [cloudwatch\_log\_retention\_in\_days](#input\_cloudwatch\_log\_retention\_in\_days) | The log retention in days of the cloudwatch log group for P4 Code Review. | `string` | `365` | no | -| [cluster\_name](#input\_cluster\_name) | The name of the cluster to deploy the P4 Code Review service into. Defaults to null and a cluster will be created. | `string` | `null` | no | -| [config\_php\_source](#input\_config\_php\_source) | Used as the ValueFrom for P4CR's config.php. Contents should be base64 encoded, and will be combined with the generated config.php via array\_replace\_recursive. | `string` | `null` | no | -| [container\_cpu](#input\_container\_cpu) | The CPU allotment for the P4 Code Review container. | `number` | `1024` | no | -| [container\_memory](#input\_container\_memory) | The memory allotment for the P4 Code Review container. | `number` | `2048` | no | -| [container\_name](#input\_container\_name) | The name of the P4 Code Review container. | `string` | `"p4-code-review-container"` | no | -| [container\_port](#input\_container\_port) | The container port that P4 Code Review runs on. | `number` | `80` | no | | [create\_application\_load\_balancer](#input\_create\_application\_load\_balancer) | This flag controls the creation of an application load balancer as part of the module. | `bool` | `true` | no | -| [create\_default\_role](#input\_create\_default\_role) | Optional creation of P4 Code Review Default IAM Role. Default is set to true. | `bool` | `true` | no | -| [custom\_role](#input\_custom\_role) | ARN of the custom IAM Role you wish to use with P4 Code Review. | `string` | `null` | no | -| [debug](#input\_debug) | Debug flag to enable execute command on service for container access. | `bool` | `false` | no | +| [custom\_config](#input\_custom\_config) | JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc. See README for examples. | `string` | `null` | no | | [deregistration\_delay](#input\_deregistration\_delay) | The amount of time to wait for in-flight requests to complete while deregistering a target. The range is 0-3600 seconds. | `number` | `30` | no | +| [ebs\_availability\_zone](#input\_ebs\_availability\_zone) | Availability zone for the EBS volume. Must match the EC2 instance AZ. If not provided, will use the AZ of the instance\_subnet\_id. | `string` | `null` | no | +| [ebs\_volume\_encrypted](#input\_ebs\_volume\_encrypted) | Enable encryption for the EBS volume storing P4 Code Review data. | `bool` | `true` | no | +| [ebs\_volume\_size](#input\_ebs\_volume\_size) | Size in GB for the EBS volume that stores P4 Code Review data (/opt/perforce/swarm/data). This volume persists across instance replacement. | `number` | `20` | no | +| [ebs\_volume\_type](#input\_ebs\_volume\_type) | EBS volume type for P4 Code Review data storage. | `string` | `"gp3"` | no | | [elasticache\_node\_count](#input\_elasticache\_node\_count) | Number of cache nodes to provision in the Elasticache cluster. | `number` | `1` | no | | [elasticache\_node\_type](#input\_elasticache\_node\_type) | The type of nodes provisioned in the Elasticache cluster. | `string` | `"cache.t4g.micro"` | no | | [enable\_alb\_access\_logs](#input\_enable\_alb\_access\_logs) | Enables access logging for the P4 Code Review ALB. Defaults to false. | `bool` | `false` | no | | [enable\_alb\_deletion\_protection](#input\_enable\_alb\_deletion\_protection) | Enables deletion protection for the P4 Code Review ALB. Defaults to true. | `bool` | `false` | no | -| [enable\_sso](#input\_enable\_sso) | Set this to true if using SSO for P4 Code Review authentication. | `bool` | `false` | no | | [existing\_redis\_connection](#input\_existing\_redis\_connection) | The connection specifications to use for an existing Redis deployment. |

object({
host = string
port = number
})
| `null` | no | | [existing\_security\_groups](#input\_existing\_security\_groups) | A list of existing security group IDs to attach to the P4 Code Review load balancer. | `list(string)` | `[]` | no | | [fully\_qualified\_domain\_name](#input\_fully\_qualified\_domain\_name) | The fully qualified domain name that P4 Code Review should use for internal URLs. | `string` | `null` | no | +| [instance\_type](#input\_instance\_type) | EC2 instance type for running P4 Code Review. Swarm requires persistent storage and runs natively on EC2. | `string` | `"m5.large"` | no | | [internal](#input\_internal) | Set this flag to true if you do not want the P4 Code Review service load balancer to have a public IP. | `bool` | `false` | no | | [name](#input\_name) | The name attached to P4 Code Review module resources. | `string` | `"p4-code-review"` | no | -| [p4charset](#input\_p4charset) | The P4CHARSET environment variable to set in the P4 Code Review container. | `string` | `"none"` | no | -| [p4d\_port](#input\_p4d\_port) | The P4D\_PORT environment variable where P4 Code Review should look for P4 Code Review. Defaults to 'ssl:perforce:1666' | `string` | `"ssl:perforce:1666"` | no | +| [p4charset](#input\_p4charset) | The P4CHARSET environment variable to set for the P4 Code Review instance. | `string` | `"none"` | no | +| [p4d\_port](#input\_p4d\_port) | The P4D\_PORT environment variable where P4 Code Review should look for P4 Server. Defaults to 'ssl:perforce:1666' | `string` | `"ssl:perforce:1666"` | no | | [project\_prefix](#input\_project\_prefix) | The project prefix for this workload. This is appended to the beginning of most resource names. | `string` | `"cgd"` | no | | [s3\_enable\_force\_destroy](#input\_s3\_enable\_force\_destroy) | Enables force destroy for the S3 bucket for P4 Code Review access log storage. Defaults to true. | `bool` | `true` | no | | [tags](#input\_tags) | Tags to apply to resources. | `map(any)` |
{
"IaC": "Terraform",
"ModuleBy": "CGD-Toolkit",
"ModuleName": "p4-code-review",
"ModuleSource": "https://github.com/aws-games/cloud-game-development-toolkit/tree/main/modules/perforce",
"RootModuleName": "terraform-aws-perforce"
}
| no | @@ -165,10 +286,11 @@ No modules. | [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the P4 Code Review ALB | | [alb\_security\_group\_id](#output\_alb\_security\_group\_id) | Security group associated with the P4 Code Review load balancer | | [alb\_zone\_id](#output\_alb\_zone\_id) | The hosted zone ID of the P4 Code Review ALB | -| [cluster\_name](#output\_cluster\_name) | Name of the ECS cluster hosting P4 Code Review | -| [default\_role\_id](#output\_default\_role\_id) | The default role for the service task | -| [execution\_role\_id](#output\_execution\_role\_id) | The default role for the service task | -| [service\_security\_group\_id](#output\_service\_security\_group\_id) | Security group associated with the ECS service running P4 Code Review | -| [target\_group\_arn](#output\_target\_group\_arn) | The service target group for P4 Code Review | +| [application\_security\_group\_id](#output\_application\_security\_group\_id) | Security group associated with the P4 Code Review application | +| [autoscaling\_group\_name](#output\_autoscaling\_group\_name) | The name of the Auto Scaling Group for P4 Code Review | +| [ebs\_volume\_id](#output\_ebs\_volume\_id) | The ID of the EBS volume storing P4 Code Review persistent data | +| [instance\_profile\_arn](#output\_instance\_profile\_arn) | The ARN of the IAM instance profile for P4 Code Review EC2 instances | +| [launch\_template\_id](#output\_launch\_template\_id) | The ID of the launch template for P4 Code Review instances | +| [target\_group\_arn](#output\_target\_group\_arn) | The target group ARN for P4 Code Review | diff --git a/modules/perforce/modules/p4-code-review/alb.tf b/modules/perforce/modules/p4-code-review/alb.tf index ca2ff00d..9b2ac8c3 100644 --- a/modules/perforce/modules/p4-code-review/alb.tf +++ b/modules/perforce/modules/p4-code-review/alb.tf @@ -42,13 +42,14 @@ resource "aws_lb" "alb" { resource "aws_lb_target_group" "alb_target_group" { #checkov:skip=CKV_AWS_378: Using ALB for TLS termination name = "${local.name_prefix}-tg" - port = var.container_port + port = local.application_port protocol = "HTTP" - target_type = "ip" + target_type = "instance" vpc_id = var.vpc_id - deregistration_delay = var.deregistration_delay # Fix LB listener from failing to be deleted because targets are still registered. + deregistration_delay = var.deregistration_delay + health_check { - path = "/login" # must match path in the health check in the ECS service that references this target group + path = "/login" protocol = "HTTP" matcher = "200" port = "traffic-port" @@ -63,14 +64,13 @@ resource "aws_lb_target_group" "alb_target_group" { Name = "${local.name_prefix}-tg" } ) - } ########################################## # Application Load Balancer | Listeners ########################################## -# HTTPS listener for p4_auth ALB +# HTTPS listener for P4 Code Review ALB resource "aws_lb_listener" "alb_https_listener" { count = var.create_application_load_balancer ? 1 : 0 load_balancer_arn = aws_lb.alb[0].arn @@ -89,8 +89,6 @@ resource "aws_lb_listener" "alb_https_listener" { Name = "${local.name_prefix}-tg-listener" } ) - - depends_on = [aws_ecs_service.service] } diff --git a/modules/perforce/modules/p4-code-review/data.tf b/modules/perforce/modules/p4-code-review/data.tf index 7ee9065f..96b80b6f 100644 --- a/modules/perforce/modules/p4-code-review/data.tf +++ b/modules/perforce/modules/p4-code-review/data.tf @@ -1,7 +1,32 @@ data "aws_region" "current" {} -# If cluster name is provided use a data source to access existing resource -data "aws_ecs_cluster" "cluster" { - count = var.cluster_name != null ? 1 : 0 - cluster_name = var.cluster_name +data "aws_caller_identity" "current" {} + +# Get the latest P4 Code Review AMI built by Packer +# Only used if ami_id variable is not provided +data "aws_ami" "p4_code_review" { + count = var.ami_id != null ? 0 : 1 + most_recent = true + owners = ["self"] + + filter { + name = "name" + values = ["p4_code_review_ubuntu-*"] + } + + filter { + name = "state" + values = ["available"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +# Lookup subnet details to determine availability zone for EBS volume +# EBS volumes must be in the same AZ as the EC2 instance +data "aws_subnet" "instance_subnet" { + id = var.instance_subnet_id } diff --git a/modules/perforce/modules/p4-code-review/ec2.tf b/modules/perforce/modules/p4-code-review/ec2.tf new file mode 100644 index 00000000..1d4e6663 --- /dev/null +++ b/modules/perforce/modules/p4-code-review/ec2.tf @@ -0,0 +1,162 @@ +########################################## +# EBS Volume for Persistent Storage +########################################## +# This volume stores /opt/perforce/swarm/data including the queue directory +# It persists across container and instance restarts +# Tagged so it can be automatically reattached to a new instance if the current one fails + +resource "aws_ebs_volume" "swarm_data" { + #checkov:skip=CKV_AWS_189:Customer-managed KMS key is optional; default AWS encryption enabled + #checkov:skip=CKV_AWS_3:Encryption is enabled via var.ebs_volume_encrypted (defaults to true) + availability_zone = local.ebs_availability_zone + size = var.ebs_volume_size + type = var.ebs_volume_type + encrypted = var.ebs_volume_encrypted + + tags = merge(var.tags, + { + Name = "${local.name_prefix}-data-volume" + SwarmDataVolume = "true" # Used by user data script to find this volume + ModuleIdentifier = local.module_identifier + Purpose = "perforce-swarm-persistent-storage" + ManagedBy = "terraform" + AutoAttachToSwarmInstance = "true" + } + ) + + lifecycle { + prevent_destroy = false # Set to true in production to prevent accidental deletion + } +} + + +########################################## +# Launch Template +########################################## +# Defines the EC2 instance configuration +# Includes user data script that automatically attaches and mounts the EBS volume + +resource "aws_launch_template" "swarm_instance" { + name_prefix = "${local.name_prefix}-" + image_id = local.selected_ami_id + instance_type = var.instance_type + + iam_instance_profile { + arn = aws_iam_instance_profile.ec2_instance_profile.arn + } + + vpc_security_group_ids = [ + aws_security_group.ec2_instance.id, + aws_security_group.application.id + ] + + # User data script handles EBS volume attachment, mounting, and Swarm configuration + user_data = base64encode(templatefile("${path.module}/user-data.sh.tpl", { + region = data.aws_region.current.name + device_name = local.ebs_device_name + mount_path = local.host_data_path + module_identifier = local.module_identifier + p4d_port = var.p4d_port + p4charset = var.p4charset + swarm_host = "https://${var.fully_qualified_domain_name}" + swarm_redis = var.existing_redis_connection != null ? var.existing_redis_connection.host : aws_elasticache_cluster.cluster[0].cache_nodes[0].address + swarm_redis_port = var.existing_redis_connection != null ? tostring(var.existing_redis_connection.port) : tostring(aws_elasticache_cluster.cluster[0].cache_nodes[0].port) + swarm_force_ext = "y" + super_user_username_secret_arn = var.super_user_username_secret_arn + super_user_password_secret_arn = var.super_user_password_secret_arn + p4_code_review_user_username_secret_arn = var.p4_code_review_user_username_secret_arn + p4_code_review_user_password_secret_arn = var.p4_code_review_user_password_secret_arn + custom_config = var.custom_config + })) + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" # Enforce IMDSv2 + http_put_response_hop_limit = 1 + } + + monitoring { + enabled = true + } + + tag_specifications { + resource_type = "instance" + tags = merge(var.tags, + { + Name = "${local.name_prefix}-instance" + SwarmInstance = "true" + ManagedBy = "terraform" + } + ) + } + + tag_specifications { + resource_type = "volume" + tags = merge(var.tags, + { + Name = "${local.name_prefix}-root-volume" + ManagedBy = "terraform" + } + ) + } + + tags = merge(var.tags, + { + Name = "${local.name_prefix}-launch-template" + } + ) +} + + +########################################## +# Auto Scaling Group +########################################## +# Single-instance ASG provides automatic instance replacement if it fails +# Min=1, Max=1 ensures only one instance runs at a time (Swarm doesn't scale horizontally) + +resource "aws_autoscaling_group" "swarm_asg" { + name_prefix = "${local.name_prefix}-asg-" + min_size = 1 + max_size = 1 + desired_capacity = 1 + vpc_zone_identifier = [var.instance_subnet_id] + + target_group_arns = [aws_lb_target_group.alb_target_group.arn] + + launch_template { + id = aws_launch_template.swarm_instance.id + version = "$Latest" + } + + health_check_type = "ELB" + health_check_grace_period = 600 # 10 minutes for instance to boot, attach volume, and configure Swarm + + # Ensure instance is in the same AZ as the EBS volume + # availability_zones is set implicitly by vpc_zone_identifier + + tag { + key = "Name" + value = "${local.name_prefix}-instance" + propagate_at_launch = true + } + + tag { + key = "ManagedBy" + value = "terraform-asg" + propagate_at_launch = true + } + + tag { + key = "SwarmInstance" + value = "true" + propagate_at_launch = true + } + + lifecycle { + create_before_destroy = true + } + + depends_on = [ + aws_ebs_volume.swarm_data + ] +} diff --git a/modules/perforce/modules/p4-code-review/elasticache.tf b/modules/perforce/modules/p4-code-review/elasticache.tf index d4320dad..3208ae97 100644 --- a/modules/perforce/modules/p4-code-review/elasticache.tf +++ b/modules/perforce/modules/p4-code-review/elasticache.tf @@ -7,6 +7,7 @@ resource "aws_elasticache_subnet_group" "subnet_group" { # Single Node Elasticache Cluster for P4 Code Review resource "aws_elasticache_cluster" "cluster" { + #checkov:skip=CKV_AWS_134:Automatic backups optional; Swarm cache is ephemeral and can be rebuilt count = var.existing_redis_connection != null ? 0 : 1 cluster_id = "${local.name_prefix}-elasticache-redis-cluster" engine = "redis" diff --git a/modules/perforce/modules/p4-code-review/iam.tf b/modules/perforce/modules/p4-code-review/iam.tf index 4feb2dda..473f353a 100644 --- a/modules/perforce/modules/p4-code-review/iam.tf +++ b/modules/perforce/modules/p4-code-review/iam.tf @@ -9,73 +9,16 @@ resource "random_string" "p4_code_review" { } -########################################## -# Trust Relationships -########################################## -# ECS - Tasks -data "aws_iam_policy_document" "ecs_tasks_trust_relationship" { - statement { - effect = "Allow" - actions = ["sts:AssumeRole"] - principals { - type = "Service" - identifiers = ["ecs-tasks.amazonaws.com"] - } - } -} - - ########################################## # Policies ########################################## -# Default Policy Document -data "aws_iam_policy_document" "default_policy" { - count = var.create_default_role ? 1 : 0 - # ECS - statement { - sid = "ECSExec" - effect = "Allow" - actions = [ - "ssmmessages:OpenDataChannel", - "ssmmessages:OpenControlChannel", - "ssmmessages:CreateDataChannel", - "ssmmessages:CreateControlChannel" - ] - resources = [ - "*" - ] - } - statement { - effect = "Allow" - actions = [ - "secretsmanager:ListSecrets", - "secretsmanager:ListSecretVersionIds", - "secretsmanager:GetRandomPassword", - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:BatchGetSecretValue" - ] - resources = [ - var.super_user_username_secret_arn, - var.super_user_password_secret_arn, - var.p4_code_review_user_username_secret_arn, - var.p4_code_review_user_password_secret_arn, - ] - } -} - -# Secrets Manager Policy Document +# Secrets Manager Policy Document for EC2 instances data "aws_iam_policy_document" "secrets_manager_policy" { - # ssm statement { effect = "Allow" actions = [ - "secretsmanager:ListSecrets", - "secretsmanager:ListSecretVersionIds", - "secretsmanager:GetRandomPassword", "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "secretsmanager:BatchGetSecretValue" + "secretsmanager:DescribeSecret" ] resources = [ var.super_user_username_secret_arn, @@ -86,25 +29,10 @@ data "aws_iam_policy_document" "secrets_manager_policy" { } } -# Default Policy -resource "aws_iam_policy" "default_policy" { - count = var.create_default_role ? 1 : 0 - - name = "${local.name_prefix}-default-policy" - description = "Policy granting permissions for ${local.name_prefix}." - policy = data.aws_iam_policy_document.default_policy[0].json - - tags = merge(var.tags, - { - Name = "${local.name_prefix}-default-policy" - } - ) -} - # Secrets Manager Policy resource "aws_iam_policy" "secrets_manager_policy" { name = "${local.name_prefix}-secrets-manager-policy" - description = "Policy granting permissions for ${local.name_prefix} task execution role to access Secrets Manager." + description = "Policy granting permissions for ${local.name_prefix} EC2 instance to access Secrets Manager." policy = data.aws_iam_policy_document.secrets_manager_policy.json tags = merge(var.tags, @@ -116,45 +44,98 @@ resource "aws_iam_policy" "secrets_manager_policy" { ########################################## -# Roles +# EC2 Instance Role ########################################## -resource "aws_iam_role" "default_role" { - # Default Role - count = var.create_default_role ? 1 : 0 - name = "${local.name_prefix}-default-role" - assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json +# EC2 - Instance Trust Relationship +data "aws_iam_policy_document" "ec2_instance_trust_relationship" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +# EBS Volume Attachment Policy +data "aws_iam_policy_document" "ebs_attachment_policy" { + # Describe operations require wildcard - AWS doesn't support resource-level permissions for these + statement { + sid = "EBSDescribeOperations" + effect = "Allow" + actions = [ + "ec2:DescribeVolumes", + "ec2:DescribeInstances" + ] + resources = ["*"] + } + + # Attach/detach operations scoped to the specific Swarm data volume + statement { + sid = "EBSVolumeAttachDetach" + effect = "Allow" + actions = [ + "ec2:AttachVolume", + "ec2:DetachVolume" + ] + resources = [ + aws_ebs_volume.swarm_data.arn, + "arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:instance/*" + ] + } +} + +resource "aws_iam_policy" "ebs_attachment_policy" { + name = "${local.name_prefix}-ebs-attachment-policy" + description = "Policy granting permissions for EC2 instance to attach EBS volumes." + policy = data.aws_iam_policy_document.ebs_attachment_policy.json tags = merge(var.tags, { - Name = "${local.name_prefix}-default-role" + Name = "${local.name_prefix}-ebs-attachment-policy" } ) } -resource "aws_iam_role_policy_attachment" "default_role" { - count = var.create_default_role ? 1 : 0 - role = aws_iam_role.default_role[0].name - policy_arn = aws_iam_policy.default_policy[0].arn -} - -# Task Execution Role -resource "aws_iam_role" "task_execution_role" { - name = "${local.name_prefix}-task-execution-role" - assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json +# EC2 Instance Role +resource "aws_iam_role" "ec2_instance_role" { + name = "${local.name_prefix}-ec2-instance-role" + assume_role_policy = data.aws_iam_policy_document.ec2_instance_trust_relationship.json tags = merge(var.tags, { - Name = "${local.name_prefix}-task-execution-role" + Name = "${local.name_prefix}-ec2-instance-role" } ) } -resource "aws_iam_role_policy_attachment" "p4_auth_task_execution_role_ecs" { - role = aws_iam_role.task_execution_role.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +# Attach SSM Managed Instance Core (for SSM Session Manager access) +resource "aws_iam_role_policy_attachment" "ec2_instance_role_ssm" { + role = aws_iam_role.ec2_instance_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } -resource "aws_iam_role_policy_attachment" "p4_auth_task_execution_role_secrets_manager" { - role = aws_iam_role.task_execution_role.name +# Attach EBS Attachment Policy (for attaching persistent data volume) +resource "aws_iam_role_policy_attachment" "ec2_instance_role_ebs" { + role = aws_iam_role.ec2_instance_role.name + policy_arn = aws_iam_policy.ebs_attachment_policy.arn +} + +# Attach Secrets Manager Policy (for retrieving P4 credentials) +resource "aws_iam_role_policy_attachment" "ec2_instance_role_secrets_manager" { + role = aws_iam_role.ec2_instance_role.name policy_arn = aws_iam_policy.secrets_manager_policy.arn } + +# Instance Profile +resource "aws_iam_instance_profile" "ec2_instance_profile" { + name = "${local.name_prefix}-ec2-instance-profile" + role = aws_iam_role.ec2_instance_role.name + + tags = merge(var.tags, + { + Name = "${local.name_prefix}-ec2-instance-profile" + } + ) +} diff --git a/modules/perforce/modules/p4-code-review/locals.tf b/modules/perforce/modules/p4-code-review/locals.tf index 52e24253..9cdd8547 100644 --- a/modules/perforce/modules/p4-code-review/locals.tf +++ b/modules/perforce/modules/p4-code-review/locals.tf @@ -1,11 +1,21 @@ locals { - image = "perforce/helix-swarm" # cannot change this until the Perforce Helix Swarm Image is updated to use the new naming for P4 Code Review - name_prefix = "${var.project_prefix}-${var.name}" - data_volume_name = "helix-swarm-data" # cannot change this until the Perforce Helix Swarm Image is updated to use the new naming for P4 Code Review - data_path = "/opt/perforce/swarm/data" # cannot change this until the Perforce Helix Swarm Image is updated to use the new naming for P4 Code Review + # AMI selection: use provided ami_id or auto-detect latest Packer-built AMI + selected_ami_id = var.ami_id != null ? var.ami_id : data.aws_ami.p4_code_review[0].id + # Module identifier for resource tagging + module_identifier = "${var.project_prefix}-${var.name}" + name_prefix = "${var.project_prefix}-${var.name}" + + # Application configuration + application_port = var.application_port + + # ElastiCache Redis configuration elasticache_redis_port = 6379 elasticache_redis_engine_version = "7.0" elasticache_redis_parameter_group_name = "default.redis7" + # EC2 and EBS configuration + ebs_availability_zone = var.ebs_availability_zone != null ? var.ebs_availability_zone : data.aws_subnet.instance_subnet.availability_zone + host_data_path = "/opt/perforce/swarm/data" + ebs_device_name = "/dev/xvdf" } diff --git a/modules/perforce/modules/p4-code-review/main.tf b/modules/perforce/modules/p4-code-review/main.tf index 1df9b3e9..42c8b109 100644 --- a/modules/perforce/modules/p4-code-review/main.tf +++ b/modules/perforce/modules/p4-code-review/main.tf @@ -1,279 +1,13 @@ ########################################## -# ECS | Cluster +# CloudWatch | Application Logging ########################################## -# If cluster name is not provided create a new cluster -resource "aws_ecs_cluster" "cluster" { - count = var.cluster_name != null ? 0 : 1 - name = "${local.name_prefix}-cluster" - - setting { - name = "containerInsights" - value = "enabled" - } - - tags = merge(var.tags, - { - Name = "${local.name_prefix}-cluster" - } - ) -} - - -########################################## -# ECS Cluster | Capacity Providers -########################################## -# If cluster name is not provided create a new cluster capacity providers -resource "aws_ecs_cluster_capacity_providers" "cluster_fargate_providers" { - count = var.cluster_name != null ? 0 : 1 - cluster_name = aws_ecs_cluster.cluster[0].name - - capacity_providers = ["FARGATE"] - - default_capacity_provider_strategy { - base = 1 - weight = 100 - capacity_provider = "FARGATE" - } -} - - -########################################## -# ECS | Task Definition -########################################## -resource "aws_ecs_task_definition" "task_definition" { - family = "${local.name_prefix}-task-definition" - requires_compatibilities = ["FARGATE"] - network_mode = "awsvpc" - cpu = var.container_cpu - memory = var.container_memory - - #checkov:skip=CKV_AWS_97: Task definition secrets are managed via AWS Secrets Manager - - volume { - name = local.data_volume_name - } - - container_definitions = jsonencode( - [ - { - name = var.container_name, - image = local.image, - cpu = var.container_cpu, - memory = var.container_memory, - essential = true, - portMappings = [ - { - containerPort = var.container_port, - hostPort = var.container_port - protocol = "tcp" - } - ] - healthCheck = { - # command = ["CMD-SHELL", "pwd || exit 1"] - command = ["CMD-SHELL", "curl -f http://localhost:${var.container_port}/login || exit 1"] - startPeriod = 30 - } - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = aws_cloudwatch_log_group.log_group.name - awslogs-region = data.aws_region.current.name - awslogs-stream-prefix = "${local.name_prefix}-service" - } - } - secrets = [ - { - name = "P4D_SUPER", - valueFrom = var.super_user_username_secret_arn - }, - { - name = "P4D_SUPER_PASSWD", - valueFrom = var.super_user_password_secret_arn - }, - { - name = "SWARM_USER" # cannot change this until the Perforce Helix Swarm Image is updated to use the new naming for P4 Code Review - valueFrom = var.p4_code_review_user_username_secret_arn - }, - { - name = "SWARM_PASSWD" # cannot change this until the Perforce Helix Swarm Image is updated to use the new naming for P4 Code Review - valueFrom = var.p4_code_review_user_password_secret_arn - } - ] - environment = [ - { - name = "P4CHARSET" - value = var.p4charset - }, - { - name = "P4D_PORT", - value = var.p4d_port - }, - { - name = "SWARM_HOST" - value = var.fully_qualified_domain_name - }, - { - name = "SWARM_REDIS" # cannot update naming until the Perforce container image is updated - value = var.existing_redis_connection != null ? var.existing_redis_connection.host : aws_elasticache_cluster.cluster[0].cache_nodes[0].address - }, - { - name = "SWARM_REDIS_PORT" # cannot update naming until the Perforce container image is updated - value = var.existing_redis_connection != null ? tostring(var.existing_redis_connection.port) : tostring(aws_elasticache_cluster.cluster[0].cache_nodes[0].port) - }, - { - name = "SWARM_FORCE_EXT" - value = "y" - } - ], - readonlyRootFilesystem = false - #checkov:skip=CKV_AWS_81: Read-only root filesystem disabled for application requirements - mountPoints = [ - { - sourceVolume = local.data_volume_name - containerPath = local.data_path - readOnly = false - } - ] - }, - { - name = "${var.container_name}-config" - image = "public.ecr.aws/debian/debian:13-slim" - essential = false - // Only run this command if enable_sso is set - command = [ - "bash", - "-ce", - <<-EOF - cd ${local.data_path} - - # Prepare the config files - mv config.php config.gen.php - echo $CONFIG_PHP | base64 --decode | tee config.php - - %{if var.config_php_source != null} - echo $CONFIG_USER_PHP | base64 --decode | tee config.user.php - %{endif} - - # Clear the cache to force a re-configure - rm -rf cache - EOF - ] - - secrets = [ - { - name = "CONFIG_USER_PHP" - valueFrom = var.config_php_source - }, - ] - environment = [ - { - name = "CONFIG_PHP" - value = base64encode(templatefile("${path.module}/assets/config.php.tftpl", { - enable_sso = var.enable_sso, - })) - } - ] - readonly_root_filesystem = false - #checkov:skip=CKV_AWS_81: Read-only root filesystem disabled for configuration container requirements - - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = aws_cloudwatch_log_group.log_group.name - awslogs-region = data.aws_region.current.name - awslogs-stream-prefix = "${local.name_prefix}-service-config" - } - } - mountPoints = [ - { - sourceVolume = local.data_volume_name - containerPath = local.data_path - } - ] - dependsOn = [ - { - containerName = var.container_name - condition = "HEALTHY" - } - ] - } - ] - ) - - task_role_arn = var.custom_role != null ? var.custom_role : aws_iam_role.default_role[0].arn - execution_role_arn = aws_iam_role.task_execution_role.arn - - runtime_platform { - operating_system_family = "LINUX" - cpu_architecture = "X86_64" - } - - tags = merge(var.tags, - { - Name = "${local.name_prefix}-task-definition" - } - ) -} - - -########################################## -# ECS | Service -########################################## -resource "aws_ecs_service" "service" { - name = "${local.name_prefix}-service" - - cluster = var.cluster_name != null ? data.aws_ecs_cluster.cluster[0].arn : aws_ecs_cluster.cluster[0].arn - task_definition = aws_ecs_task_definition.task_definition.arn - launch_type = "FARGATE" - desired_count = "1" # P4 Code Review does not support horizontal scaling, so desired container count is fixed at 1 - # Allow ECS to delete a service even if deregistration is taking time. This is to prevent the ALB listener in the parent module from failing to be deleted in the event that all registered targets (ECS services) haven't been destroyed yet. - force_new_deployment = var.debug - enable_execute_command = var.debug - - # wait_for_steady_state = true - - load_balancer { - target_group_arn = aws_lb_target_group.alb_target_group.arn - container_name = var.container_name - container_port = var.container_port - } - - network_configuration { - subnets = var.subnets - security_groups = [aws_security_group.ecs_service.id] - } - - tags = merge(var.tags, - { - Name = "${local.name_prefix}-service" - } - ) - - # lifecycle { - # create_before_destroy = true - # ignore_changes = [desired_count] # Let Application Auto Scaling manage this - # } - - timeouts { - create = "20m" - } - - - - depends_on = [aws_elasticache_cluster.cluster, aws_lb_target_group.alb_target_group] -} - - -########################################## -# CloudWatch | Redis Logging -########################################## -resource "aws_cloudwatch_log_group" "log_group" { +resource "aws_cloudwatch_log_group" "application_log_group" { #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default - name = "${local.name_prefix}-log-group" + name = "${local.name_prefix}-application-log-group" retention_in_days = var.cloudwatch_log_retention_in_days tags = merge(var.tags, { - Name = "${local.name_prefix}-log-group" + Name = "${local.name_prefix}-application-log-group" } ) } diff --git a/modules/perforce/modules/p4-code-review/outputs.tf b/modules/perforce/modules/p4-code-review/outputs.tf index 54a621ab..98280fe1 100644 --- a/modules/perforce/modules/p4-code-review/outputs.tf +++ b/modules/perforce/modules/p4-code-review/outputs.tf @@ -1,6 +1,6 @@ -output "service_security_group_id" { - value = aws_security_group.ecs_service.id - description = "Security group associated with the ECS service running P4 Code Review" +output "application_security_group_id" { + value = aws_security_group.application.id + description = "Security group associated with the P4 Code Review application" } output "alb_security_group_id" { @@ -8,11 +8,6 @@ output "alb_security_group_id" { description = "Security group associated with the P4 Code Review load balancer" } -output "cluster_name" { - value = var.cluster_name != null ? var.cluster_name : aws_ecs_cluster.cluster[0].name - description = "Name of the ECS cluster hosting P4 Code Review" -} - output "alb_dns_name" { value = var.create_application_load_balancer ? aws_lb.alb[0].dns_name : null description = "The DNS name of the P4 Code Review ALB" @@ -25,15 +20,25 @@ output "alb_zone_id" { output "target_group_arn" { value = aws_lb_target_group.alb_target_group.arn - description = "The service target group for P4 Code Review" + description = "The target group ARN for P4 Code Review" +} + +output "instance_profile_arn" { + value = aws_iam_instance_profile.ec2_instance_profile.arn + description = "The ARN of the IAM instance profile for P4 Code Review EC2 instances" +} + +output "launch_template_id" { + value = aws_launch_template.swarm_instance.id + description = "The ID of the launch template for P4 Code Review instances" } -output "default_role_id" { - value = var.create_default_role ? aws_iam_role.default_role[0].id : null - description = "The default role for the service task" +output "autoscaling_group_name" { + value = aws_autoscaling_group.swarm_asg.name + description = "The name of the Auto Scaling Group for P4 Code Review" } -output "execution_role_id" { - value = aws_iam_role.task_execution_role.id - description = "The default role for the service task" +output "ebs_volume_id" { + value = aws_ebs_volume.swarm_data.id + description = "The ID of the EBS volume storing P4 Code Review persistent data" } diff --git a/modules/perforce/modules/p4-code-review/sg.tf b/modules/perforce/modules/p4-code-review/sg.tf index 9f637d7b..bf97aaf7 100644 --- a/modules/perforce/modules/p4-code-review/sg.tf +++ b/modules/perforce/modules/p4-code-review/sg.tf @@ -15,55 +15,67 @@ resource "aws_security_group" "alb" { ) } -# Outbound access from ALB to Containers -resource "aws_vpc_security_group_egress_rule" "alb_outbound_to_ecs_service" { +# Inbound HTTPS access to ALB from Application +# Required for Swarm instance to validate itself via external URL when P4 server extension connects back +resource "aws_vpc_security_group_ingress_rule" "alb_inbound_from_application" { count = var.create_application_load_balancer ? 1 : 0 security_group_id = aws_security_group.alb[0].id - description = "Allow outbound traffic from ALB to ${local.name_prefix} ECS service" - referenced_security_group_id = aws_security_group.ecs_service.id - from_port = var.container_port - to_port = var.container_port + description = "Allow HTTPS from ${local.name_prefix} application for self-validation" + referenced_security_group_id = aws_security_group.application.id + from_port = 443 + to_port = 443 + ip_protocol = "tcp" +} + +# Outbound access from ALB to Application +resource "aws_vpc_security_group_egress_rule" "alb_outbound_to_application" { + count = var.create_application_load_balancer ? 1 : 0 + security_group_id = aws_security_group.alb[0].id + description = "Allow outbound traffic from ALB to ${local.name_prefix} application" + referenced_security_group_id = aws_security_group.application.id + from_port = local.application_port + to_port = local.application_port ip_protocol = "tcp" } ######################################## -# ECS Service Security Group +# Application Security Group ######################################## -# Service Security Group (attached to containers) -resource "aws_security_group" "ecs_service" { - name = "${local.name_prefix}-service" +# Application Security Group (attached to EC2 instances) +resource "aws_security_group" "application" { + name = "${local.name_prefix}-application" vpc_id = var.vpc_id - description = "${local.name_prefix} service Security Group" + description = "${local.name_prefix} application Security Group" tags = merge(var.tags, { - Name = "${local.name_prefix}-service" + Name = "${local.name_prefix}-application" } ) } -# Inbound access to Containers from ALB -resource "aws_vpc_security_group_ingress_rule" "ecs_service_inbound_alb" { +# Inbound access to Application from ALB +resource "aws_vpc_security_group_ingress_rule" "application_inbound_alb" { count = var.create_application_load_balancer ? 1 : 0 - security_group_id = aws_security_group.ecs_service.id - description = "Allow inbound traffic from ${local.name_prefix} ALB to ${local.name_prefix} service" + security_group_id = aws_security_group.application.id + description = "Allow inbound traffic from ${local.name_prefix} ALB to ${local.name_prefix} application" referenced_security_group_id = aws_security_group.alb[0].id - from_port = var.container_port - to_port = var.container_port + from_port = local.application_port + to_port = local.application_port ip_protocol = "tcp" } -# Outbound access from Containers to Internet (IPV4) -resource "aws_vpc_security_group_egress_rule" "ecs_service_outbound_to_internet_ipv4" { - security_group_id = aws_security_group.ecs_service.id - description = "Allow outbound traffic from ${local.name_prefix} service to internet (ipv4)" +# Outbound access from Application to Internet (IPV4) +resource "aws_vpc_security_group_egress_rule" "application_outbound_to_internet_ipv4" { + security_group_id = aws_security_group.application.id + description = "Allow outbound traffic from ${local.name_prefix} application to internet (ipv4)" cidr_ipv4 = "0.0.0.0/0" ip_protocol = "-1" # semantically equivalent to all ports } -# Outbound access from Containers to Internet (IPV6) -resource "aws_vpc_security_group_egress_rule" "ecs_service_outbound_to_internet_ipv6" { - security_group_id = aws_security_group.ecs_service.id - description = "Allow outbound traffic from ${local.name_prefix} service to internet (ipv6)" +# Outbound access from Application to Internet (IPV6) +resource "aws_vpc_security_group_egress_rule" "application_outbound_to_internet_ipv6" { + security_group_id = aws_security_group.application.id + description = "Allow outbound traffic from ${local.name_prefix} application to internet (ipv6)" cidr_ipv6 = "::/0" ip_protocol = "-1" # semantically equivalent to all ports } @@ -80,12 +92,44 @@ resource "aws_security_group" "elasticache" { description = "${local.name_prefix} Elasticache Redis Security Group" tags = var.tags } -resource "aws_vpc_security_group_ingress_rule" "elasticache_inbound_from_ecs_service" { +resource "aws_vpc_security_group_ingress_rule" "elasticache_inbound_from_application" { count = var.existing_redis_connection != null ? 0 : 1 security_group_id = aws_security_group.elasticache[0].id description = "Allow inbound traffic from P4 Code Review to Redis" - referenced_security_group_id = aws_security_group.ecs_service.id + referenced_security_group_id = aws_security_group.application.id from_port = local.elasticache_redis_port to_port = local.elasticache_redis_port ip_protocol = "tcp" } + + +######################################## +# EC2 Instance Security Group +######################################## +resource "aws_security_group" "ec2_instance" { + #checkov:skip=CKV2_AWS_5:Security group is attached to EC2 instances in Auto Scaling Group + name = "${local.name_prefix}-ec2-instance" + vpc_id = var.vpc_id + description = "${local.name_prefix} EC2 Instance Security Group" + tags = merge(var.tags, + { + Name = "${local.name_prefix}-ec2-instance" + } + ) +} + +# Outbound access to Internet (IPV4) - Required for AWS API calls and package downloads +resource "aws_vpc_security_group_egress_rule" "ec2_instance_outbound_to_internet_ipv4" { + security_group_id = aws_security_group.ec2_instance.id + description = "Allow outbound traffic from ${local.name_prefix} EC2 instance to internet (ipv4)" + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" +} + +# Outbound access to Internet (IPV6) - Required for AWS API calls and package downloads +resource "aws_vpc_security_group_egress_rule" "ec2_instance_outbound_to_internet_ipv6" { + security_group_id = aws_security_group.ec2_instance.id + description = "Allow outbound traffic from ${local.name_prefix} EC2 instance to internet (ipv6)" + cidr_ipv6 = "::/0" + ip_protocol = "-1" +} diff --git a/modules/perforce/modules/p4-code-review/user-data.sh.tpl b/modules/perforce/modules/p4-code-review/user-data.sh.tpl new file mode 100644 index 00000000..964d74f5 --- /dev/null +++ b/modules/perforce/modules/p4-code-review/user-data.sh.tpl @@ -0,0 +1,255 @@ +#!/bin/bash +# User data script for P4 Code Review native EC2 instance +# Handles EBS volume attachment/mounting and Swarm configuration + +set -e +set -o pipefail + +# Configuration variables (injected by Terraform) +REGION="${region}" +DEVICE_NAME="${device_name}" +MOUNT_PATH="${mount_path}" +VOLUME_TAG_KEY="SwarmDataVolume" +VOLUME_TAG_VALUE="true" +MODULE_TAG_VALUE="${module_identifier}" + +# P4 Code Review configuration parameters +P4D_PORT="${p4d_port}" +P4CHARSET="${p4charset}" +SWARM_HOST="${swarm_host}" +SWARM_REDIS="${swarm_redis}" +SWARM_REDIS_PORT="${swarm_redis_port}" +SWARM_FORCE_EXT="${swarm_force_ext}" + +# Secret ARNs for AWS Secrets Manager +P4D_SUPER_SECRET_ARN="${super_user_username_secret_arn}" +P4D_SUPER_PASSWD_SECRET_ARN="${super_user_password_secret_arn}" +SWARM_USER_SECRET_ARN="${p4_code_review_user_username_secret_arn}" +SWARM_PASSWD_SECRET_ARN="${p4_code_review_user_password_secret_arn}" + +# Logging function +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/swarm-startup.log +} + +log "=========================================" +log "Starting P4 Code Review native EC2 setup" +log "=========================================" + +# 1. Get instance metadata +log "Fetching instance metadata..." +IMDS_TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null) +INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id) +INSTANCE_AZ=$(curl -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone) + +log "Instance ID: $INSTANCE_ID" +log "Instance AZ: $INSTANCE_AZ" + +# 2. Find the EBS volume by tags +log "Searching for EBS volume with tags: $VOLUME_TAG_KEY=$VOLUME_TAG_VALUE, ModuleIdentifier=$MODULE_TAG_VALUE" + +VOLUME_ID=$(aws ec2 describe-volumes \ + --region "$REGION" \ + --filters \ + "Name=tag:$VOLUME_TAG_KEY,Values=$VOLUME_TAG_VALUE" \ + "Name=tag:ModuleIdentifier,Values=$MODULE_TAG_VALUE" \ + "Name=availability-zone,Values=$INSTANCE_AZ" \ + --query 'Volumes[0].VolumeId' \ + --output text) + +if [ "$VOLUME_ID" == "None" ] || [ -z "$VOLUME_ID" ]; then + log "ERROR: Could not find EBS volume with required tags in AZ $INSTANCE_AZ" + exit 1 +fi + +log "Found EBS volume: $VOLUME_ID" + +# 3. Check current volume attachment status +VOLUME_INFO=$(aws ec2 describe-volumes \ + --region "$REGION" \ + --volume-ids "$VOLUME_ID" \ + --query 'Volumes[0].{State:State,AttachedInstance:Attachments[0].InstanceId,AttachState:Attachments[0].State}' \ + --output json) + +VOLUME_STATE=$(echo "$VOLUME_INFO" | jq -r '.State') +ATTACHED_INSTANCE=$(echo "$VOLUME_INFO" | jq -r '.AttachedInstance // "none"') +ATTACH_STATE=$(echo "$VOLUME_INFO" | jq -r '.AttachState // "none"') + +log "Volume state: $VOLUME_STATE, Attached to: $ATTACHED_INSTANCE, Attach state: $ATTACH_STATE" + +if [ "$ATTACHED_INSTANCE" == "$INSTANCE_ID" ]; then + log "Volume $VOLUME_ID is already attached to this instance" +elif [ "$ATTACHED_INSTANCE" != "none" ] && [ "$ATTACHED_INSTANCE" != "null" ]; then + log "Volume is attached to different instance $ATTACHED_INSTANCE - checking instance state" + + # Check if the attached instance is terminated before force detaching + INSTANCE_STATE=$(aws ec2 describe-instances \ + --region "$REGION" \ + --instance-ids "$ATTACHED_INSTANCE" \ + --query 'Reservations[0].Instances[0].State.Name' \ + --output text 2>/dev/null || echo "unknown") + + log "Previous instance $ATTACHED_INSTANCE state: $INSTANCE_STATE" + + if [ "$INSTANCE_STATE" = "terminated" ] || [ "$INSTANCE_STATE" = "unknown" ]; then + log "Previous instance is terminated/unknown, safe to force detach" + aws ec2 detach-volume \ + --region "$REGION" \ + --volume-id "$VOLUME_ID" \ + --force 2>&1 | tee -a /tmp/swarm-setup.log || log "Warning: Force detach may have failed" + else + log "ERROR: Volume attached to running instance $ATTACHED_INSTANCE (state: $INSTANCE_STATE)" + log "Cannot safely detach volume - manual intervention required" + exit 1 + fi + + # Wait for detachment with timeout + log "Waiting up to 2 minutes for volume to become available..." + for i in {1..24}; do + CURRENT_STATE=$(aws ec2 describe-volumes --region "$REGION" --volume-ids "$VOLUME_ID" --query 'Volumes[0].State' --output text) + if [ "$CURRENT_STATE" == "available" ]; then + log "Volume is now available" + break + fi + log "Volume state: $CURRENT_STATE (attempt $i/24)" + sleep 5 + done + + log "Attaching volume $VOLUME_ID to instance $INSTANCE_ID at $DEVICE_NAME" + aws ec2 attach-volume \ + --region "$REGION" \ + --volume-id "$VOLUME_ID" \ + --instance-id "$INSTANCE_ID" \ + --device "$DEVICE_NAME" + + log "Waiting for volume attachment..." + aws ec2 wait volume-in-use \ + --region "$REGION" \ + --volume-ids "$VOLUME_ID" + + log "Volume attached successfully" +else + log "Attaching volume $VOLUME_ID to instance $INSTANCE_ID at $DEVICE_NAME" + + aws ec2 attach-volume \ + --region "$REGION" \ + --volume-id "$VOLUME_ID" \ + --instance-id "$INSTANCE_ID" \ + --device "$DEVICE_NAME" + + log "Waiting for volume attachment..." + aws ec2 wait volume-in-use \ + --region "$REGION" \ + --volume-ids "$VOLUME_ID" + + log "Volume attached successfully" +fi + +# 4. Find the actual device name (NVMe instances use different naming) +log "Looking for attached device..." +ACTUAL_DEVICE="" +for i in {1..30}; do + # Try the original device name first + if [ -e "$DEVICE_NAME" ]; then + ACTUAL_DEVICE="$DEVICE_NAME" + log "Found device at $ACTUAL_DEVICE" + break + fi + + # Look for NVMe device by volume ID symlink + NVME_LINK="/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_$${VOLUME_ID//-/}" + if [ -L "$NVME_LINK" ]; then + ACTUAL_DEVICE=$(readlink -f "$NVME_LINK") + log "Found NVMe device via symlink: $ACTUAL_DEVICE" + break + fi + + log "Attempt $i/30: Device not yet available, waiting..." + sleep 2 +done + +if [ -z "$ACTUAL_DEVICE" ]; then + log "ERROR: Could not find attached device after 60 seconds" + log "Expected: $DEVICE_NAME or /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_$${VOLUME_ID//-/}" + exit 1 +fi + +DEVICE_NAME="$ACTUAL_DEVICE" +log "Using device: $DEVICE_NAME" + +# 5. Check if the device has a filesystem, if not create one +log "Checking filesystem on $DEVICE_NAME..." +if ! blkid "$DEVICE_NAME" > /dev/null 2>&1; then + log "No filesystem detected on $DEVICE_NAME, creating ext4 filesystem..." + mkfs -t ext4 "$DEVICE_NAME" + log "Filesystem created successfully" +else + log "Existing filesystem detected on $DEVICE_NAME" +fi + +# 6. Create mount point if it doesn't exist +if [ ! -d "$MOUNT_PATH" ]; then + log "Creating mount point: $MOUNT_PATH" + mkdir -p "$MOUNT_PATH" +fi + +# 7. Mount the volume +log "Mounting $DEVICE_NAME to $MOUNT_PATH..." +mount "$DEVICE_NAME" "$MOUNT_PATH" +log "Volume mounted successfully" + +# 8. Set proper permissions for Swarm +log "Setting permissions on $MOUNT_PATH..." +chmod 755 "$MOUNT_PATH" +chown -R swarm:swarm "$MOUNT_PATH" + +# 9. Add entry to /etc/fstab for automatic mounting on reboot +if ! grep -q "$DEVICE_NAME" /etc/fstab; then + log "Adding entry to /etc/fstab for persistent mounting..." + echo "$DEVICE_NAME $MOUNT_PATH ext4 defaults,nofail 0 2" >> /etc/fstab + log "fstab entry added" +else + log "fstab entry already exists" +fi + +# 10. Verify mount +if mountpoint -q "$MOUNT_PATH"; then + log "SUCCESS: $MOUNT_PATH is mounted" + df -h "$MOUNT_PATH" +else + log "ERROR: $MOUNT_PATH is not mounted" + exit 1 +fi + +# 11. Configure Swarm using the script from the AMI +log "Configuring P4 Code Review with runtime parameters..." + +# Write custom config JSON to file if provided (for swarm_instance_init.sh to merge) +CUSTOM_CONFIG_FILE="/tmp/swarm_custom_config.json" +%{ if custom_config != null && custom_config != "" ~} +cat > "$CUSTOM_CONFIG_FILE" << 'CUSTOM_CONFIG_EOF' +${custom_config} +CUSTOM_CONFIG_EOF +log "Custom config written to $CUSTOM_CONFIG_FILE" +%{ else ~} +log "No custom config provided" +%{ endif ~} + +/home/ubuntu/swarm_scripts/swarm_instance_init.sh \ + --p4d-port "$P4D_PORT" \ + --p4charset "$P4CHARSET" \ + --swarm-host "$SWARM_HOST" \ + --swarm-redis "$SWARM_REDIS" \ + --swarm-redis-port "$SWARM_REDIS_PORT" \ + --swarm-force-ext "$SWARM_FORCE_EXT" \ + --p4d-super-secret-arn "$P4D_SUPER_SECRET_ARN" \ + --p4d-super-passwd-secret-arn "$P4D_SUPER_PASSWD_SECRET_ARN" \ + --swarm-user-secret-arn "$SWARM_USER_SECRET_ARN" \ + --swarm-passwd-secret-arn "$SWARM_PASSWD_SECRET_ARN" \ + --custom-config-file "$CUSTOM_CONFIG_FILE" + +log "=========================================" +log "P4 Code Review native EC2 setup completed successfully" +log "P4 Code Review should be accessible at: https://$SWARM_HOST" +log "Data path: $MOUNT_PATH" +log "=========================================" diff --git a/modules/perforce/modules/p4-code-review/variables.tf b/modules/perforce/modules/p4-code-review/variables.tf index 4c60f26a..08dc18da 100644 --- a/modules/perforce/modules/p4-code-review/variables.tf +++ b/modules/perforce/modules/p4-code-review/variables.tf @@ -25,58 +25,25 @@ variable "fully_qualified_domain_name" { default = null } -variable "debug" { - type = bool - default = false - description = "Debug flag to enable execute command on service for container access." -} - - ######################################## # Compute ######################################## -variable "cluster_name" { - type = string - description = "The name of the cluster to deploy the P4 Code Review service into. Defaults to null and a cluster will be created." - default = null -} - -variable "container_name" { - type = string - description = "The name of the P4 Code Review container." - default = "p4-code-review-container" - nullable = false -} - -variable "container_port" { +variable "application_port" { type = number - description = "The container port that P4 Code Review runs on." + description = "The port that P4 Code Review listens on. Used for ALB target group configuration." default = 80 nullable = false } -variable "container_cpu" { - type = number - description = "The CPU allotment for the P4 Code Review container." - default = 1024 - nullable = false -} - -variable "container_memory" { - type = number - description = "The memory allotment for the P4 Code Review container." - default = 2048 -} - variable "p4d_port" { type = string - description = "The P4D_PORT environment variable where P4 Code Review should look for P4 Code Review. Defaults to 'ssl:perforce:1666'" + description = "The P4D_PORT environment variable where P4 Code Review should look for P4 Server. Defaults to 'ssl:perforce:1666'" default = "ssl:perforce:1666" } variable "p4charset" { type = string - description = "The P4CHARSET environment variable to set in the P4 Code Review container." + description = "The P4CHARSET environment variable to set for the P4 Code Review instance." default = "none" } @@ -143,7 +110,7 @@ variable "alb_subnets" { variable "subnets" { type = list(string) - description = "A list of subnets to deploy the P4 Code Review ECS Service into. Private subnets are recommended." + description = "A list of subnets for ElastiCache Redis deployment. Private subnets are recommended." } variable "create_application_load_balancer" { @@ -196,18 +163,6 @@ variable "certificate_arn" { } } -variable "create_default_role" { - type = bool - description = "Optional creation of P4 Code Review Default IAM Role. Default is set to true." - default = true -} - -variable "custom_role" { - type = string - description = "ARN of the custom IAM Role you wish to use with P4 Code Review." - default = null -} - variable "super_user_username_secret_arn" { type = string description = "Optionally provide the ARN of an AWS Secret for the p4d super user username." @@ -228,18 +183,12 @@ variable "p4_code_review_user_password_secret_arn" { description = "Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review password." } -variable "config_php_source" { +variable "custom_config" { type = string - description = "Used as the ValueFrom for P4CR's config.php. Contents should be base64 encoded, and will be combined with the generated config.php via array_replace_recursive." + description = "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc. See README for examples." default = null } -variable "enable_sso" { - type = bool - default = false - description = "Set this to true if using SSO for P4 Code Review authentication." -} - ###################### # Caching ###################### @@ -260,6 +209,50 @@ variable "elasticache_node_type" { default = "cache.t4g.micro" } +######################################## +# EC2 Instance Configuration +######################################## +variable "ami_id" { + type = string + description = "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI with name pattern 'p4_code_review_ubuntu-*'." + default = null +} + +variable "instance_type" { + type = string + description = "EC2 instance type for running P4 Code Review. Swarm requires persistent storage and runs natively on EC2." + default = "m5.large" +} + +variable "instance_subnet_id" { + type = string + description = "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security." +} + +variable "ebs_volume_size" { + type = number + description = "Size in GB for the EBS volume that stores P4 Code Review data (/opt/perforce/swarm/data). This volume persists across instance replacement." + default = 20 +} + +variable "ebs_volume_type" { + type = string + description = "EBS volume type for P4 Code Review data storage." + default = "gp3" +} + +variable "ebs_volume_encrypted" { + type = bool + description = "Enable encryption for the EBS volume storing P4 Code Review data." + default = true +} + +variable "ebs_availability_zone" { + type = string + description = "Availability zone for the EBS volume. Must match the EC2 instance AZ. If not provided, will use the AZ of the instance_subnet_id." + default = null +} + variable "tags" { type = map(any) diff --git a/modules/perforce/outputs.tf b/modules/perforce/outputs.tf index 40df0279..0987d5aa 100644 --- a/modules/perforce/outputs.tf +++ b/modules/perforce/outputs.tf @@ -78,8 +78,8 @@ output "p4_auth_target_group_arn" { # P4 Code Review output "p4_code_review_service_security_group_id" { - value = var.p4_code_review_config != null ? module.p4_code_review[0].service_security_group_id : null - description = "Security group associated with the ECS service running P4 Code Review." + value = var.p4_code_review_config != null ? module.p4_code_review[0].application_security_group_id : null + description = "Security group associated with P4 Code Review application." } output "p4_code_review_alb_security_group_id" { @@ -87,11 +87,6 @@ output "p4_code_review_alb_security_group_id" { description = "Security group associated with the P4 Code Review load balancer." } -output "p4_code_review_perforce_cluster_name" { - value = var.p4_code_review_config != null ? module.p4_code_review[0].cluster_name : null - description = "Name of the ECS cluster hosting P4 Code Review." -} - output "p4_code_review_alb_dns_name" { value = var.p4_code_review_config != null ? module.p4_code_review[0].alb_dns_name : null description = "The DNS name of the P4 Code Review ALB." @@ -107,15 +102,6 @@ output "p4_code_review_target_group_arn" { description = "The service target group for the P4 Code Review." } -output "p4_code_review_default_role_id" { - value = var.p4_code_review_config != null ? module.p4_code_review[0].default_role_id : null - description = "The default role for the P4 Code Review service task" -} - -output "p4_code_review_execution_role_id" { - value = var.p4_code_review_config != null ? module.p4_code_review[0].execution_role_id : null - description = "The default role for the P4 Code Review service task" -} output "p4_server_lambda_link_name" { value = (var.p4_server_config.storage_type == "FSxN" && var.p4_server_config.protocol == "ISCSI" ? diff --git a/modules/perforce/sg.tf b/modules/perforce/sg.tf index edf76b9f..1a5c5455 100644 --- a/modules/perforce/sg.tf +++ b/modules/perforce/sg.tf @@ -85,12 +85,12 @@ resource "aws_vpc_security_group_ingress_rule" "perforce_web_services_inbound_fr ) } -# Perforce Web Services ALB <-- P4 Server -# Allows Perforce Web Services ALB to receive inbound traffic from P4 Server (needed for authentication using P4Auth extension) +# Perforce Web Services ALB <-- P4 Server (HTTPS) +# Allows Perforce Web Services ALB to receive inbound traffic from P4 Server (needed for P4Auth extension and Swarm triggers) resource "aws_vpc_security_group_ingress_rule" "perforce_web_services_inbound_from_p4_server" { count = (var.create_shared_application_load_balancer && var.create_default_sgs && var.p4_server_config != null ? 1 : 0) security_group_id = aws_security_group.perforce_web_services_alb[0].id - description = "Allows Perforce Web Services ALB to receive inbound traffic from P4 Server. This is used for authentication using the P4Auth extension." + description = "Allows Perforce Web Services ALB to receive inbound traffic from P4 Server. This is used for P4Auth extension authentication and Swarm trigger validation." ip_protocol = "TCP" from_port = 443 to_port = 443 @@ -128,7 +128,7 @@ resource "aws_vpc_security_group_egress_rule" "perforce_alb_outbound_to_p4_code_ from_port = 80 to_port = 80 ip_protocol = "TCP" - referenced_security_group_id = module.p4_code_review[0].service_security_group_id + referenced_security_group_id = module.p4_code_review[0].application_security_group_id } ####################################################################################### @@ -143,7 +143,19 @@ resource "aws_vpc_security_group_ingress_rule" "p4_server_inbound_from_p4_code_r ip_protocol = "TCP" from_port = 1666 to_port = 1666 - referenced_security_group_id = module.p4_code_review[0].service_security_group_id + referenced_security_group_id = module.p4_code_review[0].application_security_group_id +} + +# P4 Server --> Perforce Web Services ALB (HTTPS) +# Allows P4 Server to send HTTPS traffic to Perforce Web Services ALB for Swarm trigger validation +resource "aws_vpc_security_group_egress_rule" "p4_server_outbound_to_perforce_web_services_alb_https" { + count = var.p4_code_review_config != null && var.p4_server_config != null && var.create_default_sgs && var.create_shared_application_load_balancer ? 1 : 0 + security_group_id = module.p4_server[0].security_group_id + description = "Allows P4 Server to send HTTPS traffic to Perforce Web Services ALB for Swarm trigger validation." + from_port = 443 + to_port = 443 + ip_protocol = "TCP" + referenced_security_group_id = aws_security_group.perforce_web_services_alb[0].id } @@ -170,7 +182,7 @@ resource "aws_vpc_security_group_ingress_rule" "p4_auth_inbound_from_perforce_we # Allows P4 Code Review to receive inbound traffic from Perforce Web Services ALB resource "aws_vpc_security_group_ingress_rule" "p4_code_review_inbound_from_perforce_web_services_alb" { count = var.p4_code_review_config != null && var.create_default_sgs && var.create_shared_application_load_balancer ? 1 : 0 - security_group_id = module.p4_code_review[0].service_security_group_id + security_group_id = module.p4_code_review[0].application_security_group_id description = "Allows P4 Code Review to receive inbound traffic from Perforce Web Services ALB." ip_protocol = "TCP" from_port = 80 @@ -183,7 +195,7 @@ resource "aws_vpc_security_group_ingress_rule" "p4_code_review_inbound_from_perf # Allows P4 Code Review to send outbound traffic to P4 Server. resource "aws_vpc_security_group_egress_rule" "p4_code_review_outbound_to_p4_server" { count = var.p4_code_review_config != null && var.p4_server_config != null && var.create_default_sgs ? 1 : 0 - security_group_id = module.p4_code_review[0].service_security_group_id + security_group_id = module.p4_code_review[0].application_security_group_id description = "Allows P4 Code Review to send outbound traffic to P4 Server." from_port = 1666 to_port = 1666 diff --git a/modules/perforce/variables.tf b/modules/perforce/variables.tf index d2e2809f..46245bd8 100644 --- a/modules/perforce/variables.tf +++ b/modules/perforce/variables.tf @@ -440,14 +440,12 @@ variable "p4_code_review_config" { name = optional(string, "p4-code-review") project_prefix = optional(string, "cgd") environment = optional(string, "dev") - debug = optional(bool, false) fully_qualified_domain_name = string # Compute - container_name = optional(string, "p4-code-review-container") - container_port = optional(number, 80) - container_cpu = optional(number, 1024) - container_memory = optional(number, 4096) + application_port = optional(number, 80) + instance_type = optional(string, "m5.large") + ami_id = optional(string, null) p4d_port = optional(string, null) p4charset = optional(string, null) existing_redis_connection = optional(object({ @@ -457,22 +455,23 @@ variable "p4_code_review_config" { # Storage & Logging cloudwatch_log_retention_in_days = optional(number, 365) + ebs_volume_size = optional(number, 20) + ebs_volume_type = optional(string, "gp3") + ebs_volume_encrypted = optional(bool, true) + ebs_availability_zone = optional(string, null) # Networking & Security create_default_sgs = optional(bool, true) existing_security_groups = optional(list(string), []) internal = optional(bool, false) service_subnets = optional(list(string), null) - - create_default_role = optional(bool, true) - custom_role = optional(string, null) + instance_subnet_id = string super_user_password_secret_arn = optional(string, null) super_user_username_secret_arn = optional(string, null) p4_code_review_user_password_secret_arn = optional(string, null) p4_code_review_user_username_secret_arn = optional(string, null) - enable_sso = optional(string, true) - config_php_source = optional(string, null) + custom_config = optional(string, null) # Caching elasticache_node_count = optional(number, 1) @@ -489,23 +488,19 @@ variable "p4_code_review_config" { environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'." - debug : "Whether to enable debug mode for the P4 Code Review service. Default is 'false'." - fully_qualified_domain_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration." # Compute - container_name : "The name of the P4 Code Review service container. Default is 'p4-code-review-container'." + application_port : "The port on which the P4 Code Review service will be listening. Default is '80'." - container_port : "The port on which the P4 Code Review service will be listening. Default is '3000'." + instance_type : "EC2 instance type for running P4 Code Review. Default is 'm5.large'." - container_cpu : "The number of CPU units to reserve for the P4 Code Review service container. Default is '1024'." + ami_id : "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI." - container_memory : "The number of CPU units to reserve for the P4 Code Review service container. Default is '4096'." - - pd4_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'." + p4d_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'." - p4charset : "The P4CHARSET environment variable to set in the P4 Code Review container." + p4charset : "The P4CHARSET environment variable to set for the P4 Code Review instance." existing_redis_connection : "The existing Redis connection for the P4 Code Review service." @@ -513,29 +508,31 @@ variable "p4_code_review_config" { # Storage & Logging cloudwatch_log_retention_in_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days." + ebs_volume_size : "Size in GB for the EBS volume that stores P4 Code Review data. Default is '20'." - # Networking & Security - create_default_sgs : "Whether to create default security groups for the P4 Code Review service." + ebs_volume_type : "EBS volume type for P4 Code Review data storage. Default is 'gp3'." - internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP." + ebs_volume_encrypted : "Enable encryption for the EBS volume storing P4 Code Review data. Default is 'true'." - create_default_role : "Whether to create the P4 Code Review default IAM Role. Default is set to true." + ebs_availability_zone : "Availability zone for the EBS volume. Must match the EC2 instance AZ." - custom_role : "ARN of a custom IAM Role you wish to use with P4 Code Review." - super_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review Administrator username." + # Networking & Security + create_default_sgs : "Whether to create default security groups for the P4 Code Review service." + + internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP." - super_user_username_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review Administrator password." + instance_subnet_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security." - p4d_p4_code_review_user_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username." + super_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password." - p4d_p4_code_review_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password." + super_user_username_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user username." - p4d_p4_code_review_user_password_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password." + p4_code_review_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password." - enable_sso : "Whether to enable SSO for the P4 Code Review service. Default is set to false." + p4_code_review_user_username_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username." - config_php_source : "Used as the ValueFrom for P4CR's config.php. Contents should be base64 encoded, and will be combined with the generated config.php via array_replace_recursive." + custom_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc." # Caching From 956e332bfbcc439d226e8495c3e0796a70348870 Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Wed, 14 Jan 2026 23:04:59 -0500 Subject: [PATCH 3/7] fix(perforce/examples): update example for EC2-based P4 Code Review Update the create-resources-complete example to use the new EC2-based P4 Code Review module configuration, removing deprecated ECS-specific variables. --- .../examples/create-resources-complete/main.tf | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/perforce/examples/create-resources-complete/main.tf b/modules/perforce/examples/create-resources-complete/main.tf index 2b64079e..d9839325 100644 --- a/modules/perforce/examples/create-resources-complete/main.tf +++ b/modules/perforce/examples/create-resources-complete/main.tf @@ -1,6 +1,9 @@ module "terraform-aws-perforce" { source = "../../" + # Ensure module is destroyed before IGW to prevent "mapped public address" errors + depends_on = [aws_internet_gateway.igw] + # - Shared - project_prefix = local.project_prefix vpc_id = aws_vpc.perforce_vpc.id @@ -51,13 +54,15 @@ module "terraform-aws-perforce" { name = "p4-code-review" fully_qualified_domain_name = local.p4_code_review_fully_qualified_domain_name existing_security_groups = [aws_security_group.allow_my_ip.id] - debug = true # optional to use for debugging. Default is false if omitted - deregistration_delay = 0 service_subnets = aws_subnet.private_subnets[*].id - # Allow ECS tasks to be immediately deregistered from target group. Helps to prevent race conditions during `terraform destroy` + instance_subnet_id = aws_subnet.private_subnets[0].id - # Configuration - enable_sso = true + # SSO Configuration - uses HAS for authentication + custom_config = jsonencode({ + p4 = { + sso = "optional" # "optional" allows both SSO and password login, "enabled" forces SSO + } + }) } } From 247779c72a25cb5341c03d3b6076dff1525662a8 Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Wed, 14 Jan 2026 23:06:15 -0500 Subject: [PATCH 4/7] fix(perforce/p4-server): implement dual super user approach for Swarm compatibility Implement a dual super user approach to support Swarm extension installation while allowing custom super user configuration. Key changes: - Always create 'super' user first for Swarm extension compatibility - Create custom super user (if specified) and grant super privileges - Add 'unlimited_timeout' group for service integrations - Both users added to unlimited_timeout group to prevent ticket expiration - Update variables for super user configuration --- .../p4-code-review/swarm_instance_init.sh | 11 +- assets/packer/perforce/p4-server/README.md | 50 ++++++-- .../packer/perforce/p4-server/p4_configure.sh | 107 ++++++++++++++---- modules/perforce/README.md | 9 +- modules/perforce/main.tf | 31 +++-- .../perforce/modules/p4-code-review/README.md | 1 - .../perforce/modules/p4-code-review/ec2.tf | 1 - .../perforce/modules/p4-code-review/iam.tf | 1 - .../modules/p4-code-review/user-data.sh.tpl | 2 - .../modules/p4-code-review/variables.tf | 5 - modules/perforce/modules/p4-server/README.md | 60 ++++++---- modules/perforce/modules/p4-server/iam.tf | 5 +- modules/perforce/modules/p4-server/main.tf | 76 ++++++++----- modules/perforce/modules/p4-server/outputs.tf | 27 +++-- .../p4-server/templates/user_data.tftpl | 5 +- .../perforce/modules/p4-server/variables.tf | 10 +- modules/perforce/outputs.tf | 17 ++- modules/perforce/variables.tf | 11 +- 18 files changed, 278 insertions(+), 151 deletions(-) diff --git a/assets/packer/perforce/p4-code-review/swarm_instance_init.sh b/assets/packer/perforce/p4-code-review/swarm_instance_init.sh index b7a6c08b..f39ab0f8 100644 --- a/assets/packer/perforce/p4-code-review/swarm_instance_init.sh +++ b/assets/packer/perforce/p4-code-review/swarm_instance_init.sh @@ -30,7 +30,6 @@ SWARM_FORCE_EXT="y" CUSTOM_CONFIG_FILE="" # Secret ARNs for fetching credentials from AWS Secrets Manager -P4D_SUPER_SECRET_ARN="" P4D_SUPER_PASSWD_SECRET_ARN="" SWARM_USER_SECRET_ARN="" SWARM_PASSWD_SECRET_ARN="" @@ -65,10 +64,6 @@ while [[ $# -gt 0 ]]; do CUSTOM_CONFIG_FILE="$2" shift 2 ;; - --p4d-super-secret-arn) - P4D_SUPER_SECRET_ARN="$2" - shift 2 - ;; --p4d-super-passwd-secret-arn) P4D_SUPER_PASSWD_SECRET_ARN="$2" shift 2 @@ -104,14 +99,16 @@ SWARM_HOSTNAME="${SWARM_HOST#https://}" SWARM_HOSTNAME="${SWARM_HOSTNAME#http://}" log_message "SWARM_HOSTNAME (for configure-swarm.sh): $SWARM_HOSTNAME" +# Service account username is always "super" +P4D_SUPER="super" + # Retrieve credentials from AWS Secrets Manager log_message "Fetching secrets from AWS Secrets Manager" -P4D_SUPER=$(aws secretsmanager get-secret-value --secret-id "$P4D_SUPER_SECRET_ARN" --query SecretString --output text) P4D_SUPER_PASSWD=$(aws secretsmanager get-secret-value --secret-id "$P4D_SUPER_PASSWD_SECRET_ARN" --query SecretString --output text) SWARM_USER=$(aws secretsmanager get-secret-value --secret-id "$SWARM_USER_SECRET_ARN" --query SecretString --output text) SWARM_PASSWD=$(aws secretsmanager get-secret-value --secret-id "$SWARM_PASSWD_SECRET_ARN" --query SecretString --output text) -if [ -z "$P4D_SUPER" ] || [ -z "$P4D_SUPER_PASSWD" ] || [ -z "$SWARM_USER" ] || [ -z "$SWARM_PASSWD" ]; then +if [ -z "$P4D_SUPER_PASSWD" ] || [ -z "$SWARM_USER" ] || [ -z "$SWARM_PASSWD" ]; then log_message "ERROR: Failed to fetch secrets from AWS Secrets Manager" exit 1 fi diff --git a/assets/packer/perforce/p4-server/README.md b/assets/packer/perforce/p4-server/README.md index 7b9d82d3..8da59a60 100644 --- a/assets/packer/perforce/p4-server/README.md +++ b/assets/packer/perforce/p4-server/README.md @@ -40,17 +40,49 @@ An instance that is provisioned with this AMI will not automatically deploy a P4 ``` bash #!/bin/bash /home/ec2-user/cloud-game-development-toolkit/p4_configure.sh \ - \ - \ - \ - \ - \ - \ - \ - + --p4d_type p4d_master \ + --hx_depots /dev/sdf \ + --hx_metadata /dev/sdg \ + --hx_logs /dev/sdh \ + --super_password \ + --admin_username \ + --admin_password \ + --fqdn perforce.example.com \ + --auth https://auth.perforce.example.com ``` -As you can see, there are quite a few parameters that need to be passed to the `p4_configure.sh` script. We recommend using the [Perforce module](../../../../modules/perforce/README.md) for this reason. +### Script Options + +| Option | Description | +|--------|-------------| +| `--p4d_type` | P4 Server type: `p4d_master`, `p4d_replica`, or `p4d_edge` | +| `--hx_depots` | Path/device for P4 Server depots volume | +| `--hx_metadata` | Path/device for P4 Server metadata volume | +| `--hx_logs` | Path/device for P4 Server logs volume | +| `--super_password` | AWS Secrets Manager secret ID for service account (super) password | +| `--admin_username` | AWS Secrets Manager secret ID for admin account username | +| `--admin_password` | AWS Secrets Manager secret ID for admin account password | +| `--fqdn` | Fully Qualified Domain Name for the P4 Server | +| `--auth` | P4Auth URL (optional) | +| `--case_sensitive` | Case sensitivity: `0` (insensitive) or `1` (sensitive, default) | +| `--unicode` | Enable Unicode mode: `true` or `false` | +| `--selinux` | Update SELinux labels: `true` or `false` | +| `--plaintext` | Disable SSL: `true` or `false` | +| `--fsxn_password` | AWS Secrets Manager secret ID for FSxN password | +| `--fsxn_svm_name` | FSxN Storage Virtual Machine name | +| `--fsxn_management_ip` | FSxN management IP address | + +### User Configuration + +The script creates two Perforce users: + +1. **Service Account (`super`)**: Always created with username "super". Used internally by P4 Code Review (Helix Swarm) and other tooling. Password provided via `--super_password`. + +2. **Admin Account**: Created with the username provided via `--admin_username`. This is the account for human administrators. Password provided via `--admin_password`. + +Both users have full super privileges and are added to the `unlimited_timeout` group. + +We recommend using the [Perforce module](../../../../modules/perforce/README.md) to manage these configurations through Terraform. ## Important Notes diff --git a/assets/packer/perforce/p4-server/p4_configure.sh b/assets/packer/perforce/p4-server/p4_configure.sh index 21ae006c..30734bfd 100644 --- a/assets/packer/perforce/p4-server/p4_configure.sh +++ b/assets/packer/perforce/p4-server/p4_configure.sh @@ -238,7 +238,7 @@ prepare_iscsi_volume() { sleep $interval elapsed=$((elapsed + interval)) done - if [ ! -e $VOLUME]; then + if [ ! -e $VOLUME ]; then log_message "The device $VOLUME does not exist. Exiting." exit 1 fi @@ -315,10 +315,11 @@ log_message "Starting the p4 configure script." print_help() { echo "Usage: $0 [OPTIONS]" echo "Options:" - echo " --p4d_type Specify the type of P4 Server (p4d_master, p4d_replica, p4d_edge)" - echo " --username AWS Secrets Manager secret ID for the P4 Server admin username" - echo " --password AWS Secrets Manager secret ID for the P4 Server admin password" - echo " --auth P4Auth URL" + echo " --p4d_type Specify the type of P4 Server (p4d_master, p4d_replica, p4d_edge)" + echo " --super_password AWS Secrets Manager secret ID for the service account (super) password" + echo " --admin_username AWS Secrets Manager secret ID for the admin account username" + echo " --admin_password AWS Secrets Manager secret ID for the admin account password" + echo " --auth P4Auth URL" echo " --fqdn Fully Qualified Domain Name for the P4 Server" echo " --hx_logs Path for P4 Server logs" echo " --hx_metadata Path for P4 Server metadata" @@ -334,7 +335,7 @@ print_help() { } # Parse command-line options -OPTS=$(getopt -o '' --long p4d_type:,username:,password:,auth:,fqdn:,hx_logs:,hx_metadata:,hx_depots:,case_sensitive:,unicode:,selinux:,plaintext:,fsxn_password:,fsxn_svm_name:,fsxn_management_ip:,help -n 'parse-options' -- "$@") +OPTS=$(getopt -o '' --long p4d_type:,super_password:,admin_username:,admin_password:,auth:,fqdn:,hx_logs:,hx_metadata:,hx_depots:,case_sensitive:,unicode:,selinux:,plaintext:,fsxn_password:,fsxn_svm_name:,fsxn_management_ip:,help -n 'parse-options' -- "$@") if [ $? != 0 ]; then log_message "Failed to parse options" @@ -360,12 +361,16 @@ while true; do ;; esac ;; - --username) - P4D_ADMIN_USERNAME_SECRET_ID="$2" + --super_password) + SUPER_PASSWORD_SECRET_ID="$2" shift 2 ;; - --password) - P4D_ADMIN_PASS_SECRET_ID="$2" + --admin_username) + ADMIN_USERNAME_SECRET_ID="$2" + shift 2 + ;; + --admin_password) + ADMIN_PASSWORD_SECRET_ID="$2" shift 2 ;; --auth) @@ -462,12 +467,19 @@ if [[ "$P4D_TYPE" != "p4d_master" && "$P4D_TYPE" != "p4d_replica" && "$P4D_TYPE" exit 1 fi -# Fetch credentials for admin user from secrets manager -P4D_ADMIN_USERNAME=$(resolve_aws_secret $P4D_ADMIN_USERNAME_SECRET_ID) -P4D_ADMIN_PASS=$(resolve_aws_secret $P4D_ADMIN_PASS_SECRET_ID) +# Fetch credentials from secrets manager +# Service account (super) - used for internal tooling and Swarm extension +SUPER_PASSWORD=$(resolve_aws_secret $SUPER_PASSWORD_SECRET_ID) +# Admin account - for human administrators +ADMIN_USERNAME=$(resolve_aws_secret $ADMIN_USERNAME_SECRET_ID) +ADMIN_PASSWORD=$(resolve_aws_secret $ADMIN_PASSWORD_SECRET_ID) +# FSxN credentials (if applicable) FSXN_PASSWORD=$(resolve_aws_secret $FSXN_PASS) ONTAP_USER="fsxadmin" +log_message "Service account: super" +log_message "Admin account: $ADMIN_USERNAME" + # Function to perform operations perform_operations() { log_message "Performing operations for mounting and syncing directories." @@ -606,15 +618,15 @@ if [ ! -f "$SDP_Setup_Script_Config" ]; then exit 1 fi -# Update Perforce super user password in configuration -sed -i "s/^P4ADMINPASS=.*/P4ADMINPASS=$P4D_ADMIN_PASS/" "$SDP_Setup_Script_Config" +# Update Perforce service account (super) password in configuration +sed -i "s/^P4ADMINPASS=.*/P4ADMINPASS=$SUPER_PASSWORD/" "$SDP_Setup_Script_Config" log_message "Updated P4ADMINPASS in $SDP_Setup_Script_Config." -# Update Perforce super user password in configuration -sed -i "s/^ADMINUSER=.*/ADMINUSER=$P4D_ADMIN_USERNAME/" "$SDP_Setup_Script_Config" +# Update Perforce admin user to "super" for initial setup +sed -i "s/^ADMINUSER=.*/ADMINUSER=super/" "$SDP_Setup_Script_Config" -log_message "Updated ADMINUSER in $SDP_Setup_Script_Config." +log_message "Updated ADMINUSER to 'super' in $SDP_Setup_Script_Config." # Check if p4d_master server and update sitetags @@ -691,7 +703,7 @@ else P4PORT=ssl:1666 fi -P4USER=$P4D_ADMIN_USERNAME +P4USER=super #probably need to copy p4 binary to the /usr/bin or add to the path variable to avoid running with a full path adding: #permissions for lal users: @@ -716,14 +728,14 @@ fi if [ -f "$SDP_Live_Checkpoint" ]; then chmod +x "$SDP_Live_Checkpoint" - sudo -u "$P4USER" "$SDP_Live_Checkpoint" 1 + sudo -u perforce "$SDP_Live_Checkpoint" 1 else echo "Setup script (SDP_Live_Checkpoint) not found." fi if [ -f "$SDP_Offline_Recreate" ]; then chmod +x "$SDP_Offline_Recreate" - sudo -u "$P4USER" "$SDP_Offline_Recreate" 1 + sudo -u perforce "$SDP_Offline_Recreate" 1 else echo "Setup script (SDP_Offline_Recreate) not found." fi @@ -731,11 +743,60 @@ fi # initialize crontab for user perforce # fixing broken crontab on SDP, cron runs on minute schedule */60 is incorrect sed -i 's#\*/60#0#g' /p4/p4.crontab.1 -sudo -u "$P4USER" crontab /p4/p4.crontab.1 +sudo -u perforce crontab /p4/p4.crontab.1 # verify sdp installation should warn about missing license only: /hxdepots/p4/common/bin/verify_sdp.sh 1 +# Establish SSL trust for perforce user before running p4 commands +sudo -u perforce p4 -p "$P4PORT" trust -y + +# Login as super user for admin operations +echo "$SUPER_PASSWORD" | sudo -u perforce p4 -p "$P4PORT" -u super login + +# Create admin user for human administrators +log_message "Creating admin user: $ADMIN_USERNAME" + +# Create user spec +cat > /tmp/admin_user.txt < /dev/null +echo " super user $ADMIN_USERNAME * //..." >> /tmp/protect.txt +cat /tmp/protect.txt | sudo -u perforce p4 -p "$P4PORT" -u super protect -i + +# Clean up +rm -f /tmp/admin_user.txt /tmp/protect.txt + +log_message "Admin user $ADMIN_USERNAME created successfully" + +# Create a group with unlimited ticket timeout for service integrations (e.g., Swarm) +# This prevents ticket expiration issues for automated systems +log_message "Creating unlimited_timeout group for service integrations" + +cat > /tmp/unlimited_timeout_group.txt < [existing\_ecs\_cluster\_name](#input\_existing\_ecs\_cluster\_name) | The name of an existing ECS cluster to use for the Perforce server. If omitted a new cluster will be created. | `string` | `null` | no | | [existing\_security\_groups](#input\_existing\_security\_groups) | A list of existing security group IDs to attach to the shared network load balancer. | `list(string)` | `[]` | no | | [p4\_auth\_config](#input\_p4\_auth\_config) | # General
name: "The string including in the naming of resources related to P4Auth. Default is 'p4-auth'."

project\_prefix : "The project prefix for the P4Auth service. Default is 'cgd'."

environment : "The environment where the P4Auth service will be deployed. Default is 'dev'."

enable\_web\_based\_administration: "Whether to de enable web based administration. Default is 'true'."

debug : "Whether to enable debug mode for the P4Auth service. Default is 'false'."

fully\_qualified\_domain\_name : "The FQDN for the P4Auth Service. This is used for the P4Auth's Perforce configuration."


# Compute
cluster\_name : "The name of the ECS cluster where the P4Auth service will be deployed. Cluster is not created if this variable is null."

container\_name : "The name of the P4Auth service container. Default is 'p4-auth-container'."

container\_port : "The port on which the P4Auth service will be listening. Default is '3000'."

container\_cpu : "The number of CPU units to reserve for the P4Auth service container. Default is '1024'."

container\_memory : "The number of CPU units to reserve for the P4Auth service container. Default is '4096'."

pd4\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4Auth service logs in CloudWatch. Default is 365 days."


# Networking
create\_defaults\_sgs : "Whether to create default security groups for the P4Auth service."

internal : "Set this flag to true if you do not want the P4Auth service to have a public IP."

create\_default\_role : "Whether to create the P4Auth default IAM Role. Default is set to true."

custom\_role : "ARN of a custom IAM Role you wish to use with P4Auth."

admin\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4Auth Administrator username."

admin\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4Auth Administrator password."


# - SCIM -
p4d\_super\_user\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the super user username for p4d."

p4d\_super\_user\_password\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the super user password for p4d."

scim\_bearer\_token\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the bearer token."

extra\_env : "Extra configuration environment variables to set on the p4 auth svc container." |
object({
# - General -
name = optional(string, "p4-auth")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
enable_web_based_administration = optional(bool, true)
debug = optional(bool, false)
fully_qualified_domain_name = string

# - Compute -
container_name = optional(string, "p4-auth-container")
container_port = optional(number, 3000)
container_cpu = optional(number, 1024)
container_memory = optional(number, 4096)
p4d_port = optional(string, null)

# - Storage & Logging -
cloudwatch_log_retention_in_days = optional(number, 365)

# - Networking & Security -
service_subnets = optional(list(string), null)
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

certificate_arn = optional(string, null)
create_default_role = optional(bool, true)
custom_role = optional(string, null)
admin_username_secret_arn = optional(string, null)
admin_password_secret_arn = optional(string, null)

# SCIM
p4d_super_user_arn = optional(string, null)
p4d_super_user_password_arn = optional(string, null)
scim_bearer_token_arn = optional(string, null)
extra_env = optional(map(string), null)
})
| `null` | no | -| [p4\_code\_review\_config](#input\_p4\_code\_review\_config) | # General
name: "The string including in the naming of resources related to P4 Code Review. Default is 'p4-code-review'."

project\_prefix : "The project prefix for the P4 Code Review service. Default is 'cgd'."

environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'."

fully\_qualified\_domain\_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration."


# Compute
application\_port : "The port on which the P4 Code Review service will be listening. Default is '80'."

instance\_type : "EC2 instance type for running P4 Code Review. Default is 'm5.large'."

ami\_id : "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI."

p4d\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."

p4charset : "The P4CHARSET environment variable to set for the P4 Code Review instance."

existing\_redis\_connection : "The existing Redis connection for the P4 Code Review service."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days."

ebs\_volume\_size : "Size in GB for the EBS volume that stores P4 Code Review data. Default is '20'."

ebs\_volume\_type : "EBS volume type for P4 Code Review data storage. Default is 'gp3'."

ebs\_volume\_encrypted : "Enable encryption for the EBS volume storing P4 Code Review data. Default is 'true'."

ebs\_availability\_zone : "Availability zone for the EBS volume. Must match the EC2 instance AZ."


# Networking & Security
create\_default\_sgs : "Whether to create default security groups for the P4 Code Review service."

internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP."

instance\_subnet\_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security."

super\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password."

super\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user username."

p4\_code\_review\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password."

p4\_code\_review\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username."

custom\_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc."


# Caching
elasticache\_node\_count : "The number of Elasticache nodes to create for the P4 Code Review service. Default is '1'."

elasticache\_node\_type : "The type of Elasticache node to create for the P4 Code Review service. Default is 'cache.t4g.micro'." |
object({
# General
name = optional(string, "p4-code-review")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
fully_qualified_domain_name = string

# Compute
application_port = optional(number, 80)
instance_type = optional(string, "m5.large")
ami_id = optional(string, null)
p4d_port = optional(string, null)
p4charset = optional(string, null)
existing_redis_connection = optional(object({
host = string
port = number
}), null)

# Storage & Logging
cloudwatch_log_retention_in_days = optional(number, 365)
ebs_volume_size = optional(number, 20)
ebs_volume_type = optional(string, "gp3")
ebs_volume_encrypted = optional(bool, true)
ebs_availability_zone = optional(string, null)

# Networking & Security
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)
service_subnets = optional(list(string), null)
instance_subnet_id = string

super_user_password_secret_arn = optional(string, null)
super_user_username_secret_arn = optional(string, null)
p4_code_review_user_password_secret_arn = optional(string, null)
p4_code_review_user_username_secret_arn = optional(string, null)
custom_config = optional(string, null)

# Caching
elasticache_node_count = optional(number, 1)
elasticache_node_type = optional(string, "cache.t4g.micro")
})
| `null` | no | -| [p4\_server\_config](#input\_p4\_server\_config) | # - General -
name: "The string including in the naming of resources related to P4 Server. Default is 'p4-server'"

project\_prefix: "The project prefix for this workload. This is appended to the beginning of most resource names."

environment: "The current environment (e.g. dev, prod, etc.)"

auth\_service\_url: "The URL for the P4Auth Service."

fully\_qualified\_domain\_name = "The FQDN for the P4 Server. This is used for the P4 Server's Perforce configuration."


# - Compute -
lookup\_existing\_ami : "Whether to lookup the existing Perforce P4 Server AMI."

ami\_prefix: "The AMI prefix to use for the AMI that will be created for P4 Server."

instance\_type: "The instance type for Perforce P4 Server. Defaults to c6g.large."

instance\_architecture: "The architecture of the P4 Server instance. Allowed values are 'arm64' or 'x86\_64'."

IMPORTANT: "Ensure the instance family of the instance type you select supports the instance\_architecture you select. For example, 'c6in' instance family only works for 'x86\_64' architecture, not 'arm64'. For a full list of this mapping, see the AWS Docs for EC2 Naming Conventions: https://docs.aws.amazon.com/ec2/latest/instancetypes/instance-type-names.html"

p4\_server\_type: "The Perforce P4 Server server type. Valid values are 'p4d\_commit' or 'p4d\_replica'."

unicode: "Whether to enable Unicode configuration for P4 Server the -xi flag for p4d. Set to true to enable Unicode support."

selinux: "Whether to apply SELinux label updates for P4 Server. Don't enable this if SELinux is disabled on your target operating system."

case\_sensitive: "Whether or not the server should be case insensitive (Server will run '-C1' mode), or if the server will run with case sensitivity default of the underlying platform. False enables '-C1' mode. Default is set to true."

plaintext: "Whether to enable plaintext authentication for P4 Server. This is not recommended for production environments unless you are using a load balancer for TLS termination. Default is set to false."


# - Storage -
storage\_type: "The type of backing store. Valid values are either 'EBS' or 'FSxN'"

depot\_volume\_size: "The size of the depot volume in GiB. Defaults to 128 GiB."

metadata\_volume\_size: "The size of the metadata volume in GiB. Defaults to 32 GiB."

logs\_volume\_size: "The size of the logs volume in GiB. Defaults to 32 GiB."


# - Networking & Security -
instance\_subnet\_id: "The subnet where the P4 Server instance will be deployed."

instance\_private\_ip: "The private IP address to assign to the P4 Server."

create\_default\_sg : "Whether to create a default security group for the P4 Server instance."

existing\_security\_groups: "A list of existing security group IDs to attach to the P4 Server load balancer."

internal: "Set this flag to true if you do not want the P4 Server instance to have a public IP."

super\_user\_password\_secret\_arn: "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's username here. Otherwise, the default of 'perforce' will be used."

super\_user\_username\_secret\_arn: "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's password here."

create\_default\_role: "Optional creation of P4 Server default IAM Role with SSM managed instance core policy attached. Default is set to true."

custom\_role: "ARN of a custom IAM Role you wish to use with P4 Server." |
object({
# General
name = optional(string, "p4-server")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
auth_service_url = optional(string, null)
fully_qualified_domain_name = string

# Compute
lookup_existing_ami = optional(bool, true)
ami_prefix = optional(string, "p4_al2023")

instance_type = optional(string, "c6i.large")
instance_architecture = optional(string, "x86_64")
p4_server_type = optional(string, null)

unicode = optional(bool, false)
selinux = optional(bool, false)
case_sensitive = optional(bool, true)
plaintext = optional(bool, false)

# Storage
storage_type = optional(string, "EBS")
depot_volume_size = optional(number, 128)
metadata_volume_size = optional(number, 32)
logs_volume_size = optional(number, 32)

# Networking & Security
instance_subnet_id = optional(string, null)
instance_private_ip = optional(string, null)
create_default_sg = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

super_user_password_secret_arn = optional(string, null)
super_user_username_secret_arn = optional(string, null)

create_default_role = optional(bool, true)
custom_role = optional(string, null)

# FSxN
fsxn_password = optional(string, null)
fsxn_filesystem_security_group_id = optional(string, null)
protocol = optional(string, null)
fsxn_region = optional(string, null)
fsxn_management_ip = optional(string, null)
fsxn_svm_name = optional(string, null)
amazon_fsxn_svm_id = optional(string, null)
fsxn_aws_profile = optional(string, null)
})
| `null` | no | +| [p4\_code\_review\_config](#input\_p4\_code\_review\_config) | # General
name: "The string including in the naming of resources related to P4 Code Review. Default is 'p4-code-review'."

project\_prefix : "The project prefix for the P4 Code Review service. Default is 'cgd'."

environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'."

fully\_qualified\_domain\_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration."


# Compute
application\_port : "The port on which the P4 Code Review service will be listening. Default is '80'."

instance\_type : "EC2 instance type for running P4 Code Review. Default is 'm5.large'."

ami\_id : "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI."

p4d\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."

p4charset : "The P4CHARSET environment variable to set for the P4 Code Review instance."

existing\_redis\_connection : "The existing Redis connection for the P4 Code Review service."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days."

ebs\_volume\_size : "Size in GB for the EBS volume that stores P4 Code Review data. Default is '20'."

ebs\_volume\_type : "EBS volume type for P4 Code Review data storage. Default is 'gp3'."

ebs\_volume\_encrypted : "Enable encryption for the EBS volume storing P4 Code Review data. Default is 'true'."

ebs\_availability\_zone : "Availability zone for the EBS volume. Must match the EC2 instance AZ."


# Networking & Security
create\_default\_sgs : "Whether to create default security groups for the P4 Code Review service."

internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP."

instance\_subnet\_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security."

super\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password."

p4\_code\_review\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password."

p4\_code\_review\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username."

custom\_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc."


# Caching
elasticache\_node\_count : "The number of Elasticache nodes to create for the P4 Code Review service. Default is '1'."

elasticache\_node\_type : "The type of Elasticache node to create for the P4 Code Review service. Default is 'cache.t4g.micro'." |
object({
# General
name = optional(string, "p4-code-review")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
fully_qualified_domain_name = string

# Compute
application_port = optional(number, 80)
instance_type = optional(string, "m5.large")
ami_id = optional(string, null)
p4d_port = optional(string, null)
p4charset = optional(string, null)
existing_redis_connection = optional(object({
host = string
port = number
}), null)

# Storage & Logging
cloudwatch_log_retention_in_days = optional(number, 365)
ebs_volume_size = optional(number, 20)
ebs_volume_type = optional(string, "gp3")
ebs_volume_encrypted = optional(bool, true)
ebs_availability_zone = optional(string, null)

# Networking & Security
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)
service_subnets = optional(list(string), null)
instance_subnet_id = string

super_user_password_secret_arn = optional(string, null)
p4_code_review_user_password_secret_arn = optional(string, null)
p4_code_review_user_username_secret_arn = optional(string, null)
custom_config = optional(string, null)

# Caching
elasticache_node_count = optional(number, 1)
elasticache_node_type = optional(string, "cache.t4g.micro")
})
| `null` | no | +| [p4\_server\_config](#input\_p4\_server\_config) | # - General -
name: "The string including in the naming of resources related to P4 Server. Default is 'p4-server'"

project\_prefix: "The project prefix for this workload. This is appended to the beginning of most resource names."

environment: "The current environment (e.g. dev, prod, etc.)"

auth\_service\_url: "The URL for the P4Auth Service."

fully\_qualified\_domain\_name = "The FQDN for the P4 Server. This is used for the P4 Server's Perforce configuration."


# - Compute -
lookup\_existing\_ami : "Whether to lookup the existing Perforce P4 Server AMI."

ami\_prefix: "The AMI prefix to use for the AMI that will be created for P4 Server."

instance\_type: "The instance type for Perforce P4 Server. Defaults to c6g.large."

instance\_architecture: "The architecture of the P4 Server instance. Allowed values are 'arm64' or 'x86\_64'."

IMPORTANT: "Ensure the instance family of the instance type you select supports the instance\_architecture you select. For example, 'c6in' instance family only works for 'x86\_64' architecture, not 'arm64'. For a full list of this mapping, see the AWS Docs for EC2 Naming Conventions: https://docs.aws.amazon.com/ec2/latest/instancetypes/instance-type-names.html"

p4\_server\_type: "The Perforce P4 Server server type. Valid values are 'p4d\_commit' or 'p4d\_replica'."

unicode: "Whether to enable Unicode configuration for P4 Server the -xi flag for p4d. Set to true to enable Unicode support."

selinux: "Whether to apply SELinux label updates for P4 Server. Don't enable this if SELinux is disabled on your target operating system."

case\_sensitive: "Whether or not the server should be case insensitive (Server will run '-C1' mode), or if the server will run with case sensitivity default of the underlying platform. False enables '-C1' mode. Default is set to true."

plaintext: "Whether to enable plaintext authentication for P4 Server. This is not recommended for production environments unless you are using a load balancer for TLS termination. Default is set to false."


# - Storage -
storage\_type: "The type of backing store. Valid values are either 'EBS' or 'FSxN'"

depot\_volume\_size: "The size of the depot volume in GiB. Defaults to 128 GiB."

metadata\_volume\_size: "The size of the metadata volume in GiB. Defaults to 32 GiB."

logs\_volume\_size: "The size of the logs volume in GiB. Defaults to 32 GiB."


# - Networking & Security -
instance\_subnet\_id: "The subnet where the P4 Server instance will be deployed."

instance\_private\_ip: "The private IP address to assign to the P4 Server."

create\_default\_sg : "Whether to create a default security group for the P4 Server instance."

existing\_security\_groups: "A list of existing security group IDs to attach to the P4 Server load balancer."

internal: "Set this flag to true if you do not want the P4 Server instance to have a public IP."

admin\_username: "Username for the Perforce admin account. The 'super' service account is always created automatically for internal tooling. Default is 'perforce'."

admin\_password\_secret\_arn: "Optional ARN of existing Secrets Manager secret for admin password. If not provided, a password will be auto-generated."

create\_default\_role: "Optional creation of P4 Server default IAM Role with SSM managed instance core policy attached. Default is set to true."

custom\_role: "ARN of a custom IAM Role you wish to use with P4 Server." |
object({
# General
name = optional(string, "p4-server")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
auth_service_url = optional(string, null)
fully_qualified_domain_name = string

# Compute
lookup_existing_ami = optional(bool, true)
ami_prefix = optional(string, "p4_al2023")

instance_type = optional(string, "c6i.large")
instance_architecture = optional(string, "x86_64")
p4_server_type = optional(string, null)

unicode = optional(bool, false)
selinux = optional(bool, false)
case_sensitive = optional(bool, true)
plaintext = optional(bool, false)

# Storage
storage_type = optional(string, "EBS")
depot_volume_size = optional(number, 128)
metadata_volume_size = optional(number, 32)
logs_volume_size = optional(number, 32)

# Networking & Security
instance_subnet_id = optional(string, null)
instance_private_ip = optional(string, null)
create_default_sg = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

admin_username = optional(string, "perforce")
admin_password_secret_arn = optional(string, null)

create_default_role = optional(bool, true)
custom_role = optional(string, null)

# FSxN
fsxn_password = optional(string, null)
fsxn_filesystem_security_group_id = optional(string, null)
protocol = optional(string, null)
fsxn_region = optional(string, null)
fsxn_management_ip = optional(string, null)
fsxn_svm_name = optional(string, null)
amazon_fsxn_svm_id = optional(string, null)
fsxn_aws_profile = optional(string, null)
})
| `null` | no | | [project\_prefix](#input\_project\_prefix) | The project prefix for this workload. This is appended to the beginning of most resource names. | `string` | `"cgd"` | no | | [route53\_private\_hosted\_zone\_name](#input\_route53\_private\_hosted\_zone\_name) | The name of the private Route53 Hosted Zone for the Perforce resources. | `string` | `null` | no | | [s3\_enable\_force\_destroy](#input\_s3\_enable\_force\_destroy) | Enables force destroy for the S3 bucket for both the shared NLB and shared ALB access log storage. Defaults to true. | `bool` | `true` | no | @@ -257,14 +257,15 @@ packer build perforce_x86.pkr.hcl | [p4\_code\_review\_alb\_zone\_id](#output\_p4\_code\_review\_alb\_zone\_id) | The hosted zone ID of the P4 Code Review ALB. | | [p4\_code\_review\_service\_security\_group\_id](#output\_p4\_code\_review\_service\_security\_group\_id) | Security group associated with P4 Code Review application. | | [p4\_code\_review\_target\_group\_arn](#output\_p4\_code\_review\_target\_group\_arn) | The service target group for the P4 Code Review. | +| [p4\_server\_admin\_password\_secret\_arn](#output\_p4\_server\_admin\_password\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding the admin account password. | +| [p4\_server\_admin\_username\_secret\_arn](#output\_p4\_server\_admin\_username\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding the admin account username. | | [p4\_server\_eip\_id](#output\_p4\_server\_eip\_id) | The ID of the Elastic IP associated with your P4 Server instance. | | [p4\_server\_eip\_public\_ip](#output\_p4\_server\_eip\_public\_ip) | The public IP of your P4 Server instance. | | [p4\_server\_instance\_id](#output\_p4\_server\_instance\_id) | Instance ID for the P4 Server instance | | [p4\_server\_lambda\_link\_name](#output\_p4\_server\_lambda\_link\_name) | The name of the Lambda link for the P4 Server instance to use with FSxN. | | [p4\_server\_private\_ip](#output\_p4\_server\_private\_ip) | Private IP for the P4 Server instance | | [p4\_server\_security\_group\_id](#output\_p4\_server\_security\_group\_id) | The default security group of your P4 Server instance. | -| [p4\_server\_super\_user\_password\_secret\_arn](#output\_p4\_server\_super\_user\_password\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding your P4 Server super user's username. | -| [p4\_server\_super\_user\_username\_secret\_arn](#output\_p4\_server\_super\_user\_username\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding your P4 Server super user's password. | +| [p4\_server\_super\_password\_secret\_arn](#output\_p4\_server\_super\_password\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding the service account (super) password. | | [shared\_application\_load\_balancer\_arn](#output\_shared\_application\_load\_balancer\_arn) | The ARN of the shared application load balancer. | | [shared\_network\_load\_balancer\_arn](#output\_shared\_network\_load\_balancer\_arn) | The ARN of the shared network load balancer. | diff --git a/modules/perforce/main.tf b/modules/perforce/main.tf index 85180d63..bc8c4356 100644 --- a/modules/perforce/main.tf +++ b/modules/perforce/main.tf @@ -16,8 +16,8 @@ module "p4_server" { var.p4_auth_config != null ? ( var.create_route53_private_hosted_zone ? - "auth.${aws_route53_zone.perforce_private_hosted_zone[0].name}" : - module.p4_auth[0].alb_dns_name + "https://auth.${aws_route53_zone.perforce_private_hosted_zone[0].name}" : + "https://${module.p4_auth[0].alb_dns_name}" ) : null ) @@ -49,16 +49,16 @@ module "p4_server" { fsxn_management_ip = var.p4_server_config.fsxn_management_ip # Networking & Security - vpc_id = var.vpc_id - instance_subnet_id = var.p4_server_config.instance_subnet_id - instance_private_ip = var.p4_server_config.instance_private_ip - create_default_sg = var.p4_server_config.create_default_sg - existing_security_groups = var.p4_server_config.existing_security_groups - internal = var.p4_server_config.internal - super_user_password_secret_arn = var.p4_server_config.super_user_password_secret_arn - super_user_username_secret_arn = var.p4_server_config.super_user_username_secret_arn - create_default_role = var.p4_server_config.create_default_role - custom_role = var.p4_server_config.custom_role + vpc_id = var.vpc_id + instance_subnet_id = var.p4_server_config.instance_subnet_id + instance_private_ip = var.p4_server_config.instance_private_ip + create_default_sg = var.p4_server_config.create_default_sg + existing_security_groups = var.p4_server_config.existing_security_groups + internal = var.p4_server_config.internal + admin_username = var.p4_server_config.admin_username + admin_password_secret_arn = var.p4_server_config.admin_password_secret_arn + create_default_role = var.p4_server_config.create_default_role + custom_role = var.p4_server_config.custom_role } @@ -159,10 +159,9 @@ module "p4_code_review" { elasticache_node_count = var.p4_code_review_config.elasticache_node_count elasticache_node_type = var.p4_code_review_config.elasticache_node_type - super_user_password_secret_arn = module.p4_server[0].super_user_password_secret_arn - super_user_username_secret_arn = module.p4_server[0].super_user_username_secret_arn - p4_code_review_user_password_secret_arn = module.p4_server[0].super_user_password_secret_arn - p4_code_review_user_username_secret_arn = module.p4_server[0].super_user_username_secret_arn + super_user_password_secret_arn = module.p4_server[0].super_password_secret_arn + p4_code_review_user_password_secret_arn = module.p4_server[0].admin_password_secret_arn + p4_code_review_user_username_secret_arn = module.p4_server[0].admin_username_secret_arn custom_config = var.p4_code_review_config.custom_config diff --git a/modules/perforce/modules/p4-code-review/README.md b/modules/perforce/modules/p4-code-review/README.md index 5f7afee3..7aa625ee 100644 --- a/modules/perforce/modules/p4-code-review/README.md +++ b/modules/perforce/modules/p4-code-review/README.md @@ -246,7 +246,6 @@ No modules. | [p4\_code\_review\_user\_username\_secret\_arn](#input\_p4\_code\_review\_user\_username\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review username. | `string` | n/a | yes | | [subnets](#input\_subnets) | A list of subnets for ElastiCache Redis deployment. Private subnets are recommended. | `list(string)` | n/a | yes | | [super\_user\_password\_secret\_arn](#input\_super\_user\_password\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user password. | `string` | n/a | yes | -| [super\_user\_username\_secret\_arn](#input\_super\_user\_username\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user username. | `string` | n/a | yes | | [vpc\_id](#input\_vpc\_id) | The ID of the existing VPC you would like to deploy P4 Code Review into. | `string` | n/a | yes | | [alb\_access\_logs\_bucket](#input\_alb\_access\_logs\_bucket) | ID of the S3 bucket for P4 Code Review ALB access log storage. If access logging is enabled and this is null the module creates a bucket. | `string` | `null` | no | | [alb\_access\_logs\_prefix](#input\_alb\_access\_logs\_prefix) | Log prefix for P4 Code Review ALB access logs. If null the project prefix and module name are used. | `string` | `null` | no | diff --git a/modules/perforce/modules/p4-code-review/ec2.tf b/modules/perforce/modules/p4-code-review/ec2.tf index 1d4e6663..57c88206 100644 --- a/modules/perforce/modules/p4-code-review/ec2.tf +++ b/modules/perforce/modules/p4-code-review/ec2.tf @@ -62,7 +62,6 @@ resource "aws_launch_template" "swarm_instance" { swarm_redis = var.existing_redis_connection != null ? var.existing_redis_connection.host : aws_elasticache_cluster.cluster[0].cache_nodes[0].address swarm_redis_port = var.existing_redis_connection != null ? tostring(var.existing_redis_connection.port) : tostring(aws_elasticache_cluster.cluster[0].cache_nodes[0].port) swarm_force_ext = "y" - super_user_username_secret_arn = var.super_user_username_secret_arn super_user_password_secret_arn = var.super_user_password_secret_arn p4_code_review_user_username_secret_arn = var.p4_code_review_user_username_secret_arn p4_code_review_user_password_secret_arn = var.p4_code_review_user_password_secret_arn diff --git a/modules/perforce/modules/p4-code-review/iam.tf b/modules/perforce/modules/p4-code-review/iam.tf index 473f353a..43ad7df9 100644 --- a/modules/perforce/modules/p4-code-review/iam.tf +++ b/modules/perforce/modules/p4-code-review/iam.tf @@ -21,7 +21,6 @@ data "aws_iam_policy_document" "secrets_manager_policy" { "secretsmanager:DescribeSecret" ] resources = [ - var.super_user_username_secret_arn, var.super_user_password_secret_arn, var.p4_code_review_user_username_secret_arn, var.p4_code_review_user_password_secret_arn, diff --git a/modules/perforce/modules/p4-code-review/user-data.sh.tpl b/modules/perforce/modules/p4-code-review/user-data.sh.tpl index 964d74f5..2a248ad4 100644 --- a/modules/perforce/modules/p4-code-review/user-data.sh.tpl +++ b/modules/perforce/modules/p4-code-review/user-data.sh.tpl @@ -22,7 +22,6 @@ SWARM_REDIS_PORT="${swarm_redis_port}" SWARM_FORCE_EXT="${swarm_force_ext}" # Secret ARNs for AWS Secrets Manager -P4D_SUPER_SECRET_ARN="${super_user_username_secret_arn}" P4D_SUPER_PASSWD_SECRET_ARN="${super_user_password_secret_arn}" SWARM_USER_SECRET_ARN="${p4_code_review_user_username_secret_arn}" SWARM_PASSWD_SECRET_ARN="${p4_code_review_user_password_secret_arn}" @@ -242,7 +241,6 @@ log "No custom config provided" --swarm-redis "$SWARM_REDIS" \ --swarm-redis-port "$SWARM_REDIS_PORT" \ --swarm-force-ext "$SWARM_FORCE_EXT" \ - --p4d-super-secret-arn "$P4D_SUPER_SECRET_ARN" \ --p4d-super-passwd-secret-arn "$P4D_SUPER_PASSWD_SECRET_ARN" \ --swarm-user-secret-arn "$SWARM_USER_SECRET_ARN" \ --swarm-passwd-secret-arn "$SWARM_PASSWD_SECRET_ARN" \ diff --git a/modules/perforce/modules/p4-code-review/variables.tf b/modules/perforce/modules/p4-code-review/variables.tf index 08dc18da..38749b30 100644 --- a/modules/perforce/modules/p4-code-review/variables.tf +++ b/modules/perforce/modules/p4-code-review/variables.tf @@ -163,11 +163,6 @@ variable "certificate_arn" { } } -variable "super_user_username_secret_arn" { - type = string - description = "Optionally provide the ARN of an AWS Secret for the p4d super user username." -} - variable "super_user_password_secret_arn" { type = string description = "Optionally provide the ARN of an AWS Secret for the p4d super user password." diff --git a/modules/perforce/modules/p4-server/README.md b/modules/perforce/modules/p4-server/README.md index 8f7b7cb3..d9e3f6e7 100644 --- a/modules/perforce/modules/p4-server/README.md +++ b/modules/perforce/modules/p4-server/README.md @@ -12,29 +12,47 @@ This module provisions P4 Server on an EC2 Instance with three dedicated EBS vol This module deploys P4 Server on AWS using an Amazon Machine Image (AMI) that is included in the Cloud Game Development Toolkit. You **must** provision this AMI using [Hashicorp Packer](https://www.packer.io/) prior to deploying this module. To get started consult [the documentation for the P4 Server AMI](../../../../assets/packer/perforce/p4-server/README.md). -### Optional +### User Management -You can optionally define the Helix Core super user's credentials prior to deployment. To do so, create a secret for the Helix Core super user's username and password: +This module creates two users with super privileges: -```bash -aws secretsmanager create-secret \ - --name HelixCoreSuperUser \ - --description "Helix Core Super User" \ - --secret-string "{\"username\":\"admin\",\"password\":\"EXAMPLE-PASSWORD\"}" +1. **Service Account (`super`)**: An internal service account used by P4 Code Review (Helix Swarm) and other Perforce tooling. This user is always created automatically with a randomly generated password stored in AWS Secrets Manager. The service account uses password-based authentication (non-SSO). + +2. **Admin Account**: A human administrator account for managing the Perforce server. The username defaults to `perforce` but can be customized via the `admin_username` variable. The password is auto-generated and stored in AWS Secrets Manager, or you can provide your own secret ARN. + +#### Configuring the Admin Account + +By default, an admin user named `perforce` is created: + +```hcl +module "p4_server" { + source = "modules/perforce/modules/p4-server" + ... + # Uses default admin_username = "perforce" + # Password auto-generated and stored in Secrets Manager +} ``` -You can then provide the relevant ARN as variables when you define the Helix Core module in your Terraform configurations: +To customize the admin username: ```hcl -module "perforce_helix_core" { - source = "modules/perforce/helix-core" +module "p4_server" { + source = "modules/perforce/modules/p4-server" ... - helix_core_super_user_username_arn = "arn:aws:secretsmanager:us-west-2:123456789012:secret:HelixCoreSuperUser-a1b2c3:username::" - helix_core_super_user_password_arn = "arn:aws:secretsmanager:us-west-2:123456789012:secret:HelixCoreSuperUser-a1b2c3:password::" + admin_username = "myadmin" } ``` -If you do not provide these the module will create a random Super User and create the secret for you. The ARN of this secret is then available as an output to be referenced elsewhere. +To use an existing password secret: + +```hcl +module "p4_server" { + source = "modules/perforce/modules/p4-server" + ... + admin_username = "myadmin" + admin_password_secret_arn = "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyAdminPassword-a1b2c3" +} +``` @@ -93,8 +111,9 @@ No modules. | [aws_vpc_security_group_egress_rule.link_outbound_fsxn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | | [aws_vpc_security_group_egress_rule.server_internet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | | [aws_vpc_security_group_ingress_rule.fsxn_inbound_link](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | -| [awscc_secretsmanager_secret.super_user_password](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/secretsmanager_secret) | resource | -| [awscc_secretsmanager_secret.super_user_username](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/secretsmanager_secret) | resource | +| [awscc_secretsmanager_secret.admin_password](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/secretsmanager_secret) | resource | +| [awscc_secretsmanager_secret.admin_username](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/secretsmanager_secret) | resource | +| [awscc_secretsmanager_secret.super_password](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/secretsmanager_secret) | resource | | [netapp-ontap_lun.depots_volume_lun](https://registry.terraform.io/providers/NetApp/netapp-ontap/latest/docs/resources/lun) | resource | | [netapp-ontap_lun.logs_volume_lun](https://registry.terraform.io/providers/NetApp/netapp-ontap/latest/docs/resources/lun) | resource | | [netapp-ontap_lun.metadata_volume_lun](https://registry.terraform.io/providers/NetApp/netapp-ontap/latest/docs/resources/lun) | resource | @@ -116,6 +135,8 @@ No modules. | [p4\_server\_type](#input\_p4\_server\_type) | The Perforce P4 Server type. | `string` | n/a | yes | | [storage\_type](#input\_storage\_type) | The type of backing store [EBS, FSxN] | `string` | n/a | yes | | [vpc\_id](#input\_vpc\_id) | The VPC where P4 Server should be deployed | `string` | n/a | yes | +| [admin\_password\_secret\_arn](#input\_admin\_password\_secret\_arn) | Optional ARN of existing Secrets Manager secret for admin password. If not provided, a password will be auto-generated. | `string` | `null` | no | +| [admin\_username](#input\_admin\_username) | Username for the Perforce admin account (human user). The 'super' service account is always created automatically for internal tooling. | `string` | `"perforce"` | no | | [amazon\_fsxn\_filesystem\_id](#input\_amazon\_fsxn\_filesystem\_id) | The ID of the existing FSx ONTAP file system to use if storage type is FSxN. | `string` | `null` | no | | [amazon\_fsxn\_svm\_id](#input\_amazon\_fsxn\_svm\_id) | The ID of the Storage Virtual Machine (SVM) for the FSx ONTAP filesystem. | `string` | `null` | no | | [auth\_service\_url](#input\_auth\_service\_url) | The URL for the P4Auth Service. | `string` | `null` | no | @@ -143,22 +164,21 @@ No modules. | [project\_prefix](#input\_project\_prefix) | The project prefix for this workload. This is appended to the beginning of most resource names. | `string` | `"cgd"` | no | | [protocol](#input\_protocol) | Specify the protocol (NFS or ISCSI) | `string` | `null` | no | | [selinux](#input\_selinux) | Whether to apply SELinux label updates for P4 Server. Don't enable this if SELinux is disabled on your target operating system. | `bool` | `false` | no | -| [super\_user\_password\_secret\_arn](#input\_super\_user\_password\_secret\_arn) | If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's password here. | `string` | `null` | no | -| [super\_user\_username\_secret\_arn](#input\_super\_user\_username\_secret\_arn) | If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's username here. Otherwise, the default of 'perforce' will be used. | `string` | `null` | no | -| [tags](#input\_tags) | Tags to apply to resources. | `map(any)` |
{
"IaC": "Terraform",
"ModuleBy": "CGD-Toolkit",
"ModuleName": "p4-server",
"ModuleSource": "https://github.com/aws-games/cloud-game-development-toolkit/tree/main/modules/perforce",
"RootModuleName": "terraform-aws-perforce"
}
| no | +| [tags](#input\_tags) | Tags to apply to resources. | `map(any)` |
{
"IaC": "Terraform",
"ModuleBy": "CGD-Toolkit",
"ModuleName": "p4-server",
"ModuleSource": "https://github.com/aws-games/cloud-game-development-toolkit/tree/main/modules/perforce",
"RootModuleName": "terraform-aws-perforce"
}
| no | | [unicode](#input\_unicode) | Whether to enable Unicode configuration for P4 Server the -xi flag for p4d. Set to true to enable Unicode support. | `bool` | `false` | no | ## Outputs | Name | Description | |------|-------------| +| [admin\_password\_secret\_arn](#output\_admin\_password\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding the admin account password. | +| [admin\_username\_secret\_arn](#output\_admin\_username\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding the admin account username. | | [eip\_id](#output\_eip\_id) | The ID of the Elastic IP associated with your P4 Server instance. | | [eip\_public\_ip](#output\_eip\_public\_ip) | The public IP of your P4 Server instance. | | [instance\_id](#output\_instance\_id) | Instance ID for the P4 Server instance | | [lambda\_link\_name](#output\_lambda\_link\_name) | Lambda function name for the FSxN Link | | [private\_ip](#output\_private\_ip) | Private IP for the P4 Server instance | | [security\_group\_id](#output\_security\_group\_id) | The default security group of your P4 Server instance. | -| [super\_user\_password\_secret\_arn](#output\_super\_user\_password\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding your P4 Server super user's password. | -| [super\_user\_username\_secret\_arn](#output\_super\_user\_username\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding your P4 Server super user's username. | +| [super\_password\_secret\_arn](#output\_super\_password\_secret\_arn) | The ARN of the AWS Secrets Manager secret holding the service account (super) password. | diff --git a/modules/perforce/modules/p4-server/iam.tf b/modules/perforce/modules/p4-server/iam.tf index eed1212e..b4ea93a0 100644 --- a/modules/perforce/modules/p4-server/iam.tf +++ b/modules/perforce/modules/p4-server/iam.tf @@ -29,8 +29,9 @@ data "aws_iam_policy_document" "default_policy" { "secretsmanager:BatchGetSecretValue" ] resources = compact([ - var.super_user_password_secret_arn == null ? awscc_secretsmanager_secret.super_user_username[0].secret_id : var.super_user_password_secret_arn, - var.super_user_username_secret_arn == null ? awscc_secretsmanager_secret.super_user_password[0].secret_id : var.super_user_username_secret_arn, + local.super_password_secret, + local.admin_username_secret, + local.admin_password_secret, var.storage_type == "FSxN" && var.protocol == "ISCSI" ? var.fsxn_password : null ]) } diff --git a/modules/perforce/modules/p4-server/main.tf b/modules/perforce/modules/p4-server/main.tf index 2affa958..48ab3e60 100644 --- a/modules/perforce/modules/p4-server/main.tf +++ b/modules/perforce/modules/p4-server/main.tf @@ -1,10 +1,9 @@ ########################################## -# Perforce P4 Server Super User +# Service Account (super) ########################################## -resource "awscc_secretsmanager_secret" "super_user_password" { - count = var.super_user_password_secret_arn == null ? 1 : 0 - name = "${local.name_prefix}-SuperUserPassword" - description = "The password for the created P4 Server super user." +resource "awscc_secretsmanager_secret" "super_password" { + name = "${local.name_prefix}-ServiceAccountPassword" + description = "Internal service account password for Perforce tooling (Swarm, etc.)." generate_secret_string = { exclude_numbers = false exclude_punctuation = true @@ -12,11 +11,24 @@ resource "awscc_secretsmanager_secret" "super_user_password" { } } -resource "awscc_secretsmanager_secret" "super_user_username" { - count = var.super_user_username_secret_arn == null ? 1 : 0 - name = "${local.name_prefix}-SuperUserUsername" - description = "The username for the created P4 Server super user." - secret_string = "perforce" +########################################## +# Admin Account +########################################## +resource "awscc_secretsmanager_secret" "admin_username" { + name = "${local.name_prefix}-AdminUsername" + description = "Username for the Perforce admin account." + secret_string = var.admin_username +} + +resource "awscc_secretsmanager_secret" "admin_password" { + count = var.admin_password_secret_arn == null ? 1 : 0 + name = "${local.name_prefix}-AdminPassword" + description = "Password for the Perforce admin account." + generate_secret_string = { + exclude_numbers = false + exclude_punctuation = true + include_space = false + } } @@ -74,8 +86,9 @@ locals { } locals { - username_secret = var.super_user_username_secret_arn == null ? awscc_secretsmanager_secret.super_user_username[0].secret_id : var.super_user_username_secret_arn - password_secret = var.super_user_password_secret_arn == null ? awscc_secretsmanager_secret.super_user_password[0].secret_id : var.super_user_password_secret_arn + super_password_secret = awscc_secretsmanager_secret.super_password.secret_id + admin_username_secret = awscc_secretsmanager_secret.admin_username.secret_id + admin_password_secret = var.admin_password_secret_arn == null ? awscc_secretsmanager_secret.admin_password[0].secret_id : var.admin_password_secret_arn } resource "aws_instance" "server_instance" { ami = data.aws_ami.existing_server_ami.id @@ -88,22 +101,23 @@ resource "aws_instance" "server_instance" { iam_instance_profile = aws_iam_instance_profile.instance_profile.id user_data = templatefile("${path.module}/templates/user_data.tftpl", { - depot_volume_name = local.depot_volume_name - metadata_volume_name = local.metadata_volume_name - logs_volume_name = local.logs_volume_name - p4_server_type = var.p4_server_type - username_secret = local.username_secret - password_secret = local.password_secret - fqdn = var.fully_qualified_domain_name != null ? var.fully_qualified_domain_name : "" - auth_url = var.auth_service_url != null ? var.auth_service_url : "" - is_fsxn = local.is_fsxn - fsxn_password = var.fsxn_password - fsxn_svm_name = var.fsxn_svm_name - fsxn_management_ip = var.fsxn_management_ip - case_sensitive = var.case_sensitive ? 1 : 0 - unicode = var.unicode ? "true" : "false" - selinux = var.selinux ? "true" : "false" - plaintext = var.plaintext ? "true" : "false" + depot_volume_name = local.depot_volume_name + metadata_volume_name = local.metadata_volume_name + logs_volume_name = local.logs_volume_name + p4_server_type = var.p4_server_type + super_password_secret = local.super_password_secret + admin_username_secret = local.admin_username_secret + admin_password_secret = local.admin_password_secret + fqdn = var.fully_qualified_domain_name != null ? var.fully_qualified_domain_name : "" + auth_url = var.auth_service_url != null ? var.auth_service_url : "" + is_fsxn = local.is_fsxn + fsxn_password = var.fsxn_password + fsxn_svm_name = var.fsxn_svm_name + fsxn_management_ip = var.fsxn_management_ip + case_sensitive = var.case_sensitive ? 1 : 0 + unicode = var.unicode ? "true" : "false" + selinux = var.selinux ? "true" : "false" + plaintext = var.plaintext ? "true" : "false" }) vpc_security_group_ids = (var.create_default_sg ? @@ -128,6 +142,12 @@ resource "aws_instance" "server_instance" { Name = "${local.name_prefix}-${var.p4_server_type}-${local.p4_server_az}" }) + # Force destroy-before-create to ensure EBS volumes are detached + # before being re-attached to a new instance (e.g., during AMI updates) + lifecycle { + create_before_destroy = false + } + depends_on = [ netapp-ontap_san_lun-map.depots_lun_map, netapp-ontap_san_lun-map.logs_lun_map, diff --git a/modules/perforce/modules/p4-server/outputs.tf b/modules/perforce/modules/p4-server/outputs.tf index 38b4dece..d038cc3a 100644 --- a/modules/perforce/modules/p4-server/outputs.tf +++ b/modules/perforce/modules/p4-server/outputs.tf @@ -13,18 +13,21 @@ output "security_group_id" { description = "The default security group of your P4 Server instance." } -output "super_user_password_secret_arn" { - value = (var.super_user_password_secret_arn == null ? - awscc_secretsmanager_secret.super_user_password[0].secret_id : - var.super_user_password_secret_arn) - description = "The ARN of the AWS Secrets Manager secret holding your P4 Server super user's password." -} - -output "super_user_username_secret_arn" { - value = (var.super_user_username_secret_arn == null ? - awscc_secretsmanager_secret.super_user_username[0].secret_id : - var.super_user_username_secret_arn) - description = "The ARN of the AWS Secrets Manager secret holding your P4 Server super user's username." +output "super_password_secret_arn" { + value = awscc_secretsmanager_secret.super_password.secret_id + description = "The ARN of the AWS Secrets Manager secret holding the service account (super) password." +} + +output "admin_username_secret_arn" { + value = awscc_secretsmanager_secret.admin_username.secret_id + description = "The ARN of the AWS Secrets Manager secret holding the admin account username." +} + +output "admin_password_secret_arn" { + value = (var.admin_password_secret_arn == null ? + awscc_secretsmanager_secret.admin_password[0].secret_id : + var.admin_password_secret_arn) + description = "The ARN of the AWS Secrets Manager secret holding the admin account password." } output "instance_id" { diff --git a/modules/perforce/modules/p4-server/templates/user_data.tftpl b/modules/perforce/modules/p4-server/templates/user_data.tftpl index d438a591..66e8459b 100644 --- a/modules/perforce/modules/p4-server/templates/user_data.tftpl +++ b/modules/perforce/modules/p4-server/templates/user_data.tftpl @@ -7,8 +7,9 @@ LOGS_VOLUME_NAME=${logs_volume_name} --hx_metadata $METADATA_VOLUME_NAME \ --hx_depots $DEPOT_VOLUME_NAME \ --p4d_type ${p4_server_type} \ - --username ${username_secret} \ - --password ${password_secret} \ + --super_password ${super_password_secret} \ + --admin_username ${admin_username_secret} \ + --admin_password ${admin_password_secret} \ %{ if fqdn != "" ~} --fqdn ${fqdn} \ %{ endif ~} diff --git a/modules/perforce/modules/p4-server/variables.tf b/modules/perforce/modules/p4-server/variables.tf index 86bbbce1..cd20891f 100644 --- a/modules/perforce/modules/p4-server/variables.tf +++ b/modules/perforce/modules/p4-server/variables.tf @@ -238,15 +238,15 @@ variable "internal" { default = false } -variable "super_user_password_secret_arn" { +variable "admin_username" { type = string - description = "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's password here." - default = null + description = "Username for the Perforce admin account (human user). The 'super' service account is always created automatically for internal tooling." + default = "perforce" } -variable "super_user_username_secret_arn" { +variable "admin_password_secret_arn" { type = string - description = "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's username here. Otherwise, the default of 'perforce' will be used." + description = "Optional ARN of existing Secrets Manager secret for admin password. If not provided, a password will be auto-generated." default = null } diff --git a/modules/perforce/outputs.tf b/modules/perforce/outputs.tf index 0987d5aa..e986a422 100644 --- a/modules/perforce/outputs.tf +++ b/modules/perforce/outputs.tf @@ -24,14 +24,19 @@ output "p4_server_security_group_id" { description = "The default security group of your P4 Server instance." } -output "p4_server_super_user_password_secret_arn" { - value = var.p4_server_config != null ? module.p4_server[0].super_user_password_secret_arn : null - description = "The ARN of the AWS Secrets Manager secret holding your P4 Server super user's username." +output "p4_server_super_password_secret_arn" { + value = var.p4_server_config != null ? module.p4_server[0].super_password_secret_arn : null + description = "The ARN of the AWS Secrets Manager secret holding the service account (super) password." } -output "p4_server_super_user_username_secret_arn" { - value = var.p4_server_config != null ? module.p4_server[0].super_user_username_secret_arn : null - description = "The ARN of the AWS Secrets Manager secret holding your P4 Server super user's password." +output "p4_server_admin_username_secret_arn" { + value = var.p4_server_config != null ? module.p4_server[0].admin_username_secret_arn : null + description = "The ARN of the AWS Secrets Manager secret holding the admin account username." +} + +output "p4_server_admin_password_secret_arn" { + value = var.p4_server_config != null ? module.p4_server[0].admin_password_secret_arn : null + description = "The ARN of the AWS Secrets Manager secret holding the admin account password." } output "p4_server_instance_id" { diff --git a/modules/perforce/variables.tf b/modules/perforce/variables.tf index 46245bd8..cdf35c09 100644 --- a/modules/perforce/variables.tf +++ b/modules/perforce/variables.tf @@ -214,8 +214,8 @@ variable "p4_server_config" { existing_security_groups = optional(list(string), []) internal = optional(bool, false) - super_user_password_secret_arn = optional(string, null) - super_user_username_secret_arn = optional(string, null) + admin_username = optional(string, "perforce") + admin_password_secret_arn = optional(string, null) create_default_role = optional(bool, true) custom_role = optional(string, null) @@ -286,9 +286,9 @@ variable "p4_server_config" { internal: "Set this flag to true if you do not want the P4 Server instance to have a public IP." - super_user_password_secret_arn: "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's username here. Otherwise, the default of 'perforce' will be used." + admin_username: "Username for the Perforce admin account. The 'super' service account is always created automatically for internal tooling. Default is 'perforce'." - super_user_username_secret_arn: "If you would like to manage your own super user credentials through AWS Secrets Manager provide the ARN for the super user's password here." + admin_password_secret_arn: "Optional ARN of existing Secrets Manager secret for admin password. If not provided, a password will be auto-generated." create_default_role: "Optional creation of P4 Server default IAM Role with SSM managed instance core policy attached. Default is set to true." @@ -468,7 +468,6 @@ variable "p4_code_review_config" { instance_subnet_id = string super_user_password_secret_arn = optional(string, null) - super_user_username_secret_arn = optional(string, null) p4_code_review_user_password_secret_arn = optional(string, null) p4_code_review_user_username_secret_arn = optional(string, null) custom_config = optional(string, null) @@ -526,8 +525,6 @@ variable "p4_code_review_config" { super_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password." - super_user_username_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user username." - p4_code_review_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password." p4_code_review_user_username_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username." From a881d1d81cc9d64b91e65490cf5f6559190b21a5 Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Thu, 19 Feb 2026 16:32:15 -0500 Subject: [PATCH 5/7] fix(perforce): use super user for all Swarm operations and pin Ubuntu 24.04 Consolidate Swarm authentication to use the super user for both runtime operations (-u) and admin tasks (-U). This simplifies credential management and ensures compatibility with all authentication configurations (SSO, standard password, etc.). Changes: - Use super user for both configure-swarm.sh -u and -U parameters - Ensure super user is standard type (not service account) for p4 protects validation - Remove unused Swarm user credential variables from Terraform modules - Pin P4 Code Review AMI to Ubuntu 24.04 LTS (helix-swarm-optional requires ImageMagick 6) - Update README prerequisites to reflect simplified credential setup --- .../p4-code-review/p4_code_review_x86.pkr.hcl | 4 ++- .../p4-code-review/swarm_instance_init.sh | 34 +++++++------------ .../packer/perforce/p4-server/p4_configure.sh | 8 +++++ modules/perforce/README.md | 2 +- modules/perforce/main.tf | 4 +-- .../perforce/modules/p4-code-review/README.md | 21 +++++------- .../perforce/modules/p4-code-review/ec2.tf | 26 +++++++------- .../perforce/modules/p4-code-review/iam.tf | 2 -- .../modules/p4-code-review/user-data.sh.tpl | 7 ++-- .../modules/p4-code-review/variables.tf | 12 +------ modules/perforce/variables.tf | 12 ++----- 11 files changed, 53 insertions(+), 79 deletions(-) diff --git a/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl b/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl index 0a273ee0..76bd0bee 100644 --- a/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl +++ b/assets/packer/perforce/p4-code-review/p4_code_review_x86.pkr.hcl @@ -14,7 +14,9 @@ locals { data "amazon-ami" "ubuntu" { filters = { - name = "ubuntu/images/hvm-ssd-gp3/ubuntu-*-amd64-server-*" + # Pin to Ubuntu 24.04 LTS (noble) - helix-swarm-optional requires ImageMagick 6 + # which is not available in Ubuntu 25.x+ + name = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" architecture = "x86_64" root-device-type = "ebs" virtualization-type = "hvm" diff --git a/assets/packer/perforce/p4-code-review/swarm_instance_init.sh b/assets/packer/perforce/p4-code-review/swarm_instance_init.sh index f39ab0f8..64b611e8 100644 --- a/assets/packer/perforce/p4-code-review/swarm_instance_init.sh +++ b/assets/packer/perforce/p4-code-review/swarm_instance_init.sh @@ -29,10 +29,9 @@ SWARM_REDIS_PORT="6379" SWARM_FORCE_EXT="y" CUSTOM_CONFIG_FILE="" -# Secret ARNs for fetching credentials from AWS Secrets Manager +# Secret ARN for fetching super user password from AWS Secrets Manager +# The super user is used for both Swarm runtime operations and admin tasks P4D_SUPER_PASSWD_SECRET_ARN="" -SWARM_USER_SECRET_ARN="" -SWARM_PASSWD_SECRET_ARN="" while [[ $# -gt 0 ]]; do case $1 in @@ -68,14 +67,6 @@ while [[ $# -gt 0 ]]; do P4D_SUPER_PASSWD_SECRET_ARN="$2" shift 2 ;; - --swarm-user-secret-arn) - SWARM_USER_SECRET_ARN="$2" - shift 2 - ;; - --swarm-passwd-secret-arn) - SWARM_PASSWD_SECRET_ARN="$2" - shift 2 - ;; *) log_message "Unknown parameter: $1" shift @@ -99,21 +90,20 @@ SWARM_HOSTNAME="${SWARM_HOST#https://}" SWARM_HOSTNAME="${SWARM_HOSTNAME#http://}" log_message "SWARM_HOSTNAME (for configure-swarm.sh): $SWARM_HOSTNAME" -# Service account username is always "super" +# The super user is used for both Swarm runtime operations (-u) and admin tasks (-U) +# This simplifies credential management and works with any authentication configuration P4D_SUPER="super" -# Retrieve credentials from AWS Secrets Manager -log_message "Fetching secrets from AWS Secrets Manager" +# Retrieve super user password from AWS Secrets Manager +log_message "Fetching super user password from AWS Secrets Manager" P4D_SUPER_PASSWD=$(aws secretsmanager get-secret-value --secret-id "$P4D_SUPER_PASSWD_SECRET_ARN" --query SecretString --output text) -SWARM_USER=$(aws secretsmanager get-secret-value --secret-id "$SWARM_USER_SECRET_ARN" --query SecretString --output text) -SWARM_PASSWD=$(aws secretsmanager get-secret-value --secret-id "$SWARM_PASSWD_SECRET_ARN" --query SecretString --output text) -if [ -z "$P4D_SUPER_PASSWD" ] || [ -z "$SWARM_USER" ] || [ -z "$SWARM_PASSWD" ]; then - log_message "ERROR: Failed to fetch secrets from AWS Secrets Manager" +if [ -z "$P4D_SUPER_PASSWD" ]; then + log_message "ERROR: Failed to fetch super user password from AWS Secrets Manager" exit 1 fi -log_message "Successfully fetched secrets" +log_message "Successfully fetched credentials" # P4 Code Review data directory - stores application data and configuration SWARM_DATA_PATH="/opt/perforce/swarm/data" @@ -127,13 +117,15 @@ chmod 775 "$SWARM_DATA_PATH" # Run the official P4 Code Review configuration script # This handles initial setup and P4 Server extension installation +# Using super user for both -u (Swarm user) and -U (admin user) ensures compatibility +# with all authentication configurations (SSO, standard password, etc.) log_message "Running configure-swarm.sh with super user credentials" /opt/perforce/swarm/sbin/configure-swarm.sh \ -n \ -p "$P4D_PORT" \ - -u "$SWARM_USER" \ - -w "$SWARM_PASSWD" \ + -u "$P4D_SUPER" \ + -w "$P4D_SUPER_PASSWD" \ -H "$SWARM_HOSTNAME" \ -e localhost \ -X \ diff --git a/assets/packer/perforce/p4-server/p4_configure.sh b/assets/packer/perforce/p4-server/p4_configure.sh index 30734bfd..22d7ac4b 100644 --- a/assets/packer/perforce/p4-server/p4_configure.sh +++ b/assets/packer/perforce/p4-server/p4_configure.sh @@ -754,6 +754,14 @@ sudo -u perforce p4 -p "$P4PORT" trust -y # Login as super user for admin operations echo "$SUPER_PASSWORD" | sudo -u perforce p4 -p "$P4PORT" -u super login +# Ensure super user is a standard user type (not service account) +# This allows super to pass p4 protects validation required by tools like Swarm +log_message "Ensuring super user is standard type" +sudo -u perforce p4 -p "$P4PORT" -u super user -o super | \ + sed 's/^Type:.*/Type: standard/' > /tmp/super_user.txt +cat /tmp/super_user.txt | sudo -u perforce p4 -p "$P4PORT" -u super user -i -f +rm -f /tmp/super_user.txt + # Create admin user for human administrators log_message "Creating admin user: $ADMIN_USERNAME" diff --git a/modules/perforce/README.md b/modules/perforce/README.md index d03f22cd..257bea0a 100644 --- a/modules/perforce/README.md +++ b/modules/perforce/README.md @@ -227,7 +227,7 @@ packer build perforce_x86.pkr.hcl | [existing\_ecs\_cluster\_name](#input\_existing\_ecs\_cluster\_name) | The name of an existing ECS cluster to use for the Perforce server. If omitted a new cluster will be created. | `string` | `null` | no | | [existing\_security\_groups](#input\_existing\_security\_groups) | A list of existing security group IDs to attach to the shared network load balancer. | `list(string)` | `[]` | no | | [p4\_auth\_config](#input\_p4\_auth\_config) | # General
name: "The string including in the naming of resources related to P4Auth. Default is 'p4-auth'."

project\_prefix : "The project prefix for the P4Auth service. Default is 'cgd'."

environment : "The environment where the P4Auth service will be deployed. Default is 'dev'."

enable\_web\_based\_administration: "Whether to de enable web based administration. Default is 'true'."

debug : "Whether to enable debug mode for the P4Auth service. Default is 'false'."

fully\_qualified\_domain\_name : "The FQDN for the P4Auth Service. This is used for the P4Auth's Perforce configuration."


# Compute
cluster\_name : "The name of the ECS cluster where the P4Auth service will be deployed. Cluster is not created if this variable is null."

container\_name : "The name of the P4Auth service container. Default is 'p4-auth-container'."

container\_port : "The port on which the P4Auth service will be listening. Default is '3000'."

container\_cpu : "The number of CPU units to reserve for the P4Auth service container. Default is '1024'."

container\_memory : "The number of CPU units to reserve for the P4Auth service container. Default is '4096'."

pd4\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4Auth service logs in CloudWatch. Default is 365 days."


# Networking
create\_defaults\_sgs : "Whether to create default security groups for the P4Auth service."

internal : "Set this flag to true if you do not want the P4Auth service to have a public IP."

create\_default\_role : "Whether to create the P4Auth default IAM Role. Default is set to true."

custom\_role : "ARN of a custom IAM Role you wish to use with P4Auth."

admin\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4Auth Administrator username."

admin\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4Auth Administrator password."


# - SCIM -
p4d\_super\_user\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the super user username for p4d."

p4d\_super\_user\_password\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the super user password for p4d."

scim\_bearer\_token\_arn : "If you would like to use SCIM to provision users and groups, you need to set this variable to the ARN of an AWS Secrets Manager secret containing the bearer token."

extra\_env : "Extra configuration environment variables to set on the p4 auth svc container." |
object({
# - General -
name = optional(string, "p4-auth")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
enable_web_based_administration = optional(bool, true)
debug = optional(bool, false)
fully_qualified_domain_name = string

# - Compute -
container_name = optional(string, "p4-auth-container")
container_port = optional(number, 3000)
container_cpu = optional(number, 1024)
container_memory = optional(number, 4096)
p4d_port = optional(string, null)

# - Storage & Logging -
cloudwatch_log_retention_in_days = optional(number, 365)

# - Networking & Security -
service_subnets = optional(list(string), null)
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

certificate_arn = optional(string, null)
create_default_role = optional(bool, true)
custom_role = optional(string, null)
admin_username_secret_arn = optional(string, null)
admin_password_secret_arn = optional(string, null)

# SCIM
p4d_super_user_arn = optional(string, null)
p4d_super_user_password_arn = optional(string, null)
scim_bearer_token_arn = optional(string, null)
extra_env = optional(map(string), null)
})
| `null` | no | -| [p4\_code\_review\_config](#input\_p4\_code\_review\_config) | # General
name: "The string including in the naming of resources related to P4 Code Review. Default is 'p4-code-review'."

project\_prefix : "The project prefix for the P4 Code Review service. Default is 'cgd'."

environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'."

fully\_qualified\_domain\_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration."


# Compute
application\_port : "The port on which the P4 Code Review service will be listening. Default is '80'."

instance\_type : "EC2 instance type for running P4 Code Review. Default is 'm5.large'."

ami\_id : "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI."

p4d\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."

p4charset : "The P4CHARSET environment variable to set for the P4 Code Review instance."

existing\_redis\_connection : "The existing Redis connection for the P4 Code Review service."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days."

ebs\_volume\_size : "Size in GB for the EBS volume that stores P4 Code Review data. Default is '20'."

ebs\_volume\_type : "EBS volume type for P4 Code Review data storage. Default is 'gp3'."

ebs\_volume\_encrypted : "Enable encryption for the EBS volume storing P4 Code Review data. Default is 'true'."

ebs\_availability\_zone : "Availability zone for the EBS volume. Must match the EC2 instance AZ."


# Networking & Security
create\_default\_sgs : "Whether to create default security groups for the P4 Code Review service."

internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP."

instance\_subnet\_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security."

super\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password."

p4\_code\_review\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password."

p4\_code\_review\_user\_username\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username."

custom\_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc."


# Caching
elasticache\_node\_count : "The number of Elasticache nodes to create for the P4 Code Review service. Default is '1'."

elasticache\_node\_type : "The type of Elasticache node to create for the P4 Code Review service. Default is 'cache.t4g.micro'." |
object({
# General
name = optional(string, "p4-code-review")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
fully_qualified_domain_name = string

# Compute
application_port = optional(number, 80)
instance_type = optional(string, "m5.large")
ami_id = optional(string, null)
p4d_port = optional(string, null)
p4charset = optional(string, null)
existing_redis_connection = optional(object({
host = string
port = number
}), null)

# Storage & Logging
cloudwatch_log_retention_in_days = optional(number, 365)
ebs_volume_size = optional(number, 20)
ebs_volume_type = optional(string, "gp3")
ebs_volume_encrypted = optional(bool, true)
ebs_availability_zone = optional(string, null)

# Networking & Security
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)
service_subnets = optional(list(string), null)
instance_subnet_id = string

super_user_password_secret_arn = optional(string, null)
p4_code_review_user_password_secret_arn = optional(string, null)
p4_code_review_user_username_secret_arn = optional(string, null)
custom_config = optional(string, null)

# Caching
elasticache_node_count = optional(number, 1)
elasticache_node_type = optional(string, "cache.t4g.micro")
})
| `null` | no | +| [p4\_code\_review\_config](#input\_p4\_code\_review\_config) | # General
name: "The string including in the naming of resources related to P4 Code Review. Default is 'p4-code-review'."

project\_prefix : "The project prefix for the P4 Code Review service. Default is 'cgd'."

environment : "The environment where the P4 Code Review service will be deployed. Default is 'dev'."

fully\_qualified\_domain\_name : "The FQDN for the P4 Code Review Service. This is used for the P4 Code Review's Perforce configuration."


# Compute
application\_port : "The port on which the P4 Code Review service will be listening. Default is '80'."

instance\_type : "EC2 instance type for running P4 Code Review. Default is 'm5.large'."

ami\_id : "Optional AMI ID for P4 Code Review. If not provided, will use the latest Packer-built AMI."

p4d\_port : "The full URL you will use to access the P4 Depot in clients such P4V and P4Admin. Note, this typically starts with 'ssl:' and ends with the default port of ':1666'."

p4charset : "The P4CHARSET environment variable to set for the P4 Code Review instance."

existing\_redis\_connection : "The existing Redis connection for the P4 Code Review service."


# Storage & Logging
cloudwatch\_log\_retention\_in\_days : "The number of days to retain the P4 Code Review service logs in CloudWatch. Default is 365 days."

ebs\_volume\_size : "Size in GB for the EBS volume that stores P4 Code Review data. Default is '20'."

ebs\_volume\_type : "EBS volume type for P4 Code Review data storage. Default is 'gp3'."

ebs\_volume\_encrypted : "Enable encryption for the EBS volume storing P4 Code Review data. Default is 'true'."

ebs\_availability\_zone : "Availability zone for the EBS volume. Must match the EC2 instance AZ."


# Networking & Security
create\_default\_sgs : "Whether to create default security groups for the P4 Code Review service."

internal : "Set this flag to true if you do not want the P4 Code Review service to have a public IP."

instance\_subnet\_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security."

super\_user\_password\_secret\_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password. The super user is used for both Swarm runtime operations and administrative tasks."

custom\_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc."


# Caching
elasticache\_node\_count : "The number of Elasticache nodes to create for the P4 Code Review service. Default is '1'."

elasticache\_node\_type : "The type of Elasticache node to create for the P4 Code Review service. Default is 'cache.t4g.micro'." |
object({
# General
name = optional(string, "p4-code-review")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
fully_qualified_domain_name = string

# Compute
application_port = optional(number, 80)
instance_type = optional(string, "m5.large")
ami_id = optional(string, null)
p4d_port = optional(string, null)
p4charset = optional(string, null)
existing_redis_connection = optional(object({
host = string
port = number
}), null)

# Storage & Logging
cloudwatch_log_retention_in_days = optional(number, 365)
ebs_volume_size = optional(number, 20)
ebs_volume_type = optional(string, "gp3")
ebs_volume_encrypted = optional(bool, true)
ebs_availability_zone = optional(string, null)

# Networking & Security
create_default_sgs = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)
service_subnets = optional(list(string), null)
instance_subnet_id = string

super_user_password_secret_arn = optional(string, null)
custom_config = optional(string, null)

# Caching
elasticache_node_count = optional(number, 1)
elasticache_node_type = optional(string, "cache.t4g.micro")
})
| `null` | no | | [p4\_server\_config](#input\_p4\_server\_config) | # - General -
name: "The string including in the naming of resources related to P4 Server. Default is 'p4-server'"

project\_prefix: "The project prefix for this workload. This is appended to the beginning of most resource names."

environment: "The current environment (e.g. dev, prod, etc.)"

auth\_service\_url: "The URL for the P4Auth Service."

fully\_qualified\_domain\_name = "The FQDN for the P4 Server. This is used for the P4 Server's Perforce configuration."


# - Compute -
lookup\_existing\_ami : "Whether to lookup the existing Perforce P4 Server AMI."

ami\_prefix: "The AMI prefix to use for the AMI that will be created for P4 Server."

instance\_type: "The instance type for Perforce P4 Server. Defaults to c6g.large."

instance\_architecture: "The architecture of the P4 Server instance. Allowed values are 'arm64' or 'x86\_64'."

IMPORTANT: "Ensure the instance family of the instance type you select supports the instance\_architecture you select. For example, 'c6in' instance family only works for 'x86\_64' architecture, not 'arm64'. For a full list of this mapping, see the AWS Docs for EC2 Naming Conventions: https://docs.aws.amazon.com/ec2/latest/instancetypes/instance-type-names.html"

p4\_server\_type: "The Perforce P4 Server server type. Valid values are 'p4d\_commit' or 'p4d\_replica'."

unicode: "Whether to enable Unicode configuration for P4 Server the -xi flag for p4d. Set to true to enable Unicode support."

selinux: "Whether to apply SELinux label updates for P4 Server. Don't enable this if SELinux is disabled on your target operating system."

case\_sensitive: "Whether or not the server should be case insensitive (Server will run '-C1' mode), or if the server will run with case sensitivity default of the underlying platform. False enables '-C1' mode. Default is set to true."

plaintext: "Whether to enable plaintext authentication for P4 Server. This is not recommended for production environments unless you are using a load balancer for TLS termination. Default is set to false."


# - Storage -
storage\_type: "The type of backing store. Valid values are either 'EBS' or 'FSxN'"

depot\_volume\_size: "The size of the depot volume in GiB. Defaults to 128 GiB."

metadata\_volume\_size: "The size of the metadata volume in GiB. Defaults to 32 GiB."

logs\_volume\_size: "The size of the logs volume in GiB. Defaults to 32 GiB."


# - Networking & Security -
instance\_subnet\_id: "The subnet where the P4 Server instance will be deployed."

instance\_private\_ip: "The private IP address to assign to the P4 Server."

create\_default\_sg : "Whether to create a default security group for the P4 Server instance."

existing\_security\_groups: "A list of existing security group IDs to attach to the P4 Server load balancer."

internal: "Set this flag to true if you do not want the P4 Server instance to have a public IP."

admin\_username: "Username for the Perforce admin account. The 'super' service account is always created automatically for internal tooling. Default is 'perforce'."

admin\_password\_secret\_arn: "Optional ARN of existing Secrets Manager secret for admin password. If not provided, a password will be auto-generated."

create\_default\_role: "Optional creation of P4 Server default IAM Role with SSM managed instance core policy attached. Default is set to true."

custom\_role: "ARN of a custom IAM Role you wish to use with P4 Server." |
object({
# General
name = optional(string, "p4-server")
project_prefix = optional(string, "cgd")
environment = optional(string, "dev")
auth_service_url = optional(string, null)
fully_qualified_domain_name = string

# Compute
lookup_existing_ami = optional(bool, true)
ami_prefix = optional(string, "p4_al2023")

instance_type = optional(string, "c6i.large")
instance_architecture = optional(string, "x86_64")
p4_server_type = optional(string, null)

unicode = optional(bool, false)
selinux = optional(bool, false)
case_sensitive = optional(bool, true)
plaintext = optional(bool, false)

# Storage
storage_type = optional(string, "EBS")
depot_volume_size = optional(number, 128)
metadata_volume_size = optional(number, 32)
logs_volume_size = optional(number, 32)

# Networking & Security
instance_subnet_id = optional(string, null)
instance_private_ip = optional(string, null)
create_default_sg = optional(bool, true)
existing_security_groups = optional(list(string), [])
internal = optional(bool, false)

admin_username = optional(string, "perforce")
admin_password_secret_arn = optional(string, null)

create_default_role = optional(bool, true)
custom_role = optional(string, null)

# FSxN
fsxn_password = optional(string, null)
fsxn_filesystem_security_group_id = optional(string, null)
protocol = optional(string, null)
fsxn_region = optional(string, null)
fsxn_management_ip = optional(string, null)
fsxn_svm_name = optional(string, null)
amazon_fsxn_svm_id = optional(string, null)
fsxn_aws_profile = optional(string, null)
})
| `null` | no | | [project\_prefix](#input\_project\_prefix) | The project prefix for this workload. This is appended to the beginning of most resource names. | `string` | `"cgd"` | no | | [route53\_private\_hosted\_zone\_name](#input\_route53\_private\_hosted\_zone\_name) | The name of the private Route53 Hosted Zone for the Perforce resources. | `string` | `null` | no | diff --git a/modules/perforce/main.tf b/modules/perforce/main.tf index bc8c4356..946f270a 100644 --- a/modules/perforce/main.tf +++ b/modules/perforce/main.tf @@ -159,9 +159,7 @@ module "p4_code_review" { elasticache_node_count = var.p4_code_review_config.elasticache_node_count elasticache_node_type = var.p4_code_review_config.elasticache_node_type - super_user_password_secret_arn = module.p4_server[0].super_password_secret_arn - p4_code_review_user_password_secret_arn = module.p4_server[0].admin_password_secret_arn - p4_code_review_user_username_secret_arn = module.p4_server[0].admin_username_secret_arn + super_user_password_secret_arn = module.p4_server[0].super_password_secret_arn custom_config = var.p4_code_review_config.custom_config diff --git a/modules/perforce/modules/p4-code-review/README.md b/modules/perforce/modules/p4-code-review/README.md index 7aa625ee..6e9a6fc0 100644 --- a/modules/perforce/modules/p4-code-review/README.md +++ b/modules/perforce/modules/p4-code-review/README.md @@ -18,27 +18,26 @@ This module deploys the following resources: ## Prerequisites -P4 Code Review needs to be able to connect to a P4 Server. P4 Code Review leverages the same authentication mechanism as P4 Server, and needs to install required plugins on the upstream P4 Server instance during setup. This happens automatically, but P4 Code Review requires an administrative user's credentials to be able to initially connect. These credentials are provided to the module through variables specifying AWS Secrets Manager secrets, and then pulled into the P4 Code Review instance during startup. See the `p4d_super_user_arn`, `p4d_super_user_password_arn`, `p4d_swarm_user_arn`, and `p4d_swarm_password_arn` variables below for more details. +P4 Code Review needs to be able to connect to a P4 Server. P4 Code Review leverages the same authentication mechanism as P4 Server, and needs to install required plugins on the upstream P4 Server instance during setup. This happens automatically using the P4 Server's `super` user credentials, which are provided to the module through the `super_user_password_secret_arn` variable and pulled into the P4 Code Review instance during startup. -The [P4 Server submodule](../p4-server/README.md) creates an administrative user on initial deployment, and stores the credentials in AWS Secrets manager. The ARN of the credentials secret is then made available as a Terraform output from the module, and can be referenced elsewhere. The is done by default by the parent Perforce module. +The [P4 Server submodule](../p4-server/README.md) creates the `super` user on initial deployment and stores the password in AWS Secrets Manager. The ARN of the secret is then made available as a Terraform output from the module and can be referenced elsewhere. This is done by default by the parent Perforce module. -Should you need to manually create the administrative user secret the following AWS CLI command may prove useful: +Should you need to manually create the super user password secret, the following AWS CLI command may prove useful: ```bash aws secretsmanager create-secret \ - --name P4CodeReviewSuperUser \ - --description "P4 Code Review Super User" \ - --secret-string "{\"username\":\"swarm\",\"password\":\"EXAMPLE-PASSWORD\"}" + --name P4SuperUserPassword \ + --description "P4 Server Super User Password" \ + --secret-string "EXAMPLE-PASSWORD" ``` -You can then provide these credentials as variables when you define the P4 Code Review module in your Terraform configurations (the parent Perforce module does this for you): +You can then provide this credential as a variable when you define the P4 Code Review module in your Terraform configurations (the parent Perforce module does this for you): ```hcl module "p4_code_review" { source = "modules/perforce/modules/p4-code-review" ... - p4d_super_user_arn = "arn:aws:secretsmanager:::secret:P4CodeReviewSuperUser-a1b2c3:username::" - p4d_super_user_password_arn = "arn:aws:secretsmanager:::secret:P4CodeReviewSuperUser-a1b2c3:password::" + super_user_password_secret_arn = "arn:aws:secretsmanager:::secret:P4SuperUserPassword-a1b2c3" } ``` @@ -242,10 +241,8 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [instance\_subnet\_id](#input\_instance\_subnet\_id) | The subnet ID where the EC2 instance will be launched. Should be a private subnet for security. | `string` | n/a | yes | -| [p4\_code\_review\_user\_password\_secret\_arn](#input\_p4\_code\_review\_user\_password\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review password. | `string` | n/a | yes | -| [p4\_code\_review\_user\_username\_secret\_arn](#input\_p4\_code\_review\_user\_username\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review username. | `string` | n/a | yes | | [subnets](#input\_subnets) | A list of subnets for ElastiCache Redis deployment. Private subnets are recommended. | `list(string)` | n/a | yes | -| [super\_user\_password\_secret\_arn](#input\_super\_user\_password\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user password. | `string` | n/a | yes | +| [super\_user\_password\_secret\_arn](#input\_super\_user\_password\_secret\_arn) | ARN of the AWS Secrets Manager secret containing the P4 super user password. The super user is used for both Swarm runtime operations and administrative tasks. | `string` | n/a | yes | | [vpc\_id](#input\_vpc\_id) | The ID of the existing VPC you would like to deploy P4 Code Review into. | `string` | n/a | yes | | [alb\_access\_logs\_bucket](#input\_alb\_access\_logs\_bucket) | ID of the S3 bucket for P4 Code Review ALB access log storage. If access logging is enabled and this is null the module creates a bucket. | `string` | `null` | no | | [alb\_access\_logs\_prefix](#input\_alb\_access\_logs\_prefix) | Log prefix for P4 Code Review ALB access logs. If null the project prefix and module name are used. | `string` | `null` | no | diff --git a/modules/perforce/modules/p4-code-review/ec2.tf b/modules/perforce/modules/p4-code-review/ec2.tf index 57c88206..d7396249 100644 --- a/modules/perforce/modules/p4-code-review/ec2.tf +++ b/modules/perforce/modules/p4-code-review/ec2.tf @@ -52,20 +52,18 @@ resource "aws_launch_template" "swarm_instance" { # User data script handles EBS volume attachment, mounting, and Swarm configuration user_data = base64encode(templatefile("${path.module}/user-data.sh.tpl", { - region = data.aws_region.current.name - device_name = local.ebs_device_name - mount_path = local.host_data_path - module_identifier = local.module_identifier - p4d_port = var.p4d_port - p4charset = var.p4charset - swarm_host = "https://${var.fully_qualified_domain_name}" - swarm_redis = var.existing_redis_connection != null ? var.existing_redis_connection.host : aws_elasticache_cluster.cluster[0].cache_nodes[0].address - swarm_redis_port = var.existing_redis_connection != null ? tostring(var.existing_redis_connection.port) : tostring(aws_elasticache_cluster.cluster[0].cache_nodes[0].port) - swarm_force_ext = "y" - super_user_password_secret_arn = var.super_user_password_secret_arn - p4_code_review_user_username_secret_arn = var.p4_code_review_user_username_secret_arn - p4_code_review_user_password_secret_arn = var.p4_code_review_user_password_secret_arn - custom_config = var.custom_config + region = data.aws_region.current.name + device_name = local.ebs_device_name + mount_path = local.host_data_path + module_identifier = local.module_identifier + p4d_port = var.p4d_port + p4charset = var.p4charset + swarm_host = "https://${var.fully_qualified_domain_name}" + swarm_redis = var.existing_redis_connection != null ? var.existing_redis_connection.host : aws_elasticache_cluster.cluster[0].cache_nodes[0].address + swarm_redis_port = var.existing_redis_connection != null ? tostring(var.existing_redis_connection.port) : tostring(aws_elasticache_cluster.cluster[0].cache_nodes[0].port) + swarm_force_ext = "y" + super_user_password_secret_arn = var.super_user_password_secret_arn + custom_config = var.custom_config })) metadata_options { diff --git a/modules/perforce/modules/p4-code-review/iam.tf b/modules/perforce/modules/p4-code-review/iam.tf index 43ad7df9..ab2de314 100644 --- a/modules/perforce/modules/p4-code-review/iam.tf +++ b/modules/perforce/modules/p4-code-review/iam.tf @@ -22,8 +22,6 @@ data "aws_iam_policy_document" "secrets_manager_policy" { ] resources = [ var.super_user_password_secret_arn, - var.p4_code_review_user_username_secret_arn, - var.p4_code_review_user_password_secret_arn, ] } } diff --git a/modules/perforce/modules/p4-code-review/user-data.sh.tpl b/modules/perforce/modules/p4-code-review/user-data.sh.tpl index 2a248ad4..f07d027a 100644 --- a/modules/perforce/modules/p4-code-review/user-data.sh.tpl +++ b/modules/perforce/modules/p4-code-review/user-data.sh.tpl @@ -21,10 +21,9 @@ SWARM_REDIS="${swarm_redis}" SWARM_REDIS_PORT="${swarm_redis_port}" SWARM_FORCE_EXT="${swarm_force_ext}" -# Secret ARNs for AWS Secrets Manager +# Secret ARN for AWS Secrets Manager +# The super user is used for both Swarm runtime and admin operations P4D_SUPER_PASSWD_SECRET_ARN="${super_user_password_secret_arn}" -SWARM_USER_SECRET_ARN="${p4_code_review_user_username_secret_arn}" -SWARM_PASSWD_SECRET_ARN="${p4_code_review_user_password_secret_arn}" # Logging function log() { @@ -242,8 +241,6 @@ log "No custom config provided" --swarm-redis-port "$SWARM_REDIS_PORT" \ --swarm-force-ext "$SWARM_FORCE_EXT" \ --p4d-super-passwd-secret-arn "$P4D_SUPER_PASSWD_SECRET_ARN" \ - --swarm-user-secret-arn "$SWARM_USER_SECRET_ARN" \ - --swarm-passwd-secret-arn "$SWARM_PASSWD_SECRET_ARN" \ --custom-config-file "$CUSTOM_CONFIG_FILE" log "=========================================" diff --git a/modules/perforce/modules/p4-code-review/variables.tf b/modules/perforce/modules/p4-code-review/variables.tf index 38749b30..78243de0 100644 --- a/modules/perforce/modules/p4-code-review/variables.tf +++ b/modules/perforce/modules/p4-code-review/variables.tf @@ -165,17 +165,7 @@ variable "certificate_arn" { variable "super_user_password_secret_arn" { type = string - description = "Optionally provide the ARN of an AWS Secret for the p4d super user password." -} - -variable "p4_code_review_user_username_secret_arn" { - type = string - description = "Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review username." -} - -variable "p4_code_review_user_password_secret_arn" { - type = string - description = "Optionally provide the ARN of an AWS Secret for the p4d P4 Code Review password." + description = "ARN of the AWS Secrets Manager secret containing the P4 super user password. The super user is used for both Swarm runtime operations and administrative tasks." } variable "custom_config" { diff --git a/modules/perforce/variables.tf b/modules/perforce/variables.tf index cdf35c09..1ba7cba5 100644 --- a/modules/perforce/variables.tf +++ b/modules/perforce/variables.tf @@ -467,10 +467,8 @@ variable "p4_code_review_config" { service_subnets = optional(list(string), null) instance_subnet_id = string - super_user_password_secret_arn = optional(string, null) - p4_code_review_user_password_secret_arn = optional(string, null) - p4_code_review_user_username_secret_arn = optional(string, null) - custom_config = optional(string, null) + super_user_password_secret_arn = optional(string, null) + custom_config = optional(string, null) # Caching elasticache_node_count = optional(number, 1) @@ -523,11 +521,7 @@ variable "p4_code_review_config" { instance_subnet_id : "The subnet ID where the EC2 instance will be launched. Should be a private subnet for security." - super_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password." - - p4_code_review_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's password." - - p4_code_review_user_username_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Code Review user's username." + super_user_password_secret_arn : "Optionally provide the ARN of an AWS Secret for the P4 Server super user password. The super user is used for both Swarm runtime operations and administrative tasks." custom_config : "JSON string with additional Swarm configuration to merge with the generated config.php. Use this for SSO/SAML setup, notifications, Jira integration, etc." From 9aa4050239611f879332bf4302de6522049572cf Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Fri, 20 Feb 2026 12:27:52 -0500 Subject: [PATCH 6/7] fix(packer/p4-code-review): wait for dpkg lock before package operations Add wait_for_apt function that polls for the dpkg lock to be released before running apt-get commands. This fixes intermittent build failures caused by unattended-upgrades holding the lock after instance boot. --- .../perforce/p4-code-review/swarm_setup.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/assets/packer/perforce/p4-code-review/swarm_setup.sh b/assets/packer/perforce/p4-code-review/swarm_setup.sh index e1498d5f..d4fcfaa4 100644 --- a/assets/packer/perforce/p4-code-review/swarm_setup.sh +++ b/assets/packer/perforce/p4-code-review/swarm_setup.sh @@ -20,6 +20,24 @@ fi log_message "Starting P4 Code Review (Swarm) installation" +# Wait for dpkg lock to be released (unattended-upgrades may be running) +wait_for_apt() { + local max_wait=300 + local wait_time=0 + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do + if [ $wait_time -ge $max_wait ]; then + log_message "ERROR: Timed out waiting for apt lock after ${max_wait}s" + exit 1 + fi + log_message "Waiting for apt lock to be released..." + sleep 5 + wait_time=$((wait_time + 5)) + done +} + +log_message "Waiting for any background package operations to complete" +wait_for_apt + # Update package lists log_message "Updating package lists" apt-get update From f7e56004a34c9a1129182a0232e01df90f989b3a Mon Sep 17 00:00:00 2001 From: Gabriel Batista Date: Fri, 20 Feb 2026 14:16:40 -0500 Subject: [PATCH 7/7] fix(perforce/p4-code-review): handle ASG instance replacement race condition Wait for the previous instance to terminate before detaching the EBS volume. ASG launches new instances before terminating old ones, which can cause the new instance to fail when trying to attach the volume while the old instance is still running or shutting down. The script now waits up to 5 minutes for the old instance to reach terminated state before proceeding with force detach. --- .../modules/p4-code-review/user-data.sh.tpl | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/modules/perforce/modules/p4-code-review/user-data.sh.tpl b/modules/perforce/modules/p4-code-review/user-data.sh.tpl index f07d027a..3f36fd16 100644 --- a/modules/perforce/modules/p4-code-review/user-data.sh.tpl +++ b/modules/perforce/modules/p4-code-review/user-data.sh.tpl @@ -80,27 +80,50 @@ if [ "$ATTACHED_INSTANCE" == "$INSTANCE_ID" ]; then elif [ "$ATTACHED_INSTANCE" != "none" ] && [ "$ATTACHED_INSTANCE" != "null" ]; then log "Volume is attached to different instance $ATTACHED_INSTANCE - checking instance state" - # Check if the attached instance is terminated before force detaching - INSTANCE_STATE=$(aws ec2 describe-instances \ - --region "$REGION" \ - --instance-ids "$ATTACHED_INSTANCE" \ - --query 'Reservations[0].Instances[0].State.Name' \ - --output text 2>/dev/null || echo "unknown") + # Wait for attached instance to terminate (handles ASG replacement race condition) + # ASG launches new instance before terminating old one, so we may need to wait + MAX_WAIT=300 + WAIT_TIME=0 + while [ $WAIT_TIME -lt $MAX_WAIT ]; do + INSTANCE_STATE=$(aws ec2 describe-instances \ + --region "$REGION" \ + --instance-ids "$ATTACHED_INSTANCE" \ + --query 'Reservations[0].Instances[0].State.Name' \ + --output text 2>/dev/null || echo "unknown") - log "Previous instance $ATTACHED_INSTANCE state: $INSTANCE_STATE" + log "Previous instance $ATTACHED_INSTANCE state: $INSTANCE_STATE" - if [ "$INSTANCE_STATE" = "terminated" ] || [ "$INSTANCE_STATE" = "unknown" ]; then - log "Previous instance is terminated/unknown, safe to force detach" - aws ec2 detach-volume \ - --region "$REGION" \ - --volume-id "$VOLUME_ID" \ - --force 2>&1 | tee -a /tmp/swarm-setup.log || log "Warning: Force detach may have failed" - else - log "ERROR: Volume attached to running instance $ATTACHED_INSTANCE (state: $INSTANCE_STATE)" + if [ "$INSTANCE_STATE" = "terminated" ] || [ "$INSTANCE_STATE" = "unknown" ]; then + log "Previous instance is terminated/unknown, safe to force detach" + break + elif [ "$INSTANCE_STATE" = "shutting-down" ] || [ "$INSTANCE_STATE" = "stopping" ]; then + log "Previous instance is $INSTANCE_STATE, waiting for termination..." + sleep 10 + WAIT_TIME=$((WAIT_TIME + 10)) + elif [ "$INSTANCE_STATE" = "running" ]; then + # Instance still running - could be ASG replacement in progress + # Wait a bit to see if it starts terminating + log "Previous instance still running, waiting to see if ASG terminates it..." + sleep 10 + WAIT_TIME=$((WAIT_TIME + 10)) + else + log "ERROR: Unexpected instance state: $INSTANCE_STATE" + log "Cannot safely detach volume - manual intervention required" + exit 1 + fi + done + + if [ $WAIT_TIME -ge $MAX_WAIT ]; then + log "ERROR: Timed out waiting for previous instance to terminate after $${MAX_WAIT}s" log "Cannot safely detach volume - manual intervention required" exit 1 fi + aws ec2 detach-volume \ + --region "$REGION" \ + --volume-id "$VOLUME_ID" \ + --force 2>&1 | tee -a /tmp/swarm-setup.log || log "Warning: Force detach may have failed" + # Wait for detachment with timeout log "Waiting up to 2 minutes for volume to become available..." for i in {1..24}; do