From b7b9caa7310475c52894ca20015c735f41dbaaeb Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:27:11 +0200 Subject: [PATCH 1/5] Fix ssh key re-create same key issue for 2 envs, instead re-use --- environments/development/backend/compute.tf | 2 ++ environments/development/frontend/compute.tf | 3 ++- modules/compute-ec2/main.tf | 10 ++++++---- modules/compute-ec2/variables.tf | 7 +++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/environments/development/backend/compute.tf b/environments/development/backend/compute.tf index b369537..b0909ab 100644 --- a/environments/development/backend/compute.tf +++ b/environments/development/backend/compute.tf @@ -6,7 +6,9 @@ module "compute" { source = "../../../modules/compute-ec2" name_prefix = "ff-dev" environment = "development" + environment_type = "frontend" subnet_ids = module.network.public_subnet_ids security_group_id = module.network.host_sg_id key_name = "ff-dev-admin" + create_key_pair = true } diff --git a/environments/development/frontend/compute.tf b/environments/development/frontend/compute.tf index b326fb6..56fd04d 100644 --- a/environments/development/frontend/compute.tf +++ b/environments/development/frontend/compute.tf @@ -9,5 +9,6 @@ module "compute" { environment_type = "frontend" subnet_ids = module.network.public_subnet_ids security_group_id = module.network.host_sg_id - key_name = "ff-dev-frontend-admin" + key_name = "ff-dev-admin" + create_key_pair = false } diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index 5f9900d..f3287f1 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -61,8 +61,8 @@ resource "aws_instance" "app_server" { associate_public_ip_address = true iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.key_name - depends_on = [aws_key_pair.dev_admin] - user_data = templatefile("${path.module}/user-data.sh", { + depends_on = var.create_key_pair ? [aws_key_pair.dev_admin] : [] + user_data = templatefile("${path.module}/user-data.sh", { ENVIRONMENT_TYPE = var.environment_type }) @@ -75,12 +75,14 @@ resource "aws_instance" "app_server" { # SSH KEY PAIR resource "aws_key_pair" "dev_admin" { - key_name = "ff-dev-admin" + count = var.create_key_pair ? 1 : 0 + key_name = var.key_name public_key = file("${path.module}/../../ssh/ff-dev-admin.pub") tags = { - Name = "ff-dev-admin" + Name = var.key_name Environment = var.environment ManagedBy = "Terraform" } } + diff --git a/modules/compute-ec2/variables.tf b/modules/compute-ec2/variables.tf index df7c440..106cf1c 100644 --- a/modules/compute-ec2/variables.tf +++ b/modules/compute-ec2/variables.tf @@ -46,3 +46,10 @@ variable "user_data_script" { type = string default = "" } + +variable "create_key_pair" { + type = bool + default = true + description = "Whether to create the key pair or just reuse an existing one" +} + From 90664d42b809f3f33d82a08bfa2fdcb1c4d4f74b Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:41:03 +0200 Subject: [PATCH 2/5] Each environment (development, staging, production) runs only when its matching branch is pushed --- .github/workflows/terraform-deploy.yml | 142 ++++++++++++++----------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 90749ae..f9608f6 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -17,29 +17,26 @@ permissions: contents: read pull-requests: write +# ─────────────────────────────────────────────── +# DEVELOPMENT JOB +# ─────────────────────────────────────────────── jobs: - deploy: - name: Deploy Infrastructure + deploy-development: + name: Deploy Infrastructure (development) runs-on: ubuntu-latest - environment: ${{ matrix.env }} + if: github.ref == 'refs/heads/develop' strategy: fail-fast: false matrix: - env: [development, staging, production] + # env: [development, staging, production] stack: [backend, frontend] - include: - - env: development - branch: develop - - env: staging - branch: staging - - env: production - branch: main + environment: development steps: - - name: Checkout repository + - name: Checkout Repository uses: actions/checkout@v4 - - name: Configure AWS credentials from OIDC + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::${{ secrets.IAM_INFRA_ROLE_ID }}:role/flagging-infra-ci @@ -51,73 +48,98 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v3 - - name: Set dirs/keys + - name: Set paths id: paths run: | - echo "env_dir=environments/${{ matrix.env }}/${{ matrix.stack }}" >> $GITHUB_OUTPUT - echo "s3_key=environments/${{ matrix.env }}/${{ matrix.stack }}/terraform.tfstate" >> $GITHUB_OUTPUT + echo "env_dir=environments/development/${{ matrix.stack }}" >> $GITHUB_OUTPUT + echo "s3_key=environments/development/${{ matrix.stack }}/terraform.tfstate" >> $GITHUB_OUTPUT - - name: Terraform Init (reconfigure backend) + - name: Terraform Init run: | terraform -chdir=${{ steps.paths.outputs.env_dir }} init -reconfigure \ -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ -backend-config="key=${{ steps.paths.outputs.s3_key }}" \ -backend-config="region=${{ secrets.AWS_REGION }}" \ -backend-config="encrypt=true" - env: - AWS_REGION: ${{ secrets.AWS_REGION }} - # S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }} - # S3_BUCKET_PATH: ${{ secrets.S3_BUCKET_PATH }} - # TF_VAR_s3_bucket_name: ${{ github.ref_name == 'develop' && secrets.S3_BUCKET_NAME || github.ref_name == 'staging' && secrets.S3_BUCKET_NAME || github.ref_name == 'main' && secrets.S3_BUCKET_NAME }} - # TF_VAR_s3_bucket_path: ${{ github.ref_name == 'develop' && secrets.S3_BUCKET_PATH || github.ref_name == 'staging' && secrets.S3_BUCKET_PATH || github.ref_name == 'main' && secrets.S3_BUCKET_PATH }} - - - name: Import pre-existing AWS resources (backend only) - if: > - github.event_name == 'workflow_dispatch' && - github.event.inputs.import == 'true' && - matrix.stack == 'backend' - run: | - echo "Importing pre-existing resources into state for ${{ matrix.env }}/backend ..." - terraform -chdir=${{ steps.paths.outputs.env_dir }} import module.compute.aws_iam_role.ec2_role ff-dev-ec2-role || true - terraform -chdir=${{ steps.paths.outputs.env_dir }} import module.compute.aws_iam_instance_profile.ec2_profile ff-dev-ec2-profile || true - terraform -chdir=${{ steps.paths.outputs.env_dir }} import module.compute.aws_key_pair.dev_admin ff-dev-admin || true - - name: Terraform Plan - id: plan - run: terraform -chdir=${{ steps.paths.outputs.env_dir }} plan -no-color -out=tfplan + - name: Terraform Plan & Apply + run: | + terraform -chdir=${{ steps.paths.outputs.env_dir }} plan -out=tfplan + terraform -chdir=${{ steps.paths.outputs.env_dir }} apply -auto-approve tfplan env: - # common TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} TF_VAR_ghcr_token: ${{ secrets.GHCR_PAT }} - # backend-only secrets (blank for frontend; module uses count to skip) TF_VAR_admin_key: ${{ matrix.stack == 'backend' && secrets.ADMIN_KEY_DEV || '' }} TF_VAR_sa_password: ${{ matrix.stack == 'backend' && secrets.SA_PASSWORD_DEV || '' }} TF_VAR_redis_password: ${{ matrix.stack == 'backend' && secrets.REDIS_PASSWORD_DEV || '' }} - - name: Show Plan Summary - run: terraform -chdir=${{ steps.paths.outputs.env_dir }} show -no-color tfplan > ${{ steps.paths.outputs.env_dir }}/plan.txt +# ─────────────────────────────────────────────── +# STAGING JOB +# ─────────────────────────────────────────────── + deploy-staging: + name: Deploy Infrastructure (staging) + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/staging' + strategy: + fail-fast: false + matrix: + stack: [backend, frontend] + environment: staging + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 - - name: Upload Plan Output - uses: actions/upload-artifact@v4 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 with: - name: tfplan-${{ matrix.env }}-${{ matrix.stack }} - path: ${{ steps.paths.outputs.env_dir }}/plan.txt + role-to-assume: arn:aws:iam::${{ secrets.IAM_INFRA_ROLE_ID }}:role/flagging-infra-ci + aws-region: ${{ secrets.AWS_REGION }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform Plan + run: | + terraform -chdir=environments/staging/${{ matrix.stack }} init -reconfigure \ + -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ + -backend-config="key=environments/staging/${{ matrix.stack }}/terraform.tfstate" \ + -backend-config="region=${{ secrets.AWS_REGION }}" \ + -backend-config="encrypt=true" + terraform -chdir=environments/staging/${{ matrix.stack }} plan -no-color + +# ─────────────────────────────────────────────── +# PRODUCTION JOB +# ─────────────────────────────────────────────── + deploy-production: + name: Deploy Infrastructure (production) + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + strategy: + fail-fast: false + matrix: + stack: [backend, frontend] + environment: production + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 - - name: Comment Plan on PR - if: github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@v2 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 with: - header: "Terraform Plan – ${{ matrix.env }}/${{ matrix.stack }}" - path: ${{ steps.paths.outputs.env_dir }}/plan.txt + role-to-assume: arn:aws:iam::${{ secrets.IAM_INFRA_ROLE_ID }}:role/flagging-infra-ci + aws-region: ${{ secrets.AWS_REGION }} - - name: Terraform Apply - if: matrix.env == 'development' && github.ref == 'refs/heads/develop' - run: terraform -chdir=${{ steps.paths.outputs.env_dir }} apply -auto-approve - env: - TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} - TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} - TF_VAR_ghcr_token: ${{ secrets.GHCR_PAT }} - TF_VAR_admin_key: ${{ matrix.stack == 'backend' && secrets.ADMIN_KEY_DEV || '' }} - TF_VAR_sa_password: ${{ matrix.stack == 'backend' && secrets.SA_PASSWORD_DEV || '' }} - TF_VAR_redis_password: ${{ matrix.stack == 'backend' && secrets.REDIS_PASSWORD_DEV || '' }} + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform Plan + run: | + terraform -chdir=environments/production/${{ matrix.stack }} init -reconfigure \ + -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ + -backend-config="key=environments/production/${{ matrix.stack }}/terraform.tfstate" \ + -backend-config="region=${{ secrets.AWS_REGION }}" \ + -backend-config="encrypt=true" + terraform -chdir=environments/production/${{ matrix.stack }} plan -no-color From 2c2948f53b090c41a7ae7a53ee34fe890d05dd3f Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:47:41 +0200 Subject: [PATCH 3/5] Terraform fmt modules\compute-ec2\main.tf --- modules/compute-ec2/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index f3287f1..ccfe767 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -62,7 +62,7 @@ resource "aws_instance" "app_server" { iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.key_name depends_on = var.create_key_pair ? [aws_key_pair.dev_admin] : [] - user_data = templatefile("${path.module}/user-data.sh", { + user_data = templatefile("${path.module}/user-data.sh", { ENVIRONMENT_TYPE = var.environment_type }) From 1f96af2bb30b955bf8518fde59911df2d47c628f Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:54:01 +0200 Subject: [PATCH 4/5] Use count directly on the EC2 resource --- modules/compute-ec2/main.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index ccfe767..437e129 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -61,8 +61,7 @@ resource "aws_instance" "app_server" { associate_public_ip_address = true iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.key_name - depends_on = var.create_key_pair ? [aws_key_pair.dev_admin] : [] - user_data = templatefile("${path.module}/user-data.sh", { + user_data = templatefile("${path.module}/user-data.sh", { ENVIRONMENT_TYPE = var.environment_type }) From 6879ad9f372c74e6d8be989b9a5d9159d1e7db85 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:54:15 +0200 Subject: [PATCH 5/5] Use count directly on the EC2 resource --- modules/compute-ec2/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index 437e129..863a4b7 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -61,7 +61,7 @@ resource "aws_instance" "app_server" { associate_public_ip_address = true iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.key_name - user_data = templatefile("${path.module}/user-data.sh", { + user_data = templatefile("${path.module}/user-data.sh", { ENVIRONMENT_TYPE = var.environment_type })