From d7e6117fe53ef21270b26fce3be74cbc5d010e30 Mon Sep 17 00:00:00 2001 From: anehra-15 Date: Tue, 16 Sep 2025 19:39:39 -0400 Subject: [PATCH 01/11] terraform script for RDS deployment --- rds-terraform/main.tf | 80 +++++++++++++++++++++++++++++++++++++ rds-terraform/outputs.tf | 46 +++++++++++++++++++++ rds-terraform/qa.tfvars | 19 +++++++++ rds-terraform/variables.tf | 82 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 rds-terraform/main.tf create mode 100644 rds-terraform/outputs.tf create mode 100644 rds-terraform/qa.tfvars create mode 100644 rds-terraform/variables.tf diff --git a/rds-terraform/main.tf b/rds-terraform/main.tf new file mode 100644 index 0000000..f18cca6 --- /dev/null +++ b/rds-terraform/main.tf @@ -0,0 +1,80 @@ +========================================================================== +# Main Terraform Script to create the RDS instance based on passed values# +========================================================================== + +terraform { + + required_providers { + aws = { + source = "hashicorp/aws" # Official AWS provider from HashiCorp + version = "~> 5.0" + } + } + required_version = ">= 1.0" +} + +# ============================================================================= +# AWS PROVIDER CONFIGURATION +# ============================================================================= +provider "aws" { + region = var.aws_region # region we specify in variables +} + + +# ============================================================================= +# RDS POSTGRESQL DATABASE INSTANCE +# ============================================================================= + +resource "aws_db_instance" "postgres_db" { + + # ------------------------------------------------------------------------- + # BASIC DATABASE CONFIGURATION + # ------------------------------------------------------------------------- + identifier = "${var.environment}-postgres-db" + engine = "postgres" + instance_class = var.db_instance_class + + # ------------------------------------------------------------------------- + # STORAGE CONFIGURATION + # ------------------------------------------------------------------------- + allocated_storage = var.allocated_storage + storage_type = "gp2" # General Purpose SSD (cheapest option) + storage_encrypted = true # Encrypt data at rest (FREE security feature) + + # ------------------------------------------------------------------------- + # DATABASE ACCESS CREDENTIALS + # ------------------------------------------------------------------------- + db_name = var.database_name # Name of the database inside PostgreSQL + username = var.db_username # Admin username for database + + password = var.db_password + + # ------------------------------------------------------------------------- + # NETWORK SECURITY CONFIGURATION + # ------------------------------------------------------------------------- + vpc_security_group_ids = [var.vpc_security_group_id] + publicly_accessible = false # NO public internet access + + + # ------------------------------------------------------------------------- + # BACKUP CONFIGURATION + # ------------------------------------------------------------------------- + backup_retention_period = var.backup_retention_period # How many days to keep backups + + # ------------------------------------------------------------------------- + # CLEANUP CONFIGURATION + # ------------------------------------------------------------------------- + skip_final_snapshot = var.skip_final_snapshot # When deleting DB: + # true = Delete immediately + # false = Take final backup before deleting (costs extra) + + # ------------------------------------------------------------------------- + # OPTIONAL: RESOURCE TAGS FOR ORGANIZATION + # ------------------------------------------------------------------------- + # These are just labels - they don't cost anything but help organize resources + tags = { + Name = "${var.environment}-postgres-db" + Environment = var.environment + } +} + diff --git a/rds-terraform/outputs.tf b/rds-terraform/outputs.tf new file mode 100644 index 0000000..d45ba49 --- /dev/null +++ b/rds-terraform/outputs.tf @@ -0,0 +1,46 @@ + + +# ============================================================================= +# DATABASE CONNECTION INFORMATION +# ============================================================================= + +output "db_endpoint" { + description = "Database server address/hostname for connections" + value = aws_db_instance.postgres_db.endpoint + +} + +output "db_port" { + description = "Database port number" + value = aws_db_instance.postgres_db.port + +} + +output "db_name" { + description = "Database name to connect to" + value = aws_db_instance.postgres_db.db_name +} + +output "db_username" { + description = "Database admin username" + value = aws_db_instance.postgres_db.username + sensitive = true +} + + +# ============================================================================= +# AWS RESOURCE INFORMATION +# ============================================================================= + +output "db_instance_id" { + description = "AWS RDS instance identifier" + value = aws_db_instance.postgres_db.id + +} + +output "db_security_group_id" { + description = "Security group ID attached to the database" + value = var.vpc_security_group_id + +} + diff --git a/rds-terraform/qa.tfvars b/rds-terraform/qa.tfvars new file mode 100644 index 0000000..de3fe03 --- /dev/null +++ b/rds-terraform/qa.tfvars @@ -0,0 +1,19 @@ +# ============================================================================= +# tfvars to pass parameterized inputs tot he terraform script +# ============================================================================= +# 1. We can create similar tfvars for different instances, this way the main terraform deployment script stays untouched and we can deploy the instance across different regions with custom parameters +=============================================================================== + +aws_region = "eu-west-1" # Placeholder for EU region, will update accordingly +environment = "qa" # environment +db_instance_class = "db.t3.micro" +vpc_security_group_id = "" # saayam vpc group id + +# Optional customizations +database_name = "saayam_qa_db" # Placeholder Database name will update accordingly +db_username = "qa_admin" # Username +db_password = "" # need to discuss on appropriate password passing approach +allocated_storage = 20 +backup_retention_period = 0 +skip_final_snapshot = true + diff --git a/rds-terraform/variables.tf b/rds-terraform/variables.tf new file mode 100644 index 0000000..f5226b4 --- /dev/null +++ b/rds-terraform/variables.tf @@ -0,0 +1,82 @@ +# ============================================================================= +# TERRAFORM VARIABLES DEFINITION FILE +# ============================================================================= +# This file defines all the parameters you can customize when deploying +# the database. + +# ============================================================================= +# REQUIRED VARIABLES +# ============================================================================= + +variable "aws_region" { + description = "AWS region where the database will be created (e.g., eu-west-1 for Ireland)" + type = string + # Example values: + # - "eu-west-1" = Ireland + # - "eu-central-1" = Frankfurt + # - "eu-west-2" = London + # - "us-east-1" = N. Virginia +} + +variable "environment" { + description = "Environment name - used in naming and tagging resources" + type = string + # Example values: "qa", "staging", "prod", "dev" + +} + +variable "db_instance_class" { + description = "The size/type of the database server" + type = string +} + +variable "vpc_security_group_id" { + description = "ID of your existing VPC security group" + type = string +} + +# ============================================================================= +# OPTIONAL VARIABLES (Have defaults but can be customized) +# ============================================================================= + +variable "database_name" { + description = "Name of the database that will be created inside PostgreSQL" + type = string + default = "saayam_db" + +} + +variable "db_username" { + description = "Admin username for the database" + type = string + default = "saayam_admin" +} + +variable "db_password" { + description = "Password for the database admin user" + type = string + default = "" + sensitive = true # Terraform won't display this in logs + +} + +variable "allocated_storage" { + description = "Initial database storage size in GB" + type = number + default = 20 + +} + +variable "backup_retention_period" { + description = "How many days to keep automatic database backups (0 = no backups)" + type = number + default = 0 # No backups by default () + +} + +variable "skip_final_snapshot" { + description = "Skip taking a final backup when deleting the database" + type = bool + default = true # Skip by default (faster deletion, no extra cost) + +} From 128c0c7598f0bfd9c6e03520cbe6ec9bc66caa84 Mon Sep 17 00:00:00 2001 From: anehra-15 Date: Mon, 27 Oct 2025 22:12:58 -0400 Subject: [PATCH 02/11] pulumi script --- rds-terraform/pulumi/main.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rds-terraform/pulumi/main.py diff --git a/rds-terraform/pulumi/main.py b/rds-terraform/pulumi/main.py new file mode 100644 index 0000000..e69de29 From 8cb246ac4dd6518012f3d7bbe8f105c650eb0abd Mon Sep 17 00:00:00 2001 From: anehra-15 Date: Mon, 27 Oct 2025 22:14:36 -0400 Subject: [PATCH 03/11] pulumi script --- rds-terraform/pulumi/main.py | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/rds-terraform/pulumi/main.py b/rds-terraform/pulumi/main.py index e69de29..8af7fe2 100644 --- a/rds-terraform/pulumi/main.py +++ b/rds-terraform/pulumi/main.py @@ -0,0 +1,44 @@ +""" +Pulumi script to deploy RDS PostgreSQL instance +""" + +import pulumi +import pulumi_aws as aws + +# Read configuration values +config = pulumi.Config() +aws_region = config.require("aws_region") +environment = config.require("environment") +db_instance_class = config.require("db_instance_class") +vpc_security_group_id = config.require("vpc_security_group_id") + +# Optional configs +database_name = config.get("database_name") +db_username = config.get("db_username") +db_password = config.require("db_password") # Must provide password for now + +# Create RDS instance +rds_instance = aws.rds.Instance( + "postgres-db", + identifier=f"{environment}-postgres-db", + engine="postgres", + instance_class=db_instance_class, + allocated_storage=20, + storage_type="", + storage_encrypted=True, + db_name=database_name, + username=db_username, + password=db_password, + vpc_security_group_ids=[vpc_security_group_id], + publicly_accessible=False, + skip_final_snapshot=True, + backup_retention_period=0, + tags={ + "Name": f"{environment}-postgres-db", + "Environment": environment, + } +) + +# Export basic outputs +pulumi.export("db_endpoint", rds_instance.endpoint) +pulumi.export("db_name", rds_instance.db_name) \ No newline at end of file From b472ec00237bbd20d90d85a73a35f409087241e9 Mon Sep 17 00:00:00 2001 From: Abhinav Nehra <139734050+anehra-15@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:58:15 -0500 Subject: [PATCH 04/11] Create Pulumi.dev.yaml --- rds-terraform/pulumi/Pulumi.dev.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 rds-terraform/pulumi/Pulumi.dev.yaml diff --git a/rds-terraform/pulumi/Pulumi.dev.yaml b/rds-terraform/pulumi/Pulumi.dev.yaml new file mode 100644 index 0000000..eb5ab59 --- /dev/null +++ b/rds-terraform/pulumi/Pulumi.dev.yaml @@ -0,0 +1,12 @@ +config: + # Required Configuration + rds-deployment:aws_region: "eu-west-1" + rds-deployment:environment: "dev" + rds-deployment:db_instance_class: "db.t3.micro" + rds-deployment:vpc_security_group_id: "" # Replace with your SG ID + + # Provide Database Credentials + rds-deployment:database_name: "" + rds-deployment:db_username: "" + rds-deployment:db_password: + secure: "" From af79276645c71ce39c6eed4a374dc004d8f65d6b Mon Sep 17 00:00:00 2001 From: Abhinav Nehra <139734050+anehra-15@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:00:50 -0500 Subject: [PATCH 05/11] Create README.md --- rds-terraform/pulumi/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 rds-terraform/pulumi/README.md diff --git a/rds-terraform/pulumi/README.md b/rds-terraform/pulumi/README.md new file mode 100644 index 0000000..19a1441 --- /dev/null +++ b/rds-terraform/pulumi/README.md @@ -0,0 +1,28 @@ +# RDS PostgreSQL Deployment - DEV Environment + +Deploy AWS RDS PostgreSQL database for development using Pulumi. + +## Quick Start + +### 1. Install Requirements +```bash +pip install pulumi pulumi-aws +``` +### 2. Edit Configuration + +Open `Pulumi.dev.yaml` and replace: +- `` with your actual AWS Security Group ID + +### 3. Deploy +```bash +# Preview what will be created +pulumi preview + +# Create the database +pulumi up +``` + +## 4. Delete Everything +```bash +pulumi destroy +``` From f63f3cc2c18d4600bba46c1737162ce95e68bb9f Mon Sep 17 00:00:00 2001 From: Arup Chauhan Date: Sat, 6 Dec 2025 02:08:04 -0600 Subject: [PATCH 06/11] Added Pulumi file Signed-off-by: Arup Chauhan --- rds-terraform/pulumi/Pulumi.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 rds-terraform/pulumi/Pulumi.yaml diff --git a/rds-terraform/pulumi/Pulumi.yaml b/rds-terraform/pulumi/Pulumi.yaml new file mode 100644 index 0000000..f5944ed --- /dev/null +++ b/rds-terraform/pulumi/Pulumi.yaml @@ -0,0 +1,3 @@ +name: rds-qa-infra +runtime: python +description: Pulumi project for deploying the QA PostgreSQL RDS instance in eu-west-1 From ec2de513132ceddf7b0e5548606c33cead194c82 Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Mon, 8 Dec 2025 10:12:34 -0600 Subject: [PATCH 07/11] Fixed issues in rds-terraform/main.tf file --- rds-terraform/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rds-terraform/main.tf b/rds-terraform/main.tf index f18cca6..1a6a434 100644 --- a/rds-terraform/main.tf +++ b/rds-terraform/main.tf @@ -1,6 +1,6 @@ -========================================================================== +# ========================================================================== # Main Terraform Script to create the RDS instance based on passed values# -========================================================================== +# ========================================================================== terraform { From 66174b05ef93d8bca20ae61dc28292215b352bd4 Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Wed, 10 Dec 2025 14:41:12 -0600 Subject: [PATCH 08/11] Added Bugfixes in rds-terraform/qa.tfvars file --- rds-terraform/qa.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rds-terraform/qa.tfvars b/rds-terraform/qa.tfvars index de3fe03..e3df6ae 100644 --- a/rds-terraform/qa.tfvars +++ b/rds-terraform/qa.tfvars @@ -2,7 +2,7 @@ # tfvars to pass parameterized inputs tot he terraform script # ============================================================================= # 1. We can create similar tfvars for different instances, this way the main terraform deployment script stays untouched and we can deploy the instance across different regions with custom parameters -=============================================================================== +# =============================================================================== aws_region = "eu-west-1" # Placeholder for EU region, will update accordingly environment = "qa" # environment From 000b4d380d4b30a934ef5c9cd874ebbd0f0ecafa Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Thu, 11 Dec 2025 16:32:02 -0600 Subject: [PATCH 09/11] Fixed storage_type in the Pulumi main.py file --- rds-terraform/pulumi/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rds-terraform/pulumi/main.py b/rds-terraform/pulumi/main.py index 8af7fe2..1ba582e 100644 --- a/rds-terraform/pulumi/main.py +++ b/rds-terraform/pulumi/main.py @@ -24,7 +24,7 @@ engine="postgres", instance_class=db_instance_class, allocated_storage=20, - storage_type="", + storage_type="gp2", storage_encrypted=True, db_name=database_name, username=db_username, From cdb0ff334de365c026d5ed87d59afe98d2702601 Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Mon, 15 Dec 2025 19:52:57 -0600 Subject: [PATCH 10/11] Adding the local test plan followed for Pulumi script --- .../test-plan/pulumi-aws-test-plan.md | 485 ++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 rds-terraform/test-plan/pulumi-aws-test-plan.md diff --git a/rds-terraform/test-plan/pulumi-aws-test-plan.md b/rds-terraform/test-plan/pulumi-aws-test-plan.md new file mode 100644 index 0000000..913b5cb --- /dev/null +++ b/rds-terraform/test-plan/pulumi-aws-test-plan.md @@ -0,0 +1,485 @@ +# Pulumi AWS Test Plan + +## Document purpose + +This test plan verifies the Pulumi deployment of the Saayam PostgreSQL RDS instance in AWS. Unlike the local test plan, this plan creates real AWS infrastructure, validates it, and then destroys it unless the test environment is intentionally retained. + +## Scope + +This plan covers: + +- Pulumi deployment to AWS +- AWS RDS instance creation +- security group attachment +- private accessibility behavior +- encryption-at-rest validation +- PostgreSQL connection validation from an approved network path +- output validation +- cleanup and destroy validation + +This plan does **not** validate the full application database schema or application backend behavior. + +## AWS resources under test + +The Pulumi script is expected to create one AWS RDS PostgreSQL instance with these intended characteristics: + +| Property | Expected value | +|---|---| +| Engine | PostgreSQL | +| Instance identifier | `-postgres-db` | +| Instance class | Config value, for example `db.t3.micro` | +| Storage | 20 GB | +| Storage encryption | Enabled | +| Public accessibility | Disabled | +| Security group | Existing security group from config | +| Backups | `0` days for dev/test unless changed | +| Final snapshot on destroy | Skipped for dev/test unless changed | + +## Required approvals and safeguards + +Before running this plan, confirm: + +- The AWS account is approved for dev/test infrastructure. +- The selected region is approved, for example `eu-west-1`. +- The security group allows PostgreSQL access only from approved sources. +- The database username and password are test-only credentials. +- The RDS cost is approved. +- A cleanup owner is assigned. + +## Prerequisites + +Local tools: + +```bash +python --version +pulumi version +aws --version +psql --version +``` + +AWS prerequisites: + +- AWS credentials configured locally or in CI. +- IAM permissions to create, read, tag, and delete RDS instances. +- IAM permissions to read VPC and security group data. +- An existing VPC security group for the database. +- A network path for testing PostgreSQL access, such as a bastion host, VPN, ECS task, EC2 instance, or CloudShell/VPC-connected runner. + +## Recommended test stack + +Use a short-lived stack name such as: + +```bash +pulumi stack init aws-dev-test +pulumi stack select aws-dev-test +``` + +Use an environment value that makes the resource clearly temporary: + +```bash +pulumi config set environment dev-test +``` + +This should produce an RDS identifier similar to: + +```text +dev-test-postgres-db +``` + +## Configuration steps + +From the Pulumi directory: + +```bash +cd rds-terraform/pulumi +python -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install pulumi pulumi-aws +``` + +Set AWS and database config: + +```bash +pulumi config set aws_region eu-west-1 +pulumi config set aws:region eu-west-1 +pulumi config set environment dev-test +pulumi config set db_instance_class db.t3.micro +pulumi config set vpc_security_group_id +pulumi config set database_name saayam_dev_test_db +pulumi config set db_username dev_test_admin +pulumi config set --secret db_password '' +``` + +Confirm config: + +```bash +pulumi config +``` + +Expected result: + +- Region is set. +- Security group ID is populated. +- Database name is populated. +- Username is populated. +- Password is marked secret. + +## Test cases + +### TC-A1: Verify AWS identity and region + +**Objective:** Confirm deployment will run in the intended AWS account and region. + +**Steps:** + +```bash +aws sts get-caller-identity +aws configure get region +pulumi config get aws_region +pulumi config get aws:region +``` + +**Expected result:** + +The AWS account and region match the approved test environment. + +--- + +### TC-A2: Validate security group exists + +**Objective:** Confirm the configured security group exists before deployment. + +**Steps:** + +```bash +SG_ID=$(pulumi config get vpc_security_group_id) +aws ec2 describe-security-groups --group-ids "$SG_ID" +``` + +**Expected result:** + +AWS returns the configured security group. + +--- + +### TC-A3: Preview AWS changes + +**Objective:** Review planned infrastructure before creation. + +**Steps:** + +```bash +pulumi preview +``` + +**Expected result:** + +Pulumi plans to create one RDS instance. No unexpected resources should appear. + +Fail the test if: + +- The region is wrong. +- The identifier points to prod or a shared environment accidentally. +- The plan includes unexpected resources. +- The storage type or resource arguments are invalid. + +--- + +### TC-A4: Deploy RDS instance + +**Objective:** Create the test RDS instance. + +**Steps:** + +```bash +pulumi up +``` + +Approve the deployment only after checking the preview. + +**Expected result:** + +Pulumi completes successfully and exports at least: + +```text +db_endpoint +db_name +``` + +--- + +### TC-A5: Validate Pulumi outputs + +**Objective:** Confirm deployment outputs are available and usable. + +**Steps:** + +```bash +pulumi stack output db_endpoint +pulumi stack output db_name +``` + +**Expected result:** + +- `db_endpoint` is populated. +- `db_name` matches the configured database name. + +--- + +### TC-A6: Validate RDS instance in AWS + +**Objective:** Confirm the RDS instance exists with expected properties. + +**Steps:** + +```bash +DB_ID="$(pulumi config get environment)-postgres-db" +aws rds describe-db-instances --db-instance-identifier "$DB_ID" +``` + +Check these fields: + +```text +Engine +DBInstanceClass +AllocatedStorage +StorageEncrypted +PubliclyAccessible +VpcSecurityGroups +DBInstanceStatus +``` + +**Expected result:** + +- Engine is PostgreSQL. +- Instance class matches Pulumi config. +- Storage is 20 GB. +- Storage encryption is enabled. +- Public accessibility is false. +- Security group matches the configured security group. +- Instance status eventually becomes `available`. + +--- + +### TC-A7: Validate database is not public + +**Objective:** Confirm the database is not exposed directly to the internet. + +**Steps:** + +```bash +DB_ID="$(pulumi config get environment)-postgres-db" +aws rds describe-db-instances \ + --db-instance-identifier "$DB_ID" \ + --query 'DBInstances[0].PubliclyAccessible' +``` + +**Expected result:** + +```text +false +``` + +--- + +### TC-A8: Validate PostgreSQL connectivity from approved network path + +**Objective:** Confirm the database can be reached only from the intended network path. + +Run this from an approved source such as a bastion, EC2 instance, VPN-connected workstation, or other VPC-connected environment. + +**Steps:** + +```bash +ENDPOINT=$(pulumi stack output db_endpoint | cut -d: -f1) +DB_NAME=$(pulumi stack output db_name) +DB_USER=$(pulumi config get db_username) + +psql "host=$ENDPOINT port=5432 dbname=$DB_NAME user=$DB_USER sslmode=require" +``` + +When prompted, enter the test database password. + +Then run: + +```sql +SELECT version(); +SELECT current_database(); +SELECT current_user; +``` + +**Expected result:** + +- Connection succeeds from the approved network path. +- PostgreSQL returns version information. +- Current database matches `saayam_dev_test_db` or the configured database name. +- Current user matches the configured database username. + +--- + +### TC-A9: Validate blocked access from unapproved network path + +**Objective:** Confirm the database cannot be accessed from an unauthorized location. + +**Steps:** + +From a machine not allowed by the security group, attempt: + +```bash +ENDPOINT= +psql "host=$ENDPOINT port=5432 dbname=saayam_dev_test_db user=dev_test_admin sslmode=require" +``` + +**Expected result:** + +Connection fails or times out. This is expected because `publicly_accessible` is false and the security group should restrict inbound access. + +--- + +### TC-A10: Validate tags + +**Objective:** Confirm AWS tags were applied. + +**Steps:** + +```bash +DB_ARN=$(aws rds describe-db-instances \ + --db-instance-identifier "$(pulumi config get environment)-postgres-db" \ + --query 'DBInstances[0].DBInstanceArn' \ + --output text) + +aws rds list-tags-for-resource --resource-name "$DB_ARN" +``` + +**Expected result:** + +Tags include: + +```text +Name = -postgres-db +Environment = +``` + +--- + +### TC-A11: Validate Pulumi refresh + +**Objective:** Confirm Pulumi state matches AWS. + +**Steps:** + +```bash +pulumi refresh +``` + +**Expected result:** + +Pulumi detects no unexpected drift, or any drift is documented and explained. + +--- + +### TC-A12: Destroy test infrastructure + +**Objective:** Remove AWS resources created for the test. + +**Steps:** + +```bash +pulumi destroy +``` + +Then verify deletion: + +```bash +aws rds describe-db-instances --db-instance-identifier "$(pulumi config get environment)-postgres-db" +``` + +**Expected result:** + +Pulumi destroy completes successfully. The AWS CLI describe command eventually returns not found. + +--- + +### TC-A13: Remove test stack if no longer needed + +**Objective:** Clean up Pulumi stack state after successful destroy. + +**Steps:** + +```bash +pulumi stack rm aws-dev-test +``` + +**Expected result:** + +The temporary stack is removed. + +## AWS test exit criteria + +AWS testing is complete when: + +- Preview shows only expected resources. +- RDS instance deploys successfully. +- RDS instance becomes available. +- RDS is encrypted at rest. +- RDS is not publicly accessible. +- Configured security group is attached. +- PostgreSQL connection succeeds from an approved network path. +- PostgreSQL connection fails from an unapproved network path. +- Pulumi outputs are correct. +- Tags are correct. +- `pulumi refresh` shows no unexplained drift. +- Test infrastructure is destroyed unless there is an approved reason to keep it. + +## Rollback plan + +If deployment fails: + +1. Capture the Pulumi error output. +2. Run: + + ```bash + pulumi stack + pulumi refresh + ``` + +3. Check whether a partial RDS instance was created: + + ```bash + aws rds describe-db-instances + ``` + +4. If a partial resource exists and is safe to remove, run: + + ```bash + pulumi destroy + ``` + +5. If Pulumi state and AWS are out of sync, document the resource ID before manually deleting anything. + +## Risks and mitigations + +| Risk | Mitigation | +|---|---| +| Accidental production deployment | Use a dedicated stack and temporary environment name such as `dev-test` | +| Unexpected AWS cost | Destroy the stack immediately after validation | +| Public database exposure | Validate `PubliclyAccessible=false` and review security group rules | +| Secret leakage | Use Pulumi secrets for `db_password`; do not commit real credentials | +| Wrong region | Validate both AWS CLI region and Pulumi region before preview/apply | +| Orphaned RDS instance | Verify destroy with AWS CLI after `pulumi destroy` | + +## Recommended production-readiness checks after this test + +Before adapting this Pulumi code for production, consider adding: + +- subnet group configuration +- explicit VPC and subnet selection +- deletion protection for production +- backup retention greater than zero +- final snapshot enabled for production +- parameter group and engine version pinning +- CloudWatch logs exports +- monitoring and alarms +- secret retrieval from AWS Secrets Manager or another secure store +- environment-specific stacks for dev, QA, staging, and prod From bac51f3ce12fa5b799e95d2e6251573763f536d5 Mon Sep 17 00:00:00 2001 From: Shlok Chaudhari Date: Wed, 17 Dec 2025 18:23:18 -0600 Subject: [PATCH 11/11] Creating the expected AWS test plan for Pulumi script --- .../test-plan/pulumi-local-test-plan.md | 362 ++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 rds-terraform/test-plan/pulumi-local-test-plan.md diff --git a/rds-terraform/test-plan/pulumi-local-test-plan.md b/rds-terraform/test-plan/pulumi-local-test-plan.md new file mode 100644 index 0000000..53b3dc7 --- /dev/null +++ b/rds-terraform/test-plan/pulumi-local-test-plan.md @@ -0,0 +1,362 @@ +# Pulumi Local Test Plan + +## Document purpose + +This test plan verifies the Pulumi implementation in `rds-terraform/pulumi/` without creating real AWS resources. The goal is to catch local setup issues, syntax errors, configuration problems, and resource-definition mistakes before running against AWS. + +## Scope + +This plan covers: + +- Pulumi project structure validation +- Python dependency setup +- Static code checks +- Pulumi config validation +- Pulumi preview behavior with mocked or non-deploying workflows +- Unit-style validation of the RDS resource definition + +This plan does **not** create, update, or delete real AWS infrastructure. + +## Directory under test + +```text +rds-terraform/pulumi/ +├── Pulumi.yaml +├── Pulumi.dev.yaml +├── README.md +└── main.py +``` + +## Known issues to check before testing + +These issues should be fixed or explicitly tracked before local testing is considered complete: + +| Area | Current issue | Expected fix | +|---|---|---| +| Config namespace | `Pulumi.yaml` uses project name `rds-qa-infra`, but `Pulumi.dev.yaml` uses keys under `rds-deployment:*` | Use one consistent namespace, usually `rds-qa-infra:*` | +| Storage type | `main.py` sets `storage_type=""` | Set to `gp2`, `gp3`, or omit the field | +| AWS region | `aws_region` is read but not applied to a Pulumi AWS provider | Configure `aws:region` or create an explicit AWS provider using that region | +| Required values | security group ID, database name, username, and password are empty in `Pulumi.dev.yaml` | Use safe dummy/local values for local validation, and real secure values only for AWS testing | +| Secrets | `db_password` should be stored as a Pulumi secret | Use `pulumi config set --secret db_password ` | + +## Test environment + +Recommended local versions: + +```bash +python --version +pulumi version +pip --version +``` + +Minimum expectations: + +- Python 3.10+ +- Pulumi CLI installed +- `pulumi_aws` Python package installed +- Local shell access to the repository + +## Setup steps + +From the Pulumi directory: + +```bash +cd rds-terraform/pulumi +python -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install pulumi pulumi-aws pytest ruff +``` + +Optional: create a `requirements.txt` so local and CI installs are repeatable. + +```bash +cat > requirements.txt <<'REQ' +pulumi +pulumi-aws +pytest +ruff +REQ +``` + +## Test cases + +### TC-L1: Verify Pulumi project files exist + +**Objective:** Confirm the Pulumi project has the required files. + +**Steps:** + +```bash +test -f Pulumi.yaml +test -f Pulumi.dev.yaml +test -f main.py +``` + +**Expected result:** + +All commands exit successfully. + +--- + +### TC-L2: Validate Python syntax + +**Objective:** Confirm `main.py` is valid Python. + +**Steps:** + +```bash +python -m py_compile main.py +``` + +**Expected result:** + +The command exits successfully with no syntax errors. + +--- + +### TC-L3: Run basic linting + +**Objective:** Catch obvious code-quality and import issues. + +**Steps:** + +```bash +ruff check main.py +``` + +**Expected result:** + +No lint errors, or only approved warnings documented in the test notes. + +--- + +### TC-L4: Validate Pulumi project metadata + +**Objective:** Confirm the project name is what the config expects. + +**Steps:** + +```bash +cat Pulumi.yaml +cat Pulumi.dev.yaml +``` + +**Expected result:** + +The project name in `Pulumi.yaml` should match the namespace used in `Pulumi.dev.yaml`. + +Example expected alignment: + +```yaml +# Pulumi.yaml +name: rds-qa-infra +``` + +```yaml +# Pulumi.dev.yaml +config: + rds-qa-infra:aws_region: "eu-west-1" + rds-qa-infra:environment: "dev" +``` + +--- + +### TC-L5: Initialize or select the local Pulumi stack + +**Objective:** Confirm Pulumi can read the project locally. + +**Steps:** + +```bash +pulumi stack select dev || pulumi stack init dev +``` + +**Expected result:** + +The `dev` stack is selected or created successfully. + +--- + +### TC-L6: Validate required config keys exist + +**Objective:** Confirm all required config values are present. + +**Steps:** + +```bash +pulumi config +``` + +Check for: + +```text +aws_region +environment +db_instance_class +vpc_security_group_id +database_name +db_username +db_password +``` + +**Expected result:** + +All required keys are present. `db_password` is marked as secret. + +--- + +### TC-L7: Set safe local test config + +**Objective:** Use non-production placeholder values for local validation. + +**Steps:** + +```bash +pulumi config set aws_region eu-west-1 +pulumi config set environment dev +pulumi config set db_instance_class db.t3.micro +pulumi config set vpc_security_group_id sg-00000000000000000 +pulumi config set database_name saayam_dev_db +pulumi config set db_username dev_admin +pulumi config set --secret db_password 'replace-with-local-test-secret' +``` + +If using Pulumi's AWS provider region directly, also run: + +```bash +pulumi config set aws:region eu-west-1 +``` + +**Expected result:** + +Config is saved locally. The password appears as a secret. + +--- + +### TC-L8: Preview without approving changes + +**Objective:** Confirm Pulumi can build the resource graph. + +**Steps:** + +```bash +pulumi preview +``` + +**Expected result:** + +Pulumi shows one planned RDS instance named similar to: + +```text +postgres-db +``` + +The preview should not fail due to missing config, invalid Python, or invalid Pulumi resource arguments. + +**Important:** A preview may still contact AWS for provider checks. This test should not be treated as a deployment. + +--- + +### TC-L9: Unit-style resource validation with Pulumi mocks + +**Objective:** Validate the resource definition without AWS credentials. + +**Suggested file:** `test_main.py` + +```python +import pulumi +from pulumi.runtime import mocks + + +class PulumiMocks(mocks.Mocks): + def new_resource(self, args: mocks.MockResourceArgs): + outputs = dict(args.inputs) + outputs["id"] = f"{args.name}_id" + outputs["endpoint"] = "mock-endpoint.rds.amazonaws.com" + return [f"{args.name}_id", outputs] + + def call(self, args: mocks.MockCallArgs): + return {} + + +pulumi.runtime.set_mocks(PulumiMocks()) + + +def test_rds_instance_definition(): + # Import after mocks are set. + import main # noqa: F401 + + # A stronger version of this test can expose the rds_instance object + # from main.py and assert its expected output properties. +``` + +**Steps:** + +```bash +pytest -q +``` + +**Expected result:** + +Tests pass without real AWS credentials. + +--- + +### TC-L10: Verify no production secrets are committed + +**Objective:** Make sure sensitive values are not stored in plain text. + +**Steps:** + +```bash +grep -R "db_password" -n . +grep -R "password" -n . +``` + +**Expected result:** + +No real password appears in committed files. Any password in `Pulumi.*.yaml` is stored under `secure:`. + +## Local test exit criteria + +Local testing is complete when: + +- `main.py` compiles successfully +- linting passes or known warnings are documented +- Pulumi stack config is complete +- config namespace matches the project name +- `storage_type` is valid or removed +- password is configured as a secret +- `pulumi preview` succeeds without creating resources +- mock-based tests pass, if implemented + +## Recommended local fixes before AWS testing + +Update `main.py` so the AWS region is actually used and storage type is valid. Example pattern: + +```python +provider = aws.Provider("aws-provider", region=aws_region) + +rds_instance = aws.rds.Instance( + "postgres-db", + identifier=f"{environment}-postgres-db", + engine="postgres", + instance_class=db_instance_class, + allocated_storage=20, + storage_type="gp2", + storage_encrypted=True, + db_name=database_name, + username=db_username, + password=db_password, + vpc_security_group_ids=[vpc_security_group_id], + publicly_accessible=False, + skip_final_snapshot=True, + backup_retention_period=0, + tags={ + "Name": f"{environment}-postgres-db", + "Environment": environment, + }, + opts=pulumi.ResourceOptions(provider=provider), +) +```