From bb03c7edcd2cead7bb7c3e4840b80e8ea0dce3a0 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 27 Oct 2025 01:44:57 +0200 Subject: [PATCH 01/86] CI OIDC Test and README for the REPO --- .github/workflows/terraform-deploy.yml | 37 +++++++++ README.md | 108 ++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/terraform-deploy.yml diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml new file mode 100644 index 0000000..c82bfdf --- /dev/null +++ b/.github/workflows/terraform-deploy.yml @@ -0,0 +1,37 @@ +name: Terraform Deploy + +on: + push: + branches: [ main, staging,develop ] + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + deploy: + name: Deploy Infrastructure + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure AWS credentials from OIDC + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ secrets.IAM_INFRA_ROLE_ID }}:role/flagging-infra-ci + aws-region: ${{ secrets.AWS_REGION }} + + - name: Verify AWS access + run: aws sts get-caller-identity + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform Init & Apply + run: | + cd environments/dev + terraform init + terraform apply -auto-approve diff --git a/README.md b/README.md index 53953ff..bbfb663 100644 --- a/README.md +++ b/README.md @@ -1 +1,107 @@ -# flagging-infrastructure \ No newline at end of file +# ๐Ÿ—๏ธ Feature Flags Infrastructure + +This repository manages the **cloud infrastructure** for the **Feature Flags Platform**, which includes: + +- The **backend API** (built with .NET) +- The **frontend dashboard** (built with Vue.js) +- Supporting services such as **Redis** and **SQL Server** + +All infrastructure is defined using **Terraform (Infrastructure as Code)** and deployed to **AWS**. + +--- + +## ๐Ÿ“‚ Repository Structure + +feature-flags-infra/ +โ”œโ”€โ”€ .github/workflows/ โ†’ CI/CD automation for Terraform and deployments +โ”œโ”€โ”€ bootstrap/ โ†’ One-time setup for remote Terraform state (S3 + DynamoDB) +โ”œโ”€โ”€ environments/ โ†’ Per-environment Terraform configurations +โ”‚ โ”œโ”€โ”€ development/ โ†’ Dev environment (testing, internal usage) +โ”‚ โ”œโ”€โ”€ staging/ โ†’ Staging environment (QA, integration) +โ”‚ โ””โ”€โ”€ production/ โ†’ Production environment (live deployment) +โ”œโ”€โ”€ modules/ โ†’ Reusable Terraform modules +โ”‚ โ”œโ”€โ”€ compose/ โ†’ Handles Docker Compose deployments on EC2 +โ”‚ โ”œโ”€โ”€ compute-ec2/ โ†’ Provisions EC2 instances and security groups +โ”‚ โ”œโ”€โ”€ dns/ โ†’ Manages DNS records, SSL certs, and optional load balancer +โ”‚ โ””โ”€โ”€ network/ โ†’ Creates VPCs, subnets, and networking resources +โ”œโ”€โ”€ scripts/ โ†’ Helper shell scripts for rendering, deployment, and SSM commands +โ””โ”€โ”€ templates/ โ†’ Template files (e.g., docker-compose.yaml) used for deployments + +markdown +Copy code + +--- + +## ๐Ÿงฉ Key Concepts + +| **Component** | **Description** | +|--------------------|------------------------------------------------------------------------| +| **Terraform** | Used to define, provision, and manage AWS resources. | +| **AWS EC2** | Hosts Docker Compose deployments for API + Frontend containers. | +| **AWS SSM** | Enables secure, keyless remote commands and configuration. | +| **Docker Compose** | Orchestrates multi-container setup (API, Frontend, Redis, SQL Server). | +| **GitHub Actions** | Automates build, plan, and deploy workflows across environments. | + +--- + +## โš™๏ธ How It Works + +1. **API & Frontend Repositories** + - Build and push Docker images to **GitHub Container Registry (GHCR)**. + - Trigger a `repository_dispatch` event to this infrastructure repository. + +2. **Infrastructure Repository** + - Terraform provisions AWS resources per environment (**Development**, **Staging**, **Production**). + - AWS **SSM** executes deployment commands on EC2 instances such as: + + ```text + docker compose pull && docker compose up -d + ``` + +3. **Environment Isolation** + - Each environment has its own **Terraform state**, **variables**, and **resource set**. + - Promoting changes is done by merging `develop โ†’ staging โ†’ main`. + +--- + +## ๐Ÿš€ Environments + +| **Environment** | **Branch** | **Purpose** | **Trigger** | +|------------------|------------|--------------|----------------------------------------| +| ๐Ÿงช Development | `develop` | Active feature testing | On merge to `develop` | +| ๐Ÿš€ Staging | `staging` | QA and pre-production testing | On merge to `staging` | +| ๐Ÿ† Production | `main` | Live production deployment | On merge to `main` | + +--- + +## Typical Workflow + +### Bootstrap Terraform Remote State + +Used to create the S3 bucket and DynamoDB table for Terraform state management. + +```bash +cd bootstrap +terraform init +terraform apply -auto-approve + +cd environments/development +terraform init -backend-config=backend.hcl +terraform apply -auto-approve +``` + +## Promote to Staging / Production + +Merge develop โ†’ staging โ†’ main + +GitHub Actions automatically runs terraform apply for each environment + +## Notes for Contributors + +Each environment is fully isolated and can be applied independently. + +Never commit AWS credentials โ€” use GitHub OIDC authentication for Terraform. + +Keep module logic reusable; environment folders should only contain configuration. + +Use tags (e.g., Project, Env) on all resources for cost tracking and organization. From 1055245d1e5842a9040275acdb8808555c6ecd34 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 27 Oct 2025 02:37:58 +0200 Subject: [PATCH 02/86] Bootstrap Terraform backend (S3 + DynamoDB lock) --- .gitignore | 1 + bootstrap/main.tf | 46 ++++++++++++++++++++++++++++++++++++++++++ bootstrap/variables.tf | 14 +++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 bootstrap/main.tf create mode 100644 bootstrap/variables.tf diff --git a/.gitignore b/.gitignore index 6349e36..86319a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Local .terraform directories .terraform/ +.terraform.lock.hcl # .tfstate files *.tfstate diff --git a/bootstrap/main.tf b/bootstrap/main.tf new file mode 100644 index 0000000..85ecfe5 --- /dev/null +++ b/bootstrap/main.tf @@ -0,0 +1,46 @@ +terraform { + required_version = ">= 1.6.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +resource "aws_s3_bucket" "tf_state" { + bucket = var.state_bucket_name +} + +resource "aws_s3_bucket_versioning" "tf_state" { + bucket = aws_s3_bucket.tf_state.id + versioning_configuration { status = "Enabled" } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state" { + bucket = aws_s3_bucket.tf_state.id + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_dynamodb_table" "tf_lock" { + name = var.lock_table_name + billing_mode = "PAY_PER_REQUEST" + hash_key = "LockID" + + attribute { + name = "LockID" + type = "S" + } +} + +output "state_bucket" { value = aws_s3_bucket.tf_state.bucket } +output "lock_table" { value = aws_dynamodb_table.tf_lock.name } +output "region" { value = var.aws_region } diff --git a/bootstrap/variables.tf b/bootstrap/variables.tf new file mode 100644 index 0000000..b7eda04 --- /dev/null +++ b/bootstrap/variables.tf @@ -0,0 +1,14 @@ +variable "aws_region" { + type = string + default = "af-south-1" +} + +variable "state_bucket_name" { + type = string + default = "flagging-infra-tf-state-code-crafters" +} + +variable "lock_table_name" { + type = string + default = "flagging-infra-tf-locks" +} \ No newline at end of file From 26fd0903a95c7763ee1976c05b418d519f9be589 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 27 Oct 2025 03:46:55 +0200 Subject: [PATCH 03/86] feat(dev-setup): add Terraform setup for development environment with remote backend --- environments/development/backend.hcl | 5 +++++ environments/development/main.tf | 12 ++++++++++++ environments/development/outputs.tf | 7 +++++++ environments/development/providers.tf | 15 +++++++++++++++ environments/development/variables.tf | 9 +++++++++ 5 files changed, 48 insertions(+) create mode 100644 environments/development/backend.hcl create mode 100644 environments/development/main.tf create mode 100644 environments/development/outputs.tf create mode 100644 environments/development/providers.tf create mode 100644 environments/development/variables.tf diff --git a/environments/development/backend.hcl b/environments/development/backend.hcl new file mode 100644 index 0000000..e457b39 --- /dev/null +++ b/environments/development/backend.hcl @@ -0,0 +1,5 @@ +bucket = "flagging-infra-tf-state-code-crafters" +key = "environments/development/terraform.tfstate" +region = "af-south-1" +dynamodb_table = "flagging-infra-tf-locks" +encrypt = true diff --git a/environments/development/main.tf b/environments/development/main.tf new file mode 100644 index 0000000..a7bf0aa --- /dev/null +++ b/environments/development/main.tf @@ -0,0 +1,12 @@ +resource "aws_s3_bucket" "dev_test_bucket" { + bucket = "flagging-api-dev-${random_id.suffix.hex}" + tags = { + Name = "DevTestBucket" + Environment = var.environment + ManagedBy = "Terraform" + } +} + +resource "random_id" "suffix" { + byte_length = 4 +} diff --git a/environments/development/outputs.tf b/environments/development/outputs.tf new file mode 100644 index 0000000..d6e4fb0 --- /dev/null +++ b/environments/development/outputs.tf @@ -0,0 +1,7 @@ +output "test_bucket_name" { + value = aws_s3_bucket.dev_test_bucket.bucket +} + +output "region" { + value = var.aws_region +} \ No newline at end of file diff --git a/environments/development/providers.tf b/environments/development/providers.tf new file mode 100644 index 0000000..fd2057d --- /dev/null +++ b/environments/development/providers.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 1.6.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + backend "s3" {} +} + +provider "aws" { + region = var.aws_region +} diff --git a/environments/development/variables.tf b/environments/development/variables.tf new file mode 100644 index 0000000..6e5bb02 --- /dev/null +++ b/environments/development/variables.tf @@ -0,0 +1,9 @@ +variable "aws_region" { + type = string + default = "af-south-1" +} + +variable "environment" { + type = string + default = "development" +} From ca44b313b6284da5bf2a5d69a40e808e37d17cdd Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 27 Oct 2025 04:13:46 +0200 Subject: [PATCH 04/86] feat(ci-setup) Terraform validation and plan preview workflow" --- .github/workflows/terraform-deploy.yml | 12 ++-- .github/workflows/terraform-validate.yml | 87 ++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/terraform-validate.yml diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index c82bfdf..aa96429 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -2,7 +2,7 @@ name: Terraform Deploy on: push: - branches: [ main, staging,develop ] + branches: [ main, staging, develop ] workflow_dispatch: permissions: @@ -30,8 +30,8 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v3 - - name: Terraform Init & Apply - run: | - cd environments/dev - terraform init - terraform apply -auto-approve + # - name: Terraform Init & Apply + # run: | + # cd environments/development + # terraform init + # terraform apply -auto-approve diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml new file mode 100644 index 0000000..c7181e3 --- /dev/null +++ b/.github/workflows/terraform-validate.yml @@ -0,0 +1,87 @@ +name: Terraform Validation + +on: + pull_request: + branches: [main, staging, develop] + + push: + branches: [main, staging, develop] + +jobs: + terraform-validation: + name: Validate Terraform configuration + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.6.6 + + - name: Terraform Format Check + run: terraform fmt -check -recursive + + - name: Terraform Init (development env) + working-directory: environments/development + run: terraform init -backend=false + + - name: Terraform Validate + working-directory: environments/development + run: terraform validate + + - name: Terraform Lint + uses: terraform-linters/setup-tflint@v6 + with: + tflint_version: v0.52.0 + + - name: Run TFLint recursively + run: | + find . -type f -name "*.tf" -exec dirname {} \; | sort -u | while read dir; do + echo "Running TFLint in $dir" + (cd "$dir" && tflint --init && tflint) || true + done + + - name: Summary + run: echo "Terraform syntax and lint checks completed successfully." + + terraform-plan: + name: Terraform Plan + runs-on: ubuntu-latest + needs: terraform-validation + if: github.event_name == 'pull_request' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.6.6 + + - name: Terraform Init (with backend) + working-directory: environments/development + run: terraform init -backend-config=backend.hcl + + - name: Terraform Plan + id: plan + working-directory: environments/development + run: terraform plan -no-color -out=tfplan + + - name: Save Plan Output + run: terraform show -no-color tfplan > plan.txt + working-directory: environments/development + + - name: Upload Plan as Artifact + uses: actions/upload-artifact@v4 + with: + name: terraform-plan + path: environments/development/plan.txt + + - name: Comment Plan on PR + uses: marocchino/sticky-pull-request-comment@v2 + with: + path: environments/development/plan.txt From a9dd4b9b745d70332135e5ff774ab3a31343826b Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 27 Oct 2025 04:19:41 +0200 Subject: [PATCH 05/86] style(terraform): format terraform files to pass ci fmt check --- bootstrap/main.tf | 6 +++--- bootstrap/variables.tf | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bootstrap/main.tf b/bootstrap/main.tf index 85ecfe5..f037a80 100644 --- a/bootstrap/main.tf +++ b/bootstrap/main.tf @@ -41,6 +41,6 @@ resource "aws_dynamodb_table" "tf_lock" { } } -output "state_bucket" { value = aws_s3_bucket.tf_state.bucket } -output "lock_table" { value = aws_dynamodb_table.tf_lock.name } -output "region" { value = var.aws_region } +output "state_bucket" { value = aws_s3_bucket.tf_state.bucket } +output "lock_table" { value = aws_dynamodb_table.tf_lock.name } +output "region" { value = var.aws_region } diff --git a/bootstrap/variables.tf b/bootstrap/variables.tf index b7eda04..a324a09 100644 --- a/bootstrap/variables.tf +++ b/bootstrap/variables.tf @@ -4,7 +4,7 @@ variable "aws_region" { } variable "state_bucket_name" { - type = string + type = string default = "flagging-infra-tf-state-code-crafters" } From 061ebbfd0a16ca0afe37bace718c1d74e5614aed Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Mon, 27 Oct 2025 04:33:46 +0200 Subject: [PATCH 06/86] Configure AWS credentials in Terraform workflow Added AWS credentials configuration step for Terraform validation. --- .github/workflows/terraform-validate.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index c7181e3..79eacf3 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -57,6 +57,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + 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 with: From 28c3e2afe2b0eb9624165f139fbf7c9c691c3fd2 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Mon, 27 Oct 2025 09:47:51 +0200 Subject: [PATCH 07/86] Update permissions in terraform-validate workflow Add permissions for id-token and contents in workflow --- .github/workflows/terraform-validate.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 79eacf3..6df9181 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -7,6 +7,10 @@ on: push: branches: [main, staging, develop] +permissions: + id-token: write + contents: read + jobs: terraform-validation: name: Validate Terraform configuration From 08137d36f11da625a8e16ce8c928c02f6b3b5762 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Mon, 27 Oct 2025 09:55:18 +0200 Subject: [PATCH 08/86] Add pull-requests permission(write) to workflow --- .github/workflows/terraform-validate.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 6df9181..eb276a2 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -10,6 +10,7 @@ on: permissions: id-token: write contents: read + pull-requests: write jobs: terraform-validation: From a081b0e771ed63b876be34440f600ae62c45700b Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Mon, 27 Oct 2025 14:16:20 +0200 Subject: [PATCH 09/86] Configure Terraform environment directory based on branch Added environment directory setup for Terraform based on branch name. --- .github/workflows/terraform-deploy.yml | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index aa96429..192243c 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -8,6 +8,7 @@ on: permissions: id-token: write contents: read + pull-requests: write jobs: deploy: @@ -30,6 +31,42 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v3 + - name: Set Terraform Environment Directory + id: set-env + run: | + if [[ "${GITHUB_REF##*/}" == "develop" ]]; then + echo "env_dir=environments/development" >> $GITHUB_OUTPUT + elif [[ "${GITHUB_REF##*/}" == "staging" ]]; then + echo "env_dir=environments/staging" >> $GITHUB_OUTPUT + elif [[ "${GITHUB_REF##*/}" == "main" ]]; then + echo "env_dir=environments/production" >> $GITHUB_OUTPUT + else + echo "No matching environment for branch ${GITHUB_REF##*/}" + exit 1 + fi + + - name: Terraform Init + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -backend-config=backend.hcl + + - name: Terraform Plan + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} plan -no-color -out=tfplan + + - name: Show Plan Summary + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt + + - name: Upload Plan Output + uses: actions/upload-artifact@v4 + with: + name: ${{ github.ref_name }}-tfplan + path: ${{ steps.set-env.outputs.env_dir }}/plan.txt + + - name: Comment Plan on PR + if: github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: "Terraform Plan โ€“ ${{ steps.env_dir.outputs.env }}" + path: ${{ steps.env_dir.outputs.dir }}/plan.txt + # - name: Terraform Init & Apply # run: | # cd environments/development From fca54ddc8dc36057da35484cbd7a160466500388 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 27 Oct 2025 19:22:21 +0200 Subject: [PATCH 10/86] Updated the tf deploy workflow --- .github/workflows/terraform-deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 192243c..6203f55 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -64,8 +64,8 @@ jobs: if: github.event_name == 'pull_request' uses: marocchino/sticky-pull-request-comment@v2 with: - header: "Terraform Plan โ€“ ${{ steps.env_dir.outputs.env }}" - path: ${{ steps.env_dir.outputs.dir }}/plan.txt + header: "Terraform Plan โ€“ ${{ steps.set-env.outputs.env_dir }}" + path: ${{ steps.set-env.outputs.env_dir }}/plan.txt # - name: Terraform Init & Apply # run: | From 29e5ec72ba2d2a7a4f96ed5d390f1dd87b6cba73 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Wed, 29 Oct 2025 14:17:18 +0200 Subject: [PATCH 11/86] feat(tf-networks-ec2-dev): update development environment configuration and add new modules --- environments/development/backend.hcl | 4 ++ environments/development/compute.tf | 8 +++ environments/development/main.tf | 18 +++-- environments/development/network.tf | 10 +++ environments/development/outputs.tf | 35 +++++++-- environments/development/providers.tf | 4 ++ environments/development/secrets.tf | 10 +++ environments/development/variables.tf | 33 +++++++-- modules/compute-ec2/main.tf | 66 +++++++++++++++++ modules/compute-ec2/outputs.tf | 28 ++++++++ modules/compute-ec2/user-data.sh | 17 +++++ modules/compute-ec2/variables.tf | 41 +++++++++++ modules/network/main.tf | 100 ++++++++++++++++++++++++++ modules/network/outputs.tf | 7 ++ modules/network/variables.tf | 11 +++ modules/secrets/main.tf | 21 ++++++ modules/secrets/outputs.tf | 7 ++ modules/secrets/variables.tf | 7 ++ 18 files changed, 411 insertions(+), 16 deletions(-) create mode 100644 environments/development/compute.tf create mode 100644 environments/development/network.tf create mode 100644 environments/development/secrets.tf create mode 100644 modules/compute-ec2/main.tf create mode 100644 modules/compute-ec2/outputs.tf create mode 100644 modules/compute-ec2/user-data.sh create mode 100644 modules/compute-ec2/variables.tf create mode 100644 modules/network/main.tf create mode 100644 modules/network/outputs.tf create mode 100644 modules/network/variables.tf create mode 100644 modules/secrets/main.tf create mode 100644 modules/secrets/outputs.tf create mode 100644 modules/secrets/variables.tf diff --git a/environments/development/backend.hcl b/environments/development/backend.hcl index e457b39..aa20a36 100644 --- a/environments/development/backend.hcl +++ b/environments/development/backend.hcl @@ -1,3 +1,7 @@ +############################################## +# DEVELOPMENT ENVIRONMENT BACKEND +############################################## + bucket = "flagging-infra-tf-state-code-crafters" key = "environments/development/terraform.tfstate" region = "af-south-1" diff --git a/environments/development/compute.tf b/environments/development/compute.tf new file mode 100644 index 0000000..48bbe08 --- /dev/null +++ b/environments/development/compute.tf @@ -0,0 +1,8 @@ +module "compute" { + source = "../../modules/compute-ec2" + name_prefix = "ff-dev" + environment = "development" + subnet_ids = module.network.public_subnet_ids + security_group_id = module.network.host_sg_id + key_name = var.key_name +} diff --git a/environments/development/main.tf b/environments/development/main.tf index a7bf0aa..e0b3f92 100644 --- a/environments/development/main.tf +++ b/environments/development/main.tf @@ -1,12 +1,18 @@ +############################################## +# DEVELOPMENT ENVIRONMENT INFRASTRUCTURE +############################################## + +# S3 Bucket for Dev Testing +resource "random_id" "suffix" { + byte_length = 4 +} + resource "aws_s3_bucket" "dev_test_bucket" { - bucket = "flagging-api-dev-${random_id.suffix.hex}" + bucket = "ff-dev-test-bucket-${random_id.suffix.hex}" + tags = { Name = "DevTestBucket" - Environment = var.environment + Environment = "development" ManagedBy = "Terraform" } } - -resource "random_id" "suffix" { - byte_length = 4 -} diff --git a/environments/development/network.tf b/environments/development/network.tf new file mode 100644 index 0000000..95de05d --- /dev/null +++ b/environments/development/network.tf @@ -0,0 +1,10 @@ +module "network" { + source = "../../modules/network" + name = "ff-dev" + environment = "development" + vpc_cidr = "10.10.0.0/16" + public_subnet_cidr_a = "10.10.1.0/24" + public_subnet_cidr_b = "10.10.2.0/24" + az_a = "af-south-1a" + az_b = "af-south-1b" +} diff --git a/environments/development/outputs.tf b/environments/development/outputs.tf index d6e4fb0..463f4a9 100644 --- a/environments/development/outputs.tf +++ b/environments/development/outputs.tf @@ -1,7 +1,32 @@ -output "test_bucket_name" { - value = aws_s3_bucket.dev_test_bucket.bucket +############################################## +# DEVELOPMENT ENVIRONMENT OUTPUTS +############################################## +output "region" { + description = "Region where resources are deployed" + value = var.aws_region } -output "region" { - value = var.aws_region -} \ No newline at end of file +output "vpc_id" { + description = "VPC ID for development environment" + value = module.network.vpc_id +} + +output "public_subnets" { + description = "List of public subnet IDs" + value = module.network.public_subnet_ids +} + +output "instance_public_ip" { + description = "Public IP of the EC2 instance" + value = module.compute.instance_public_ip +} + +output "dev_test_bucket" { + description = "S3 bucket name for dev test bucket" + value = aws_s3_bucket.dev_test_bucket.bucket +} + +output "admin_key_arn" { + description = "ARN of the admin key parameter in SSM" + value = module.secrets.admin_key_arn +} diff --git a/environments/development/providers.tf b/environments/development/providers.tf index fd2057d..d3255bd 100644 --- a/environments/development/providers.tf +++ b/environments/development/providers.tf @@ -1,3 +1,7 @@ +############################################## +# DEVELOPMENT ENVIRONMENT PROVIDERS +############################################## + terraform { required_version = ">= 1.6.0" required_providers { diff --git a/environments/development/secrets.tf b/environments/development/secrets.tf new file mode 100644 index 0000000..87fe3e4 --- /dev/null +++ b/environments/development/secrets.tf @@ -0,0 +1,10 @@ +############################################## +# DEVELOPMENT ENVIRONMENT SECRETS +############################################## + +module "secrets" { + source = "../../modules/secrets" + sa_password = var.sa_password + admin_key = var.admin_key + redis_password = var.redis_password +} diff --git a/environments/development/variables.tf b/environments/development/variables.tf index 6e5bb02..4f1e43f 100644 --- a/environments/development/variables.tf +++ b/environments/development/variables.tf @@ -1,9 +1,32 @@ +############################################## +# DEVELOPMENT ENVIRONMENT VARIABLES +############################################## variable "aws_region" { - type = string - default = "af-south-1" + description = "AWS region to deploy resources" + type = string + default = "af-south-1" } -variable "environment" { - type = string - default = "development" +variable "sa_password" { + description = "SQL SA password for development" + type = string + sensitive = true +} + +variable "redis_password" { + description = "Redis password for development" + type = string + sensitive = true +} + +variable "admin_key" { + description = "Admin API key for development" + type = string + sensitive = true +} + +variable "key_name" { + description = "EC2 key pair name for SSH access (optional)" + type = string + default = null } diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf new file mode 100644 index 0000000..a04f084 --- /dev/null +++ b/modules/compute-ec2/main.tf @@ -0,0 +1,66 @@ +############################################## +# COMPUTE (EC2) MODULE +############################################## + +# Amazon Linux 2023 AMI (x86_64 architecture) +data "aws_ami" "al2023" { + most_recent = true + owners = ["137112412989"] + + filter { + name = "name" + values = ["al2023-ami-*-x86_64-gp3"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } +} + +# IAM ROLE + INSTANCE PROFILE +resource "aws_iam_role" "ec2_role" { + name = "${var.name_prefix}-ec2-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = { Service = "ec2.amazonaws.com" } + Action = "sts:AssumeRole" + }] + }) + + tags = { + Name = "${var.name_prefix}-ec2-role" + } +} + +resource "aws_iam_role_policy_attachment" "ssm_core" { + role = aws_iam_role.ec2_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_iam_instance_profile" "ec2_profile" { + name = "${var.name_prefix}-ec2-profile" + role = aws_iam_role.ec2_role.name +} + +# EC2 INSTANCE +resource "aws_instance" "app_server" { + ami = data.aws_ami.al2023.id + instance_type = var.instance_type + subnet_id = element(var.subnet_ids, 0) + vpc_security_group_ids = [var.security_group_id] + associate_public_ip_address = true + iam_instance_profile = aws_iam_instance_profile.ec2_profile.name + key_name = var.key_name + + user_data = file("${path.module}/user-data.sh") + + tags = { + Name = "${var.name_prefix}-server" + Environment = var.environment + ManagedBy = "Terraform" + } +} diff --git a/modules/compute-ec2/outputs.tf b/modules/compute-ec2/outputs.tf new file mode 100644 index 0000000..709bed0 --- /dev/null +++ b/modules/compute-ec2/outputs.tf @@ -0,0 +1,28 @@ +############################################## +# COMPUTE (EC2) MODULE OUTPUTS +############################################## + +output "instance_id" { + description = "The ID of the EC2 instance" + value = aws_instance.app_server.id +} + +output "instance_public_ip" { + description = "The public IP address of the EC2 instance" + value = aws_instance.app_server.public_ip +} + +output "instance_ami_id" { + description = "The AMI ID used for this EC2 instance" + value = data.aws_ami.al2023.id +} + +output "iam_role_name" { + description = "IAM Role name attached to the EC2 instance" + value = aws_iam_role.ec2_role.name +} + +output "instance_profile_name" { + description = "Instance profile name for EC2" + value = aws_iam_instance_profile.ec2_profile.name +} diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh new file mode 100644 index 0000000..981a365 --- /dev/null +++ b/modules/compute-ec2/user-data.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Bootstrapping EC2 instance for Feature Flag API +# Installs Docker and prepares environment + +set -xe + +# Update and install Docker +yum update -y +amazon-linux-extras install docker -y +systemctl enable docker +systemctl start docker + +# Optional: Add ec2-user to Docker group +usermod -aG docker ec2-user + +# # Log to CloudWatch +# echo "EC2 instance bootstrapped successfully" >> /var/log/user-data.log diff --git a/modules/compute-ec2/variables.tf b/modules/compute-ec2/variables.tf new file mode 100644 index 0000000..87ffd36 --- /dev/null +++ b/modules/compute-ec2/variables.tf @@ -0,0 +1,41 @@ +############################################## +# COMPUTE (EC2) MODULE VARIABLES +############################################## + +variable "name_prefix" { + description = "Prefix used for naming resources (e.g., ff-dev)" + type = string +} + +variable "environment" { + description = "Environment name (e.g., development, staging, production)" + type = string +} + +variable "instance_type" { + description = "EC2 instance type for compute environment" + type = string + default = "t3.micro" +} + +variable "subnet_ids" { + description = "List of subnet IDs for placing the EC2 instance" + type = list(string) +} + +variable "security_group_id" { + description = "Security group ID for the EC2 instance" + type = string +} + +variable "key_name" { + description = "EC2 key pair name for SSH access" + type = string + default = null +} + +variable "user_data_script" { + description = "User data script for EC2 instance initialization" + type = string + default = "" +} diff --git a/modules/network/main.tf b/modules/network/main.tf new file mode 100644 index 0000000..1154823 --- /dev/null +++ b/modules/network/main.tf @@ -0,0 +1,100 @@ +############################################## +# NETWORK MODULE +############################################## + +resource "aws_vpc" "this" { + cidr_block = var.vpc_cidr + enable_dns_support = true + enable_dns_hostnames = true + tags = { Name = "${var.name}-vpc", Environment = var.environment } +} + +# Internet Gateway +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.this.id + tags = { Name = "${var.name}-igw" } +} + +# Public Subnet a +resource "aws_subnet" "public_a" { + vpc_id = aws_vpc.this.id + cidr_block = var.public_subnet_cidr_a + map_public_ip_on_launch = true + availability_zone = var.az_a + tags = { Name = "${var.name}-public-a" } +} + +# Public Subnets b +resource "aws_subnet" "public_b" { + vpc_id = aws_vpc.this.id + cidr_block = var.public_subnet_cidr_b + map_public_ip_on_launch = true + availability_zone = var.az_b + tags = { Name = "${var.name}-public-b" } +} + +# Public Route Table +resource "aws_route_table" "public" { + vpc_id = aws_vpc.this.id + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id + } + tags = { Name = "${var.name}-public-rt" } +} + +# Route Table Associations a +resource "aws_route_table_association" "a" { + subnet_id = aws_subnet.public_a.id + route_table_id = aws_route_table.public.id +} + +# Route Table Associations b +resource "aws_route_table_association" "b" { + subnet_id = aws_subnet.public_b.id + route_table_id = aws_route_table.public.id +} + +# Security group for the EC2 host that will run docker +resource "aws_security_group" "host" { + name = "${var.name}-host-sg" + description = "Allow web/ssh (dev), restrict db/redis to self" + vpc_id = aws_vpc.this.id + + # (TEMP) open 8080 for API in dev; will remove later when you add a reverse proxy + ingress { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + # SSH for emergency; prefer SSM instead + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + # Web if you expose via nginx later + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { Name = "${var.name}-host-sg" } +} diff --git a/modules/network/outputs.tf b/modules/network/outputs.tf new file mode 100644 index 0000000..ad6287d --- /dev/null +++ b/modules/network/outputs.tf @@ -0,0 +1,7 @@ +############################################## +# NETWORK MODULE OUTPUTS +############################################## + +output "vpc_id" { value = aws_vpc.this.id } +output "public_subnet_ids" { value = [aws_subnet.public_a.id, aws_subnet.public_b.id] } +output "host_sg_id" { value = aws_security_group.host.id } diff --git a/modules/network/variables.tf b/modules/network/variables.tf new file mode 100644 index 0000000..2ff12e4 --- /dev/null +++ b/modules/network/variables.tf @@ -0,0 +1,11 @@ +############################################## +# NETWORK MODULE VARIABLES +############################################## + +variable "name" { type = string } +variable "environment" { type = string } +variable "vpc_cidr" { type = string } +variable "public_subnet_cidr_a" { type = string } +variable "public_subnet_cidr_b" { type = string } +variable "az_a" { type = string } +variable "az_b" { type = string } diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf new file mode 100644 index 0000000..2352979 --- /dev/null +++ b/modules/secrets/main.tf @@ -0,0 +1,21 @@ +############################################## +# SECRETS MODULE +############################################## + +resource "aws_ssm_parameter" "sa_password" { + name = "/ff/dev/SA_PASSWORD" + type = "SecureString" + value = var.sa_password +} + +resource "aws_ssm_parameter" "admin_key" { + name = "/ff/dev/ADMIN_KEY" + type = "SecureString" + value = var.admin_key +} + +resource "aws_ssm_parameter" "redis_password" { + name = "/ff/dev/REDIS_PASSWORD" + type = "SecureString" + value = var.redis_password +} diff --git a/modules/secrets/outputs.tf b/modules/secrets/outputs.tf new file mode 100644 index 0000000..170ae77 --- /dev/null +++ b/modules/secrets/outputs.tf @@ -0,0 +1,7 @@ +############################################## +# SECRETS MODULE OUTPUTS +############################################## + +output "admin_key_arn" { + value = aws_ssm_parameter.admin_key.arn +} diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf new file mode 100644 index 0000000..35da454 --- /dev/null +++ b/modules/secrets/variables.tf @@ -0,0 +1,7 @@ +############################################## +# SECRETS MODULE VARIABLES +############################################## + +variable "sa_password" { type = string } +variable "admin_key" { type = string } +variable "redis_password"{ type = string } From aeb0393cb6fecf9f944a50397db6425b86c86e23 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Wed, 29 Oct 2025 14:19:53 +0200 Subject: [PATCH 12/86] terraform fmt on modules --- modules/compute-ec2/main.tf | 4 +-- modules/network/main.tf | 52 ++++++++++++++++++------------------ modules/network/outputs.tf | 4 +-- modules/network/variables.tf | 10 +++---- modules/secrets/outputs.tf | 2 +- modules/secrets/variables.tf | 6 ++--- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index a04f084..3ba9f7f 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -25,9 +25,9 @@ resource "aws_iam_role" "ec2_role" { assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ - Effect = "Allow" + Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } - Action = "sts:AssumeRole" + Action = "sts:AssumeRole" }] }) diff --git a/modules/network/main.tf b/modules/network/main.tf index 1154823..51a0bdd 100644 --- a/modules/network/main.tf +++ b/modules/network/main.tf @@ -6,7 +6,7 @@ resource "aws_vpc" "this" { cidr_block = var.vpc_cidr enable_dns_support = true enable_dns_hostnames = true - tags = { Name = "${var.name}-vpc", Environment = var.environment } + tags = { Name = "${var.name}-vpc", Environment = var.environment } } # Internet Gateway @@ -21,7 +21,7 @@ resource "aws_subnet" "public_a" { cidr_block = var.public_subnet_cidr_a map_public_ip_on_launch = true availability_zone = var.az_a - tags = { Name = "${var.name}-public-a" } + tags = { Name = "${var.name}-public-a" } } # Public Subnets b @@ -30,7 +30,7 @@ resource "aws_subnet" "public_b" { cidr_block = var.public_subnet_cidr_b map_public_ip_on_launch = true availability_zone = var.az_b - tags = { Name = "${var.name}-public-b" } + tags = { Name = "${var.name}-public-b" } } # Public Route Table @@ -62,38 +62,38 @@ resource "aws_security_group" "host" { vpc_id = aws_vpc.this.id # (TEMP) open 8080 for API in dev; will remove later when you add a reverse proxy - ingress { - from_port = 8080 - to_port = 8080 - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] -} + ingress { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } # SSH for emergency; prefer SSM instead - ingress { - from_port = 22 - to_port = 22 - protocol = "tcp" + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Web if you expose via nginx later - ingress { - from_port = 80 - to_port = 80 - protocol = "tcp" + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } - ingress { - from_port = 443 - to_port = 443 - protocol = "tcp" + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.name}-host-sg" } diff --git a/modules/network/outputs.tf b/modules/network/outputs.tf index ad6287d..7b07437 100644 --- a/modules/network/outputs.tf +++ b/modules/network/outputs.tf @@ -2,6 +2,6 @@ # NETWORK MODULE OUTPUTS ############################################## -output "vpc_id" { value = aws_vpc.this.id } +output "vpc_id" { value = aws_vpc.this.id } output "public_subnet_ids" { value = [aws_subnet.public_a.id, aws_subnet.public_b.id] } -output "host_sg_id" { value = aws_security_group.host.id } +output "host_sg_id" { value = aws_security_group.host.id } diff --git a/modules/network/variables.tf b/modules/network/variables.tf index 2ff12e4..3fc08f1 100644 --- a/modules/network/variables.tf +++ b/modules/network/variables.tf @@ -2,10 +2,10 @@ # NETWORK MODULE VARIABLES ############################################## -variable "name" { type = string } -variable "environment" { type = string } -variable "vpc_cidr" { type = string } +variable "name" { type = string } +variable "environment" { type = string } +variable "vpc_cidr" { type = string } variable "public_subnet_cidr_a" { type = string } variable "public_subnet_cidr_b" { type = string } -variable "az_a" { type = string } -variable "az_b" { type = string } +variable "az_a" { type = string } +variable "az_b" { type = string } diff --git a/modules/secrets/outputs.tf b/modules/secrets/outputs.tf index 170ae77..fb6b77e 100644 --- a/modules/secrets/outputs.tf +++ b/modules/secrets/outputs.tf @@ -3,5 +3,5 @@ ############################################## output "admin_key_arn" { - value = aws_ssm_parameter.admin_key.arn + value = aws_ssm_parameter.admin_key.arn } diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf index 35da454..6eda70c 100644 --- a/modules/secrets/variables.tf +++ b/modules/secrets/variables.tf @@ -2,6 +2,6 @@ # SECRETS MODULE VARIABLES ############################################## -variable "sa_password" { type = string } -variable "admin_key" { type = string } -variable "redis_password"{ type = string } +variable "sa_password" { type = string } +variable "admin_key" { type = string } +variable "redis_password" { type = string } From 51a61372b8b2e633eb9fdda38eef51bcc458124f Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Wed, 29 Oct 2025 14:57:14 +0200 Subject: [PATCH 13/86] Enable DEBUG logging for Terraform Plan Add TF_LOG environment variable for debugging in Terraform Plan step. --- .github/workflows/terraform-validate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index eb276a2..6e756bd 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -81,6 +81,8 @@ jobs: id: plan working-directory: environments/development run: terraform plan -no-color -out=tfplan + env: + TF_LOG: DEBUG - name: Save Plan Output run: terraform show -no-color tfplan > plan.txt From b368fa37db01841e31a778d517d5397e1ddb5260 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Wed, 29 Oct 2025 23:19:23 +0200 Subject: [PATCH 14/86] updated the terraform-plan job and backend.hcl --- .github/workflows/terraform-validate.yml | 7 +++++++ environments/development/backend.hcl | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 6e756bd..86aa75e 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -98,3 +98,10 @@ jobs: uses: marocchino/sticky-pull-request-comment@v2 with: path: environments/development/plan.txt + + - name: Force unlock on failure + if: failure() + working-directory: environments/development + run: | + echo "Attempting to remove Terraform lock..." + terraform force-unlock -force $(terraform show -json | jq -r '.lock.id') || true diff --git a/environments/development/backend.hcl b/environments/development/backend.hcl index aa20a36..cb500a1 100644 --- a/environments/development/backend.hcl +++ b/environments/development/backend.hcl @@ -5,5 +5,5 @@ bucket = "flagging-infra-tf-state-code-crafters" key = "environments/development/terraform.tfstate" region = "af-south-1" -dynamodb_table = "flagging-infra-tf-locks" +use_lockfile = true encrypt = true From 2be6207ecc4ef086c3bfe81661f8810dd63d7e8d Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Wed, 29 Oct 2025 23:39:16 +0200 Subject: [PATCH 15/86] updated the terraform-setup version to use default (latest) --- .github/workflows/terraform-validate.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 86aa75e..3c8f9e5 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -23,8 +23,6 @@ jobs: - name: Setup Terraform uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.6.6 - name: Terraform Format Check run: terraform fmt -check -recursive @@ -70,8 +68,6 @@ jobs: - name: Setup Terraform uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.6.6 - name: Terraform Init (with backend) working-directory: environments/development From ae2d99301b873e1b3a749fee6138d5e7e041f93c Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Wed, 29 Oct 2025 23:47:27 +0200 Subject: [PATCH 16/86] Added tf vars for tf plan --- .github/workflows/terraform-validate.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 3c8f9e5..923178f 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -76,9 +76,13 @@ jobs: - name: Terraform Plan id: plan working-directory: environments/development - run: terraform plan -no-color -out=tfplan env: + TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} + TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} + TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} TF_LOG: DEBUG + run: terraform plan -no-color -out=tfplan + - name: Save Plan Output run: terraform show -no-color tfplan > plan.txt From 056300dae147a252e599697ae3e83b386888515d Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Thu, 30 Oct 2025 00:20:11 +0200 Subject: [PATCH 17/86] Updated the ec2 module aws ami for cpt --- modules/compute-ec2/main.tf | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index 3ba9f7f..86d5374 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -5,17 +5,22 @@ # Amazon Linux 2023 AMI (x86_64 architecture) data "aws_ami" "al2023" { most_recent = true - owners = ["137112412989"] + owners = ["amazon"] filter { name = "name" - values = ["al2023-ami-*-x86_64-gp3"] + values = ["al2023-ami-*-x86_64"] } filter { name = "architecture" values = ["x86_64"] } + + filter { + name = "root-device-type" + values = ["ebs"] + } } # IAM ROLE + INSTANCE PROFILE From 700548a06af29b43b033bd98c5b8c5daf4a2b25e Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Thu, 30 Oct 2025 00:34:28 +0200 Subject: [PATCH 18/86] Add environment variables for Terraform Plan --- .github/workflows/terraform-deploy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 6203f55..9a11a8f 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -49,6 +49,10 @@ jobs: run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -backend-config=backend.hcl - name: Terraform Plan + env: + TF_VAR_admin_key: ${{ secrets['ADMIN_KEY_' + github.ref_name | upper] }} + TF_VAR_redis_password: ${{ secrets['REDIS_PASSWORD_' + github.ref_name | upper] }} + TF_VAR_sa_password: ${{ secrets['SA_PASSWORD_' + github.ref_name | upper] }} run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} plan -no-color -out=tfplan - name: Show Plan Summary From dfdaba0e2d7cc89bec1a0a0168587fb894de5a47 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Thu, 30 Oct 2025 00:39:31 +0200 Subject: [PATCH 19/86] Refactor secret access to use format function --- .github/workflows/terraform-deploy.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 9a11a8f..11e0dcd 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -50,9 +50,9 @@ jobs: - name: Terraform Plan env: - TF_VAR_admin_key: ${{ secrets['ADMIN_KEY_' + github.ref_name | upper] }} - TF_VAR_redis_password: ${{ secrets['REDIS_PASSWORD_' + github.ref_name | upper] }} - TF_VAR_sa_password: ${{ secrets['SA_PASSWORD_' + github.ref_name | upper] }} + TF_VAR_admin_key: ${{ secrets[format('ADMIN_KEY_%s', github.ref_name) | upper] }} + TF_VAR_redis_password: ${{ secrets[format('REDIS_PASSWORD_%s', github.ref_name) | upper] }} + TF_VAR_sa_password: ${{ secrets[format('SA_PASSWORD_%s', github.ref_name) | upper] }} run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} plan -no-color -out=tfplan - name: Show Plan Summary From 7fe66f63e100066ab4fdbbfa2dcd1da05661b05e Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Thu, 30 Oct 2025 00:45:51 +0200 Subject: [PATCH 20/86] Refactor Terraform secrets handling for environments --- .github/workflows/terraform-deploy.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 11e0dcd..9eaeecc 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -50,11 +50,12 @@ jobs: - name: Terraform Plan env: - TF_VAR_admin_key: ${{ secrets[format('ADMIN_KEY_%s', github.ref_name) | upper] }} - TF_VAR_redis_password: ${{ secrets[format('REDIS_PASSWORD_%s', github.ref_name) | upper] }} - TF_VAR_sa_password: ${{ secrets[format('SA_PASSWORD_%s', github.ref_name) | upper] }} + TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} + TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} + TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} plan -no-color -out=tfplan + - name: Show Plan Summary run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt From 45c98e52f74fb6b6f6398f898929b70935e775cf Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Thu, 30 Oct 2025 00:56:50 +0200 Subject: [PATCH 21/86] Refactor Terraform workflow and add apply step Updated branch formatting and added Terraform apply step. --- .github/workflows/terraform-deploy.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 9eaeecc..67215cc 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -2,7 +2,7 @@ name: Terraform Deploy on: push: - branches: [ main, staging, develop ] + branches: [main, staging, develop] workflow_dispatch: permissions: @@ -14,6 +14,7 @@ jobs: deploy: name: Deploy Infrastructure runs-on: ubuntu-latest + environment: ${{ github.ref_name }} steps: - name: Checkout repository @@ -30,6 +31,8 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.6.6 - name: Set Terraform Environment Directory id: set-env @@ -49,12 +52,12 @@ jobs: run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -backend-config=backend.hcl - name: Terraform Plan + id: plan + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} plan -no-color -out=tfplan env: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} plan -no-color -out=tfplan - - name: Show Plan Summary run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt @@ -72,8 +75,10 @@ jobs: header: "Terraform Plan โ€“ ${{ steps.set-env.outputs.env_dir }}" path: ${{ steps.set-env.outputs.env_dir }}/plan.txt - # - name: Terraform Init & Apply - # run: | - # cd environments/development - # terraform init - # terraform apply -auto-approve + - name: Terraform Apply + if: github.ref_name == 'develop' || github.ref_name == 'staging' || github.ref_name == 'main' + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} apply -auto-approve + env: + TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} + TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} + TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} From aa2f75a9a2a80b38ea39e3065c049648ca5194b3 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Thu, 30 Oct 2025 00:57:46 +0200 Subject: [PATCH 22/86] Remove Terraform version specification in workflow Removed specific Terraform version setup in workflow. --- .github/workflows/terraform-deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 67215cc..74f4acc 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -31,8 +31,6 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.6.6 - name: Set Terraform Environment Directory id: set-env From 1e73cbff6a2b0c8bb883010d83e084239bda7d72 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Thu, 30 Oct 2025 17:21:56 +0200 Subject: [PATCH 23/86] feat(tf-ec2-setup) Adding ssh to ec2 and Elastic IP --- bootstrap/main.tf | 4 -- bootstrap/outputs.tf | 3 ++ environments/development/compute.tf | 2 +- environments/development/main.tf | 11 +++++ environments/development/network.tf | 2 + environments/development/outputs.tf | 5 +++ environments/development/variables.tf | 10 +++++ modules/network/main.tf | 65 ++++++++++++++++++--------- modules/network/variables.tf | 12 +++++ 9 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 bootstrap/outputs.tf diff --git a/bootstrap/main.tf b/bootstrap/main.tf index f037a80..3a15d5d 100644 --- a/bootstrap/main.tf +++ b/bootstrap/main.tf @@ -40,7 +40,3 @@ resource "aws_dynamodb_table" "tf_lock" { type = "S" } } - -output "state_bucket" { value = aws_s3_bucket.tf_state.bucket } -output "lock_table" { value = aws_dynamodb_table.tf_lock.name } -output "region" { value = var.aws_region } diff --git a/bootstrap/outputs.tf b/bootstrap/outputs.tf new file mode 100644 index 0000000..a57dabf --- /dev/null +++ b/bootstrap/outputs.tf @@ -0,0 +1,3 @@ +output "state_bucket" { value = aws_s3_bucket.tf_state.bucket } +output "lock_table" { value = aws_dynamodb_table.tf_lock.name } +output "region" { value = var.aws_region } \ No newline at end of file diff --git a/environments/development/compute.tf b/environments/development/compute.tf index 48bbe08..ef60ea9 100644 --- a/environments/development/compute.tf +++ b/environments/development/compute.tf @@ -4,5 +4,5 @@ module "compute" { environment = "development" subnet_ids = module.network.public_subnet_ids security_group_id = module.network.host_sg_id - key_name = var.key_name + key_name = aws_key_pair.dev_admin.key_name } diff --git a/environments/development/main.tf b/environments/development/main.tf index e0b3f92..149db18 100644 --- a/environments/development/main.tf +++ b/environments/development/main.tf @@ -16,3 +16,14 @@ resource "aws_s3_bucket" "dev_test_bucket" { ManagedBy = "Terraform" } } + +resource "aws_key_pair" "dev_admin" { + key_name = "ff-dev-admin" + public_key = file("~/.ssh/ff-dev-admin.pub") +} + +resource "aws_eip" "dev_app" { + instance = module.compute.instance_id + domain = "vpc" + tags = { Name = "ff-dev-eip" } +} diff --git a/environments/development/network.tf b/environments/development/network.tf index 95de05d..ee64c30 100644 --- a/environments/development/network.tf +++ b/environments/development/network.tf @@ -7,4 +7,6 @@ module "network" { public_subnet_cidr_b = "10.10.2.0/24" az_a = "af-south-1a" az_b = "af-south-1b" + allowed_ssh_cidrs = var.allowed_ssh_cidrs + allowed_api_cidrs = var.allowed_api_cidrs } diff --git a/environments/development/outputs.tf b/environments/development/outputs.tf index 463f4a9..c0f73e4 100644 --- a/environments/development/outputs.tf +++ b/environments/development/outputs.tf @@ -30,3 +30,8 @@ output "admin_key_arn" { description = "ARN of the admin key parameter in SSM" value = module.secrets.admin_key_arn } + +output "elastic_ip" { + description = "Elastic IP address associated with the EC2 instance" + value = aws_eip.dev_app.public_ip +} diff --git a/environments/development/variables.tf b/environments/development/variables.tf index 4f1e43f..dc66e53 100644 --- a/environments/development/variables.tf +++ b/environments/development/variables.tf @@ -30,3 +30,13 @@ variable "key_name" { type = string default = null } + +variable "allowed_ssh_cidrs" { + description = "List of CIDR blocks allowed to SSH into EC2 (22)" + type = list(string) +} + +variable "allowed_api_cidrs" { + description = "List of CIDR blocks allowed to reach API (8080)" + type = list(string) +} diff --git a/modules/network/main.tf b/modules/network/main.tf index 51a0bdd..85e4d69 100644 --- a/modules/network/main.tf +++ b/modules/network/main.tf @@ -1,12 +1,16 @@ ############################################## -# NETWORK MODULE +# NETWORK MODULE (Updated for Multi-Env + Security) ############################################## resource "aws_vpc" "this" { cidr_block = var.vpc_cidr enable_dns_support = true enable_dns_hostnames = true - tags = { Name = "${var.name}-vpc", Environment = var.environment } + + tags = { + Name = "${var.name}-vpc" + Environment = var.environment + } } # Internet Gateway @@ -15,35 +19,39 @@ resource "aws_internet_gateway" "igw" { tags = { Name = "${var.name}-igw" } } -# Public Subnet a +# Public Subnet A resource "aws_subnet" "public_a" { vpc_id = aws_vpc.this.id cidr_block = var.public_subnet_cidr_a map_public_ip_on_launch = true availability_zone = var.az_a - tags = { Name = "${var.name}-public-a" } + + tags = { Name = "${var.name}-public-a" } } -# Public Subnets b +# Public Subnet B resource "aws_subnet" "public_b" { vpc_id = aws_vpc.this.id cidr_block = var.public_subnet_cidr_b map_public_ip_on_launch = true availability_zone = var.az_b - tags = { Name = "${var.name}-public-b" } + + tags = { Name = "${var.name}-public-b" } } -# Public Route Table +# Public Route Table + Default Route resource "aws_route_table" "public" { vpc_id = aws_vpc.this.id + route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } + tags = { Name = "${var.name}-public-rt" } } -# Route Table Associations a +# Associate Subnets with Route Table resource "aws_route_table_association" "a" { subnet_id = aws_subnet.public_a.id route_table_id = aws_route_table.public.id @@ -55,40 +63,51 @@ resource "aws_route_table_association" "b" { route_table_id = aws_route_table.public.id } -# Security group for the EC2 host that will run docker +#--------------------------------------------- +# SECURITY GROUP (param-driven, safer setup) +#--------------------------------------------- resource "aws_security_group" "host" { name = "${var.name}-host-sg" - description = "Allow web/ssh (dev), restrict db/redis to self" + description = "Allow limited SSH and web/API access" vpc_id = aws_vpc.this.id - # (TEMP) open 8080 for API in dev; will remove later when you add a reverse proxy - ingress { - from_port = 8080 - to_port = 8080 - protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] - } - # SSH for emergency; prefer SSM instead + # Controlled SSH Access (team IPs) ingress { from_port = 22 to_port = 22 protocol = "tcp" - cidr_blocks = ["0.0.0.0/0"] + cidr_blocks = var.allowed_ssh_cidrs + description = "SSH access (restricted)" } - # Web if you expose via nginx later + + # HTTP (for testing / nginx reverse proxy) ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] + description = "HTTP (public)" } + + # HTTPS (for secure web) ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] + description = "HTTPS (public)" } + # API / Dev-only access + ingress { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = var.allowed_api_cidrs + description = "API (restricted to team or open during dev)" + } + + # Egress - Allow all outbound egress { from_port = 0 to_port = 0 @@ -96,5 +115,9 @@ resource "aws_security_group" "host" { cidr_blocks = ["0.0.0.0/0"] } - tags = { Name = "${var.name}-host-sg" } + tags = { + Name = "${var.name}-host-sg" + Environment = var.environment + ManagedBy = "Terraform" + } } diff --git a/modules/network/variables.tf b/modules/network/variables.tf index 3fc08f1..997b540 100644 --- a/modules/network/variables.tf +++ b/modules/network/variables.tf @@ -9,3 +9,15 @@ variable "public_subnet_cidr_a" { type = string } variable "public_subnet_cidr_b" { type = string } variable "az_a" { type = string } variable "az_b" { type = string } + +variable "allowed_ssh_cidrs" { + type = list(string) + description = "List of CIDR blocks allowed to SSH into EC2 (22)" + default = ["0.0.0.0/0"] +} + +variable "allowed_api_cidrs" { + type = list(string) + description = "List of CIDR blocks allowed to reach API (8080)" + default = ["0.0.0.0/0"] +} From b9bb85eff6c5252d302e2a0d94514d7fbce0c76a Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Thu, 30 Oct 2025 23:42:12 +0200 Subject: [PATCH 24/86] Patching the ssh issue onto the ec2 instance --- environments/development/backend.hcl | 1 + environments/development/compute.tf | 6 +++++- environments/development/main.tf | 6 +----- environments/development/network.tf | 5 +++++ environments/development/outputs.tf | 1 + environments/development/providers.tf | 1 + environments/development/secrets.tf | 1 + environments/development/variables.tf | 1 + modules/compute-ec2/main.tf | 1 + modules/compute-ec2/outputs.tf | 1 + modules/compute-ec2/user-data.sh | 4 ++++ modules/compute-ec2/variables.tf | 1 + modules/network/main.tf | 1 + modules/network/outputs.tf | 1 + modules/network/variables.tf | 1 + modules/secrets/main.tf | 1 + modules/secrets/outputs.tf | 1 + modules/secrets/variables.tf | 1 + 18 files changed, 29 insertions(+), 6 deletions(-) diff --git a/environments/development/backend.hcl b/environments/development/backend.hcl index cb500a1..a87e9b3 100644 --- a/environments/development/backend.hcl +++ b/environments/development/backend.hcl @@ -1,5 +1,6 @@ ############################################## # DEVELOPMENT ENVIRONMENT BACKEND +# env/dev/backend.hcl ############################################## bucket = "flagging-infra-tf-state-code-crafters" diff --git a/environments/development/compute.tf b/environments/development/compute.tf index ef60ea9..6d6806a 100644 --- a/environments/development/compute.tf +++ b/environments/development/compute.tf @@ -1,8 +1,12 @@ +############################################## +# DEVELOPMENT ENVIRONMENT OUTPUTS +# env/dev/compute.tf +############################################## module "compute" { source = "../../modules/compute-ec2" name_prefix = "ff-dev" environment = "development" subnet_ids = module.network.public_subnet_ids security_group_id = module.network.host_sg_id - key_name = aws_key_pair.dev_admin.key_name + key_name = "ff-dev-admin" } diff --git a/environments/development/main.tf b/environments/development/main.tf index 149db18..1a218a7 100644 --- a/environments/development/main.tf +++ b/environments/development/main.tf @@ -1,5 +1,6 @@ ############################################## # DEVELOPMENT ENVIRONMENT INFRASTRUCTURE +# env/dev/main.tf ############################################## # S3 Bucket for Dev Testing @@ -17,11 +18,6 @@ resource "aws_s3_bucket" "dev_test_bucket" { } } -resource "aws_key_pair" "dev_admin" { - key_name = "ff-dev-admin" - public_key = file("~/.ssh/ff-dev-admin.pub") -} - resource "aws_eip" "dev_app" { instance = module.compute.instance_id domain = "vpc" diff --git a/environments/development/network.tf b/environments/development/network.tf index ee64c30..2bb0e92 100644 --- a/environments/development/network.tf +++ b/environments/development/network.tf @@ -1,3 +1,8 @@ +############################################## +# DEVELOPMENT ENVIRONMENT NETWORK +# env/dev/network.tf +############################################## + module "network" { source = "../../modules/network" name = "ff-dev" diff --git a/environments/development/outputs.tf b/environments/development/outputs.tf index c0f73e4..d9c6791 100644 --- a/environments/development/outputs.tf +++ b/environments/development/outputs.tf @@ -1,5 +1,6 @@ ############################################## # DEVELOPMENT ENVIRONMENT OUTPUTS +# env/dev/outputs.tf ############################################## output "region" { description = "Region where resources are deployed" diff --git a/environments/development/providers.tf b/environments/development/providers.tf index d3255bd..f77aaf7 100644 --- a/environments/development/providers.tf +++ b/environments/development/providers.tf @@ -1,5 +1,6 @@ ############################################## # DEVELOPMENT ENVIRONMENT PROVIDERS +# env/dev/providers.tf ############################################## terraform { diff --git a/environments/development/secrets.tf b/environments/development/secrets.tf index 87fe3e4..9a35426 100644 --- a/environments/development/secrets.tf +++ b/environments/development/secrets.tf @@ -1,5 +1,6 @@ ############################################## # DEVELOPMENT ENVIRONMENT SECRETS +# env/dev/secrets.tf ############################################## module "secrets" { diff --git a/environments/development/variables.tf b/environments/development/variables.tf index dc66e53..4c8f496 100644 --- a/environments/development/variables.tf +++ b/environments/development/variables.tf @@ -1,5 +1,6 @@ ############################################## # DEVELOPMENT ENVIRONMENT VARIABLES +# env/dev/variables.tf ############################################## variable "aws_region" { description = "AWS region to deploy resources" diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index 86d5374..ab0ccf0 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -1,5 +1,6 @@ ############################################## # COMPUTE (EC2) MODULE +# modules/compute-ec2/main.tf ############################################## # Amazon Linux 2023 AMI (x86_64 architecture) diff --git a/modules/compute-ec2/outputs.tf b/modules/compute-ec2/outputs.tf index 709bed0..6a0ba18 100644 --- a/modules/compute-ec2/outputs.tf +++ b/modules/compute-ec2/outputs.tf @@ -1,5 +1,6 @@ ############################################## # COMPUTE (EC2) MODULE OUTPUTS +modules/compute-ec2/outputs.tf ############################################## output "instance_id" { diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 981a365..e4cd56d 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -1,3 +1,7 @@ +############################################## +# COMPUTE (EC2) MODULE USER DATA +# modules/compute-ec2/user-data.sh +############################################## #!/bin/bash # Bootstrapping EC2 instance for Feature Flag API # Installs Docker and prepares environment diff --git a/modules/compute-ec2/variables.tf b/modules/compute-ec2/variables.tf index 87ffd36..0e56a39 100644 --- a/modules/compute-ec2/variables.tf +++ b/modules/compute-ec2/variables.tf @@ -1,5 +1,6 @@ ############################################## # COMPUTE (EC2) MODULE VARIABLES +# modules/compute-ec2/variables.tf ############################################## variable "name_prefix" { diff --git a/modules/network/main.tf b/modules/network/main.tf index 85e4d69..42e13cb 100644 --- a/modules/network/main.tf +++ b/modules/network/main.tf @@ -1,5 +1,6 @@ ############################################## # NETWORK MODULE (Updated for Multi-Env + Security) +# modules/network/main.tf ############################################## resource "aws_vpc" "this" { diff --git a/modules/network/outputs.tf b/modules/network/outputs.tf index 7b07437..cea0747 100644 --- a/modules/network/outputs.tf +++ b/modules/network/outputs.tf @@ -1,5 +1,6 @@ ############################################## # NETWORK MODULE OUTPUTS +# modules/network/outputs.tf ############################################## output "vpc_id" { value = aws_vpc.this.id } diff --git a/modules/network/variables.tf b/modules/network/variables.tf index 997b540..2015694 100644 --- a/modules/network/variables.tf +++ b/modules/network/variables.tf @@ -1,5 +1,6 @@ ############################################## # NETWORK MODULE VARIABLES +\# modules/network/variables.tf ############################################## variable "name" { type = string } diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf index 2352979..deeffcb 100644 --- a/modules/secrets/main.tf +++ b/modules/secrets/main.tf @@ -1,5 +1,6 @@ ############################################## # SECRETS MODULE +# modules/secrets/main.tf ############################################## resource "aws_ssm_parameter" "sa_password" { diff --git a/modules/secrets/outputs.tf b/modules/secrets/outputs.tf index fb6b77e..b605772 100644 --- a/modules/secrets/outputs.tf +++ b/modules/secrets/outputs.tf @@ -1,5 +1,6 @@ ############################################## # SECRETS MODULE OUTPUTS +# modules/secrets/outputs.tf ############################################## output "admin_key_arn" { diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf index 6eda70c..6e4dcbe 100644 --- a/modules/secrets/variables.tf +++ b/modules/secrets/variables.tf @@ -1,5 +1,6 @@ ############################################## # SECRETS MODULE VARIABLES +# modules/secrets/variables.tf ############################################## variable "sa_password" { type = string } From 6d3f50e690f1a1b523a5998816e3d55cd3b956ca Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Thu, 30 Oct 2025 23:48:48 +0200 Subject: [PATCH 25/86] interminent ssh issues fix --- environments/development/network.tf | 1 - modules/compute-ec2/outputs.tf | 2 +- modules/network/variables.tf | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/environments/development/network.tf b/environments/development/network.tf index 2bb0e92..cceb88c 100644 --- a/environments/development/network.tf +++ b/environments/development/network.tf @@ -12,6 +12,5 @@ module "network" { public_subnet_cidr_b = "10.10.2.0/24" az_a = "af-south-1a" az_b = "af-south-1b" - allowed_ssh_cidrs = var.allowed_ssh_cidrs allowed_api_cidrs = var.allowed_api_cidrs } diff --git a/modules/compute-ec2/outputs.tf b/modules/compute-ec2/outputs.tf index 6a0ba18..5407780 100644 --- a/modules/compute-ec2/outputs.tf +++ b/modules/compute-ec2/outputs.tf @@ -1,6 +1,6 @@ ############################################## # COMPUTE (EC2) MODULE OUTPUTS -modules/compute-ec2/outputs.tf +# modules/compute-ec2/outputs.tf ############################################## output "instance_id" { diff --git a/modules/network/variables.tf b/modules/network/variables.tf index 2015694..8f54524 100644 --- a/modules/network/variables.tf +++ b/modules/network/variables.tf @@ -1,6 +1,6 @@ ############################################## # NETWORK MODULE VARIABLES -\# modules/network/variables.tf +# modules/network/variables.tf ############################################## variable "name" { type = string } From d465898339adb22dbee79cae21399fee86f66228 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 00:03:14 +0200 Subject: [PATCH 26/86] Add SSH and API CIDR variables to Terraform workflow --- .github/workflows/terraform-validate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 923178f..4d2dfa2 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -80,6 +80,8 @@ jobs: TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} + TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS } TF_LOG: DEBUG run: terraform plan -no-color -out=tfplan From 7f0bf14a6788c61337c5b081797fb3211202f822 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 00:08:14 +0200 Subject: [PATCH 27/86] Add allowed SSH and API CIDRs to Terraform deploy --- .github/workflows/terraform-deploy.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 74f4acc..43b5b43 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -56,7 +56,9 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - + TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} + - name: Show Plan Summary run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt @@ -80,3 +82,5 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} + TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} From d9f22f73579f3688e4926b68fb0fbf14f826847f Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 00:08:33 +0200 Subject: [PATCH 28/86] Fix syntax error in terraform-validate.yml --- .github/workflows/terraform-validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 4d2dfa2..77301d4 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -81,7 +81,7 @@ jobs: TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} - TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS } + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} TF_LOG: DEBUG run: terraform plan -no-color -out=tfplan From d6e6ccedabaad96c55cda86eac8771343bfbd235 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 00:29:43 +0200 Subject: [PATCH 29/86] syntax issue update on ci ref of ALLOWEDSSH and PI CIDRS --- .github/workflows/terraform-deploy.yml | 8 ++++---- .github/workflows/terraform-validate.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 43b5b43..a38c932 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -56,8 +56,8 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} - TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} + TF_VAR_allowed_ssh_cidrs: '${{ secrets.ALLOWED_SSH_CIDRS }}' + TF_VAR_allowed_api_cidrs: '${{ secrets.ALLOWED_API_CIDRS }}' - name: Show Plan Summary run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt @@ -82,5 +82,5 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} - TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} + TF_VAR_allowed_ssh_cidrs: '${{ secrets.ALLOWED_SSH_CIDRS }}' + TF_VAR_allowed_api_cidrs: '${{ secrets.ALLOWED_API_CIDRS }}' diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 77301d4..e94ca79 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -80,8 +80,8 @@ jobs: TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} - TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} - TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} + TF_VAR_allowed_ssh_cidrs: '${{ secrets.ALLOWED_SSH_CIDRS }}' + TF_VAR_allowed_api_cidrs: '${{ secrets.ALLOWED_API_CIDRS }}' TF_LOG: DEBUG run: terraform plan -no-color -out=tfplan From 5aa81b85819ba0171f37d0be0f4bdeb56f1391d4 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Fri, 31 Oct 2025 00:53:53 +0200 Subject: [PATCH 30/86] Syntax issue of CI for ref secrets --- .github/workflows/terraform-deploy.yml | 8 ++++---- .github/workflows/terraform-validate.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index a38c932..43b5b43 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -56,8 +56,8 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_allowed_ssh_cidrs: '${{ secrets.ALLOWED_SSH_CIDRS }}' - TF_VAR_allowed_api_cidrs: '${{ secrets.ALLOWED_API_CIDRS }}' + TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} - name: Show Plan Summary run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt @@ -82,5 +82,5 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_allowed_ssh_cidrs: '${{ secrets.ALLOWED_SSH_CIDRS }}' - TF_VAR_allowed_api_cidrs: '${{ secrets.ALLOWED_API_CIDRS }}' + TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index e94ca79..77301d4 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -80,8 +80,8 @@ jobs: TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} - TF_VAR_allowed_ssh_cidrs: '${{ secrets.ALLOWED_SSH_CIDRS }}' - TF_VAR_allowed_api_cidrs: '${{ secrets.ALLOWED_API_CIDRS }}' + TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} TF_LOG: DEBUG run: terraform plan -no-color -out=tfplan From 8cd406b2f0747d39c52dde20c8a257e41c475672 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 01:15:52 +0200 Subject: [PATCH 31/86] Update terraform-validate workflow triggers Removed push trigger from terraform-validate workflow and added workflow_dispatch. --- .github/workflows/terraform-validate.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 77301d4..83ab49e 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -3,10 +3,9 @@ name: Terraform Validation on: pull_request: branches: [main, staging, develop] - - push: - branches: [main, staging, develop] - + + workflow_dispatch: {} + permissions: id-token: write contents: read From a4537896cadc1d2beb1dea50137e743408c68449 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Fri, 31 Oct 2025 01:35:13 +0200 Subject: [PATCH 32/86] Adding pub key for ec2 ssh setup --- environments/development/compute.tf | 2 +- modules/compute-ec2/main.tf | 12 ++++++++++++ ssh/ff-dev-admin.pub | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 ssh/ff-dev-admin.pub diff --git a/environments/development/compute.tf b/environments/development/compute.tf index 6d6806a..34d2fee 100644 --- a/environments/development/compute.tf +++ b/environments/development/compute.tf @@ -8,5 +8,5 @@ module "compute" { environment = "development" subnet_ids = module.network.public_subnet_ids security_group_id = module.network.host_sg_id - key_name = "ff-dev-admin" + key_name = aws_key_pair.dev_admin.key_name } diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index ab0ccf0..3964c95 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -70,3 +70,15 @@ resource "aws_instance" "app_server" { ManagedBy = "Terraform" } } + +# SSH KEY PAIR +resource "aws_key_pair" "dev_admin" { + key_name = "ff-dev-admin" + public_key = file("${path.module}/../../ssh/ff-dev-admin.pub") + + tags = { + Name = "ff-dev-admin" + Environment = var.environment + ManagedBy = "Terraform" + } +} diff --git a/ssh/ff-dev-admin.pub b/ssh/ff-dev-admin.pub new file mode 100644 index 0000000..8cc725c --- /dev/null +++ b/ssh/ff-dev-admin.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDL0jT8kt8H2Dv7VWO28IAiu89xTZHrhEvBu8qZy2wMeoaEmyj168h7yQF+FoO/HGFxPvySnVjZ7IG9R+ZsFCPsJvd4AqAp9sZ8CBqNkPGpaxj1ytOicluQOtZF/IhW0Q6xlwn0WgEI/myFwj51rAFAfuNm+sqPm7b6W2SRGcr06p67LYsGi0BPdVcgqo0x1LscT+y6Zi7GtyZOoWKZTTsMeSrN1qxnnT21viIc6N/KZjIqomt9Qp7f17GemjC0pu2m0x8KOa/iruyHGY/aAJpEb2RSU0dP5fAe4zq/8qnwBtyrhe0L6/2Npm6ssU1Jah2qt89xVPg62NjcRSJQKyukV1WqFLXDX17ZBwXTGvKPz8PHk7Ve2yHRD15DOTuwIPMLvJ6AoFTSyhJd+NXFK9nPIYB4Dy3SrxsjVm+DHw1zXn7CXN7EoaGEEp+kUEnSxXmxmlb3/iBTE1+Rckma4niwqWWJUEIwqNRbPCph8qtI3WfuXpCLtUsYJ47135Plowbe0p7tBo+lw6v9LS79qpS5oxqbXTm9KFabOk0XssIX6WFFvI3BZx0+Ol5MIW0bN+FKcQ2ktXGK3m/ZqTR6z26zP0lGxLguqWAGgz7hr3Yi7gfyMrF4NhhztPFNDAhRzdrm8sbu/ouGxpShEdcq33fEleEQjOES1L1PRRlk2CD6vQ== hollywoodbets\ongeziwem@ZAWC-BET-3FDD8I From 514826652cc6bf2234384543dc6e143cbb770320 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Fri, 31 Oct 2025 01:39:43 +0200 Subject: [PATCH 33/86] resolving reference to unknown resource error --- environments/development/compute.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environments/development/compute.tf b/environments/development/compute.tf index 34d2fee..6d6806a 100644 --- a/environments/development/compute.tf +++ b/environments/development/compute.tf @@ -8,5 +8,5 @@ module "compute" { environment = "development" subnet_ids = module.network.public_subnet_ids security_group_id = module.network.host_sg_id - key_name = aws_key_pair.dev_admin.key_name + key_name = "ff-dev-admin" } From 3ad8280209d25d952dd5882ed5315f5716fc421e Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Fri, 31 Oct 2025 01:55:27 +0200 Subject: [PATCH 34/86] set work dir on tf deploy workflow for plan output and added a dependency for pair creation in ec2 --- .github/workflows/terraform-deploy.yml | 1 + modules/compute-ec2/main.tf | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 43b5b43..3376b36 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -60,6 +60,7 @@ jobs: TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} - name: Show Plan Summary + working-directory: ${{ steps.set-env.outputs.env_dir }} run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt - name: Upload Plan Output diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index 3964c95..70ac66c 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 - - user_data = file("${path.module}/user-data.sh") + depends_on = [aws_key_pair.dev_admin] + user_data = file("${path.module}/user-data.sh") tags = { Name = "${var.name_prefix}-server" From 431f9dd8c6b59883ef7781c4b24cadebbb8b6b01 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Fri, 31 Oct 2025 02:13:55 +0200 Subject: [PATCH 35/86] resolving the 'file not found error' on tf deploy workflow --- .github/workflows/terraform-deploy.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 3376b36..2827303 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -60,8 +60,7 @@ jobs: TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} - name: Show Plan Summary - working-directory: ${{ steps.set-env.outputs.env_dir }} - run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan | tee plan.txt + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan > ${{ steps.set-env.outputs.env_dir }}/plan.txt - name: Upload Plan Output uses: actions/upload-artifact@v4 From a37903cdf73cb5ff748cf95b19ec431e35c98718 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 10:05:45 +0200 Subject: [PATCH 36/86] Refactor README for clarity and structure Updated README to improve clarity. --- README.md | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index bbfb663..17232b1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ๐Ÿ—๏ธ Feature Flags Infrastructure +# Feature Flags Infrastructure This repository manages the **cloud infrastructure** for the **Feature Flags Platform**, which includes: @@ -10,29 +10,32 @@ All infrastructure is defined using **Terraform (Infrastructure as Code)** and d --- -## ๐Ÿ“‚ Repository Structure +## Repository Structure feature-flags-infra/ -โ”œโ”€โ”€ .github/workflows/ โ†’ CI/CD automation for Terraform and deployments -โ”œโ”€โ”€ bootstrap/ โ†’ One-time setup for remote Terraform state (S3 + DynamoDB) -โ”œโ”€โ”€ environments/ โ†’ Per-environment Terraform configurations -โ”‚ โ”œโ”€โ”€ development/ โ†’ Dev environment (testing, internal usage) -โ”‚ โ”œโ”€โ”€ staging/ โ†’ Staging environment (QA, integration) -โ”‚ โ””โ”€โ”€ production/ โ†’ Production environment (live deployment) -โ”œโ”€โ”€ modules/ โ†’ Reusable Terraform modules -โ”‚ โ”œโ”€โ”€ compose/ โ†’ Handles Docker Compose deployments on EC2 -โ”‚ โ”œโ”€โ”€ compute-ec2/ โ†’ Provisions EC2 instances and security groups -โ”‚ โ”œโ”€โ”€ dns/ โ†’ Manages DNS records, SSL certs, and optional load balancer -โ”‚ โ””โ”€โ”€ network/ โ†’ Creates VPCs, subnets, and networking resources -โ”œโ”€โ”€ scripts/ โ†’ Helper shell scripts for rendering, deployment, and SSM commands -โ””โ”€โ”€ templates/ โ†’ Template files (e.g., docker-compose.yaml) used for deployments - -markdown -Copy code +โ”œโ”€โ”€ .github/workflows/ # CI/CD automation for Terraform and deployments +โ”‚ +โ”œโ”€โ”€ bootstrap/ # One-time setup for remote Terraform state (S3 + DynamoDB to keep infrastructure state consistent) +โ”‚ +โ”œโ”€โ”€ environments/ # Per-environment Terraform configurations +โ”‚ โ”œโ”€โ”€ development/ # Dev environment (testing, internal usage) +โ”‚ โ”œโ”€โ”€ staging/ # Staging environment (QA, integration) +โ”‚ โ””โ”€โ”€ production/ # Production environment (Production deployment) +โ”‚ +โ”œโ”€โ”€ modules/ # Reusable Terraform modules +โ”‚ โ”œโ”€โ”€ compose/ # Handles Docker Compose deployments on EC2 +โ”‚ โ”œโ”€โ”€ compute-ec2/ # Provisions EC2 instances and security groups +โ”‚ โ”œโ”€โ”€ dns/ # Manages DNS records, SSL certs, and optional load balancer +โ”‚ โ”œโ”€โ”€ network/ # Creates VPCs, subnets, and networking resources +โ”‚ โ””โ”€โ”€ secrets/ # Manages sensitive data +โ”‚ +โ”œโ”€โ”€ scripts/ # Helper shell scripts for rendering, deployment, and SSM commands +โ”‚ +โ””โ”€โ”€ templates/ # Template files (e.g., docker-compose.yaml) used for deployments --- -## ๐Ÿงฉ Key Concepts +## Key Concepts | **Component** | **Description** | |--------------------|------------------------------------------------------------------------| @@ -64,7 +67,7 @@ Copy code --- -## ๐Ÿš€ Environments +## Environments | **Environment** | **Branch** | **Purpose** | **Trigger** | |------------------|------------|--------------|----------------------------------------| From 263c708187fbfaad97ccb87de041ffddf09a8281 Mon Sep 17 00:00:00 2001 From: Ongeziwe Mtolo Date: Fri, 31 Oct 2025 10:07:07 +0200 Subject: [PATCH 37/86] Fix formatting in README.md for repository structure --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 17232b1..28a038f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ All infrastructure is defined using **Terraform (Infrastructure as Code)** and d --- ## Repository Structure - +``` feature-flags-infra/ โ”œโ”€โ”€ .github/workflows/ # CI/CD automation for Terraform and deployments โ”‚ @@ -32,7 +32,7 @@ feature-flags-infra/ โ”œโ”€โ”€ scripts/ # Helper shell scripts for rendering, deployment, and SSM commands โ”‚ โ””โ”€โ”€ templates/ # Template files (e.g., docker-compose.yaml) used for deployments - +``` --- ## Key Concepts From d86b41d275793bb76efe3b14c0411d65ae967efd Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Fri, 31 Oct 2025 19:01:30 +0200 Subject: [PATCH 38/86] user management + SSH key-based access automation on the bash script --- modules/compute-ec2/user-data.sh | 57 ++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index e4cd56d..5bcf330 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -1,21 +1,58 @@ -############################################## -# COMPUTE (EC2) MODULE USER DATA -# modules/compute-ec2/user-data.sh -############################################## #!/bin/bash -# Bootstrapping EC2 instance for Feature Flag API -# Installs Docker and prepares environment +# ------------------------------------------------------------------ +# Bootstrapping EC2 instance for Feature Flag API development +# Setup: Docker + user management + SSH key-based access +# ------------------------------------------------------------------ set -xe -# Update and install Docker +# === system setup === yum update -y amazon-linux-extras install docker -y systemctl enable docker systemctl start docker -# Optional: Add ec2-user to Docker group +# --- ec2-user can use docker (for safety/debugging) --- usermod -aG docker ec2-user -# # Log to CloudWatch -# echo "EC2 instance bootstrapped successfully" >> /var/log/user-data.log +# === create users with SSH key-based access === +create_user() { + local username=$1 + local pubkey=$2 + local sudo_access=$3 + + echo "Creating user: $username" + useradd -m -s /bin/bash "$username" + usermod -aG docker "$username" + + # grant sudo + if [ "$sudo_access" = "true" ]; then + usermod -aG sudo "$username" + echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$username + fi + + # setup SSH directory and authorized_keys + mkdir -p /home/$username/.ssh + echo "$pubkey" > /home/$username/.ssh/authorized_keys + chmod 700 /home/$username/.ssh + chmod 600 /home/$username/.ssh/authorized_keys + chown -R $username:$username /home/$username/.ssh +} + +# === team SSH public keys === +ongeziwe_pubkey="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDL0jT8kt8H2Dv7VWO28IAiu89xTZHrhEvBu8qZy2wMeoaEmyj168h7yQF+FoO/HGFxPvySnVjZ7IG9R+ZsFCPsJvd4AqAp9sZ8CBqNkPGpaxj1ytOicluQOtZF/IhW0Q6xlwn0WgEI/myFwj51rAFAfuNm+sqPm7b6W2SRGcr06p67LYsGi0BPdVcgqo0x1LscT+y6Zi7GtyZOoWKZTTsMeSrN1qxnnT21viIc6N/KZjIqomt9Qp7f17GemjC0pu2m0x8KOa/iruyHGY/aAJpEb2RSU0dP5fAe4zq/8qnwBtyrhe0L6/2Npm6ssU1Jah2qt89xVPg62NjcRSJQKyukV1WqFLXDX17ZBwXTGvKPz8PHk7Ve2yHRD15DOTuwIPMLvJ6AoFTSyhJd+NXFK9nPIYB4Dy3SrxsjVm+DHw1zXn7CXN7EoaGEEp+kUEnSxXmxmlb3/iBTE1+Rckma4niwqWWJUEIwqNRbPCph8qtI3WfuXpCLtUsYJ47135Plowbe0p7tBo+lw6v9LS79qpS5oxqbXTm9KFabOk0XssIX6WFFvI3BZx0+Ol5MIW0bN+FKcQ2ktXGK3m/ZqTR6z26zP0lGxLguqWAGgz7hr3Yi7gfyMrF4NhhztPFNDAhRzdrm8sbu/ouGxpShEdcq33fEleEQjOES1L1PRRlk2CD6vQ== hollywoodbets\ongeziwem@ZAWC-BET-3FDD8I" +# teammate1_pubkey="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD...TEAMMATE1_KEY" + +# === Create users === +create_user "ongeziwe" "$ongeziwe_pubkey" "true" +# create_user "teammate1" "$teammate1_pubkey" "false" + +# === disable password login globally (key-based only) === +sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config +sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config +sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config +sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config +systemctl restart sshd + +# === Clean up and log === +echo "User and Docker setup completed successfully." >> /var/log/user-setup.log From d166f7b5ba096d7a6aeb3dac8dfd3f33403b0c67 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sat, 1 Nov 2025 21:41:28 +0200 Subject: [PATCH 39/86] Updated SSH key-based access automation on the bash script --- modules/compute-ec2/user-data.sh | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 5bcf330..63f6536 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -1,58 +1,57 @@ #!/bin/bash # ------------------------------------------------------------------ -# Bootstrapping EC2 instance for Feature Flag API development -# Setup: Docker + user management + SSH key-based access +# create users (superuser + normal users), installs Docker, +# enables SSH key-based access. # ------------------------------------------------------------------ set -xe -# === system setup === yum update -y amazon-linux-extras install docker -y systemctl enable docker systemctl start docker - -# --- ec2-user can use docker (for safety/debugging) --- usermod -aG docker ec2-user -# === create users with SSH key-based access === create_user() { local username=$1 local pubkey=$2 local sudo_access=$3 - echo "Creating user: $username" - useradd -m -s /bin/bash "$username" + if id "$username" &>/dev/null; then + echo "User '$username' already exists, updating SSH keys..." + else + useradd -m -s /bin/bash "$username" + echo "User '$username' created." + fi + usermod -aG docker "$username" - # grant sudo if [ "$sudo_access" = "true" ]; then usermod -aG sudo "$username" echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$username fi - # setup SSH directory and authorized_keys mkdir -p /home/$username/.ssh echo "$pubkey" > /home/$username/.ssh/authorized_keys chmod 700 /home/$username/.ssh chmod 600 /home/$username/.ssh/authorized_keys chown -R $username:$username /home/$username/.ssh + + echo "SSH access configured for $username" } -# === team SSH public keys === ongeziwe_pubkey="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDL0jT8kt8H2Dv7VWO28IAiu89xTZHrhEvBu8qZy2wMeoaEmyj168h7yQF+FoO/HGFxPvySnVjZ7IG9R+ZsFCPsJvd4AqAp9sZ8CBqNkPGpaxj1ytOicluQOtZF/IhW0Q6xlwn0WgEI/myFwj51rAFAfuNm+sqPm7b6W2SRGcr06p67LYsGi0BPdVcgqo0x1LscT+y6Zi7GtyZOoWKZTTsMeSrN1qxnnT21viIc6N/KZjIqomt9Qp7f17GemjC0pu2m0x8KOa/iruyHGY/aAJpEb2RSU0dP5fAe4zq/8qnwBtyrhe0L6/2Npm6ssU1Jah2qt89xVPg62NjcRSJQKyukV1WqFLXDX17ZBwXTGvKPz8PHk7Ve2yHRD15DOTuwIPMLvJ6AoFTSyhJd+NXFK9nPIYB4Dy3SrxsjVm+DHw1zXn7CXN7EoaGEEp+kUEnSxXmxmlb3/iBTE1+Rckma4niwqWWJUEIwqNRbPCph8qtI3WfuXpCLtUsYJ47135Plowbe0p7tBo+lw6v9LS79qpS5oxqbXTm9KFabOk0XssIX6WFFvI3BZx0+Ol5MIW0bN+FKcQ2ktXGK3m/ZqTR6z26zP0lGxLguqWAGgz7hr3Yi7gfyMrF4NhhztPFNDAhRzdrm8sbu/ouGxpShEdcq33fEleEQjOES1L1PRRlk2CD6vQ== hollywoodbets\ongeziwem@ZAWC-BET-3FDD8I" + +# normal users # teammate1_pubkey="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD...TEAMMATE1_KEY" -# === Create users === create_user "ongeziwe" "$ongeziwe_pubkey" "true" # create_user "teammate1" "$teammate1_pubkey" "false" -# === disable password login globally (key-based only) === sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config systemctl restart sshd -# === Clean up and log === -echo "User and Docker setup completed successfully." >> /var/log/user-setup.log +echo "Bootstrap complete. Users ready for SSH access." >> /var/log/userdata-bootstrap.log From ed95384e2bf115a5eecce5676afe7059d9575b9e Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sat, 1 Nov 2025 22:26:00 +0200 Subject: [PATCH 40/86] Updated SSH key-based access automation on the bash script --- modules/compute-ec2/user-data.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 63f6536..9f9895c 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -6,8 +6,8 @@ set -xe -yum update -y -amazon-linux-extras install docker -y +dnf update -y +dnf install -y docker systemctl enable docker systemctl start docker usermod -aG docker ec2-user From 3c7f29248a6dd6a892276a9f5965baf57f6b4a1f Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sat, 1 Nov 2025 22:36:33 +0200 Subject: [PATCH 41/86] Updated SSH key-based access automation on the bash script. v.3 --- modules/compute-ec2/user-data.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 9f9895c..0517ad8 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -27,7 +27,7 @@ create_user() { usermod -aG docker "$username" if [ "$sudo_access" = "true" ]; then - usermod -aG sudo "$username" + usermod -aG wheel "$username" echo "$username ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$username fi From c5ee3a50ca016f15cb26d8285f50723587b61f1e Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sun, 2 Nov 2025 16:07:36 +0200 Subject: [PATCH 42/86] Updated secrets module and dev env secrets --- .github/workflows/terraform-deploy.yml | 2 ++ .github/workflows/terraform-validate.yml | 1 + environments/development/outputs.tf | 14 ++++++++++++++ environments/development/secrets.tf | 1 + environments/development/variables.tf | 6 ++++++ modules/secrets/main.tf | 6 ++++++ modules/secrets/outputs.tf | 12 ++++++++++++ modules/secrets/variables.tf | 1 + 8 files changed, 43 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 2827303..e814329 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -56,6 +56,7 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} + TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} @@ -82,5 +83,6 @@ jobs: TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} + TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 83ab49e..a36e10e 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -79,6 +79,7 @@ jobs: TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} + TF_VAR_ghcr_token: ${{ secrets.GHCR_PAT }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} TF_LOG: DEBUG diff --git a/environments/development/outputs.tf b/environments/development/outputs.tf index d9c6791..c2f9008 100644 --- a/environments/development/outputs.tf +++ b/environments/development/outputs.tf @@ -32,6 +32,20 @@ output "admin_key_arn" { value = module.secrets.admin_key_arn } +output "sa_password" { + description = "SA password parameter in SSM" + value = module.secrets.sa_password_arn +} + +output "redis_password_arn" { + description = "Redis password parameter in SSM" + value = module.secrets.redis_password_arn +} + +output "ghcr_token_arn" { + description = "GHCR Token parameter in SSM" + value = module.secrets.ghcr_token_arn +} output "elastic_ip" { description = "Elastic IP address associated with the EC2 instance" value = aws_eip.dev_app.public_ip diff --git a/environments/development/secrets.tf b/environments/development/secrets.tf index 9a35426..44c6722 100644 --- a/environments/development/secrets.tf +++ b/environments/development/secrets.tf @@ -8,4 +8,5 @@ module "secrets" { sa_password = var.sa_password admin_key = var.admin_key redis_password = var.redis_password + ghcr_token = var.ghcr_token } diff --git a/environments/development/variables.tf b/environments/development/variables.tf index 4c8f496..53ad708 100644 --- a/environments/development/variables.tf +++ b/environments/development/variables.tf @@ -26,6 +26,12 @@ variable "admin_key" { sensitive = true } +variable "ghcr_token" { + description = "GHCR token" + type = string + sensitive = true +} + variable "key_name" { description = "EC2 key pair name for SSH access (optional)" type = string diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf index deeffcb..ee3f549 100644 --- a/modules/secrets/main.tf +++ b/modules/secrets/main.tf @@ -20,3 +20,9 @@ resource "aws_ssm_parameter" "redis_password" { type = "SecureString" value = var.redis_password } + +resource "aws_ssm_parameter" "ghcr_token" { + name = "/ff/dev/GHCR_PAT" + type = "SecureString" + value = var.ghcr_token +} diff --git a/modules/secrets/outputs.tf b/modules/secrets/outputs.tf index b605772..10ff89d 100644 --- a/modules/secrets/outputs.tf +++ b/modules/secrets/outputs.tf @@ -6,3 +6,15 @@ output "admin_key_arn" { value = aws_ssm_parameter.admin_key.arn } + +output "sa_password_arn" { + value = aws_ssm_parameter.sa_password.arn +} + +output "redis_password_arn" { + value = aws_ssm_parameter.redis_password.arn +} + +output "ghcr_token_arn" { + value = aws_ssm_parameter.ghcr_token.arn +} diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf index 6e4dcbe..041172d 100644 --- a/modules/secrets/variables.tf +++ b/modules/secrets/variables.tf @@ -6,3 +6,4 @@ variable "sa_password" { type = string } variable "admin_key" { type = string } variable "redis_password" { type = string } +variable "ghcr_token" { type = string } From e191ca9163066e303ba995e897bff2fbb75506a9 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sun, 2 Nov 2025 16:16:34 +0200 Subject: [PATCH 43/86] Updated secrets module and dev env secrets v2 --- environments/development/secrets.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environments/development/secrets.tf b/environments/development/secrets.tf index 44c6722..860934b 100644 --- a/environments/development/secrets.tf +++ b/environments/development/secrets.tf @@ -8,5 +8,5 @@ module "secrets" { sa_password = var.sa_password admin_key = var.admin_key redis_password = var.redis_password - ghcr_token = var.ghcr_token + ghcr_token = var.ghcr_token } From 016652ce5fdc3669057ff8ffa653a5b33c266734 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sun, 2 Nov 2025 17:01:25 +0200 Subject: [PATCH 44/86] Update user-data.ssh to install docker compose on ec2 instance --- modules/compute-ec2/user-data.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 0517ad8..7232a90 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -8,6 +8,7 @@ set -xe dnf update -y dnf install -y docker +dnf install -y docker-compose-plugin systemctl enable docker systemctl start docker usermod -aG docker ec2-user From bd481ae968d3bcdc12918fcb8c34d255537da24d Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sun, 2 Nov 2025 17:41:39 +0200 Subject: [PATCH 45/86] Update user-data.ssh to install docker and docker compose on ec2 instance using docker repository --- modules/compute-ec2/user-data.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 7232a90..7291107 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -6,11 +6,21 @@ set -xe +if [[ "$EUID" -ne 0 ]]; then + echo "This script must be run as root or with sudo." + exit 1 +fi + dnf update -y -dnf install -y docker -dnf install -y docker-compose-plugin -systemctl enable docker -systemctl start docker + +dnf remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine || true +dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo +# Explicitly trust Docker's GPG key +# rpm --import https://download.docker.com/linux/centos/gpg + +dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +systemctl enable --now docker usermod -aG docker ec2-user create_user() { From e18a1fdcd47c9d74b5b345620539a4d0857fef80 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sun, 2 Nov 2025 21:59:37 +0200 Subject: [PATCH 46/86] Update user-data.ssh to install docker and docker compose on ec2 instanCE using get.docker.com script --- modules/compute-ec2/user-data.sh | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 7291107..96551a9 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -12,17 +12,18 @@ if [[ "$EUID" -ne 0 ]]; then fi dnf update -y - -dnf remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine || true -dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo -# Explicitly trust Docker's GPG key -# rpm --import https://download.docker.com/linux/centos/gpg - -dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh systemctl enable --now docker usermod -aG docker ec2-user +# Ensure docker compose v2 works +if ! docker compose version >/dev/null 2>&1; then + echo "Installing Compose plugin via pip fallback..." + dnf install -y python3-pip + pip3 install docker-compose +fi + create_user() { local username=$1 local pubkey=$2 From e8e19d42aefd47ef42a6b595ae367b6cb706a553 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Sun, 2 Nov 2025 22:23:23 +0200 Subject: [PATCH 47/86] Update user-data.ssh to install docker and docker compose on ec2 instance --- modules/compute-ec2/user-data.sh | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 96551a9..4c3d78f 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -11,18 +11,32 @@ if [[ "$EUID" -ne 0 ]]; then exit 1 fi +# --- Install Docker manually for Amazon Linux 2023 --- dnf update -y -curl -fsSL https://get.docker.com -o get-docker.sh -sh get-docker.sh + +# Remove any previous versions +dnf remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine || true + +# Add Dockerโ€™s Fedora 38 repo (compatible with AL2023) +cat <<'EOF' > /etc/yum.repos.d/docker-ce.repo +[docker-ce-stable] +name=Docker CE Stable - Fedora 38 +baseurl=https://download.docker.com/linux/fedora/38/x86_64/stable +enabled=1 +gpgcheck=1 +gpgkey=https://download.docker.com/linux/fedora/gpg +EOF + +# Install Docker and Compose plugin +dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +# Enable and start Docker systemctl enable --now docker usermod -aG docker ec2-user -# Ensure docker compose v2 works -if ! docker compose version >/dev/null 2>&1; then - echo "Installing Compose plugin via pip fallback..." - dnf install -y python3-pip - pip3 install docker-compose -fi +# Verify Docker and Compose are available +docker --version +docker compose version create_user() { local username=$1 From 0e33100a70327c8aed4039d29142e434bafb352d Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 3 Nov 2025 02:05:45 +0200 Subject: [PATCH 48/86] Update user-data.ssh, prepping api dir --- modules/compute-ec2/user-data.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 4c3d78f..a662eb1 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -74,6 +74,12 @@ ongeziwe_pubkey="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDL0jT8kt8H2Dv7VWO28IAiu89 create_user "ongeziwe" "$ongeziwe_pubkey" "true" # create_user "teammate1" "$teammate1_pubkey" "false" +# --- api directory --- +APP_DIR="/app/flagging-api" +mkdir -p $APP_DIR +chown -R ongeziwe:ec2-user $APP_DIR +chmod 775 $APP_DIR + sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config From 31f7c8b7d6b6315b8f323f9ada5fb9748bd166a4 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Mon, 3 Nov 2025 11:42:33 +0200 Subject: [PATCH 49/86] Update user-data.ssh, prepping api dir v2 --- modules/compute-ec2/user-data.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index a662eb1..f8762c2 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -77,8 +77,14 @@ create_user "ongeziwe" "$ongeziwe_pubkey" "true" # --- api directory --- APP_DIR="/app/flagging-api" mkdir -p $APP_DIR -chown -R ongeziwe:ec2-user $APP_DIR -chmod 775 $APP_DIR +chmod 775 /app +chmod 775 /app/flagging-api + +# group ownership to 'docker' for collaborative work +chown root:docker /app +chown root:docker /app/flagging-api +chown -R ongeziwe:docker /app/flagging-api +chmod g+s /app /app/flagging-api sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config From e2977da5536b5f91f082d6536697941601084b7c Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Tue, 4 Nov 2025 14:23:58 +0200 Subject: [PATCH 50/86] Upping ec2 instance resource to run sql-server (require 2GB RAM and more) --- modules/compute-ec2/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/compute-ec2/variables.tf b/modules/compute-ec2/variables.tf index 0e56a39..bf4dd53 100644 --- a/modules/compute-ec2/variables.tf +++ b/modules/compute-ec2/variables.tf @@ -16,7 +16,7 @@ variable "environment" { variable "instance_type" { description = "EC2 instance type for compute environment" type = string - default = "t3.micro" + default = "t3.medium" } variable "subnet_ids" { From 6498c9e1ef87b8d8f8ff32a1d268cde37616fcf3 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Tue, 4 Nov 2025 14:37:31 +0200 Subject: [PATCH 51/86] Upping ec2 instance to t3.small --- modules/compute-ec2/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/compute-ec2/variables.tf b/modules/compute-ec2/variables.tf index bf4dd53..a841d78 100644 --- a/modules/compute-ec2/variables.tf +++ b/modules/compute-ec2/variables.tf @@ -16,7 +16,7 @@ variable "environment" { variable "instance_type" { description = "EC2 instance type for compute environment" type = string - default = "t3.medium" + default = "t3.small" } variable "subnet_ids" { From ab9180f7fa934e96bf7a32804837c817c8715fb7 Mon Sep 17 00:00:00 2001 From: OngeziweM5 Date: Tue, 4 Nov 2025 15:00:38 +0200 Subject: [PATCH 52/86] adding a swapfile for 1GB more on the t3.small instance --- modules/compute-ec2/user-data.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index f8762c2..ba5bf96 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -11,6 +11,15 @@ if [[ "$EUID" -ne 0 ]]; then exit 1 fi +echo "Creating 1GB swap file..." +fallocate -l 1G /swapfile +chmod 600 /swapfile +mkswap /swapfile +swapon /swapfile +echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab +swapon --show +free -h + # --- Install Docker manually for Amazon Linux 2023 --- dnf update -y From a67f656198f43ec2ca40d8e0372adcb9da243976 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Tue, 11 Nov 2025 22:26:37 +0200 Subject: [PATCH 53/86] Update the README, testing the worklfows --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28a038f..1f5d2d1 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ feature-flags-infra/ --- -## Environments +## Environments and branches | **Environment** | **Branch** | **Purpose** | **Trigger** | |------------------|------------|--------------|----------------------------------------| From 3c0afd8296eff9fcfffcc4355452a1e8e0768fb0 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 03:22:16 +0200 Subject: [PATCH 54/86] Updated the infrastructure for adding frontend infra --- environments/development/{ => backend}/backend.hcl | 0 environments/development/{ => backend}/compute.tf | 0 environments/development/{ => backend}/main.tf | 0 environments/development/{ => backend}/network.tf | 0 environments/development/{ => backend}/outputs.tf | 0 environments/development/{ => backend}/providers.tf | 0 environments/development/{ => backend}/secrets.tf | 0 environments/development/{ => backend}/variables.tf | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename environments/development/{ => backend}/backend.hcl (100%) rename environments/development/{ => backend}/compute.tf (100%) rename environments/development/{ => backend}/main.tf (100%) rename environments/development/{ => backend}/network.tf (100%) rename environments/development/{ => backend}/outputs.tf (100%) rename environments/development/{ => backend}/providers.tf (100%) rename environments/development/{ => backend}/secrets.tf (100%) rename environments/development/{ => backend}/variables.tf (100%) diff --git a/environments/development/backend.hcl b/environments/development/backend/backend.hcl similarity index 100% rename from environments/development/backend.hcl rename to environments/development/backend/backend.hcl diff --git a/environments/development/compute.tf b/environments/development/backend/compute.tf similarity index 100% rename from environments/development/compute.tf rename to environments/development/backend/compute.tf diff --git a/environments/development/main.tf b/environments/development/backend/main.tf similarity index 100% rename from environments/development/main.tf rename to environments/development/backend/main.tf diff --git a/environments/development/network.tf b/environments/development/backend/network.tf similarity index 100% rename from environments/development/network.tf rename to environments/development/backend/network.tf diff --git a/environments/development/outputs.tf b/environments/development/backend/outputs.tf similarity index 100% rename from environments/development/outputs.tf rename to environments/development/backend/outputs.tf diff --git a/environments/development/providers.tf b/environments/development/backend/providers.tf similarity index 100% rename from environments/development/providers.tf rename to environments/development/backend/providers.tf diff --git a/environments/development/secrets.tf b/environments/development/backend/secrets.tf similarity index 100% rename from environments/development/secrets.tf rename to environments/development/backend/secrets.tf diff --git a/environments/development/variables.tf b/environments/development/backend/variables.tf similarity index 100% rename from environments/development/variables.tf rename to environments/development/backend/variables.tf From 7d599eec91496a2bba9be0edef579b38b4ac8b7c Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 03:26:54 +0200 Subject: [PATCH 55/86] Missed the dir setup to point to dev backend --- .github/workflows/terraform-deploy.yml | 2 +- .github/workflows/terraform-validate.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index e814329..7a707df 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -36,7 +36,7 @@ jobs: id: set-env run: | if [[ "${GITHUB_REF##*/}" == "develop" ]]; then - echo "env_dir=environments/development" >> $GITHUB_OUTPUT + echo "env_dir=environments/development/backend" >> $GITHUB_OUTPUT elif [[ "${GITHUB_REF##*/}" == "staging" ]]; then echo "env_dir=environments/staging" >> $GITHUB_OUTPUT elif [[ "${GITHUB_REF##*/}" == "main" ]]; then diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index a36e10e..fe43763 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -27,11 +27,11 @@ jobs: run: terraform fmt -check -recursive - name: Terraform Init (development env) - working-directory: environments/development + working-directory: environments/development/backend run: terraform init -backend=false - name: Terraform Validate - working-directory: environments/development + working-directory: environments/development/backend run: terraform validate - name: Terraform Lint @@ -69,12 +69,12 @@ jobs: uses: hashicorp/setup-terraform@v3 - name: Terraform Init (with backend) - working-directory: environments/development + working-directory: environments/development/backend run: terraform init -backend-config=backend.hcl - name: Terraform Plan id: plan - working-directory: environments/development + working-directory: environments/development/backend env: TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} @@ -88,22 +88,22 @@ jobs: - name: Save Plan Output run: terraform show -no-color tfplan > plan.txt - working-directory: environments/development + working-directory: environments/development/backend - name: Upload Plan as Artifact uses: actions/upload-artifact@v4 with: name: terraform-plan - path: environments/development/plan.txt + path: environments/development/backend/plan.txt - name: Comment Plan on PR uses: marocchino/sticky-pull-request-comment@v2 with: - path: environments/development/plan.txt + path: environments/development/backend/plan.txt - name: Force unlock on failure if: failure() - working-directory: environments/development + working-directory: environments/development/backend run: | echo "Attempting to remove Terraform lock..." terraform force-unlock -force $(terraform show -json | jq -r '.lock.id') || true From 726afef1100e660ad94e89a03f6f114b51edfc1f Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 03:29:15 +0200 Subject: [PATCH 56/86] Missed the dir setup to point to modules from dev backend --- environments/development/backend/compute.tf | 2 +- environments/development/backend/network.tf | 2 +- environments/development/backend/secrets.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/environments/development/backend/compute.tf b/environments/development/backend/compute.tf index 6d6806a..b369537 100644 --- a/environments/development/backend/compute.tf +++ b/environments/development/backend/compute.tf @@ -3,7 +3,7 @@ # env/dev/compute.tf ############################################## module "compute" { - source = "../../modules/compute-ec2" + source = "../../../modules/compute-ec2" name_prefix = "ff-dev" environment = "development" subnet_ids = module.network.public_subnet_ids diff --git a/environments/development/backend/network.tf b/environments/development/backend/network.tf index cceb88c..4766dbe 100644 --- a/environments/development/backend/network.tf +++ b/environments/development/backend/network.tf @@ -4,7 +4,7 @@ ############################################## module "network" { - source = "../../modules/network" + source = "../../../modules/network" name = "ff-dev" environment = "development" vpc_cidr = "10.10.0.0/16" diff --git a/environments/development/backend/secrets.tf b/environments/development/backend/secrets.tf index 860934b..0759515 100644 --- a/environments/development/backend/secrets.tf +++ b/environments/development/backend/secrets.tf @@ -4,7 +4,7 @@ ############################################## module "secrets" { - source = "../../modules/secrets" + source = "../../../modules/secrets" sa_password = var.sa_password admin_key = var.admin_key redis_password = var.redis_password From 6ba76969ab8dd88dc9f0b5eeac196acba45cc8ef Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 09:07:07 +0200 Subject: [PATCH 57/86] Adjust backend.hcl for dev --- environments/development/backend/backend.hcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environments/development/backend/backend.hcl b/environments/development/backend/backend.hcl index a87e9b3..def1fcf 100644 --- a/environments/development/backend/backend.hcl +++ b/environments/development/backend/backend.hcl @@ -4,7 +4,7 @@ ############################################## bucket = "flagging-infra-tf-state-code-crafters" -key = "environments/development/terraform.tfstate" +key = "environments/development/backend/terraform.tfstate" region = "af-south-1" use_lockfile = true encrypt = true From 6bb19956406846c4c7f4d50043773d58ede2732f Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 10:35:43 +0200 Subject: [PATCH 58/86] Update secrets and vars in secrest module --- modules/secrets/main.tf | 20 ++++++++++++++++++++ modules/secrets/variables.tf | 11 +++++++++++ 2 files changed, 31 insertions(+) diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf index ee3f549..fdbf3d6 100644 --- a/modules/secrets/main.tf +++ b/modules/secrets/main.tf @@ -7,22 +7,42 @@ resource "aws_ssm_parameter" "sa_password" { name = "/ff/dev/SA_PASSWORD" type = "SecureString" value = var.sa_password + overwrite = true + tags = { + Environment = var.backend_environment + ManagedBy = "Terraform" + } } resource "aws_ssm_parameter" "admin_key" { name = "/ff/dev/ADMIN_KEY" type = "SecureString" value = var.admin_key + overwrite = true + tags = { + Environment = var.backend_environment + ManagedBy = "Terraform" + } } resource "aws_ssm_parameter" "redis_password" { name = "/ff/dev/REDIS_PASSWORD" type = "SecureString" value = var.redis_password + overwrite = true + tags = { + Environment = var.backend_environment + ManagedBy = "Terraform" + } } resource "aws_ssm_parameter" "ghcr_token" { name = "/ff/dev/GHCR_PAT" type = "SecureString" value = var.ghcr_token + overwrite = true + tags = { + Environment = var.backend_environment + ManagedBy = "Terraform" + } } diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf index 041172d..c3cf828 100644 --- a/modules/secrets/variables.tf +++ b/modules/secrets/variables.tf @@ -7,3 +7,14 @@ variable "sa_password" { type = string } variable "admin_key" { type = string } variable "redis_password" { type = string } variable "ghcr_token" { type = string } +variable "backend_environment" { + description = "The environment for backend secrets" + type = string + default = "development" +} + +variable "frontend_environment" { + description = "The environment for backend secrets" + type = string + default = "development" +} From b20b36958be628044c1df558dda31feb553f80a0 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 10:37:08 +0200 Subject: [PATCH 59/86] Update secrets and vars in secrest module vv2 --- modules/secrets/main.tf | 24 ++++++++++++------------ modules/secrets/variables.tf | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf index fdbf3d6..8c46f33 100644 --- a/modules/secrets/main.tf +++ b/modules/secrets/main.tf @@ -4,9 +4,9 @@ ############################################## resource "aws_ssm_parameter" "sa_password" { - name = "/ff/dev/SA_PASSWORD" - type = "SecureString" - value = var.sa_password + name = "/ff/dev/SA_PASSWORD" + type = "SecureString" + value = var.sa_password overwrite = true tags = { Environment = var.backend_environment @@ -15,9 +15,9 @@ resource "aws_ssm_parameter" "sa_password" { } resource "aws_ssm_parameter" "admin_key" { - name = "/ff/dev/ADMIN_KEY" - type = "SecureString" - value = var.admin_key + name = "/ff/dev/ADMIN_KEY" + type = "SecureString" + value = var.admin_key overwrite = true tags = { Environment = var.backend_environment @@ -26,9 +26,9 @@ resource "aws_ssm_parameter" "admin_key" { } resource "aws_ssm_parameter" "redis_password" { - name = "/ff/dev/REDIS_PASSWORD" - type = "SecureString" - value = var.redis_password + name = "/ff/dev/REDIS_PASSWORD" + type = "SecureString" + value = var.redis_password overwrite = true tags = { Environment = var.backend_environment @@ -37,9 +37,9 @@ resource "aws_ssm_parameter" "redis_password" { } resource "aws_ssm_parameter" "ghcr_token" { - name = "/ff/dev/GHCR_PAT" - type = "SecureString" - value = var.ghcr_token + name = "/ff/dev/GHCR_PAT" + type = "SecureString" + value = var.ghcr_token overwrite = true tags = { Environment = var.backend_environment diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf index c3cf828..fa675c5 100644 --- a/modules/secrets/variables.tf +++ b/modules/secrets/variables.tf @@ -8,13 +8,13 @@ variable "admin_key" { type = string } variable "redis_password" { type = string } variable "ghcr_token" { type = string } variable "backend_environment" { - description = "The environment for backend secrets" - type = string - default = "development" + description = "The environment for backend secrets" + type = string + default = "development" } variable "frontend_environment" { - description = "The environment for backend secrets" - type = string - default = "development" + description = "The environment for backend secrets" + type = string + default = "development" } From 40dd14444e5bf2fd06f7de3cb19ed8d0fab14bec Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 10:46:57 +0200 Subject: [PATCH 60/86] Update deploy infra workflow to use matching state from aws --- .github/workflows/terraform-deploy.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 7a707df..5b1818a 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -46,8 +46,18 @@ jobs: exit 1 fi - - name: Terraform Init - run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -backend-config=backend.hcl + - name: Terraform Init (Reconfigure Backend) + run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -reconfigure + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + + - name: Import Pre-existing AWS Resources + run: | + terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_iam_role.ec2_role ff-dev-ec2-role || true + terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_iam_instance_profile.ec2_profile ff-dev-ec2-profile || true + terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_key_pair.dev_admin ff-dev-admin || true + env: + AWS_REGION: ${{ secrets.AWS_REGION }} - name: Terraform Plan id: plan @@ -59,7 +69,7 @@ jobs: TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} - + - name: Show Plan Summary run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} show -no-color tfplan > ${{ steps.set-env.outputs.env_dir }}/plan.txt From 57106b9ff89922436e920ecd0be44ef2a85f6be9 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 11:01:30 +0200 Subject: [PATCH 61/86] Update deploy infra workflow env vars for s3 bucket name and path --- .github/workflows/terraform-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 5b1818a..f23aeb9 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -50,6 +50,8 @@ jobs: run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -reconfigure env: AWS_REGION: ${{ secrets.AWS_REGION }} + S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }} + S3_BUCKET_PATH: ${{ secrets.S3_BUCKET_PATH }} - name: Import Pre-existing AWS Resources run: | From b74890657a1347000b7edba558b7eaf16b7fc60b Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 11:19:33 +0200 Subject: [PATCH 62/86] Update deploy infra workflow env tf-vars for s3 bucket name and path --- .github/workflows/terraform-deploy.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index f23aeb9..486c160 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -52,6 +52,8 @@ jobs: 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 run: | @@ -60,6 +62,10 @@ jobs: terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_key_pair.dev_admin ff-dev-admin || true env: AWS_REGION: ${{ secrets.AWS_REGION }} + TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} + TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} + TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} + TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} - name: Terraform Plan id: plan From f65dcf96f5a13b52c94922e861718e997f643a4e Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 11:30:49 +0200 Subject: [PATCH 63/86] Update deploy infra workflow env backend config vars for s3 bucket name and path --- .github/workflows/terraform-deploy.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 486c160..d663e21 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -47,13 +47,18 @@ jobs: fi - name: Terraform Init (Reconfigure Backend) - run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -reconfigure + run: | + terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -reconfigure \ + -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ + -backend-config="key=${{ steps.set-env.outputs.env_dir }}/terraform.tfstate" \ + -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 }} + # 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 run: | From b3e009b3c02b39a399ef6728ece715e6c0def364 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 11:38:50 +0200 Subject: [PATCH 64/86] Updated Import Pre-existing AWS Resources Step --- .github/workflows/terraform-deploy.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index d663e21..74e7fde 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -62,6 +62,17 @@ jobs: - name: Import Pre-existing AWS Resources run: | + echo "Importing pre-existing AWS resources into Terraform state..." + + # Ensure backend is configured before import + terraform -chdir=${{ steps.set-env.outputs.env_dir }} init \ + -reconfigure \ + -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ + -backend-config="key=${{ steps.set-env.outputs.env_dir }}/terraform.tfstate" \ + -backend-config="region=${{ secrets.AWS_REGION }}" \ + -backend-config="encrypt=true" + + # Import existing resources terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_iam_role.ec2_role ff-dev-ec2-role || true terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_iam_instance_profile.ec2_profile ff-dev-ec2-profile || true terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_key_pair.dev_admin ff-dev-admin || true From 22af1bddf285e49f09dc08bf4152caa6672e5d19 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 11:41:31 +0200 Subject: [PATCH 65/86] Updated Import Pre-existing AWS Resources Step v2 --- .github/workflows/terraform-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 74e7fde..169743c 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -82,6 +82,8 @@ jobs: TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} + TF_VAR_allowed_ssh_cidrs: ${{ github.ref_name == 'develop' && secrets.ALLOWED_SSH_CIDRS || github.ref_name == 'staging' && secrets.ALLOWED_SSH_CIDRS || github.ref_name == 'main' && secrets.ALLOWED_SSH_CIDRS }} + TF_VAR_allowed_api_cidrs: ${{ github.ref_name == 'develop' && secrets.ALLOWED_API_CIDRS || github.ref_name == 'staging' && secrets.ALLOWED_API_CIDRS || github.ref_name == 'main' && secrets.ALLOWED_API_CIDRS }} - name: Terraform Plan id: plan From 8c283dbb1452db0d4ff0b390e2ed98694e032b89 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 13:05:21 +0200 Subject: [PATCH 66/86] Update script to install git --- modules/compute-ec2/user-data.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index ba5bf96..72bd60f 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -38,6 +38,8 @@ EOF # Install Docker and Compose plugin dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +# Install git +dnf install -y git # Enable and start Docker systemctl enable --now docker From 7f027309801607658671eda3437691980845bc65 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 13:11:28 +0200 Subject: [PATCH 67/86] Deploy workflow condition to run import when needed --- .github/workflows/terraform-deploy.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 169743c..0f2780f 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -3,7 +3,14 @@ name: Terraform Deploy on: push: branches: [main, staging, develop] + workflow_dispatch: + inputs: + import: + description: 'Import existing resources into Terraform state' + required: false + default: false + type: boolean permissions: id-token: write @@ -61,6 +68,7 @@ jobs: # 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 + if: github.event_name == 'workflow_dispatch' && github.event.inputs.import == 'true' run: | echo "Importing pre-existing AWS resources into Terraform state..." From be62d01d851f774b309b4e2cca5803b81e6180d7 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 20:45:06 +0200 Subject: [PATCH 68/86] Add infrastructure for frontend --- .github/workflows/terraform-deploy.yml | 105 ++++++++---------- .github/workflows/terraform-validate.yml | 45 ++++---- environments/development/backend/secrets.tf | 11 +- environments/development/backend/variables.tf | 5 + environments/development/frontend/backend.hcl | 10 ++ environments/development/frontend/compute.tf | 13 +++ environments/development/frontend/main.tf | 25 +++++ environments/development/frontend/network.tf | 16 +++ environments/development/frontend/outputs.tf | 52 +++++++++ .../development/frontend/providers.tf | 20 ++++ environments/development/frontend/secrets.tf | 10 ++ .../development/frontend/variables.tf | 54 +++++++++ modules/compute-ec2/main.tf | 4 +- modules/compute-ec2/user-data.sh | 8 ++ modules/compute-ec2/variables.tf | 6 + modules/secrets/main.tf | 9 ++ modules/secrets/variables.tf | 35 +++--- 17 files changed, 332 insertions(+), 96 deletions(-) create mode 100644 environments/development/frontend/backend.hcl create mode 100644 environments/development/frontend/compute.tf create mode 100644 environments/development/frontend/main.tf create mode 100644 environments/development/frontend/network.tf create mode 100644 environments/development/frontend/outputs.tf create mode 100644 environments/development/frontend/providers.tf create mode 100644 environments/development/frontend/secrets.tf create mode 100644 environments/development/frontend/variables.tf diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index 0f2780f..b73d804 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -21,7 +21,19 @@ jobs: deploy: name: Deploy Infrastructure runs-on: ubuntu-latest - environment: ${{ github.ref_name }} + environment: ${{ matrix.env }} + strategy: + fail-fast: false + matrix: + env: [develop, staging, main] + stack: [backend, frontend] + include: + - env: develop + branch: develop + - env: staging + branch: staging + - env: main + branch: main steps: - name: Checkout repository @@ -39,25 +51,17 @@ jobs: - name: Set up Terraform uses: hashicorp/setup-terraform@v3 - - name: Set Terraform Environment Directory - id: set-env + - name: Set dirs/keys + id: paths run: | - if [[ "${GITHUB_REF##*/}" == "develop" ]]; then - echo "env_dir=environments/development/backend" >> $GITHUB_OUTPUT - elif [[ "${GITHUB_REF##*/}" == "staging" ]]; then - echo "env_dir=environments/staging" >> $GITHUB_OUTPUT - elif [[ "${GITHUB_REF##*/}" == "main" ]]; then - echo "env_dir=environments/production" >> $GITHUB_OUTPUT - else - echo "No matching environment for branch ${GITHUB_REF##*/}" - exit 1 - fi - - - name: Terraform Init (Reconfigure Backend) + echo "env_dir=environments/${{ matrix.env }}/${{ matrix.stack }}" >> $GITHUB_OUTPUT + echo "s3_key=environments/${{ matrix.env }}/${{ matrix.stack }}/terraform.tfstate" >> $GITHUB_OUTPUT + + - name: Terraform Init (reconfigure backend) run: | - terraform -chdir=${{ steps.set-env.outputs.env_dir }} init -reconfigure \ + terraform -chdir=${{ steps.paths.outputs.env_dir }} init -reconfigure \ -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ - -backend-config="key=${{ steps.set-env.outputs.env_dir }}/terraform.tfstate" \ + -backend-config="key=${{ steps.paths.outputs.s3_key }}" \ -backend-config="region=${{ secrets.AWS_REGION }}" \ -backend-config="encrypt=true" env: @@ -67,66 +71,53 @@ jobs: # 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 - if: github.event_name == 'workflow_dispatch' && github.event.inputs.import == 'true' + - 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 AWS resources into Terraform state..." - - # Ensure backend is configured before import - terraform -chdir=${{ steps.set-env.outputs.env_dir }} init \ - -reconfigure \ - -backend-config="bucket=${{ secrets.S3_BUCKET_NAME }}" \ - -backend-config="key=${{ steps.set-env.outputs.env_dir }}/terraform.tfstate" \ - -backend-config="region=${{ secrets.AWS_REGION }}" \ - -backend-config="encrypt=true" - - # Import existing resources - terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_iam_role.ec2_role ff-dev-ec2-role || true - terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_iam_instance_profile.ec2_profile ff-dev-ec2-profile || true - terraform -chdir=${{ steps.set-env.outputs.env_dir }} import module.compute.aws_key_pair.dev_admin ff-dev-admin || true - env: - AWS_REGION: ${{ secrets.AWS_REGION }} - TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} - TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} - TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} - TF_VAR_allowed_ssh_cidrs: ${{ github.ref_name == 'develop' && secrets.ALLOWED_SSH_CIDRS || github.ref_name == 'staging' && secrets.ALLOWED_SSH_CIDRS || github.ref_name == 'main' && secrets.ALLOWED_SSH_CIDRS }} - TF_VAR_allowed_api_cidrs: ${{ github.ref_name == 'develop' && secrets.ALLOWED_API_CIDRS || github.ref_name == 'staging' && secrets.ALLOWED_API_CIDRS || github.ref_name == 'main' && secrets.ALLOWED_API_CIDRS }} + 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.set-env.outputs.env_dir }} plan -no-color -out=tfplan + run: terraform -chdir=${{ steps.paths.outputs.env_dir }} plan -no-color -out=tfplan env: - TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} - TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} - TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} + # 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.set-env.outputs.env_dir }} show -no-color tfplan > ${{ steps.set-env.outputs.env_dir }}/plan.txt + run: terraform -chdir=${{ steps.paths.outputs.env_dir }} show -no-color tfplan > ${{ steps.paths.outputs.env_dir }}/plan.txt - name: Upload Plan Output uses: actions/upload-artifact@v4 with: - name: ${{ github.ref_name }}-tfplan - path: ${{ steps.set-env.outputs.env_dir }}/plan.txt + name: tfplan-${{ matrix.env }}-${{ matrix.stack }} + path: ${{ steps.paths.outputs.env_dir }}/plan.txt - name: Comment Plan on PR if: github.event_name == 'pull_request' uses: marocchino/sticky-pull-request-comment@v2 with: - header: "Terraform Plan โ€“ ${{ steps.set-env.outputs.env_dir }}" - path: ${{ steps.set-env.outputs.env_dir }}/plan.txt + header: "Terraform Plan โ€“ ${{ matrix.env }}/${{ matrix.stack }}" + path: ${{ steps.paths.outputs.env_dir }}/plan.txt - name: Terraform Apply - if: github.ref_name == 'develop' || github.ref_name == 'staging' || github.ref_name == 'main' - run: terraform -chdir=${{ steps.set-env.outputs.env_dir }} apply -auto-approve + if: matrix.env == 'develop' && github.ref == 'refs/heads/develop' + run: terraform -chdir=${{ steps.paths.outputs.env_dir }} apply -auto-approve env: - TF_VAR_admin_key: ${{ github.ref_name == 'develop' && secrets.ADMIN_KEY_DEV || github.ref_name == 'staging' && secrets.ADMIN_KEY_STAGING || github.ref_name == 'main' && secrets.ADMIN_KEY_PROD }} - TF_VAR_redis_password: ${{ github.ref_name == 'develop' && secrets.REDIS_PASSWORD_DEV || github.ref_name == 'staging' && secrets.REDIS_PASSWORD_STAGING || github.ref_name == 'main' && secrets.REDIS_PASSWORD_PROD }} - TF_VAR_sa_password: ${{ github.ref_name == 'develop' && secrets.SA_PASSWORD_DEV || github.ref_name == 'staging' && secrets.SA_PASSWORD_STAGING || github.ref_name == 'main' && secrets.SA_PASSWORD_PROD }} - TF_VAR_ghcr_token: ${{ github.ref_name == 'develop' && secrets.GHCR_PAT || github.ref_name == 'staging' && secrets.GHCR_PAT || github.ref_name == 'main' && secrets.GHCR_PAT }} 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 || '' }} diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index fe43763..84d1b7f 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -15,6 +15,9 @@ jobs: terraform-validation: name: Validate Terraform configuration runs-on: ubuntu-latest + strategy: + matrix: + stack: [backend, frontend] steps: - name: Checkout repository @@ -27,11 +30,11 @@ jobs: run: terraform fmt -check -recursive - name: Terraform Init (development env) - working-directory: environments/development/backend + working-directory: environments/development/${{ matrix.stack }} run: terraform init -backend=false - name: Terraform Validate - working-directory: environments/development/backend + working-directory: environments/development/${{ matrix.stack }} run: terraform validate - name: Terraform Lint @@ -39,19 +42,21 @@ jobs: with: tflint_version: v0.52.0 - - name: Run TFLint recursively + - name: Run TFLint in stack dir + working-directory: environments/development/${{ matrix.stack }} run: | - find . -type f -name "*.tf" -exec dirname {} \; | sort -u | while read dir; do - echo "Running TFLint in $dir" - (cd "$dir" && tflint --init && tflint) || true - done + tflint --init + tflint - name: Summary run: echo "Terraform syntax and lint checks completed successfully." terraform-plan: - name: Terraform Plan + name: Terraform Plan (${{ matrix.stack }}) runs-on: ubuntu-latest + strategy: + matrix: + stack: [backend, frontend] needs: terraform-validation if: github.event_name == 'pull_request' @@ -69,16 +74,16 @@ jobs: uses: hashicorp/setup-terraform@v3 - name: Terraform Init (with backend) - working-directory: environments/development/backend + working-directory: environments/development/${{ matrix.stack }} run: terraform init -backend-config=backend.hcl - - name: Terraform Plan + - name: Terraform Plan (${{ matrix.stack }}) id: plan - working-directory: environments/development/backend + working-directory: environments/development/${{ matrix.stack }} env: - TF_VAR_admin_key: ${{ secrets.ADMIN_KEY_DEV }} - TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD_DEV }} - TF_VAR_sa_password: ${{ secrets.SA_PASSWORD_DEV }} + 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 || '' }} TF_VAR_ghcr_token: ${{ secrets.GHCR_PAT }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} @@ -87,23 +92,23 @@ jobs: - name: Save Plan Output - run: terraform show -no-color tfplan > plan.txt - working-directory: environments/development/backend + working-directory: environments/development/${{ matrix.stack }} + run: terraform show -no-color tfplan > plan-${{ matrix.stack }}.txt - name: Upload Plan as Artifact uses: actions/upload-artifact@v4 with: - name: terraform-plan - path: environments/development/backend/plan.txt + name: terraform-plan ${{ matrix.stack }} + path: environments/development/${{ matrix.stack }}/plan-${{ matrix.stack }}.txt - name: Comment Plan on PR uses: marocchino/sticky-pull-request-comment@v2 with: - path: environments/development/backend/plan.txt + path: environments/development/${{ matrix.stack }}/plan-${{ matrix.stack }}.txt - name: Force unlock on failure if: failure() - working-directory: environments/development/backend + working-directory: environments/development/${{ matrix.stack }}/plan-${{ matrix.stack }}.txt run: | echo "Attempting to remove Terraform lock..." terraform force-unlock -force $(terraform show -json | jq -r '.lock.id') || true diff --git a/environments/development/backend/secrets.tf b/environments/development/backend/secrets.tf index 0759515..f88858e 100644 --- a/environments/development/backend/secrets.tf +++ b/environments/development/backend/secrets.tf @@ -4,9 +4,10 @@ ############################################## module "secrets" { - source = "../../../modules/secrets" - sa_password = var.sa_password - admin_key = var.admin_key - redis_password = var.redis_password - ghcr_token = var.ghcr_token + source = "../../../modules/secrets" + backend_environment = "development" + sa_password = var.sa_password + admin_key = var.admin_key + redis_password = var.redis_password + ghcr_token = var.ghcr_token } diff --git a/environments/development/backend/variables.tf b/environments/development/backend/variables.tf index 53ad708..68a1f44 100644 --- a/environments/development/backend/variables.tf +++ b/environments/development/backend/variables.tf @@ -8,6 +8,11 @@ variable "aws_region" { default = "af-south-1" } +variable "backend_environment" { + description = "The environment (backend) to deploy resources" + type = string +} + variable "sa_password" { description = "SQL SA password for development" type = string diff --git a/environments/development/frontend/backend.hcl b/environments/development/frontend/backend.hcl new file mode 100644 index 0000000..a1e6291 --- /dev/null +++ b/environments/development/frontend/backend.hcl @@ -0,0 +1,10 @@ +############################################## +# DEVELOPMENT ENVIRONMENT BACKEND +# env/dev/frontend/backend.hcl +############################################## + +bucket = "flagging-infra-tf-state-code-crafters" +key = "environments/development/frontend/terraform.tfstate" +region = "af-south-1" +use_lockfile = true +encrypt = true diff --git a/environments/development/frontend/compute.tf b/environments/development/frontend/compute.tf new file mode 100644 index 0000000..b326fb6 --- /dev/null +++ b/environments/development/frontend/compute.tf @@ -0,0 +1,13 @@ +############################################## +# DEVELOPMENT ENVIRONMENT OUTPUTS +# env/dev/frontend/compute.tf +############################################## +module "compute" { + source = "../../../modules/compute-ec2" + name_prefix = "ff-dev-frontend" + environment = "development" + environment_type = "frontend" + subnet_ids = module.network.public_subnet_ids + security_group_id = module.network.host_sg_id + key_name = "ff-dev-frontend-admin" +} diff --git a/environments/development/frontend/main.tf b/environments/development/frontend/main.tf new file mode 100644 index 0000000..f548eeb --- /dev/null +++ b/environments/development/frontend/main.tf @@ -0,0 +1,25 @@ +############################################## +# DEVELOPMENT ENVIRONMENT INFRASTRUCTURE +# env/dev/frontend/main.tf +############################################## + +# S3 Bucket for Dev Testing +resource "random_id" "suffix" { + byte_length = 4 +} + +resource "aws_s3_bucket" "dev_test_bucket" { + bucket = "ff-dev-frontend-test-bucket-${random_id.suffix.hex}" + + tags = { + Name = "DevFrontendTestBucket" + Environment = "development" + ManagedBy = "Terraform" + } +} + +resource "aws_eip" "dev_app" { + instance = module.compute.instance_id + domain = "vpc" + tags = { Name = "ff-dev-frontend-eip" } +} diff --git a/environments/development/frontend/network.tf b/environments/development/frontend/network.tf new file mode 100644 index 0000000..bd15646 --- /dev/null +++ b/environments/development/frontend/network.tf @@ -0,0 +1,16 @@ +############################################## +# DEVELOPMENT ENVIRONMENT NETWORK +# env/dev/frontend/network.tf +############################################## + +module "network" { + source = "../../../modules/network" + name = "ff-dev-frontend" + environment = "development" + vpc_cidr = "10.10.0.0/16" + public_subnet_cidr_a = "10.10.1.0/24" + public_subnet_cidr_b = "10.10.2.0/24" + az_a = "af-south-1a" + az_b = "af-south-1b" + allowed_api_cidrs = var.allowed_api_cidrs +} diff --git a/environments/development/frontend/outputs.tf b/environments/development/frontend/outputs.tf new file mode 100644 index 0000000..a925594 --- /dev/null +++ b/environments/development/frontend/outputs.tf @@ -0,0 +1,52 @@ +############################################## +# DEVELOPMENT ENVIRONMENT OUTPUTS +# env/dev/frontend/outputs.tf +############################################## +output "region" { + description = "Region where resources are deployed" + value = var.aws_region +} + +output "vpc_id" { + description = "VPC ID for development environment" + value = module.network.vpc_id +} + +output "public_subnets" { + description = "List of public subnet IDs" + value = module.network.public_subnet_ids +} + +output "instance_public_ip" { + description = "Public IP of the EC2 instance" + value = module.compute.instance_public_ip +} + +output "dev_frontend_test_bucket" { + description = "S3 bucket name for dev frontend test bucket" + value = aws_s3_bucket.dev_test_bucket.bucket +} + +# output "admin_key_arn" { +# description = "ARN of the admin key parameter in SSM" +# value = module.secrets.admin_key_arn +# } + +# output "sa_password" { +# description = "SA password parameter in SSM" +# value = module.secrets.sa_password_arn +# } + +# output "redis_password_arn" { +# description = "Redis password parameter in SSM" +# value = module.secrets.redis_password_arn +# } + +output "ghcr_token_arn" { + description = "GHCR Token parameter in SSM" + value = module.secrets.ghcr_token_arn +} +output "elastic_ip" { + description = "Elastic IP address associated with the EC2 instance" + value = aws_eip.dev_app.public_ip +} diff --git a/environments/development/frontend/providers.tf b/environments/development/frontend/providers.tf new file mode 100644 index 0000000..5053696 --- /dev/null +++ b/environments/development/frontend/providers.tf @@ -0,0 +1,20 @@ +############################################## +# DEVELOPMENT ENVIRONMENT PROVIDERS +# env/dev/frontend/providers.tf +############################################## + +terraform { + required_version = ">= 1.6.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + backend "s3" {} +} + +provider "aws" { + region = var.aws_region +} diff --git a/environments/development/frontend/secrets.tf b/environments/development/frontend/secrets.tf new file mode 100644 index 0000000..85b3f4b --- /dev/null +++ b/environments/development/frontend/secrets.tf @@ -0,0 +1,10 @@ +############################################## +# DEVELOPMENT ENVIRONMENT SECRETS +# eenv/dev/frontend/secrets.tf +############################################## + +module "secrets" { + source = "../../../modules/secrets" + backend_environment = "development" + ghcr_token = var.ghcr_token +} diff --git a/environments/development/frontend/variables.tf b/environments/development/frontend/variables.tf new file mode 100644 index 0000000..37e2cc4 --- /dev/null +++ b/environments/development/frontend/variables.tf @@ -0,0 +1,54 @@ +############################################## +# DEVELOPMENT ENVIRONMENT VARIABLES +# env/dev/frontend/variables.tf +############################################## +variable "aws_region" { + description = "AWS region to deploy resources" + type = string + default = "af-south-1" +} + +variable "backend_environment" { + description = "The environment (frontend) to deploy resources" + type = string +} + +variable "sa_password" { + description = "SQL SA password for development" + type = string + sensitive = true +} + +variable "redis_password" { + description = "Redis password for development" + type = string + sensitive = true +} + +variable "admin_key" { + description = "Admin API key for development" + type = string + sensitive = true +} + +variable "ghcr_token" { + description = "GHCR token" + type = string + sensitive = true +} + +variable "key_name" { + description = "EC2 key pair name for SSH access (optional)" + type = string + default = null +} + +variable "allowed_ssh_cidrs" { + description = "List of CIDR blocks allowed to SSH into EC2 (22)" + type = list(string) +} + +variable "allowed_api_cidrs" { + description = "List of CIDR blocks allowed to reach API (8080)" + type = list(string) +} diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index 70ac66c..e4f49aa 100644 --- a/modules/compute-ec2/main.tf +++ b/modules/compute-ec2/main.tf @@ -62,7 +62,9 @@ resource "aws_instance" "app_server" { iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.key_name depends_on = [aws_key_pair.dev_admin] - user_data = file("${path.module}/user-data.sh") + user_data = templatefile("${path.module}/scripts/userdata.sh", { + ENVIRONMENT_TYPE = var.environment_type + }) tags = { Name = "${var.name_prefix}-server" diff --git a/modules/compute-ec2/user-data.sh b/modules/compute-ec2/user-data.sh index 72bd60f..0aaaf88 100644 --- a/modules/compute-ec2/user-data.sh +++ b/modules/compute-ec2/user-data.sh @@ -103,4 +103,12 @@ sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config systemctl restart sshd +# ------- Frontend-only bit (Node.js + npm) ------- +if [[ "${ENVIRONMENT_TYPE}" == "frontend" ]]; then + # Node 22 on AL2023 + curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - + dnf install -y nodejs + npm install -g npm@latest +fi + echo "Bootstrap complete. Users ready for SSH access." >> /var/log/userdata-bootstrap.log diff --git a/modules/compute-ec2/variables.tf b/modules/compute-ec2/variables.tf index a841d78..df7c440 100644 --- a/modules/compute-ec2/variables.tf +++ b/modules/compute-ec2/variables.tf @@ -13,6 +13,12 @@ variable "environment" { type = string } +variable "environment_type" { + description = "Type of environment to configure (backend or frontend)" + type = string + default = "backend" +} + variable "instance_type" { description = "EC2 instance type for compute environment" type = string diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf index 8c46f33..966e335 100644 --- a/modules/secrets/main.tf +++ b/modules/secrets/main.tf @@ -4,6 +4,7 @@ ############################################## resource "aws_ssm_parameter" "sa_password" { + count = var.sa_password == null ? 0 : 1 name = "/ff/dev/SA_PASSWORD" type = "SecureString" value = var.sa_password @@ -15,6 +16,7 @@ resource "aws_ssm_parameter" "sa_password" { } resource "aws_ssm_parameter" "admin_key" { + count = var.admin_key == null ? 0 : 1 name = "/ff/dev/ADMIN_KEY" type = "SecureString" value = var.admin_key @@ -26,6 +28,7 @@ resource "aws_ssm_parameter" "admin_key" { } resource "aws_ssm_parameter" "redis_password" { + count = var.redis_password == null ? 0 : 1 name = "/ff/dev/REDIS_PASSWORD" type = "SecureString" value = var.redis_password @@ -37,6 +40,7 @@ resource "aws_ssm_parameter" "redis_password" { } resource "aws_ssm_parameter" "ghcr_token" { + count = var.ghcr_token == null ? 0 : 1 name = "/ff/dev/GHCR_PAT" type = "SecureString" value = var.ghcr_token @@ -46,3 +50,8 @@ resource "aws_ssm_parameter" "ghcr_token" { ManagedBy = "Terraform" } } + +output "ghcr_token_arn" { + value = try(aws_ssm_parameter.ghcr_token[0].arn, null) + description = "ARN of GHCR PAT if created" +} diff --git a/modules/secrets/variables.tf b/modules/secrets/variables.tf index fa675c5..58154aa 100644 --- a/modules/secrets/variables.tf +++ b/modules/secrets/variables.tf @@ -3,18 +3,27 @@ # modules/secrets/variables.tf ############################################## -variable "sa_password" { type = string } -variable "admin_key" { type = string } -variable "redis_password" { type = string } -variable "ghcr_token" { type = string } -variable "backend_environment" { - description = "The environment for backend secrets" - type = string - default = "development" +variable "sa_password" { + type = string + nullable = true + default = null } - -variable "frontend_environment" { - description = "The environment for backend secrets" - type = string - default = "development" +variable "admin_key" { + type = string + nullable = true + default = null } +variable "redis_password" { + type = string + nullable = true + default = null +} +variable "ghcr_token" { + type = string + nullable = true + default = null +} +variable "backend_environment" { + type = string + description = "env label for SSM path tags" +} \ No newline at end of file From c262dcadef9192a9ec7c51b0c7c54222afec85e7 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 20:49:49 +0200 Subject: [PATCH 69/86] Fix Duplicate output definition for backend and frontend --- environments/development/backend/outputs.tf | 20 +++++++-------- environments/development/frontend/outputs.tf | 27 +++++--------------- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/environments/development/backend/outputs.tf b/environments/development/backend/outputs.tf index c2f9008..589e34f 100644 --- a/environments/development/backend/outputs.tf +++ b/environments/development/backend/outputs.tf @@ -2,51 +2,51 @@ # DEVELOPMENT ENVIRONMENT OUTPUTS # env/dev/outputs.tf ############################################## -output "region" { +output "dev_backend_region" { description = "Region where resources are deployed" value = var.aws_region } -output "vpc_id" { +output "dev_backend_vpc_id" { description = "VPC ID for development environment" value = module.network.vpc_id } -output "public_subnets" { +output "dev_backend_public_subnets" { description = "List of public subnet IDs" value = module.network.public_subnet_ids } -output "instance_public_ip" { +output "dev_backend_instance_public_ip" { description = "Public IP of the EC2 instance" value = module.compute.instance_public_ip } -output "dev_test_bucket" { +output "dev_backend_dev_test_bucket" { description = "S3 bucket name for dev test bucket" value = aws_s3_bucket.dev_test_bucket.bucket } -output "admin_key_arn" { +output "dev_backend_admin_key_arn" { description = "ARN of the admin key parameter in SSM" value = module.secrets.admin_key_arn } -output "sa_password" { +output "dev_backend_sa_password" { description = "SA password parameter in SSM" value = module.secrets.sa_password_arn } -output "redis_password_arn" { +output "dev_backend_redis_password_arn" { description = "Redis password parameter in SSM" value = module.secrets.redis_password_arn } -output "ghcr_token_arn" { +output "dev_backend_ghcr_token_arn" { description = "GHCR Token parameter in SSM" value = module.secrets.ghcr_token_arn } -output "elastic_ip" { +output "dev_backend_elastic_ip" { description = "Elastic IP address associated with the EC2 instance" value = aws_eip.dev_app.public_ip } diff --git a/environments/development/frontend/outputs.tf b/environments/development/frontend/outputs.tf index a925594..552b97a 100644 --- a/environments/development/frontend/outputs.tf +++ b/environments/development/frontend/outputs.tf @@ -2,22 +2,22 @@ # DEVELOPMENT ENVIRONMENT OUTPUTS # env/dev/frontend/outputs.tf ############################################## -output "region" { +output "dev_frontend_region" { description = "Region where resources are deployed" value = var.aws_region } -output "vpc_id" { +output "dev_frontend_vpc_id" { description = "VPC ID for development environment" value = module.network.vpc_id } -output "public_subnets" { +output "dev_frontend_public_subnets" { description = "List of public subnet IDs" value = module.network.public_subnet_ids } -output "instance_public_ip" { +output "dev_frontend_instance_public_ip" { description = "Public IP of the EC2 instance" value = module.compute.instance_public_ip } @@ -27,26 +27,11 @@ output "dev_frontend_test_bucket" { value = aws_s3_bucket.dev_test_bucket.bucket } -# output "admin_key_arn" { -# description = "ARN of the admin key parameter in SSM" -# value = module.secrets.admin_key_arn -# } - -# output "sa_password" { -# description = "SA password parameter in SSM" -# value = module.secrets.sa_password_arn -# } - -# output "redis_password_arn" { -# description = "Redis password parameter in SSM" -# value = module.secrets.redis_password_arn -# } - -output "ghcr_token_arn" { +output "dev_frontend_ghcr_token_arn" { description = "GHCR Token parameter in SSM" value = module.secrets.ghcr_token_arn } -output "elastic_ip" { +output "dev_frontend_elastic_ip" { description = "Elastic IP address associated with the EC2 instance" value = aws_eip.dev_app.public_ip } From 8e4119ef9e646e353e0da4e4e84c279925b931ee Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 20:57:01 +0200 Subject: [PATCH 70/86] Fix Duplicate output definition for backend and frontend --- modules/secrets/main.tf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/secrets/main.tf b/modules/secrets/main.tf index 966e335..94e8537 100644 --- a/modules/secrets/main.tf +++ b/modules/secrets/main.tf @@ -50,8 +50,3 @@ resource "aws_ssm_parameter" "ghcr_token" { ManagedBy = "Terraform" } } - -output "ghcr_token_arn" { - value = try(aws_ssm_parameter.ghcr_token[0].arn, null) - description = "ARN of GHCR PAT if created" -} From 5219ed7e37ec4374bfbdd5c331bfcbf011324185 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 21:02:13 +0200 Subject: [PATCH 71/86] Fix Invalid function argument in the compute-ec2 module pointing to user-data.sh script --- 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 e4f49aa..c5bb694 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 = [aws_key_pair.dev_admin] - user_data = templatefile("${path.module}/scripts/userdata.sh", { + user_data = templatefile("${path.module}/user-data.sh", { ENVIRONMENT_TYPE = var.environment_type }) From fd5270fe9010e9640952f8148495715b75db743c Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 21:05:10 +0200 Subject: [PATCH 72/86] Reference ws_ssm_parameter resources as a list --- modules/compute-ec2/main.tf | 2 +- modules/secrets/outputs.tf | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/compute-ec2/main.tf b/modules/compute-ec2/main.tf index c5bb694..5f9900d 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 = [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 }) diff --git a/modules/secrets/outputs.tf b/modules/secrets/outputs.tf index 10ff89d..f0b6a9c 100644 --- a/modules/secrets/outputs.tf +++ b/modules/secrets/outputs.tf @@ -4,17 +4,21 @@ ############################################## output "admin_key_arn" { - value = aws_ssm_parameter.admin_key.arn + description = "ARN of the Admin key parameter (if created)" + value = try(aws_ssm_parameter.admin_key[0].arn, null) } output "sa_password_arn" { - value = aws_ssm_parameter.sa_password.arn + description = "ARN of the SA password parameter (if created)" + value = try(aws_ssm_parameter.sa_password[0].arn, null) } output "redis_password_arn" { - value = aws_ssm_parameter.redis_password.arn + description = "ARN of the Redis password parameter (if created)" + value = try(aws_ssm_parameter.redis_password[0].arn, null) } output "ghcr_token_arn" { - value = aws_ssm_parameter.ghcr_token.arn + description = "ARN of the GHCR token parameter (if created)" + value = try(aws_ssm_parameter.ghcr_token[0].arn, null) } From b64753c940ae66be58dca179d046bf5a5032d903 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 22:16:58 +0200 Subject: [PATCH 73/86] Commented out declared vars but unused --- environments/development/backend/variables.tf | 18 +++++++++--------- environments/development/frontend/network.tf | 1 + environments/development/frontend/providers.tf | 4 ++++ environments/development/frontend/variables.tf | 18 +++++++++--------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/environments/development/backend/variables.tf b/environments/development/backend/variables.tf index 68a1f44..1a96b8c 100644 --- a/environments/development/backend/variables.tf +++ b/environments/development/backend/variables.tf @@ -8,10 +8,10 @@ variable "aws_region" { default = "af-south-1" } -variable "backend_environment" { - description = "The environment (backend) to deploy resources" - type = string -} +# variable "backend_environment" { +# description = "The environment (backend) to deploy resources" +# type = string +# } variable "sa_password" { description = "SQL SA password for development" @@ -37,11 +37,11 @@ variable "ghcr_token" { sensitive = true } -variable "key_name" { - description = "EC2 key pair name for SSH access (optional)" - type = string - default = null -} +# variable "key_name" { +# description = "EC2 key pair name for SSH access (optional)" +# type = string +# default = null +# } variable "allowed_ssh_cidrs" { description = "List of CIDR blocks allowed to SSH into EC2 (22)" diff --git a/environments/development/frontend/network.tf b/environments/development/frontend/network.tf index bd15646..b93cf3e 100644 --- a/environments/development/frontend/network.tf +++ b/environments/development/frontend/network.tf @@ -13,4 +13,5 @@ module "network" { az_a = "af-south-1a" az_b = "af-south-1b" allowed_api_cidrs = var.allowed_api_cidrs + allowed_ssh_cidrs = var.allowed_ssh_cidrs } diff --git a/environments/development/frontend/providers.tf b/environments/development/frontend/providers.tf index 5053696..837bf9b 100644 --- a/environments/development/frontend/providers.tf +++ b/environments/development/frontend/providers.tf @@ -10,6 +10,10 @@ terraform { source = "hashicorp/aws" version = "~> 5.0" } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } } backend "s3" {} diff --git a/environments/development/frontend/variables.tf b/environments/development/frontend/variables.tf index 37e2cc4..0cebd3a 100644 --- a/environments/development/frontend/variables.tf +++ b/environments/development/frontend/variables.tf @@ -8,10 +8,10 @@ variable "aws_region" { default = "af-south-1" } -variable "backend_environment" { - description = "The environment (frontend) to deploy resources" - type = string -} +# variable "backend_environment" { +# description = "The environment (frontend) to deploy resources" +# type = string +# } variable "sa_password" { description = "SQL SA password for development" @@ -37,11 +37,11 @@ variable "ghcr_token" { sensitive = true } -variable "key_name" { - description = "EC2 key pair name for SSH access (optional)" - type = string - default = null -} +# variable "key_name" { +# description = "EC2 key pair name for SSH access (optional)" +# type = string +# default = null +# } variable "allowed_ssh_cidrs" { description = "List of CIDR blocks allowed to SSH into EC2 (22)" From a1a250effbedfe1c8d15fefd1ec278a73a56a1e9 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 23:11:38 +0200 Subject: [PATCH 74/86] sa_password, redis_password, and admin_key not used for frontend --- .../development/frontend/variables.tf | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/environments/development/frontend/variables.tf b/environments/development/frontend/variables.tf index 0cebd3a..eeb60fa 100644 --- a/environments/development/frontend/variables.tf +++ b/environments/development/frontend/variables.tf @@ -13,23 +13,23 @@ variable "aws_region" { # type = string # } -variable "sa_password" { - description = "SQL SA password for development" - type = string - sensitive = true -} +# variable "sa_password" { +# description = "SQL SA password for development" +# type = string +# sensitive = true +# } -variable "redis_password" { - description = "Redis password for development" - type = string - sensitive = true -} +# variable "redis_password" { +# description = "Redis password for development" +# type = string +# sensitive = true +# } -variable "admin_key" { - description = "Admin API key for development" - type = string - sensitive = true -} +# variable "admin_key" { +# description = "Admin API key for development" +# type = string +# sensitive = true +# } variable "ghcr_token" { description = "GHCR token" From dcd482dc7d0a75143e0b186e5c70ac3aabead6c0 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 23:15:19 +0200 Subject: [PATCH 75/86] backend provider random id and allowed_ssh_cidrs on network --- environments/development/backend/network.tf | 1 + environments/development/backend/providers.tf | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/environments/development/backend/network.tf b/environments/development/backend/network.tf index 4766dbe..6416f1e 100644 --- a/environments/development/backend/network.tf +++ b/environments/development/backend/network.tf @@ -13,4 +13,5 @@ module "network" { az_a = "af-south-1a" az_b = "af-south-1b" allowed_api_cidrs = var.allowed_api_cidrs + allowed_ssh_cidrs = var.allowed_ssh_cidrs } diff --git a/environments/development/backend/providers.tf b/environments/development/backend/providers.tf index f77aaf7..8d325dc 100644 --- a/environments/development/backend/providers.tf +++ b/environments/development/backend/providers.tf @@ -10,6 +10,10 @@ terraform { source = "hashicorp/aws" version = "~> 5.0" } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } } backend "s3" {} From 852adf26200a6115665697c7a1e89b115920d0bc Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 12 Nov 2025 23:28:42 +0200 Subject: [PATCH 76/86] Add header for each dev backend and frontend for tf plan and work dir in force unlock --- .github/workflows/terraform-validate.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 84d1b7f..8897065 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -104,11 +104,12 @@ jobs: - name: Comment Plan on PR uses: marocchino/sticky-pull-request-comment@v2 with: + header: "Terraform Plan โ€“ ${{ matrix.stack }}" path: environments/development/${{ matrix.stack }}/plan-${{ matrix.stack }}.txt - name: Force unlock on failure if: failure() - working-directory: environments/development/${{ matrix.stack }}/plan-${{ matrix.stack }}.txt + working-directory: environments/development/${{ matrix.stack }} run: | echo "Attempting to remove Terraform lock..." terraform force-unlock -force $(terraform show -json | jq -r '.lock.id') || true From 0d26df13888e1beb528b6507f485ec211524938c Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:00:58 +0200 Subject: [PATCH 77/86] Update all env_dir and S3 key paths to use development --- .github/workflows/terraform-deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index b73d804..90749ae 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -25,14 +25,14 @@ jobs: strategy: fail-fast: false matrix: - env: [develop, staging, main] + env: [development, staging, production] stack: [backend, frontend] include: - - env: develop + - env: development branch: develop - env: staging branch: staging - - env: main + - env: production branch: main steps: @@ -112,7 +112,7 @@ jobs: path: ${{ steps.paths.outputs.env_dir }}/plan.txt - name: Terraform Apply - if: matrix.env == 'develop' && github.ref == 'refs/heads/develop' + 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 }} From b7b9caa7310475c52894ca20015c735f41dbaaeb Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Thu, 13 Nov 2025 00:27:11 +0200 Subject: [PATCH 78/86] 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 79/86] 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 80/86] 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 81/86] 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 82/86] 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 }) From 1f3e2041fa61c9b0d479ad8b064367a8cae5f326 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Fri, 14 Nov 2025 02:37:48 +0200 Subject: [PATCH 83/86] add 8081 to Terraform security group for cms --- environments/development/backend/variables.tf | 16 ++++------ .../development/frontend/variables.tf | 29 ------------------- modules/network/main.tf | 9 ++++++ modules/network/variables.tf | 6 ++++ 4 files changed, 20 insertions(+), 40 deletions(-) diff --git a/environments/development/backend/variables.tf b/environments/development/backend/variables.tf index 1a96b8c..670da0f 100644 --- a/environments/development/backend/variables.tf +++ b/environments/development/backend/variables.tf @@ -8,11 +8,6 @@ variable "aws_region" { default = "af-south-1" } -# variable "backend_environment" { -# description = "The environment (backend) to deploy resources" -# type = string -# } - variable "sa_password" { description = "SQL SA password for development" type = string @@ -37,12 +32,6 @@ variable "ghcr_token" { sensitive = true } -# variable "key_name" { -# description = "EC2 key pair name for SSH access (optional)" -# type = string -# default = null -# } - variable "allowed_ssh_cidrs" { description = "List of CIDR blocks allowed to SSH into EC2 (22)" type = list(string) @@ -52,3 +41,8 @@ variable "allowed_api_cidrs" { description = "List of CIDR blocks allowed to reach API (8080)" type = list(string) } + +variable "allowed_cms_cidrs" { + description = "CIDR blocks allowed to access CMS (8081)" + type = list(string) +} diff --git a/environments/development/frontend/variables.tf b/environments/development/frontend/variables.tf index eeb60fa..be8c03c 100644 --- a/environments/development/frontend/variables.tf +++ b/environments/development/frontend/variables.tf @@ -8,41 +8,12 @@ variable "aws_region" { default = "af-south-1" } -# variable "backend_environment" { -# description = "The environment (frontend) to deploy resources" -# type = string -# } - -# variable "sa_password" { -# description = "SQL SA password for development" -# type = string -# sensitive = true -# } - -# variable "redis_password" { -# description = "Redis password for development" -# type = string -# sensitive = true -# } - -# variable "admin_key" { -# description = "Admin API key for development" -# type = string -# sensitive = true -# } - variable "ghcr_token" { description = "GHCR token" type = string sensitive = true } -# variable "key_name" { -# description = "EC2 key pair name for SSH access (optional)" -# type = string -# default = null -# } - variable "allowed_ssh_cidrs" { description = "List of CIDR blocks allowed to SSH into EC2 (22)" type = list(string) diff --git a/modules/network/main.tf b/modules/network/main.tf index 42e13cb..07c0918 100644 --- a/modules/network/main.tf +++ b/modules/network/main.tf @@ -108,6 +108,15 @@ resource "aws_security_group" "host" { description = "API (restricted to team or open during dev)" } + # CMS Access (restricted) + ingress { + from_port = 8081 + to_port = 8081 + protocol = "tcp" + cidr_blocks = var.allowed_cms_cidrs + description = "CMS Access (restricted)" + } + # Egress - Allow all outbound egress { from_port = 0 diff --git a/modules/network/variables.tf b/modules/network/variables.tf index 8f54524..3502379 100644 --- a/modules/network/variables.tf +++ b/modules/network/variables.tf @@ -22,3 +22,9 @@ variable "allowed_api_cidrs" { description = "List of CIDR blocks allowed to reach API (8080)" default = ["0.0.0.0/0"] } + +variable "allowed_cms_cidrs" { + type = list(string) + description = "CIDR blocks allowed to access CMS (8081)" + default = ["0.0.0.0/0"] +} From e3574ec1943395c37f19bddc8945d7f3168524a6 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Fri, 14 Nov 2025 02:46:06 +0200 Subject: [PATCH 84/86] add 8081 to Terraform security group for cms V2 --- .github/workflows/terraform-deploy.yml | 1 + .github/workflows/terraform-validate.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/terraform-deploy.yml b/.github/workflows/terraform-deploy.yml index f9608f6..06e5952 100644 --- a/.github/workflows/terraform-deploy.yml +++ b/.github/workflows/terraform-deploy.yml @@ -69,6 +69,7 @@ jobs: env: TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} + TF_VAR_allowed_cms_cidrs: ${{ secrets.ALLOWED_CMS_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 || '' }} diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index 8897065..aead7d1 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -87,6 +87,7 @@ jobs: TF_VAR_ghcr_token: ${{ secrets.GHCR_PAT }} TF_VAR_allowed_ssh_cidrs: ${{ secrets.ALLOWED_SSH_CIDRS }} TF_VAR_allowed_api_cidrs: ${{ secrets.ALLOWED_API_CIDRS }} + TF_VAR_allowed_cms_cidrs: ${{ secrets.ALLOWED_CMS_CIDRS }} TF_LOG: DEBUG run: terraform plan -no-color -out=tfplan From 8111e52a6b83dd114190cb2927178b5304c3dd3c Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Fri, 14 Nov 2025 02:53:44 +0200 Subject: [PATCH 85/86] add 8081 to Terraform security group for cms V3 --- environments/development/backend/network.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/environments/development/backend/network.tf b/environments/development/backend/network.tf index 6416f1e..027e863 100644 --- a/environments/development/backend/network.tf +++ b/environments/development/backend/network.tf @@ -14,4 +14,5 @@ module "network" { az_b = "af-south-1b" allowed_api_cidrs = var.allowed_api_cidrs allowed_ssh_cidrs = var.allowed_ssh_cidrs + allowed_cms_cidrs = var.allowed_cms_cidrs } From ec023166cb613afea5f41fcfe7fd79fede3b2d85 Mon Sep 17 00:00:00 2001 From: ongeziwe17 Date: Wed, 3 Dec 2025 01:01:23 +0200 Subject: [PATCH 86/86] Updated the backend.hcl to include the lock file --- environments/development/backend/backend.hcl | 1 + environments/development/frontend/backend.hcl | 1 + 2 files changed, 2 insertions(+) diff --git a/environments/development/backend/backend.hcl b/environments/development/backend/backend.hcl index def1fcf..e2344d1 100644 --- a/environments/development/backend/backend.hcl +++ b/environments/development/backend/backend.hcl @@ -4,6 +4,7 @@ ############################################## bucket = "flagging-infra-tf-state-code-crafters" +dynamodb_table = "flagging-infra-tf-locks" key = "environments/development/backend/terraform.tfstate" region = "af-south-1" use_lockfile = true diff --git a/environments/development/frontend/backend.hcl b/environments/development/frontend/backend.hcl index a1e6291..66d690c 100644 --- a/environments/development/frontend/backend.hcl +++ b/environments/development/frontend/backend.hcl @@ -4,6 +4,7 @@ ############################################## bucket = "flagging-infra-tf-state-code-crafters" +dynamodb_table = "flagging-infra-tf-locks" key = "environments/development/frontend/terraform.tfstate" region = "af-south-1" use_lockfile = true