From 09b08cc66e884404c533bc4d1efb794509ac5ae9 Mon Sep 17 00:00:00 2001 From: "Matthew C. Morgan" Date: Thu, 28 May 2026 18:12:10 -0400 Subject: [PATCH] refactor and add rds --- .github/workflows/terraform-validate.yml | 40 +++++ CHANGELOG.md | 2 +- README.md | 88 +++++++++-- REQUIREMENTS.md | 2 +- examples/ec2/README.md | 26 ++- examples/ec2/linux/README.md | 49 ++++-- examples/ec2/linux/census-rhel-instance.tf | 4 +- examples/ec2/linux/main.tf | 1 - examples/ec2/linux/terraform.tfvars.example | 19 +-- examples/ec2/linux/variables.tf | 12 +- examples/ecs/simple-ecs.tf | 22 ++- examples/rds/README.md | 63 ++++++++ examples/rds/outputs.tf | 39 +++++ examples/rds/providers.tf | 3 + examples/rds/simple-rds.tf | 108 ++++--------- examples/rds/terraform.tfvars.example | 50 ++++++ examples/rds/variables.tf | 149 ++++++++++++++++++ examples/rds/versions.tf | 9 ++ examples/s3/README.md | 62 ++++++++ examples/s3/simple-s3.tf | 4 +- examples/s3/terraform.tfvars.example | 10 +- examples/s3/variables.tf | 4 +- modules/ec2/README.md | 43 +++++ modules/ec2/data.tf | 36 +++++ modules/ec2/linux/README.md | 53 ++++--- modules/ec2/linux/data.tf | 36 ----- modules/ec2/linux/locals.tf | 26 --- modules/ec2/linux/main.tf | 23 ++- modules/ec2/linux/module_name.tf | 4 +- modules/ec2/linux/outputs.tf | 30 ++-- modules/ec2/linux/variables.product.tf | 16 +- modules/ec2/linux/variables.servicecatalog.tf | 16 +- modules/ec2/locals.tf | 57 +++++++ modules/ec2/main.tf | 34 ++++ modules/ec2/module_name.tf | 4 + modules/ec2/outputs.tf | 75 +++++++++ modules/ec2/variables.common.tf | 60 +++++++ modules/ec2/variables.product.tf | 55 +++++++ modules/ec2/variables.servicecatalog.tf | 71 +++++++++ modules/ec2/variables.tags.tf | 5 + modules/ec2/versions.tf | 9 ++ modules/ec2/windows/README.md | 59 +++---- modules/ec2/windows/data.tf | 36 ----- modules/ec2/windows/locals.tf | 27 ---- modules/ec2/windows/main.tf | 23 ++- modules/ec2/windows/module_name.tf | 4 +- modules/ec2/windows/outputs.tf | 30 ++-- modules/ec2/windows/variables.product.tf | 16 +- .../ec2/windows/variables.servicecatalog.tf | 16 +- modules/product/README.md | 13 +- modules/product/locals.tf | 6 +- modules/product/module_name.tf | 2 +- modules/product/variables.servicecatalog.tf | 12 +- modules/rds/README.md | 60 +++++++ modules/rds/data.tf | 26 +++ modules/rds/locals.tf | 38 +++++ modules/rds/main.tf | 34 ++++ modules/rds/module_name.tf | 4 + modules/rds/outputs.tf | 70 ++++++++ modules/rds/variables.common.tf | 60 +++++++ modules/rds/variables.product.tf | 55 +++++++ modules/rds/variables.servicecatalog.tf | 71 +++++++++ modules/rds/variables.tags.tf | 5 + modules/rds/versions.tf | 9 ++ modules/s3/README.md | 23 +-- modules/s3/main.tf | 1 + modules/s3/module_name.tf | 2 +- modules/s3/outputs.tf | 2 +- modules/s3/variables.servicecatalog.tf | 12 +- version.tf | 2 +- 70 files changed, 1736 insertions(+), 401 deletions(-) create mode 100644 .github/workflows/terraform-validate.yml delete mode 100644 examples/ec2/linux/main.tf create mode 100644 examples/rds/README.md create mode 100644 examples/rds/outputs.tf create mode 100644 examples/rds/providers.tf create mode 100644 examples/rds/terraform.tfvars.example create mode 100644 examples/rds/variables.tf create mode 100644 examples/rds/versions.tf create mode 100644 examples/s3/README.md create mode 100644 modules/ec2/README.md create mode 100644 modules/ec2/data.tf delete mode 100644 modules/ec2/linux/data.tf create mode 100644 modules/ec2/locals.tf create mode 100644 modules/ec2/main.tf create mode 100644 modules/ec2/module_name.tf create mode 100644 modules/ec2/outputs.tf create mode 100644 modules/ec2/variables.common.tf create mode 100644 modules/ec2/variables.product.tf create mode 100644 modules/ec2/variables.servicecatalog.tf create mode 100644 modules/ec2/variables.tags.tf create mode 100644 modules/ec2/versions.tf delete mode 100644 modules/ec2/windows/data.tf create mode 100644 modules/rds/README.md create mode 100644 modules/rds/data.tf create mode 100644 modules/rds/locals.tf create mode 100644 modules/rds/main.tf create mode 100644 modules/rds/module_name.tf create mode 100644 modules/rds/outputs.tf create mode 100644 modules/rds/variables.common.tf create mode 100644 modules/rds/variables.product.tf create mode 100644 modules/rds/variables.servicecatalog.tf create mode 100644 modules/rds/variables.tags.tf create mode 100644 modules/rds/versions.tf diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml new file mode 100644 index 0000000..325960f --- /dev/null +++ b/.github/workflows/terraform-validate.yml @@ -0,0 +1,40 @@ +name: Terraform Validate + +on: + pull_request: + push: + branches: + - main + +jobs: + validate: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + dir: + - modules/product + - modules/ec2 + - modules/ec2/linux + - modules/ec2/windows + - modules/rds + - modules/s3 + - examples/ec2/linux + - examples/rds + - examples/s3 + - examples/ecs + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.9.8 + + - name: Terraform Init + run: terraform -chdir=${{ matrix.dir }} init -backend=false -input=false -no-color + + - name: Terraform Validate + run: terraform -chdir=${{ matrix.dir }} validate -no-color diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce3418..876c1f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Versions -* v1.0.0 -- {{ yyyy-mm-dd }} +* v1.0.0 -- 2026-05-28 - initial creation diff --git a/README.md b/README.md index 7fb7576..8cc9b3d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,10 @@ This module provides simplified Terraform wrappers for provisioning AWS Service The module uses a layered architecture: - **product module** - Base module handling Service Catalog provisioning (internal use) -- **ec2 module** - EC2-specific wrapper with instance parameters and VPC networking +- **ec2 module** - Shared EC2 implementation with parameter mapping and networking resolution +- **ec2/linux module** - Linux EC2 wrapper that preserves Linux defaults +- **ec2/windows module** - Windows EC2 wrapper that preserves Windows defaults +- **rds module** - RDS-specific wrapper with database parameters - **s3 module** - S3-specific wrapper with bucket parameters and data classification Each wrapper module provides: @@ -30,29 +33,38 @@ Each wrapper module provides: aws-servicecatalog/ ├── modules/ │ ├── product/ # Base Service Catalog provisioning module (internal) -│ ├── ec2/ # EC2 instance provisioning wrapper +│ ├── ec2/ +│ │ ├── linux/ # Linux EC2 compatibility wrapper +│ │ └── windows/ # Windows EC2 compatibility wrapper +│ ├── rds/ # RDS provisioning wrapper │ └── s3/ # S3 bucket provisioning wrapper └── examples/ ├── ec2/linux/ # EC2 Linux instance example + ├── rds/ # RDS instance example └── s3/ # S3 bucket example ``` ### Currently Implemented -- ✅ **modules/ec2/** - Provision EC2 instances from Service Catalog +- ✅ **modules/ec2/** - Shared EC2 provisioning implementation +- ✅ **modules/ec2/linux/** - Linux EC2 compatibility wrapper +- ✅ **modules/ec2/windows/** - Windows EC2 compatibility wrapper +- ✅ **modules/rds/** - Provision RDS instances from Service Catalog - ✅ **modules/s3/** - Provision S3 buckets from Service Catalog - ✅ **modules/product/** - Base module for Service Catalog provisioning (internal use only) ### Planned/Future Enhancements -- ⏳ **RDS module** - Provision RDS databases - ⏳ **ECS module** - Provision ECS services - ⏳ **Service actions** - Execute service actions (start, stop, reboot) ## Usage See individual module README files for detailed documentation: -- [EC2 Module Documentation](modules/ec2/README.md) +- [EC2 Shared Module Documentation](modules/ec2/README.md) +- [EC2 Linux Module Documentation](modules/ec2/linux/README.md) +- [EC2 Windows Module Documentation](modules/ec2/windows/README.md) +- [RDS Module Documentation](modules/rds/README.md) - [S3 Module Documentation](modules/s3/README.md) - [Product Module Documentation](modules/product/README.md) @@ -64,6 +76,8 @@ module "ec2_instance" { # Product identity provisioned_product_name = "my-web-server" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" # EC2-specific parameters instance_type = "t3.small" @@ -71,7 +85,9 @@ module "ec2_instance" { requires_backup = "yes" power_schedule = "Always_On" - # Networking (auto-discovers VPC/subnets by name tag) + # Networking (explicit IDs take precedence over lookup values) + subnet_ids = ["subnet-0123456789abcdef0"] + vpc_id = "vpc-0123456789abcdef0" vpc_name = "my-vpc" subnets_name = "*-apps-*" @@ -102,6 +118,8 @@ module "s3_bucket" { # Product identity provisioned_product_name = "my-data-bucket" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" # S3-specific parameters bucket_name = "my-data-lake" @@ -127,10 +145,61 @@ module "s3_bucket" { - Terraform >= 1.0 - AWS Provider >= 6.0 +## Discovering Service Catalog IDs With AWS CLI + +If you do not already know `portfolio_id` and `product_id`, you can resolve them from your current AWS environment. + +Prerequisites: +- AWS CLI configured for the target account/org +- IAM permission to read Service Catalog portfolios/products + +```bash +# Set your context +AWS_REGION="us-east-1" +PORTFOLIO_NAME="My Service Catalog Portfolio" +PRODUCT_NAME="My Product Name" + +# 1) Get portfolio ID by display name +PORTFOLIO_ID=$(aws servicecatalog list-portfolios \ + --region "$AWS_REGION" \ + --query "PortfolioDetails[?DisplayName=='${PORTFOLIO_NAME}'].Id | [0]" \ + --output text) + +# 2) Get product ID by product name within that portfolio +PRODUCT_ID=$(aws servicecatalog search-products-as-admin \ + --region "$AWS_REGION" \ + --portfolio-id "$PORTFOLIO_ID" \ + --query "ProductViewDetails[?ProductViewSummary.Name=='${PRODUCT_NAME}'].ProductViewSummary.ProductId | [0]" \ + --output text) + +# 3) Optional: get latest active provisioning artifact ID +PROVISIONING_ARTIFACT_ID=$(aws servicecatalog list-provisioning-artifacts \ + --region "$AWS_REGION" \ + --product-id "$PRODUCT_ID" \ + --query "ProvisioningArtifactDetails[?Active==\`true\`]|sort_by(@,&CreatedTime)[-1].Id" \ + --output text) + +# 4) Optional: get launch path ID +PATH_ID=$(aws servicecatalog list-launch-paths \ + --region "$AWS_REGION" \ + --product-id "$PRODUCT_ID" \ + --query "LaunchPathSummaries[0].Id" \ + --output text) + +echo "portfolio_id=$PORTFOLIO_ID" +echo "product_id=$PRODUCT_ID" +echo "provisioning_artifact_id=$PROVISIONING_ARTIFACT_ID" +echo "path_id=$PATH_ID" +``` + +Note: +- If `search-products-as-admin` is restricted in your org, use the Service Catalog console to locate product IDs or ask your portfolio admins for read access. + ## Examples Complete working examples with terraform.tfvars.example files are available in: - [examples/ec2/linux/](examples/ec2/linux/) - EC2 Linux instance provisioning +- [examples/rds/](examples/rds/) - RDS instance provisioning - [examples/s3/](examples/s3/) - S3 bucket provisioning ## Architecture @@ -138,9 +207,10 @@ Complete working examples with terraform.tfvars.example files are available in: This module follows a base + specialized wrapper pattern: 1. **product module** provides generic Service Catalog provisioning -2. **ec2/s3 modules** add product-specific parameters and data sources -3. Each wrapper handles parameter mapping to CloudFormation format -4. Networking data sources are only in modules that need them (EC2, not S3) +2. **ec2/rds/s3 modules** add product-specific parameters and data sources +3. **ec2/linux and ec2/windows wrappers** provide OS-specific defaults on top of shared ec2 +4. Each wrapper handles parameter mapping to CloudFormation format +5. Networking data sources are used only in modules that need them (EC2 and RDS, not S3) ## Contributing diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 37d5136..93fb7e9 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -4,7 +4,7 @@ - Terraform >= 1.0 ## Provider Requirements -- AWS Provider >= 5.0 +- AWS Provider >= 6.0 ## Permissions diff --git a/examples/ec2/README.md b/examples/ec2/README.md index ad2934f..88bda97 100644 --- a/examples/ec2/README.md +++ b/examples/ec2/README.md @@ -2,6 +2,10 @@ This example demonstrates how to provision an EC2 instance from AWS Service Catalog. +Networking behavior is standardized across wrappers: +- explicit `subnet_ids`/`vpc_id` take precedence +- lookup via `vpc_name`/`subnets_name` is used when explicit IDs are not provided + ## Usage ```bash @@ -14,17 +18,19 @@ terraform apply ```hcl module "ec2_instance" { - source = "../../ec2" + source = "../../modules/ec2/linux" provisioned_product_name = "my-web-server" - portfolio_name_pattern = "edl-portfolio" - product_name_pattern = "linux-product" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" + + # Explicit networking (preferred for deterministic plans) + subnet_ids = ["subnet-12345"] + vpc_id = "vpc-12345" parameters = { InstanceType = "t3.medium" KeyName = "my-key" - SubnetId = "subnet-12345" - VpcId = "vpc-12345" } tags = { @@ -48,15 +54,19 @@ output "instance_private_ip" { ```hcl # Provision the instance module "ec2_instance" { - source = "../../ec2" + source = "../../modules/ec2/linux" provisioned_product_name = "my-web-server" - product_name_pattern = "linux-product" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" + + # Lookup networking + vpc_name = "my-vpc" + subnets_name = "*-apps-*" parameters = { InstanceType = "t3.medium" KeyName = "my-key" - SubnetId = "subnet-12345" } } diff --git a/examples/ec2/linux/README.md b/examples/ec2/linux/README.md index b531263..c9d7237 100644 --- a/examples/ec2/linux/README.md +++ b/examples/ec2/linux/README.md @@ -4,10 +4,10 @@ This example demonstrates how to provision a Census RHEL EC2 instance using the ## CloudFormation Template Reference -This example implements the parameters from the **2-0-2.yaml** CloudFormation template, which provides a comprehensive Census EC2 instance provisioning workflow including: +This example implements the parameters from the EC2 Service Catalog product template, with a standardized networking model shared across wrappers. - **EC2 Configuration**: Instance type, hostname, OS version (RHEL8/RHEL9), disk sizing -- **Network Setup**: VPC, availability zone, security groups +- **Network Setup**: Explicit subnet_ids/vpc_id inputs or lookup via vpc_name/subnets_name - **Storage**: Apps disk plus optional extra volumes for /data mounts - **Creator/Contact Info**: JBID, email addresses for notifications - **Compliance Tags**: FISMA ID, Title Data classifications @@ -28,7 +28,10 @@ This example implements the parameters from the **2-0-2.yaml** CloudFormation te ``` 2. Edit `terraform.tfvars` with your specific values: - - Set your VPC ID and Availability Zone + - Provide required Service Catalog identifiers: portfolio_id and product_id + - Choose one networking approach: + - explicit `subnet_ids` and `vpc_id`, or + - lookup via `vpc_name` and `subnets_name` - Provide census project identifier - Set creator/contact email addresses - Customize instance type, hostname, OS version as needed @@ -40,17 +43,46 @@ This example implements the parameters from the **2-0-2.yaml** CloudFormation te terraform apply ``` +### Lookup IDs (AWS CLI) + +```bash +AWS_REGION="us-east-1" +PORTFOLIO_NAME="My Service Catalog Portfolio" +PRODUCT_NAME="My Product Name" + +PORTFOLIO_ID=$(aws servicecatalog list-portfolios \ + --region "$AWS_REGION" \ + --query "PortfolioDetails[?DisplayName=='${PORTFOLIO_NAME}'].Id | [0]" \ + --output text) + +PRODUCT_ID=$(aws servicecatalog search-products-as-admin \ + --region "$AWS_REGION" \ + --portfolio-id "$PORTFOLIO_ID" \ + --query "ProductViewDetails[?ProductViewSummary.Name=='${PRODUCT_NAME}'].ProductViewSummary.ProductId | [0]" \ + --output text) + +echo "portfolio_id=$PORTFOLIO_ID" +echo "product_id=$PRODUCT_ID" +``` + +See the main module docs for optional provisioning artifact/path lookups: [Discovering Service Catalog IDs With AWS CLI](../../../README.md#discovering-service-catalog-ids-with-aws-cli). + ## Key Parameters ### Required Parameters - `project_name` - Census project identifier (org_project_env_type-accountid) -- `vpc_id` - VPC where instance will be deployed -- `availability_zone` - AZ for the instance -- `instance_hostname` - Local hostname (3-16 chars) - `creator_jbid` - Census user ID of creator - `contact_email` - Provisioning user's email - `incident_poc_email` - Team distribution list email +### Networking Parameters +- `subnet_ids` + `vpc_id` - Explicit network placement (takes precedence) +- `vpc_name` + `subnets_name` - Lookup-based placement when explicit IDs are not set +- `availability_zone` - Derived from the first selected subnet + +### Other Required Parameters +- `instance_hostname` - Local hostname (3-16 chars) + ### Optional Parameters - `instance_type` - EC2 type (default: t3.small) - `os_name` - OS version (default: RHEL9) @@ -109,6 +141,5 @@ Note: If `enable_deletion_protection` is set to true, you must change it to fals ## Related Documentation -- CloudFormation Template: `../../templates/products/ec2/instance-linux/2-0-2.yaml` -- Module Documentation: `../../ec2/README.md` -- Parent Module: `../../ec2/main.tf` +- Module Documentation: `../../../modules/ec2/linux/README.md` +- Parent Module: `../../../modules/ec2/linux/main.tf` diff --git a/examples/ec2/linux/census-rhel-instance.tf b/examples/ec2/linux/census-rhel-instance.tf index d85fa41..88da975 100644 --- a/examples/ec2/linux/census-rhel-instance.tf +++ b/examples/ec2/linux/census-rhel-instance.tf @@ -4,10 +4,12 @@ # Aligns with the 2-0-2.yaml CloudFormation product template. module "ec2_instance" { - source = "../../../modules/ec2" + source = "../../../modules/ec2/linux" # === Product Identity === provisioned_product_name = var.provisioned_product_name + portfolio_id = var.portfolio_id + product_id = var.product_id # === Required EC2-Specific Parameters === instance_type = var.instance_type diff --git a/examples/ec2/linux/main.tf b/examples/ec2/linux/main.tf deleted file mode 100644 index d41aae3..0000000 --- a/examples/ec2/linux/main.tf +++ /dev/null @@ -1 +0,0 @@ -# Module is defined in census-rhel-instance.tf diff --git a/examples/ec2/linux/terraform.tfvars.example b/examples/ec2/linux/terraform.tfvars.example index e2cc121..e436ace 100644 --- a/examples/ec2/linux/terraform.tfvars.example +++ b/examples/ec2/linux/terraform.tfvars.example @@ -13,6 +13,10 @@ environment = "Production" # Name for the provisioned product (unique identifier in your account) provisioned_product_name = "census-rhel-instance" +# Required explicit IDs +portfolio_id = "port-xxxxxxxxxxxx" +product_id = "prod-xxxxxxxxxxxx" + # === EC2 INSTANCE CONFIGURATION === # Instance Type - Options: t3.nano, t3.micro, t3.small (default), t3.medium, c5.large, m5.large, etc. @@ -74,13 +78,13 @@ extra_volumes = [] # === CREATOR AND CONTACT INFORMATION === # Creator JBID (Census User ID) -creator_jbid = "morga471" +creator_jbid = "user123" # Provisioning User's Email (REQUIRED) -contact_email = "matthew.c.morgan@census.gov" +contact_email = "user@example.com" # Team Distribution List Email (REQUIRED) -incident_poc_email = "matthew.c.morgan@census.gov" +incident_poc_email = "team@example.com" # === COMPLIANCE AND TAGGING === @@ -113,13 +117,6 @@ power_schedule = "Always_On" # Example: "migMSDNVLIJDZ: On-Prem Server, Storage, Networking" map_migrated = "" -power_schedule = "Weekday_Core_Hours_7-7" - -# MAP Migration Tag - For AWS Migration Accelerator Program credits -# Leave empty ("") if not applicable -# Examples: "migMSDNVLIJDZ: On-Prem Server, Storage, Networking" -map_migrated = "" - # === SAFEGUARDS === # Enable Deletion Protection - Prevents accidental instance termination @@ -131,5 +128,5 @@ enable_deletion_protection = false additional_tags = { CostCenter = "12345" DataClass = "Internal" - Owner = "morga471" + Owner = "team-name" } diff --git a/examples/ec2/linux/variables.tf b/examples/ec2/linux/variables.tf index 7cffb74..48b4652 100644 --- a/examples/ec2/linux/variables.tf +++ b/examples/ec2/linux/variables.tf @@ -20,16 +20,16 @@ variable "provisioned_product_name" { default = "census-rhel-instance" } -variable "portfolio_name_pattern" { - description = "Regex pattern to match the Service Catalog portfolio name" +variable "portfolio_id" { + description = "Portfolio ID for the EC2 product (required for provisioning)" type = string - default = "edl-portfolio" + default = null } -variable "product_name_pattern" { - description = "Regex pattern to match the Service Catalog product name" +variable "product_id" { + description = "Product ID for the EC2 product (required for provisioning)" type = string - default = "linux-product" + default = null } # === EC2 Instance Configuration === diff --git a/examples/ecs/simple-ecs.tf b/examples/ecs/simple-ecs.tf index 981dc62..4ba9020 100644 --- a/examples/ecs/simple-ecs.tf +++ b/examples/ecs/simple-ecs.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.0" + version = ">= 6.0" } } } @@ -24,6 +24,18 @@ variable "provisioned_product_name" { default = "example-ecs-service" } +variable "portfolio_id" { + description = "Portfolio ID for the ECS product" + type = string + default = null +} + +variable "product_id" { + description = "Product ID for the ECS product" + type = string + default = null +} + variable "cluster_name" { description = "ECS cluster name" type = string @@ -40,13 +52,13 @@ variable "vpc_id" { } module "ecs_service" { - source = "../../ecs" + source = "../../modules/product" provisioned_product_name = var.provisioned_product_name - portfolio_name_pattern = "edl-portfolio" - product_name_pattern = "ecs-product" + portfolio_id = var.portfolio_id + product_id = var.product_id - parameters = { + product_parameters = { ClusterName = var.cluster_name SubnetIds = join(",", var.subnet_ids) VpcId = var.vpc_id diff --git a/examples/rds/README.md b/examples/rds/README.md new file mode 100644 index 0000000..8ea53b2 --- /dev/null +++ b/examples/rds/README.md @@ -0,0 +1,63 @@ +# RDS Service Catalog Example + +This example provisions an RDS product via the aws-servicecatalog RDS module. + +## Files + +- simple-rds.tf: Module invocation +- variables.tf: Input variable definitions +- providers.tf: AWS provider configuration +- versions.tf: Terraform and provider requirements +- outputs.tf: Example outputs +- terraform.tfvars.example: Sample values + +## Quick Start + +1. Copy example values: + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +2. Edit terraform.tfvars with account-specific values and choose one networking approach: + - Provide required Service Catalog identifiers: portfolio_id and product_id + - explicit subnet_ids and vpc_id, or + - lookup with vpc_name and subnets_name. + +3. Run Terraform: + +```bash +terraform init +terraform plan +terraform apply +``` + +### Lookup IDs (AWS CLI) + +```bash +AWS_REGION="us-east-1" +PORTFOLIO_NAME="My Service Catalog Portfolio" +PRODUCT_NAME="My Product Name" + +PORTFOLIO_ID=$(aws servicecatalog list-portfolios \ + --region "$AWS_REGION" \ + --query "PortfolioDetails[?DisplayName=='${PORTFOLIO_NAME}'].Id | [0]" \ + --output text) + +PRODUCT_ID=$(aws servicecatalog search-products-as-admin \ + --region "$AWS_REGION" \ + --portfolio-id "$PORTFOLIO_ID" \ + --query "ProductViewDetails[?ProductViewSummary.Name=='${PRODUCT_NAME}'].ProductViewSummary.ProductId | [0]" \ + --output text) + +echo "portfolio_id=$PORTFOLIO_ID" +echo "product_id=$PRODUCT_ID" +``` + +See the main module docs for optional provisioning artifact/path lookups: [Discovering Service Catalog IDs With AWS CLI](../../README.md#discovering-service-catalog-ids-with-aws-cli). + +## Notes + +- The module is at ../../modules/rds. +- Empty optional values are omitted before parameters are sent to Service Catalog. +- Additional template parameters can be passed via additional_parameters. diff --git a/examples/rds/outputs.tf b/examples/rds/outputs.tf new file mode 100644 index 0000000..e0e6af3 --- /dev/null +++ b/examples/rds/outputs.tf @@ -0,0 +1,39 @@ +# === Provisioned Product Outputs === + +output "provisioned_product_id" { + description = "The ID of the provisioned RDS product" + value = module.rds_instance.provisioned_product_id +} + +output "provisioned_product_arn" { + description = "The ARN of the provisioned RDS product" + value = module.rds_instance.provisioned_product_arn +} + +output "provisioned_product_status" { + description = "The current status of the provisioned product" + value = module.rds_instance.provisioned_product_status +} + +# === RDS Parameter Summary === + +output "rds_configuration_summary" { + description = "Summary of configured RDS input parameters" + value = { + db_instance_class = var.db_instance_class + engine = var.engine + engine_version = var.engine_version + multi_az = var.multi_az + allocated_storage = var.allocated_storage + vpc_id = var.vpc_id + subnet_ids = var.subnet_ids + } +} + +# === CloudFormation Stack Outputs === + +output "stack_outputs" { + description = "All outputs from the CloudFormation stack that provisioned the RDS resource" + value = module.rds_instance.stack_outputs + sensitive = true +} diff --git a/examples/rds/providers.tf b/examples/rds/providers.tf new file mode 100644 index 0000000..dc58d9a --- /dev/null +++ b/examples/rds/providers.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = var.region +} diff --git a/examples/rds/simple-rds.tf b/examples/rds/simple-rds.tf index c416f51..6594a48 100644 --- a/examples/rds/simple-rds.tf +++ b/examples/rds/simple-rds.tf @@ -1,77 +1,39 @@ -terraform { - required_version = ">= 1.0" - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 5.0" - } - } -} - -provider "aws" { - region = var.region -} - -variable "region" { - description = "AWS region" - type = string - default = "us-east-1" -} - -variable "provisioned_product_name" { - description = "Name for the RDS provisioned product" - type = string - default = "example-rds-postgres" -} - -variable "db_instance_class" { - description = "RDS instance class" - type = string - default = "db.t3.medium" -} - -variable "subnet_ids" { - description = "Subnet IDs for the RDS instance" - type = list(string) -} - -variable "vpc_id" { - description = "VPC ID for the RDS instance" - type = string -} - module "rds_instance" { - source = "../../rds" + source = "../../modules/rds" provisioned_product_name = var.provisioned_product_name - portfolio_name_pattern = "edl-portfolio" - product_name_pattern = "rds-product" - - parameters = { - DBInstanceClass = var.db_instance_class - SubnetIds = join(",", var.subnet_ids) - VpcId = var.vpc_id - Engine = "postgres" - EngineVersion = "15.3" - } - - tags = { - Environment = "example" - ManagedBy = "Terraform" - } -} - -output "provisioned_product_id" { - description = "The ID of the provisioned product" - value = module.rds_instance.provisioned_product_id -} - -output "provisioned_product_arn" { - description = "The ARN of the provisioned product" - value = module.rds_instance.provisioned_product_arn -} - -output "stack_outputs" { - description = "CloudFormation stack outputs" - value = module.rds_instance.stack_outputs + portfolio_id = var.portfolio_id != "" ? var.portfolio_id : null + product_id = var.product_id != "" ? var.product_id : null + provisioning_artifact_id = var.provisioning_artifact_id != "" ? var.provisioning_artifact_id : null + path_id = var.path_id != "" ? var.path_id : null + + db_instance_class = var.db_instance_class + subnet_ids = var.subnet_ids + vpc_id = var.vpc_id + vpc_name = var.vpc_name + subnets_name = var.subnets_name + engine = var.engine + engine_version = var.engine_version + multi_az = var.multi_az + allocated_storage = var.allocated_storage + + project_name = var.project_name + creator = var.creator + contact_email = var.contact_email + inc_poc_email = var.incident_poc_email + fisma_id = var.fisma_id + + parameters = merge( + {}, + var.additional_parameters + ) + + tags = merge( + { + Environment = var.environment + ManagedBy = "Terraform" + ServiceCatalog = "true" + }, + var.additional_tags + ) } diff --git a/examples/rds/terraform.tfvars.example b/examples/rds/terraform.tfvars.example new file mode 100644 index 0000000..4286b16 --- /dev/null +++ b/examples/rds/terraform.tfvars.example @@ -0,0 +1,50 @@ +# === Census RDS Example === +# Copy this file to terraform.tfvars and customize values. + +# AWS Region +region = "us-east-1" +environment = "Production" + +# === SERVICE CATALOG CONFIGURATION === + +provisioned_product_name = "example-rds-postgres" + +# Required explicit product IDs +portfolio_id = "port-xxxxxxxxxxxx" +product_id = "prod-xxxxxxxxxxxx" +provisioning_artifact_id = "" +path_id = "" + +# === RDS CONFIGURATION === + +db_instance_class = "db.t3.medium" + +# Choose one networking approach: +# 1) Explicit IDs (recommended for deterministic plans) +subnet_ids = ["subnet-aaaaaaaa", "subnet-bbbbbbbb"] +vpc_id = "vpc-1234567890abcdef0" + +# 2) Lookup by tags (leave subnet_ids and vpc_id empty when using this) +vpc_name = "" +subnets_name = "" + +engine = "postgres" +engine_version = "15.3" +multi_az = false +allocated_storage = 20 + +# === PROJECT CONFIGURATION === + +project_name = "" +creator = "" +contact_email = "" +incident_poc_email = "" +fisma_id = "" + +# === OPTIONAL ADDITIONAL PARAMETERS === + +additional_parameters = {} + +# === CUSTOM TAGS === + +additional_tags = {} diff --git a/examples/rds/variables.tf b/examples/rds/variables.tf new file mode 100644 index 0000000..24b9296 --- /dev/null +++ b/examples/rds/variables.tf @@ -0,0 +1,149 @@ +# === Provider and Global Configuration === + +variable "region" { + description = "AWS region where resources will be deployed" + type = string + default = "us-east-1" +} + +variable "environment" { + description = "Environment name for tagging (e.g., Production, QA, Dev)" + type = string + default = "Production" +} + +# === Service Catalog Configuration === + +variable "provisioned_product_name" { + description = "Name of the provisioned RDS product in Service Catalog" + type = string + default = "example-rds-postgres" +} + +variable "portfolio_id" { + description = "Portfolio ID for the RDS product (required for provisioning)" + type = string + default = "" +} + +variable "product_id" { + description = "Product ID for the RDS product (required for provisioning)" + type = string + default = "" +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version)" + type = string + default = "" +} + +variable "path_id" { + description = "Launch path identifier of the product" + type = string + default = "" +} + +# === RDS Configuration === + +variable "db_instance_class" { + description = "RDS instance class" + type = string + default = "db.t3.medium" +} + +variable "subnet_ids" { + description = "Explicit subnet IDs for the RDS instance. If set, subnet lookup by name is skipped" + type = list(string) + default = [] +} + +variable "vpc_id" { + description = "Explicit VPC ID for the RDS instance. If set, VPC lookup by name is skipped" + type = string + default = "" +} + +variable "vpc_name" { + description = "Name tag of the VPC to deploy into when vpc_id is not set" + type = string + default = "" +} + +variable "subnets_name" { + description = "Name tag pattern of subnets to deploy into when subnet_ids are not set" + type = string + default = "" +} + +variable "engine" { + description = "RDS database engine" + type = string + default = "postgres" +} + +variable "engine_version" { + description = "RDS database engine version" + type = string + default = "15.3" +} + +variable "multi_az" { + description = "Deploy database in Multi-AZ" + type = bool + default = false +} + +variable "allocated_storage" { + description = "Allocated storage in GiB" + type = number + default = 20 +} + +# === Project Configuration === + +variable "project_name" { + description = "Census project identifier (format: org_project_env_type-accountid)" + type = string + default = "" +} + +variable "creator" { + description = "Creator's JBID (Census system user ID)" + type = string + default = "" +} + +variable "contact_email" { + description = "Provisioning user's email address for notifications" + type = string + default = "" +} + +variable "incident_poc_email" { + description = "Team distribution list email for incident notifications" + type = string + default = "" +} + +variable "fisma_id" { + description = "FISMA ID for compliance tagging" + type = string + default = "" +} + +# === Optional CloudFormation Parameters === + +variable "additional_parameters" { + description = "Additional CloudFormation parameters for the RDS product" + type = map(string) + default = {} +} + +# === Custom Tags === + +variable "additional_tags" { + description = "Additional custom tags to apply" + type = map(string) + default = {} +} diff --git a/examples/rds/versions.tf b/examples/rds/versions.tf new file mode 100644 index 0000000..773514a --- /dev/null +++ b/examples/rds/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + } +} diff --git a/examples/s3/README.md b/examples/s3/README.md new file mode 100644 index 0000000..a371eb8 --- /dev/null +++ b/examples/s3/README.md @@ -0,0 +1,62 @@ +# S3 Service Catalog Example + +This example provisions an S3 product via the aws-servicecatalog S3 module. + +## Files + +- simple-s3.tf: Module invocation +- variables.tf: Input variable definitions +- providers.tf: AWS provider configuration +- versions.tf: Terraform and provider requirements +- outputs.tf: Example outputs +- terraform.tfvars.example: Sample values + +## Quick Start + +1. Copy example values: + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +2. Edit terraform.tfvars with account-specific values: + - set required product inputs (for example bucket_name and project_name) + - Provide required Service Catalog identifiers: portfolio_id and product_id + +3. Run Terraform: + +```bash +terraform init +terraform plan +terraform apply +``` + +### Lookup IDs (AWS CLI) + +```bash +AWS_REGION="us-east-1" +PORTFOLIO_NAME="My Service Catalog Portfolio" +PRODUCT_NAME="My Product Name" + +PORTFOLIO_ID=$(aws servicecatalog list-portfolios \ + --region "$AWS_REGION" \ + --query "PortfolioDetails[?DisplayName=='${PORTFOLIO_NAME}'].Id | [0]" \ + --output text) + +PRODUCT_ID=$(aws servicecatalog search-products-as-admin \ + --region "$AWS_REGION" \ + --portfolio-id "$PORTFOLIO_ID" \ + --query "ProductViewDetails[?ProductViewSummary.Name=='${PRODUCT_NAME}'].ProductViewSummary.ProductId | [0]" \ + --output text) + +echo "portfolio_id=$PORTFOLIO_ID" +echo "product_id=$PRODUCT_ID" +``` + +See the main module docs for optional provisioning artifact/path lookups: [Discovering Service Catalog IDs With AWS CLI](../../README.md#discovering-service-catalog-ids-with-aws-cli). + +## Notes + +- The module is at ../../modules/s3. +- Empty optional values are omitted before parameters are sent to Service Catalog. +- Additional template parameters can be passed via additional_parameters. diff --git a/examples/s3/simple-s3.tf b/examples/s3/simple-s3.tf index 7fd6347..8c6f70f 100644 --- a/examples/s3/simple-s3.tf +++ b/examples/s3/simple-s3.tf @@ -3,10 +3,12 @@ # via AWS Service Catalog using the aws-servicecatalog s3 module. module "s3_bucket" { - source = "../../../modules/s3" + source = "../../modules/s3" # === Product Identity === provisioned_product_name = var.provisioned_product_name + portfolio_id = var.portfolio_id != "" ? var.portfolio_id : null + product_id = var.product_id != "" ? var.product_id : null # === Required S3-Specific Parameters === bucket_name = var.bucket_name diff --git a/examples/s3/terraform.tfvars.example b/examples/s3/terraform.tfvars.example index ef5cb41..898a324 100644 --- a/examples/s3/terraform.tfvars.example +++ b/examples/s3/terraform.tfvars.example @@ -11,6 +11,10 @@ environment = "Production" # Name for the provisioned product (unique identifier in your account) provisioned_product_name = "example-data-lake-bucket" +# Required Service Catalog IDs +portfolio_id = "port-xxxxxxxxxxxx" +product_id = "prod-xxxxxxxxxxxx" + # === S3 BUCKET CONFIGURATION === # Bucket Name - Will have account number appended (e.g., my-bucket-123456789012) @@ -39,13 +43,13 @@ project_role = "" project_name = "edl_adsd_s3_prod-260954754267" # Creator JBID (Census User ID) - Optional -creator = "morga471" +creator = "user123" # Provisioning User's Email (REQUIRED) -contact_email = "matthew.c.morgan@census.gov" +contact_email = "user@example.com" # Team Distribution List Email (REQUIRED) -incident_poc_email = "matthew.c.morgan@census.gov" +incident_poc_email = "team@example.com" # === OPTIONAL ADDITIONAL PARAMETERS === diff --git a/examples/s3/variables.tf b/examples/s3/variables.tf index d730530..2da9f97 100644 --- a/examples/s3/variables.tf +++ b/examples/s3/variables.tf @@ -21,13 +21,13 @@ variable "provisioned_product_name" { } variable "portfolio_id" { - description = "Portfolio ID for the S3 product (if not provided, uses default)" + description = "Portfolio ID for the S3 product (required for provisioning)" type = string default = "" } variable "product_id" { - description = "Product ID for the S3 product (if not provided, uses default)" + description = "Product ID for the S3 product (required for provisioning)" type = string default = "" } diff --git a/modules/ec2/README.md b/modules/ec2/README.md new file mode 100644 index 0000000..d1cebe5 --- /dev/null +++ b/modules/ec2/README.md @@ -0,0 +1,43 @@ +# Service Catalog EC2 Module + +## Overview + +This is the shared EC2 implementation for AWS Service Catalog provisioning. +OS-specific wrappers (`ec2/linux` and `ec2/windows`) call this module to reduce duplication. + +## Features + +- Shared EC2 parameter mapping and Service Catalog integration +- Flexible networking inputs with explicit-first precedence +- Consistent outputs and tagging behavior + +## Networking Behavior + +- Explicit `subnet_ids`/`vpc_id` take precedence. +- If explicit IDs are not provided, lookup is performed via `vpc_name`/`subnets_name`. +- `AZName` is derived from the first selected subnet. + +## Usage + +```hcl +module "ec2_instance" { + source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2?ref=v1.0.0" + + provisioned_product_name = "my-ec2" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" + + instance_type = "t3.small" + os_name = "RHEL9" + + subnet_ids = ["subnet-aaaa"] + vpc_id = "vpc-bbbb" +} +``` + +## Notes + +- Provide required Service Catalog identifiers: `portfolio_id` and `product_id`. +- To discover IDs with AWS CLI, see [Discovering Service Catalog IDs With AWS CLI](../../README.md#discovering-service-catalog-ids-with-aws-cli). +- Prefer OS-specific wrappers when you want curated defaults. +- Use this module directly when you need a single configurable EC2 implementation. diff --git a/modules/ec2/data.tf b/modules/ec2/data.tf new file mode 100644 index 0000000..86ddd0e --- /dev/null +++ b/modules/ec2/data.tf @@ -0,0 +1,36 @@ +data "aws_vpc" "vpc" { + count = var.vpc_id == "" && var.vpc_name != "" ? 1 : 0 + filter { + name = "tag:Name" + values = [var.vpc_name] + } +} + +# Get all subnets matching name and VPC ID when explicit subnet IDs are not provided +data "aws_subnets" "subnets" { + count = length(var.subnet_ids) == 0 && var.subnets_name != "" && (var.vpc_id != "" || var.vpc_name != "") ? 1 : 0 + filter { + name = "tag:Name" + values = [var.subnets_name] + } + filter { + name = "vpc-id" + values = [var.vpc_id != "" ? var.vpc_id : data.aws_vpc.vpc[0].id] + } +} + +# Get discovered subnet details for AZ derivation +data "aws_subnet" "discovered_subnets" { + for_each = length(var.subnet_ids) == 0 && var.subnets_name != "" && length(data.aws_subnets.subnets) > 0 ? toset(data.aws_subnets.subnets[0].ids) : [] + id = each.key +} + +# Get explicit subnet details for AZ/VPC derivation +data "aws_subnet" "explicit_subnets" { + for_each = toset(var.subnet_ids) + id = each.key +} + +data "aws_availability_zones" "zones" { + state = "available" +} diff --git a/modules/ec2/linux/README.md b/modules/ec2/linux/README.md index d80ac57..0dbf50f 100644 --- a/modules/ec2/linux/README.md +++ b/modules/ec2/linux/README.md @@ -4,12 +4,12 @@ This module provisions EC2 instances via AWS Service Catalog. It provides a simplified Terraform interface to the Service Catalog EC2 product, handling all the complexity of parameter mapping, networking lookups, and provisioning. -This is a product-specific wrapper around the base [`product`](../product/) module, adding EC2-specific parameters and defaults. +This is a Linux-focused wrapper around the shared [`ec2`](../) module, preserving Linux defaults while reusing common EC2 implementation logic. ## Features - **Simplified Interface**: Clean Terraform variables instead of raw Service Catalog parameters -- **Automatic VPC/Subnet Resolution**: Looks up VPC and subnet information by name tags +- **Flexible Networking Inputs**: Supports explicit `subnet_ids`/`vpc_id` or lookup via `vpc_name`/`subnets_name` - **Latest Version by Default**: Automatically uses the latest active provisioning artifact - **Flexible Parameter Override**: Pass additional parameters via the `parameters` variable - **Standard Tagging**: Consistent tagging across all resources @@ -21,11 +21,11 @@ This is a product-specific wrapper around the base [`product`](../product/) modu ```hcl module "ec2_instance" { - source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2?ref=v1.0.0" + source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2/linux?ref=v1.0.0" # Service Catalog Configuration - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-43foqxjcq5isw" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" # Instance Identity provisioned_product_name = "app-web-01" @@ -58,10 +58,10 @@ module "ec2_instance" { ```hcl module "ec2_instance" { - source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2?ref=v1.0.0" + source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2/linux?ref=v1.0.0" - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-43foqxjcq5isw" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" provisioned_product_name = "app-db-01" project_name = "csvd_database_prod-229685449397" @@ -123,10 +123,10 @@ No providers are used directly - all AWS resources are provisioned by the underl | Name | Description | Type | Default | |------|-------------|------|---------| -| `portfolio_id` | Portfolio ID containing the EC2 product | `string` | `"port-pgj3zvoqca7ya"` | -| `product_id` | Product ID for the EC2 product | `string` | `"prod-43foqxjcq5isw"` | +| `portfolio_id` | Portfolio ID containing the EC2 product | `string` | `null` | +| `product_id` | Product ID for the EC2 product | `string` | `null` | | `path_id` | Path identifier (version). Uses latest if not provided | `string` | `null` | -| `timeout` | Timeout for provisioning operations | `string` | `"15m"` | +| `timeout` | Timeout for provisioning operations | `string` | `"5m"` | | `accept_language` | Language code for API calls | `string` | `"en"` | | `ignore_errors` | Ignore errors during provisioning | `bool` | `false` | | `notification_arns` | SNS topic ARNs for notifications | `list(string)` | `[]` | @@ -156,8 +156,10 @@ No providers are used directly - all AWS resources are provisioned by the underl | Name | Description | Type | Default | |------|-------------|------|---------| -| `vpc_name` | Name tag of the VPC | `string` | `""` | -| `subnets_name` | Name tag pattern for subnets | `string` | `"*-apps-*"` | +| `subnet_ids` | Explicit subnet IDs. If set, subnet lookup is skipped | `list(string)` | `[]` | +| `vpc_id` | Explicit VPC ID. If set, VPC lookup is skipped | `string` | `""` | +| `vpc_name` | VPC name tag for lookup when `vpc_id` is empty | `string` | `""` | +| `subnets_name` | Subnet name tag pattern for lookup when `subnet_ids` is empty | `string` | `"*-apps-*"` | ### Other @@ -184,15 +186,16 @@ No providers are used directly - all AWS resources are provisioned by the underl | `availability_zone` | The availability zone used | | `availability_zone_names` | List of all availability zone names | | `availability_zone_ids` | List of all availability zone IDs | -| `availability_zone_suffixes` | List of availability zone suffixes | ## Module Architecture -This module is built on top of the generic [`product`](../product/) module: +This module is built on top of the shared [`ec2`](../) module: ``` -ec2 module (this) +ec2/linux wrapper (this) ↓ +ec2 module (shared EC2 logic) + ↓ product module (base Service Catalog logic) ↓ AWS Service Catalog @@ -200,11 +203,10 @@ AWS Service Catalog CloudFormation Stack (EC2 instance) ``` -The `ec2` module: -1. Accepts EC2-specific variables -2. Maps them to Service Catalog parameters -3. Passes them to the `product` module via `product_parameters` -4. Exposes outputs from the `product` module +The `ec2/linux` wrapper: +1. Preserves Linux-focused defaults +2. Delegates EC2 implementation to the shared `ec2` module +3. Exposes outputs from the shared module ## Parameter Mapping @@ -222,15 +224,18 @@ The module automatically maps Terraform variables to Service Catalog parameters: | `os_name` | `OSName` | | `requires_backup` | `RequiresBackup` | | `power_schedule` | `PowerSchedule` | -| `vpc_name` | Resolved to `VpcId` | -| `subnets_name` | Resolved to `AZName` | +| `vpc_id`/`vpc_name` | Resolved to `VpcId` | +| `subnet_ids`/`subnets_name` | First selected subnet resolves to `AZName` | Additional parameters can be passed via the `parameters` variable, which will override any defaults. ## Notes +- Provide required Service Catalog identifiers: `portfolio_id` and `product_id`. +- To discover IDs with AWS CLI, see [Discovering Service Catalog IDs With AWS CLI](../../../README.md#discovering-service-catalog-ids-with-aws-cli). - The `project_name` must end with the AWS account ID (last 12 digits) -- VPC and subnet lookups are performed automatically based on name tags +- For networking, explicit `subnet_ids`/`vpc_id` take precedence over lookup inputs. +- When explicit IDs are not provided, VPC/subnet lookups are performed using `vpc_name`/`subnets_name`. - The module uses the latest active provisioning artifact by default unless `path_id` is specified - All resources created by Service Catalog are managed by CloudFormation - Deleting the Terraform resource will delete the CloudFormation stack unless `retain_physical_resources = true` diff --git a/modules/ec2/linux/data.tf b/modules/ec2/linux/data.tf deleted file mode 100644 index c3717ad..0000000 --- a/modules/ec2/linux/data.tf +++ /dev/null @@ -1,36 +0,0 @@ -data "aws_vpc" "vpc" { - count = var.vpc_name != "" ? 1 : 0 - filter { - name = "tag:Name" - values = [var.vpc_name] - } -} - -# Get all subnets matching name and VPC ID -data "aws_subnets" "subnets" { - count = var.vpc_name != "" && var.subnets_name != "" ? 1 : 0 - filter { - name = "tag:Name" - values = [var.subnets_name] - } - filter { - name = "vpc-id" - values = [data.aws_vpc.vpc[0].id] - } -} - -# get ids for each subnet for use in provisioning -data "aws_subnet" "subnets" { - for_each = var.vpc_name != "" && var.subnets_name != "" ? toset(data.aws_subnets.subnets[0].ids) : [] - id = each.key -} - -data "aws_availability_zones" "zones" { - state = "available" -} - -data "aws_availability_zone" "zone" { - for_each = toset(data.aws_availability_zones.zones.names) - state = "available" - name = each.key -} \ No newline at end of file diff --git a/modules/ec2/linux/locals.tf b/modules/ec2/linux/locals.tf index 16368f7..3c318cf 100644 --- a/modules/ec2/linux/locals.tf +++ b/modules/ec2/linux/locals.tf @@ -1,30 +1,4 @@ locals { - # EC2-specific parameters to pass to the product module - ec2_parameters = merge( - { - InstanceType = var.instance_type - OSName = var.os_name - RequiresBackup = var.requires_backup - PowerSchedule = var.power_schedule - }, - var.parameters # Allow user to override any parameter - ) - - # VPC and networking - only resolve if vpc_name is provided - vpc_id = var.vpc_name != "" ? data.aws_vpc.vpc[0].id : null - az_name = var.vpc_name != "" && var.subnets_name != "" && length(data.aws_subnets.subnets[0].ids) > 0 ? data.aws_subnet.subnets[sort(data.aws_subnets.subnets[0].ids)[0]].availability_zone : null - - # Add networking parameters if available - network_parameters = local.vpc_id != null ? { - VpcId = local.vpc_id - AZName = local.az_name - } : {} - - parameters = merge( - local.network_parameters, - local.ec2_parameters - ) - base_tags = { "boc:tf_module_name" = local.module_name "boc:tf_module_version" = local.module_version diff --git a/modules/ec2/linux/main.tf b/modules/ec2/linux/main.tf index 378f098..2313ff2 100644 --- a/modules/ec2/linux/main.tf +++ b/modules/ec2/linux/main.tf @@ -1,14 +1,14 @@ -# EC2 Product Module +# EC2 Linux Wrapper Module # -# Provisions an EC2 instance via Service Catalog -# This is a thin wrapper around the product module with EC2-specific parameters +# Thin wrapper around the shared EC2 module, preserving Linux-specific defaults. -module "ec2-linux" { - source = "../../product" +module "ec2" { + source = "./.." # Service Catalog configuration portfolio_id = var.portfolio_id product_id = var.product_id + provisioning_artifact_id = var.provisioning_artifact_id path_id = var.path_id accept_language = var.accept_language timeout = var.timeout @@ -25,8 +25,17 @@ module "ec2-linux" { inc_poc_email = var.inc_poc_email fisma_id = var.fisma_id - # EC2-specific + networking parameters (merged in locals.tf) - product_parameters = local.parameters + # EC2-specific + networking parameters + instance_type = var.instance_type + os_name = var.os_name + requires_backup = var.requires_backup + power_schedule = var.power_schedule + availability_zones = var.availability_zones + subnet_ids = var.subnet_ids + vpc_id = var.vpc_id + vpc_name = var.vpc_name + subnets_name = var.subnets_name + parameters = var.parameters # Tags tags = local.tags diff --git a/modules/ec2/linux/module_name.tf b/modules/ec2/linux/module_name.tf index d20f85f..5751422 100644 --- a/modules/ec2/linux/module_name.tf +++ b/modules/ec2/linux/module_name.tf @@ -1,4 +1,4 @@ locals { - module_name = "aws-servicecatalog/ec2" - module_version = "0.0.0" + module_name = "aws-servicecatalog/ec2/linux" + module_version = "1.0.0" } diff --git a/modules/ec2/linux/outputs.tf b/modules/ec2/linux/outputs.tf index 306299a..b0093c6 100644 --- a/modules/ec2/linux/outputs.tf +++ b/modules/ec2/linux/outputs.tf @@ -1,75 +1,75 @@ output "provisioned_product_id" { description = "The ID of the provisioned product" - value = module.ec2-linux.provisioned_product_id + value = module.ec2.provisioned_product_id } output "provisioned_product_name" { description = "The name of the provisioned product" - value = module.ec2-linux.provisioned_product_name + value = module.ec2.provisioned_product_name } output "provisioned_product_arn" { description = "The ARN of the provisioned product" - value = module.ec2-linux.provisioned_product_arn + value = module.ec2.provisioned_product_arn } output "provisioned_product_type" { description = "The type of the provisioned product" - value = module.ec2-linux.provisioned_product_type + value = module.ec2.provisioned_product_type } output "provisioned_product_status" { description = "The status of the provisioned product" - value = module.ec2-linux.provisioned_product_status + value = module.ec2.provisioned_product_status } output "provisioned_product_status_message" { description = "The status message for the provisioned product" - value = module.ec2-linux.provisioned_product_status_message + value = module.ec2.provisioned_product_status_message } output "launch_role_arn" { description = "The ARN of the launch role" - value = module.ec2-linux.launch_role_arn + value = module.ec2.launch_role_arn } output "portfolio_id" { description = "The ID of the portfolio used" - value = module.ec2-linux.portfolio_id + value = module.ec2.portfolio_id } output "product_id" { description = "The ID of the product used" - value = module.ec2-linux.product_id + value = module.ec2.product_id } output "provisioning_artifact_id" { description = "The ID of the provisioning artifact used" - value = module.ec2-linux.provisioning_artifact_id + value = module.ec2.provisioning_artifact_id } output "stack_outputs" { description = "CloudFormation stack outputs from the provisioned product" - value = module.ec2-linux.stack_outputs + value = module.ec2.stack_outputs sensitive = true } output "vpc_id" { description = "The VPC ID where the instance will be provisioned" - value = local.vpc_id + value = module.ec2.vpc_id } output "availability_zone" { description = "The availability zone of the first selected subnet" - value = local.az_name + value = module.ec2.availability_zone } output "availability_zone_names" { description = "Available availability zone names" - value = data.aws_availability_zones.zones.names + value = module.ec2.availability_zone_names } output "availability_zone_ids" { description = "Available availability zone IDs" - value = data.aws_availability_zones.zones.zone_ids + value = module.ec2.availability_zone_ids } \ No newline at end of file diff --git a/modules/ec2/linux/variables.product.tf b/modules/ec2/linux/variables.product.tf index ba7d465..8e90d10 100644 --- a/modules/ec2/linux/variables.product.tf +++ b/modules/ec2/linux/variables.product.tf @@ -30,14 +30,26 @@ variable "availability_zones" { default = [] } +variable "subnet_ids" { + description = "Explicit subnet IDs to use for placement. If set, subnet lookup by name is skipped" + type = list(string) + default = [] +} + +variable "vpc_id" { + description = "Explicit VPC ID. If set, VPC lookup by name is skipped" + type = string + default = "" +} + variable "vpc_name" { - description = "Name tag of the VPC to deploy into" + description = "Name tag of the VPC to deploy into when vpc_id is not set" type = string default = "" } variable "subnets_name" { - description = "Name tag of the subnets to deploy into" + description = "Name tag pattern of subnets to deploy into when subnet_ids are not set" type = string default = "*-apps-*" } diff --git a/modules/ec2/linux/variables.servicecatalog.tf b/modules/ec2/linux/variables.servicecatalog.tf index 92d1a58..5fd4268 100644 --- a/modules/ec2/linux/variables.servicecatalog.tf +++ b/modules/ec2/linux/variables.servicecatalog.tf @@ -1,17 +1,23 @@ variable "portfolio_id" { - description = "Portfolio ID. If not provided, will lookup by portfolio_name_pattern" + description = "Portfolio ID for the EC2 product" type = string - default = "port-l2v5cyrvr4exa" + default = null } variable "product_id" { - description = "Product ID. If not provided, will lookup by product_name_pattern" + description = "Product ID for the EC2 product" + type = string + default = null +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version). If not provided, the latest active artifact is used" type = string - default = "prod-43foqxjcq5isw" + default = null } variable "path_id" { - description = "Path identifier of the product. If not provided, will use the latest active artifact" + description = "Launch path identifier of the product" type = string default = null } diff --git a/modules/ec2/locals.tf b/modules/ec2/locals.tf new file mode 100644 index 0000000..01dea70 --- /dev/null +++ b/modules/ec2/locals.tf @@ -0,0 +1,57 @@ +locals { + # EC2-specific parameters to pass to the product module + ec2_parameters = merge( + { + InstanceType = var.instance_type + OSName = var.os_name + RequiresBackup = var.requires_backup + PowerSchedule = var.power_schedule + }, + var.parameters # Allow user to override any parameter + ) + + # Resolve subnet IDs from explicit values first, then discovery + resolved_subnet_ids = length(var.subnet_ids) > 0 ? var.subnet_ids : ( + var.subnets_name != "" && length(data.aws_subnets.subnets) > 0 ? sort(data.aws_subnets.subnets[0].ids) : [] + ) + + selected_subnet_id = length(local.resolved_subnet_ids) > 0 ? local.resolved_subnet_ids[0] : null + + selected_subnet = local.selected_subnet_id != null ? ( + length(var.subnet_ids) > 0 + ? data.aws_subnet.explicit_subnets[local.selected_subnet_id] + : data.aws_subnet.discovered_subnets[local.selected_subnet_id] + ) : null + + # Resolve VPC from explicit value first, then subnet-derived value, then VPC name lookup + resolved_vpc_id = var.vpc_id != "" ? var.vpc_id : ( + local.selected_subnet != null ? local.selected_subnet.vpc_id : ( + var.vpc_name != "" ? data.aws_vpc.vpc[0].id : null + ) + ) + + vpc_id = local.resolved_vpc_id + az_name = local.selected_subnet != null ? local.selected_subnet.availability_zone : null + + # Add networking parameters if available + network_parameters = local.resolved_vpc_id != null ? { + VpcId = local.resolved_vpc_id + AZName = local.az_name + } : {} + + parameters = merge( + local.network_parameters, + local.ec2_parameters + ) + + base_tags = { + "boc:tf_module_name" = local.module_name + "boc:tf_module_version" = local.module_version + "boc:created_by" = "terraform" + } + + tags = merge( + local.base_tags, + var.tags + ) +} diff --git a/modules/ec2/main.tf b/modules/ec2/main.tf new file mode 100644 index 0000000..63524d7 --- /dev/null +++ b/modules/ec2/main.tf @@ -0,0 +1,34 @@ +# EC2 Product Module +# +# Provisions an EC2 instance via Service Catalog. +# This module is the shared implementation used by OS-specific wrappers. + +module "ec2" { + source = "../product" + + # Service Catalog configuration + portfolio_id = var.portfolio_id + product_id = var.product_id + provisioning_artifact_id = var.provisioning_artifact_id + path_id = var.path_id + accept_language = var.accept_language + timeout = var.timeout + ignore_errors = var.ignore_errors + notification_arns = var.notification_arns + retain_physical_resources = var.retain_physical_resources + stack_set_provisioning_preferences = var.stack_set_provisioning_preferences + + # Common parameters + provisioned_product_name = var.provisioned_product_name + project_name = var.project_name + creator = var.creator + contact_email = var.contact_email + inc_poc_email = var.inc_poc_email + fisma_id = var.fisma_id + + # EC2-specific + networking parameters + product_parameters = local.parameters + + # Tags + tags = local.tags +} diff --git a/modules/ec2/module_name.tf b/modules/ec2/module_name.tf new file mode 100644 index 0000000..8ab39fc --- /dev/null +++ b/modules/ec2/module_name.tf @@ -0,0 +1,4 @@ +locals { + module_name = "aws-servicecatalog/ec2" + module_version = "1.0.0" +} diff --git a/modules/ec2/outputs.tf b/modules/ec2/outputs.tf new file mode 100644 index 0000000..8e9a191 --- /dev/null +++ b/modules/ec2/outputs.tf @@ -0,0 +1,75 @@ +output "provisioned_product_id" { + description = "The ID of the provisioned product" + value = module.ec2.provisioned_product_id +} + +output "provisioned_product_name" { + description = "The name of the provisioned product" + value = module.ec2.provisioned_product_name +} + +output "provisioned_product_arn" { + description = "The ARN of the provisioned product" + value = module.ec2.provisioned_product_arn +} + +output "provisioned_product_type" { + description = "The type of the provisioned product" + value = module.ec2.provisioned_product_type +} + +output "provisioned_product_status" { + description = "The status of the provisioned product" + value = module.ec2.provisioned_product_status +} + +output "provisioned_product_status_message" { + description = "The status message for the provisioned product" + value = module.ec2.provisioned_product_status_message +} + +output "launch_role_arn" { + description = "The ARN of the launch role" + value = module.ec2.launch_role_arn +} + +output "portfolio_id" { + description = "The ID of the portfolio used" + value = module.ec2.portfolio_id +} + +output "product_id" { + description = "The ID of the product used" + value = module.ec2.product_id +} + +output "provisioning_artifact_id" { + description = "The ID of the provisioning artifact used" + value = module.ec2.provisioning_artifact_id +} + +output "stack_outputs" { + description = "CloudFormation stack outputs from the provisioned product" + value = module.ec2.stack_outputs + sensitive = true +} + +output "vpc_id" { + description = "The VPC ID where the instance will be provisioned" + value = local.vpc_id +} + +output "availability_zone" { + description = "The availability zone of the first selected subnet" + value = local.az_name +} + +output "availability_zone_names" { + description = "Available availability zone names" + value = data.aws_availability_zones.zones.names +} + +output "availability_zone_ids" { + description = "Available availability zone IDs" + value = data.aws_availability_zones.zones.zone_ids +} diff --git a/modules/ec2/variables.common.tf b/modules/ec2/variables.common.tf new file mode 100644 index 0000000..1a9a1a0 --- /dev/null +++ b/modules/ec2/variables.common.tf @@ -0,0 +1,60 @@ +#--- +# Common variables +#--- +variable "account_id" { + description = "AWS Account ID (default will pull from current user)" + type = string + default = "" +} + +variable "account_alias" { + description = "AWS Account Alias" + type = string + default = "" +} + +variable "parameters" { + description = "Additional parameters to pass to the Service Catalog product. Map of parameter names to values." + type = map(string) + default = {} +} + +variable "provisioned_product_name" { + description = "Name of the provisioned product" + type = string + + validation { + condition = length(var.provisioned_product_name) > 0 && length(var.provisioned_product_name) <= 128 + error_message = "provisioned_product_name must be between 1 and 128 characters" + } +} + +variable "project_name" { + description = "Project name (ProjectName parameter). Make sure to select the project designated for your account. The build will fail if an incorrect project is selected. The list of values can be found in the ProjectName parameter of the product's provisioning artifact." + type = string + default = "" +} + +variable "creator" { + description = "Creator's JBID (Creator parameter)" + type = string + default = "" +} + +variable "contact_email" { + description = "Provisioning user's email (ContactEmail parameter)" + type = string + default = "" +} + +variable "inc_poc_email" { + description = "Incident POC email (IncPocEmail parameter)" + type = string + default = "" +} + +variable "fisma_id" { + description = "FISMA ID" + type = string + default = "" +} diff --git a/modules/ec2/variables.product.tf b/modules/ec2/variables.product.tf new file mode 100644 index 0000000..8e90d10 --- /dev/null +++ b/modules/ec2/variables.product.tf @@ -0,0 +1,55 @@ +# EC2-specific product parameters + +variable "instance_type" { + description = "EC2 instance type" + type = string + default = "t3.small" +} + +variable "os_name" { + description = "Operating system version" + type = string + default = "RHEL9" +} + +variable "requires_backup" { + description = "Backup requirement" + type = string + default = "no" +} + +variable "power_schedule" { + description = "Power schedule" + type = string + default = "" +} + +variable "availability_zones" { + description = "AWS Availability Zones to use (by default will use all available)" + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "Explicit subnet IDs to use for placement. If set, subnet lookup by name is skipped" + type = list(string) + default = [] +} + +variable "vpc_id" { + description = "Explicit VPC ID. If set, VPC lookup by name is skipped" + type = string + default = "" +} + +variable "vpc_name" { + description = "Name tag of the VPC to deploy into when vpc_id is not set" + type = string + default = "" +} + +variable "subnets_name" { + description = "Name tag pattern of subnets to deploy into when subnet_ids are not set" + type = string + default = "*-apps-*" +} diff --git a/modules/ec2/variables.servicecatalog.tf b/modules/ec2/variables.servicecatalog.tf new file mode 100644 index 0000000..81c3fde --- /dev/null +++ b/modules/ec2/variables.servicecatalog.tf @@ -0,0 +1,71 @@ +variable "portfolio_id" { + description = "Portfolio ID for the EC2 product" + type = string + default = null +} + +variable "product_id" { + description = "Product ID for the EC2 product" + type = string + default = null +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version). If not provided, the latest active artifact is used" + type = string + default = null +} + +variable "path_id" { + description = "Launch path identifier of the product" + type = string + default = null +} + +variable "timeout" { + description = "Timeout for provisioned product operations (create/update/delete)" + type = string + default = "5m" +} + +variable "accept_language" { + description = "Language code for Service Catalog API calls" + type = string + default = "en" + + validation { + condition = contains(["en", "jp", "zh"], var.accept_language) + error_message = "accept_language must be one of: en, jp, zh" + } +} + +variable "ignore_errors" { + description = "Whether to ignore errors during provisioning" + type = bool + default = false +} + +variable "notification_arns" { + description = "List of SNS topic ARNs to send provisioning notifications to" + type = list(string) + default = [] +} + +variable "retain_physical_resources" { + description = "Whether to retain physical resources when deleting the provisioned product" + type = bool + default = false +} + +variable "stack_set_provisioning_preferences" { + description = "StackSet provisioning preferences to use when provisioning the product" + type = object({ + accounts = optional(list(string)) + failure_tolerance_count = optional(number) + failure_tolerance_percentage = optional(number) + max_concurrency_count = optional(number) + max_concurrency_percentage = optional(number) + regions = optional(list(string)) + }) + default = null +} diff --git a/modules/ec2/variables.tags.tf b/modules/ec2/variables.tags.tf new file mode 100644 index 0000000..6ebf2ec --- /dev/null +++ b/modules/ec2/variables.tags.tf @@ -0,0 +1,5 @@ +variable "tags" { + description = "Additional tags to apply to resources" + type = map(string) + default = {} +} diff --git a/modules/ec2/versions.tf b/modules/ec2/versions.tf new file mode 100644 index 0000000..773514a --- /dev/null +++ b/modules/ec2/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + } +} diff --git a/modules/ec2/windows/README.md b/modules/ec2/windows/README.md index d80ac57..f3a9773 100644 --- a/modules/ec2/windows/README.md +++ b/modules/ec2/windows/README.md @@ -4,12 +4,12 @@ This module provisions EC2 instances via AWS Service Catalog. It provides a simplified Terraform interface to the Service Catalog EC2 product, handling all the complexity of parameter mapping, networking lookups, and provisioning. -This is a product-specific wrapper around the base [`product`](../product/) module, adding EC2-specific parameters and defaults. +This is a Windows-focused wrapper around the shared [`ec2`](../) module, preserving Windows defaults while reusing common EC2 implementation logic. ## Features - **Simplified Interface**: Clean Terraform variables instead of raw Service Catalog parameters -- **Automatic VPC/Subnet Resolution**: Looks up VPC and subnet information by name tags +- **Flexible Networking Inputs**: Supports explicit `subnet_ids`/`vpc_id` or lookup via `vpc_name`/`subnets_name` - **Latest Version by Default**: Automatically uses the latest active provisioning artifact - **Flexible Parameter Override**: Pass additional parameters via the `parameters` variable - **Standard Tagging**: Consistent tagging across all resources @@ -21,11 +21,11 @@ This is a product-specific wrapper around the base [`product`](../product/) modu ```hcl module "ec2_instance" { - source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2?ref=v1.0.0" + source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2/windows?ref=v1.0.0" # Service Catalog Configuration - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-43foqxjcq5isw" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" # Instance Identity provisioned_product_name = "app-web-01" @@ -37,7 +37,7 @@ module "ec2_instance" { # EC2 Configuration instance_type = "t3a.medium" - os_name = "RHEL9" + os_name = "MS2022" power_schedule = "Weekday_Core_Hours_7-7" requires_backup = "yes" @@ -58,10 +58,10 @@ module "ec2_instance" { ```hcl module "ec2_instance" { - source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2?ref=v1.0.0" + source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/ec2/windows?ref=v1.0.0" - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-43foqxjcq5isw" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" provisioned_product_name = "app-db-01" project_name = "csvd_database_prod-229685449397" @@ -69,7 +69,7 @@ module "ec2_instance" { subnets_name = "subnet-data-*" instance_type = "r5.xlarge" - os_name = "RHEL9" + os_name = "MS2022" power_schedule = "24x7" requires_backup = "yes" @@ -123,10 +123,10 @@ No providers are used directly - all AWS resources are provisioned by the underl | Name | Description | Type | Default | |------|-------------|------|---------| -| `portfolio_id` | Portfolio ID containing the EC2 product | `string` | `"port-pgj3zvoqca7ya"` | -| `product_id` | Product ID for the EC2 product | `string` | `"prod-43foqxjcq5isw"` | +| `portfolio_id` | Portfolio ID containing the EC2 product | `string` | `null` | +| `product_id` | Product ID for the EC2 product | `string` | `null` | | `path_id` | Path identifier (version). Uses latest if not provided | `string` | `null` | -| `timeout` | Timeout for provisioning operations | `string` | `"15m"` | +| `timeout` | Timeout for provisioning operations | `string` | `"5m"` | | `accept_language` | Language code for API calls | `string` | `"en"` | | `ignore_errors` | Ignore errors during provisioning | `bool` | `false` | | `notification_arns` | SNS topic ARNs for notifications | `list(string)` | `[]` | @@ -138,7 +138,7 @@ No providers are used directly - all AWS resources are provisioned by the underl | Name | Description | Type | Default | |------|-------------|------|---------| | `instance_type` | EC2 instance type | `string` | `"t3.small"` | -| `os_name` | Operating system version | `string` | `"RHEL9"` | +| `os_name` | Operating system version | `string` | `"MS2022"` | | `requires_backup` | Backup requirement (yes/no) | `string` | `"no"` | | `power_schedule` | Power schedule name | `string` | `""` | @@ -156,8 +156,10 @@ No providers are used directly - all AWS resources are provisioned by the underl | Name | Description | Type | Default | |------|-------------|------|---------| -| `vpc_name` | Name tag of the VPC | `string` | `""` | -| `subnets_name` | Name tag pattern for subnets | `string` | `"*-apps-*"` | +| `subnet_ids` | Explicit subnet IDs. If set, subnet lookup is skipped | `list(string)` | `[]` | +| `vpc_id` | Explicit VPC ID. If set, VPC lookup is skipped | `string` | `""` | +| `vpc_name` | VPC name tag for lookup when `vpc_id` is empty | `string` | `""` | +| `subnets_name` | Subnet name tag pattern for lookup when `subnet_ids` is empty | `string` | `"*-apps-*"` | ### Other @@ -184,15 +186,16 @@ No providers are used directly - all AWS resources are provisioned by the underl | `availability_zone` | The availability zone used | | `availability_zone_names` | List of all availability zone names | | `availability_zone_ids` | List of all availability zone IDs | -| `availability_zone_suffixes` | List of availability zone suffixes | ## Module Architecture -This module is built on top of the generic [`product`](../product/) module: +This module is built on top of the shared [`ec2`](../) module: ``` -ec2 module (this) +ec2/windows wrapper (this) ↓ +ec2 module (shared EC2 logic) + ↓ product module (base Service Catalog logic) ↓ AWS Service Catalog @@ -200,11 +203,10 @@ AWS Service Catalog CloudFormation Stack (EC2 instance) ``` -The `ec2` module: -1. Accepts EC2-specific variables -2. Maps them to Service Catalog parameters -3. Passes them to the `product` module via `product_parameters` -4. Exposes outputs from the `product` module +The `ec2/windows` wrapper: +1. Preserves Windows-focused defaults +2. Delegates EC2 implementation to the shared `ec2` module +3. Exposes outputs from the shared module ## Parameter Mapping @@ -222,15 +224,18 @@ The module automatically maps Terraform variables to Service Catalog parameters: | `os_name` | `OSName` | | `requires_backup` | `RequiresBackup` | | `power_schedule` | `PowerSchedule` | -| `vpc_name` | Resolved to `VpcId` | -| `subnets_name` | Resolved to `AZName` | +| `vpc_id`/`vpc_name` | Resolved to `VpcId` | +| `subnet_ids`/`subnets_name` | First selected subnet resolves to `AZName` | Additional parameters can be passed via the `parameters` variable, which will override any defaults. ## Notes +- Provide required Service Catalog identifiers: `portfolio_id` and `product_id`. +- To discover IDs with AWS CLI, see [Discovering Service Catalog IDs With AWS CLI](../../../README.md#discovering-service-catalog-ids-with-aws-cli). - The `project_name` must end with the AWS account ID (last 12 digits) -- VPC and subnet lookups are performed automatically based on name tags +- For networking, explicit `subnet_ids`/`vpc_id` take precedence over lookup inputs. +- When explicit IDs are not provided, VPC/subnet lookups are performed using `vpc_name`/`subnets_name`. - The module uses the latest active provisioning artifact by default unless `path_id` is specified - All resources created by Service Catalog are managed by CloudFormation - Deleting the Terraform resource will delete the CloudFormation stack unless `retain_physical_resources = true` diff --git a/modules/ec2/windows/data.tf b/modules/ec2/windows/data.tf deleted file mode 100644 index c3717ad..0000000 --- a/modules/ec2/windows/data.tf +++ /dev/null @@ -1,36 +0,0 @@ -data "aws_vpc" "vpc" { - count = var.vpc_name != "" ? 1 : 0 - filter { - name = "tag:Name" - values = [var.vpc_name] - } -} - -# Get all subnets matching name and VPC ID -data "aws_subnets" "subnets" { - count = var.vpc_name != "" && var.subnets_name != "" ? 1 : 0 - filter { - name = "tag:Name" - values = [var.subnets_name] - } - filter { - name = "vpc-id" - values = [data.aws_vpc.vpc[0].id] - } -} - -# get ids for each subnet for use in provisioning -data "aws_subnet" "subnets" { - for_each = var.vpc_name != "" && var.subnets_name != "" ? toset(data.aws_subnets.subnets[0].ids) : [] - id = each.key -} - -data "aws_availability_zones" "zones" { - state = "available" -} - -data "aws_availability_zone" "zone" { - for_each = toset(data.aws_availability_zones.zones.names) - state = "available" - name = each.key -} \ No newline at end of file diff --git a/modules/ec2/windows/locals.tf b/modules/ec2/windows/locals.tf index 286af3b..3c318cf 100644 --- a/modules/ec2/windows/locals.tf +++ b/modules/ec2/windows/locals.tf @@ -1,31 +1,4 @@ locals { - # EC2-specific parameters to pass to the product module - ec2_parameters = merge( - { - InstanceType = var.instance_type - OSName = var.os_name - RequiresBackup = var.requires_backup - PowerSchedule = var.power_schedule - }, - var.parameters # Allow user to override any parameter - ) - - # VPC and networking - only resolve if vpc_name is provided - vpc_id = var.vpc_name != "" ? data.aws_vpc.vpc[0].id : null - selected_subnet_id = var.vpc_name != "" && var.subnets_name != "" && length(data.aws_subnets.subnets[0].ids) > 0 ? sort(data.aws_subnets.subnets[0].ids)[0] : null - az_name = local.selected_subnet_id != null ? data.aws_subnet.subnets[local.selected_subnet_id].availability_zone : null - - # Add networking parameters if available - network_parameters = local.vpc_id != null ? { - VpcId = local.vpc_id - AZName = local.az_name - } : {} - - parameters = merge( - local.network_parameters, - local.ec2_parameters - ) - base_tags = { "boc:tf_module_name" = local.module_name "boc:tf_module_version" = local.module_version diff --git a/modules/ec2/windows/main.tf b/modules/ec2/windows/main.tf index 323ff58..d9346a8 100644 --- a/modules/ec2/windows/main.tf +++ b/modules/ec2/windows/main.tf @@ -1,14 +1,14 @@ -# EC2 Product Module +# EC2 Windows Wrapper Module # -# Provisions an EC2 instance via Service Catalog -# This is a thin wrapper around the product module with EC2-specific parameters +# Thin wrapper around the shared EC2 module, preserving Windows-specific defaults. -module "ec2-windows" { - source = "../../product" +module "ec2" { + source = "./.." # Service Catalog configuration portfolio_id = var.portfolio_id product_id = var.product_id + provisioning_artifact_id = var.provisioning_artifact_id path_id = var.path_id accept_language = var.accept_language timeout = var.timeout @@ -25,8 +25,17 @@ module "ec2-windows" { inc_poc_email = var.inc_poc_email fisma_id = var.fisma_id - # EC2-specific + networking parameters (merged in locals.tf) - product_parameters = local.parameters + # EC2-specific + networking parameters + instance_type = var.instance_type + os_name = var.os_name + requires_backup = var.requires_backup + power_schedule = var.power_schedule + availability_zones = var.availability_zones + subnet_ids = var.subnet_ids + vpc_id = var.vpc_id + vpc_name = var.vpc_name + subnets_name = var.subnets_name + parameters = var.parameters # Tags tags = local.tags diff --git a/modules/ec2/windows/module_name.tf b/modules/ec2/windows/module_name.tf index d20f85f..579ff12 100644 --- a/modules/ec2/windows/module_name.tf +++ b/modules/ec2/windows/module_name.tf @@ -1,4 +1,4 @@ locals { - module_name = "aws-servicecatalog/ec2" - module_version = "0.0.0" + module_name = "aws-servicecatalog/ec2/windows" + module_version = "1.0.0" } diff --git a/modules/ec2/windows/outputs.tf b/modules/ec2/windows/outputs.tf index 4855aeb..b0093c6 100644 --- a/modules/ec2/windows/outputs.tf +++ b/modules/ec2/windows/outputs.tf @@ -1,75 +1,75 @@ output "provisioned_product_id" { description = "The ID of the provisioned product" - value = module.ec2-windows.provisioned_product_id + value = module.ec2.provisioned_product_id } output "provisioned_product_name" { description = "The name of the provisioned product" - value = module.ec2-windows.provisioned_product_name + value = module.ec2.provisioned_product_name } output "provisioned_product_arn" { description = "The ARN of the provisioned product" - value = module.ec2-windows.provisioned_product_arn + value = module.ec2.provisioned_product_arn } output "provisioned_product_type" { description = "The type of the provisioned product" - value = module.ec2-windows.provisioned_product_type + value = module.ec2.provisioned_product_type } output "provisioned_product_status" { description = "The status of the provisioned product" - value = module.ec2-windows.provisioned_product_status + value = module.ec2.provisioned_product_status } output "provisioned_product_status_message" { description = "The status message for the provisioned product" - value = module.ec2-windows.provisioned_product_status_message + value = module.ec2.provisioned_product_status_message } output "launch_role_arn" { description = "The ARN of the launch role" - value = module.ec2-windows.launch_role_arn + value = module.ec2.launch_role_arn } output "portfolio_id" { description = "The ID of the portfolio used" - value = module.ec2-windows.portfolio_id + value = module.ec2.portfolio_id } output "product_id" { description = "The ID of the product used" - value = module.ec2-windows.product_id + value = module.ec2.product_id } output "provisioning_artifact_id" { description = "The ID of the provisioning artifact used" - value = module.ec2-windows.provisioning_artifact_id + value = module.ec2.provisioning_artifact_id } output "stack_outputs" { description = "CloudFormation stack outputs from the provisioned product" - value = module.ec2-windows.stack_outputs + value = module.ec2.stack_outputs sensitive = true } output "vpc_id" { description = "The VPC ID where the instance will be provisioned" - value = local.vpc_id + value = module.ec2.vpc_id } output "availability_zone" { description = "The availability zone of the first selected subnet" - value = local.az_name + value = module.ec2.availability_zone } output "availability_zone_names" { description = "Available availability zone names" - value = data.aws_availability_zones.zones.names + value = module.ec2.availability_zone_names } output "availability_zone_ids" { description = "Available availability zone IDs" - value = data.aws_availability_zones.zones.zone_ids + value = module.ec2.availability_zone_ids } \ No newline at end of file diff --git a/modules/ec2/windows/variables.product.tf b/modules/ec2/windows/variables.product.tf index afaa7bd..b42bad4 100644 --- a/modules/ec2/windows/variables.product.tf +++ b/modules/ec2/windows/variables.product.tf @@ -30,14 +30,26 @@ variable "availability_zones" { default = [] } +variable "subnet_ids" { + description = "Explicit subnet IDs to use for placement. If set, subnet lookup by name is skipped" + type = list(string) + default = [] +} + +variable "vpc_id" { + description = "Explicit VPC ID. If set, VPC lookup by name is skipped" + type = string + default = "" +} + variable "vpc_name" { - description = "Name tag of the VPC to deploy into" + description = "Name tag of the VPC to deploy into when vpc_id is not set" type = string default = "" } variable "subnets_name" { - description = "Name tag of the subnets to deploy into" + description = "Name tag pattern of subnets to deploy into when subnet_ids are not set" type = string default = "*-apps-*" } diff --git a/modules/ec2/windows/variables.servicecatalog.tf b/modules/ec2/windows/variables.servicecatalog.tf index a73c076..5fd4268 100644 --- a/modules/ec2/windows/variables.servicecatalog.tf +++ b/modules/ec2/windows/variables.servicecatalog.tf @@ -1,17 +1,23 @@ variable "portfolio_id" { - description = "Portfolio ID. If not provided, will lookup by portfolio_name_pattern" + description = "Portfolio ID for the EC2 product" type = string - default = "port-l2v5cyrvr4exa" + default = null } variable "product_id" { - description = "Product ID. If not provided, will lookup by product_name_pattern" + description = "Product ID for the EC2 product" + type = string + default = null +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version). If not provided, the latest active artifact is used" type = string - default = "prod-vbno3ll5bvsfw" + default = null } variable "path_id" { - description = "Path identifier of the product. If not provided, will use the latest active artifact" + description = "Launch path identifier of the product" type = string default = null } diff --git a/modules/product/README.md b/modules/product/README.md index d3b96bc..4bfc00d 100644 --- a/modules/product/README.md +++ b/modules/product/README.md @@ -5,13 +5,13 @@ This is the base module for provisioning AWS Service Catalog products. It provides common functionality and defaults that apply to ALL Service Catalog products. Product-specific modules (like `ec2`, `s3`, `rds`, etc.) should use this module as their foundation, passing in product-specific parameters via the `product_parameters` variable. +Callers must provide Service Catalog identifiers via `portfolio_id` and `product_id`. ## Purpose This module abstracts away the common Service Catalog provisioning logic: - Portfolio and product resolution -- VPC and networking lookups - Provisioning artifact selection (latest version by default) - Common tagging strategy - Standard timeout and error handling configuration @@ -46,10 +46,6 @@ module "product" { inc_poc_email = var.inc_poc_email fisma_id = var.fisma_id - # Networking - vpc_name = var.vpc_name - subnets_name = var.subnets_name - # Product-specific parameters product_parameters = { InstanceType = "t3.small" @@ -64,20 +60,19 @@ module "product" { ## Features -- **Automatic VPC/Subnet Resolution**: Automatically looks up VPC and subnet information if `vpc_name` is provided - **Latest Version Selection**: Uses the latest active provisioning artifact by default - **Flexible Parameter Passing**: Accepts product-specific parameters that are merged with common parameters - **Standard Tagging**: Applies consistent base tags across all Service Catalog products -- **Comprehensive Outputs**: Exposes all relevant Service Catalog and networking outputs +- **Comprehensive Outputs**: Exposes all relevant Service Catalog outputs ## Variables See individual `variables.*.tf` files for details: -- `variables.common.tf` - Account and networking configuration +- `variables.common.tf` - Account-related configuration - `variables.servicecatalog.tf` - Service Catalog-specific settings - `variables.product.tf` - Product parameters and naming - `variables.tags.tf` - Tagging configuration ## Outputs -All standard Service Catalog outputs plus networking information. See `outputs.tf` for complete list. +Standard Service Catalog outputs. See `outputs.tf` for complete list. diff --git a/modules/product/locals.tf b/modules/product/locals.tf index 436f4a6..2fe62c1 100644 --- a/modules/product/locals.tf +++ b/modules/product/locals.tf @@ -1,7 +1,7 @@ locals { account_id = data.aws_caller_identity.current.account_id partition = data.aws_partition.current.partition - region = data.aws_region.current.id + region = data.aws_region.current.region # Use provided IDs portfolio_id = var.portfolio_id @@ -15,8 +15,8 @@ locals { null ) - # Use provided path_id or default to latest - provisioning_artifact_id = var.path_id != null ? var.path_id : local.latest_artifact_id + # Use provided artifact ID or default to latest active artifact + provisioning_artifact_id = var.provisioning_artifact_id != null ? var.provisioning_artifact_id : local.latest_artifact_id # Build common default parameters base_parameters = { diff --git a/modules/product/module_name.tf b/modules/product/module_name.tf index 6e45f8e..ace8268 100644 --- a/modules/product/module_name.tf +++ b/modules/product/module_name.tf @@ -1,4 +1,4 @@ locals { module_name = "aws-servicecatalog/product" - module_version = "0.0.0" + module_version = "1.0.0" } diff --git a/modules/product/variables.servicecatalog.tf b/modules/product/variables.servicecatalog.tf index 16ba64e..39bdda4 100644 --- a/modules/product/variables.servicecatalog.tf +++ b/modules/product/variables.servicecatalog.tf @@ -1,11 +1,17 @@ variable "portfolio_id" { - description = "Portfolio ID. If not provided, will lookup by portfolio_name_pattern" + description = "Portfolio ID for the Service Catalog product" type = string default = null } variable "product_id" { - description = "Product ID. If not provided, will lookup by product_name_pattern" + description = "Product ID for the Service Catalog product" + type = string + default = null +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version). If not provided, the latest active artifact is used" type = string default = null } @@ -59,7 +65,7 @@ variable "stack_set_provisioning_preferences" { } variable "path_id" { - description = "Path identifier of the product. If not provided, will use the latest active artifact" + description = "Launch path identifier for the product" type = string default = null } diff --git a/modules/rds/README.md b/modules/rds/README.md new file mode 100644 index 0000000..06688a2 --- /dev/null +++ b/modules/rds/README.md @@ -0,0 +1,60 @@ +# Service Catalog RDS Module + +## Overview + +This module provisions RDS instances via AWS Service Catalog. It is a product-specific wrapper around the base product module and provides strongly-typed RDS inputs. + +## Features + +- Simplified Terraform interface for common RDS parameters +- ID-based Service Catalog selection (portfolio_id, product_id) +- Optional launch path and provisioning artifact controls +- Consistent tagging and outputs across modules +- Flexible parameter override with parameters map +- **Flexible Networking Inputs**: Supports explicit `subnet_ids`/`vpc_id` or lookup via `vpc_name`/`subnets_name` + +## Usage + +```hcl +module "rds_instance" { + source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/rds?ref=v1.0.0" + + provisioned_product_name = "example-rds-postgres" + + # Service Catalog product IDs + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" + + # RDS parameters + db_instance_class = "db.t3.medium" + subnet_ids = ["subnet-aaaa", "subnet-bbbb"] + vpc_id = "vpc-1234567890abcdef0" + # Optional lookup approach when explicit IDs are not provided: + # vpc_name = "my-vpc" + # subnets_name = "*-data-*" + engine = "postgres" + engine_version = "15.3" + multi_az = false + allocated_storage = 20 + + tags = { + Environment = "example" + ManagedBy = "Terraform" + } +} +``` + +## Requirements + +- Terraform >= 1.0 +- AWS Provider >= 6.0 + +## Notes + +- Provide required Service Catalog identifiers: `portfolio_id` and `product_id`. +- To discover IDs with AWS CLI, see [Discovering Service Catalog IDs With AWS CLI](../../README.md#discovering-service-catalog-ids-with-aws-cli). +- Additional product template parameters can be passed through parameters. +- Empty values are omitted before provisioning parameters are sent to Service Catalog. +- This module provisions through CloudFormation under AWS Service Catalog. +- For networking, explicit `subnet_ids`/`vpc_id` take precedence over lookup inputs. +- When explicit IDs are not provided, VPC/subnet lookups are performed using `vpc_name`/`subnets_name`. diff --git a/modules/rds/data.tf b/modules/rds/data.tf new file mode 100644 index 0000000..60fd837 --- /dev/null +++ b/modules/rds/data.tf @@ -0,0 +1,26 @@ +data "aws_vpc" "vpc" { + count = var.vpc_id == "" && var.vpc_name != "" ? 1 : 0 + filter { + name = "tag:Name" + values = [var.vpc_name] + } +} + +# Get subnets by name pattern when explicit subnet IDs are not provided +data "aws_subnets" "subnets" { + count = length(var.subnet_ids) == 0 && var.subnets_name != "" && (var.vpc_id != "" || var.vpc_name != "") ? 1 : 0 + filter { + name = "tag:Name" + values = [var.subnets_name] + } + filter { + name = "vpc-id" + values = [var.vpc_id != "" ? var.vpc_id : data.aws_vpc.vpc[0].id] + } +} + +# Get explicit subnet details for VPC derivation fallback +data "aws_subnet" "explicit_subnets" { + for_each = toset(var.subnet_ids) + id = each.key +} diff --git a/modules/rds/locals.tf b/modules/rds/locals.tf new file mode 100644 index 0000000..6c542e1 --- /dev/null +++ b/modules/rds/locals.tf @@ -0,0 +1,38 @@ +locals { + # Resolve subnet IDs from explicit values first, then discovery + resolved_subnet_ids = length(var.subnet_ids) > 0 ? var.subnet_ids : ( + var.subnets_name != "" && length(data.aws_subnets.subnets) > 0 ? sort(data.aws_subnets.subnets[0].ids) : [] + ) + + # Resolve VPC from explicit value first, then explicit subnet-derived value, then VPC name lookup + resolved_vpc_id = var.vpc_id != "" ? var.vpc_id : ( + length(var.subnet_ids) > 0 && length(local.resolved_subnet_ids) > 0 ? data.aws_subnet.explicit_subnets[local.resolved_subnet_ids[0]].vpc_id : ( + var.vpc_name != "" ? data.aws_vpc.vpc[0].id : null + ) + ) + + # RDS-specific parameters to pass to the product module + rds_parameters = merge( + { + DBInstanceClass = var.db_instance_class + SubnetIds = length(local.resolved_subnet_ids) > 0 ? join(",", local.resolved_subnet_ids) : "" + VpcId = local.resolved_vpc_id != null ? local.resolved_vpc_id : "" + Engine = var.engine + EngineVersion = var.engine_version + MultiAZ = tostring(var.multi_az) + AllocatedStorage = tostring(var.allocated_storage) + }, + var.parameters # Allow user to override any parameter + ) + + base_tags = { + "boc:tf_module_name" = local.module_name + "boc:tf_module_version" = local.module_version + "boc:created_by" = "terraform" + } + + tags = merge( + local.base_tags, + var.tags + ) +} diff --git a/modules/rds/main.tf b/modules/rds/main.tf new file mode 100644 index 0000000..eeace6b --- /dev/null +++ b/modules/rds/main.tf @@ -0,0 +1,34 @@ +# RDS Product Module +# +# Provisions an RDS instance via Service Catalog +# This is a thin wrapper around the product module with RDS-specific parameters + +module "rds" { + source = "../product" + + # Service Catalog configuration + portfolio_id = var.portfolio_id + product_id = var.product_id + provisioning_artifact_id = var.provisioning_artifact_id + path_id = var.path_id + accept_language = var.accept_language + timeout = var.timeout + ignore_errors = var.ignore_errors + notification_arns = var.notification_arns + retain_physical_resources = var.retain_physical_resources + stack_set_provisioning_preferences = var.stack_set_provisioning_preferences + + # Common parameters + provisioned_product_name = var.provisioned_product_name + project_name = var.project_name + creator = var.creator + contact_email = var.contact_email + inc_poc_email = var.inc_poc_email + fisma_id = var.fisma_id + + # RDS-specific parameters + product_parameters = local.rds_parameters + + # Tags + tags = local.tags +} diff --git a/modules/rds/module_name.tf b/modules/rds/module_name.tf new file mode 100644 index 0000000..cab32f2 --- /dev/null +++ b/modules/rds/module_name.tf @@ -0,0 +1,4 @@ +locals { + module_name = "aws-servicecatalog/rds" + module_version = "1.0.0" +} diff --git a/modules/rds/outputs.tf b/modules/rds/outputs.tf new file mode 100644 index 0000000..8cf933a --- /dev/null +++ b/modules/rds/outputs.tf @@ -0,0 +1,70 @@ +output "provisioned_product_id" { + description = "The ID of the provisioned product" + value = module.rds.provisioned_product_id +} + +output "provisioned_product_name" { + description = "The name of the provisioned product" + value = module.rds.provisioned_product_name +} + +output "provisioned_product_arn" { + description = "The ARN of the provisioned product" + value = module.rds.provisioned_product_arn +} + +output "provisioned_product_type" { + description = "The type of the provisioned product" + value = module.rds.provisioned_product_type +} + +output "provisioned_product_status" { + description = "The status of the provisioned product" + value = module.rds.provisioned_product_status +} + +output "provisioned_product_status_message" { + description = "The status message for the provisioned product" + value = module.rds.provisioned_product_status_message +} + +output "launch_role_arn" { + description = "The ARN of the launch role" + value = module.rds.launch_role_arn +} + +output "portfolio_id" { + description = "The ID of the portfolio used" + value = module.rds.portfolio_id +} + +output "product_id" { + description = "The ID of the product used" + value = module.rds.product_id +} + +output "provisioning_artifact_id" { + description = "The ID of the provisioning artifact used" + value = module.rds.provisioning_artifact_id +} + +output "stack_outputs" { + description = "CloudFormation stack outputs from the provisioned product" + value = module.rds.stack_outputs + sensitive = true +} + +output "db_instance_class" { + description = "Configured RDS instance class parameter" + value = var.db_instance_class +} + +output "engine" { + description = "Configured database engine parameter" + value = var.engine +} + +output "engine_version" { + description = "Configured database engine version parameter" + value = var.engine_version +} diff --git a/modules/rds/variables.common.tf b/modules/rds/variables.common.tf new file mode 100644 index 0000000..1a9a1a0 --- /dev/null +++ b/modules/rds/variables.common.tf @@ -0,0 +1,60 @@ +#--- +# Common variables +#--- +variable "account_id" { + description = "AWS Account ID (default will pull from current user)" + type = string + default = "" +} + +variable "account_alias" { + description = "AWS Account Alias" + type = string + default = "" +} + +variable "parameters" { + description = "Additional parameters to pass to the Service Catalog product. Map of parameter names to values." + type = map(string) + default = {} +} + +variable "provisioned_product_name" { + description = "Name of the provisioned product" + type = string + + validation { + condition = length(var.provisioned_product_name) > 0 && length(var.provisioned_product_name) <= 128 + error_message = "provisioned_product_name must be between 1 and 128 characters" + } +} + +variable "project_name" { + description = "Project name (ProjectName parameter). Make sure to select the project designated for your account. The build will fail if an incorrect project is selected. The list of values can be found in the ProjectName parameter of the product's provisioning artifact." + type = string + default = "" +} + +variable "creator" { + description = "Creator's JBID (Creator parameter)" + type = string + default = "" +} + +variable "contact_email" { + description = "Provisioning user's email (ContactEmail parameter)" + type = string + default = "" +} + +variable "inc_poc_email" { + description = "Incident POC email (IncPocEmail parameter)" + type = string + default = "" +} + +variable "fisma_id" { + description = "FISMA ID" + type = string + default = "" +} diff --git a/modules/rds/variables.product.tf b/modules/rds/variables.product.tf new file mode 100644 index 0000000..d8820dc --- /dev/null +++ b/modules/rds/variables.product.tf @@ -0,0 +1,55 @@ +# RDS-specific product parameters + +variable "db_instance_class" { + description = "RDS instance class" + type = string + default = "db.t3.medium" +} + +variable "subnet_ids" { + description = "Explicit subnet IDs for the RDS instance. If set, subnet lookup by name is skipped" + type = list(string) + default = [] +} + +variable "vpc_id" { + description = "Explicit VPC ID for the RDS instance. If set, VPC lookup by name is skipped" + type = string + default = "" +} + +variable "vpc_name" { + description = "Name tag of the VPC to deploy into when vpc_id is not set" + type = string + default = "" +} + +variable "subnets_name" { + description = "Name tag pattern of subnets to deploy into when subnet_ids are not set" + type = string + default = "" +} + +variable "engine" { + description = "RDS database engine" + type = string + default = "postgres" +} + +variable "engine_version" { + description = "RDS database engine version" + type = string + default = "15.3" +} + +variable "multi_az" { + description = "Whether to deploy RDS in Multi-AZ mode" + type = bool + default = false +} + +variable "allocated_storage" { + description = "Allocated storage in GiB" + type = number + default = 20 +} diff --git a/modules/rds/variables.servicecatalog.tf b/modules/rds/variables.servicecatalog.tf new file mode 100644 index 0000000..29582ab --- /dev/null +++ b/modules/rds/variables.servicecatalog.tf @@ -0,0 +1,71 @@ +variable "portfolio_id" { + description = "Portfolio ID for the RDS product" + type = string + default = null +} + +variable "product_id" { + description = "Product ID for the RDS product" + type = string + default = null +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version). If not provided, the latest active artifact is used" + type = string + default = null +} + +variable "path_id" { + description = "Launch path identifier of the product" + type = string + default = null +} + +variable "timeout" { + description = "Timeout for provisioned product operations (create/update/delete)" + type = string + default = "5m" +} + +variable "accept_language" { + description = "Language code for Service Catalog API calls" + type = string + default = "en" + + validation { + condition = contains(["en", "jp", "zh"], var.accept_language) + error_message = "accept_language must be one of: en, jp, zh" + } +} + +variable "ignore_errors" { + description = "Whether to ignore errors during provisioning" + type = bool + default = false +} + +variable "notification_arns" { + description = "List of SNS topic ARNs to send provisioning notifications to" + type = list(string) + default = [] +} + +variable "retain_physical_resources" { + description = "Whether to retain physical resources when deleting the provisioned product" + type = bool + default = false +} + +variable "stack_set_provisioning_preferences" { + description = "StackSet provisioning preferences to use when provisioning the product" + type = object({ + accounts = optional(list(string)) + failure_tolerance_count = optional(number) + failure_tolerance_percentage = optional(number) + max_concurrency_count = optional(number) + max_concurrency_percentage = optional(number) + regions = optional(list(string)) + }) + default = null +} diff --git a/modules/rds/variables.tags.tf b/modules/rds/variables.tags.tf new file mode 100644 index 0000000..6ebf2ec --- /dev/null +++ b/modules/rds/variables.tags.tf @@ -0,0 +1,5 @@ +variable "tags" { + description = "Additional tags to apply to resources" + type = map(string) + default = {} +} diff --git a/modules/rds/versions.tf b/modules/rds/versions.tf new file mode 100644 index 0000000..773514a --- /dev/null +++ b/modules/rds/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + } +} diff --git a/modules/s3/README.md b/modules/s3/README.md index 97dd961..3dbab66 100644 --- a/modules/s3/README.md +++ b/modules/s3/README.md @@ -26,8 +26,8 @@ module "s3_bucket" { source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/s3?ref=v1.0.0" # Service Catalog Configuration - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-s3bucket123xyz" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" # Bucket Configuration provisioned_product_name = "my-data-bucket" @@ -59,8 +59,8 @@ module "s3_bucket" { module "s3_bucket_title26" { source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/s3?ref=v1.0.0" - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-s3bucket123xyz" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" provisioned_product_name = "tax-data-bucket" bucket_name = "tax-data-bucket" project_name = "csvd_taxsys_prod-229685449397" @@ -93,8 +93,8 @@ module "s3_bucket_title26" { module "s3_bucket_advanced" { source = "git::https://your-repo/terraform-modules/aws-servicecatalog//modules/s3?ref=v1.0.0" - portfolio_id = "port-pgj3zvoqca7ya" - product_id = "prod-s3bucket123xyz" + portfolio_id = "port-xxxxxxxxxxxx" + product_id = "prod-xxxxxxxxxxxx" provisioned_product_name = "archive-bucket" bucket_name = "archive-bucket" project_name = "csvd_archive_prod-229685449397" @@ -151,10 +151,10 @@ No providers are used directly - all AWS resources are provisioned by the underl | Name | Description | Type | Default | |------|-------------|------|---------| -| `portfolio_id` | Portfolio ID containing the S3 product | `string` | `"port-pgj3zvoqca7ya"` | -| `product_id` | Product ID for the S3 product | `string` | `"prod-s3bucket123xyz"` | +| `portfolio_id` | Portfolio ID containing the S3 product | `string` | `null` | +| `product_id` | Product ID for the S3 product | `string` | `null` | | `path_id` | Path identifier (version). Uses latest if not provided | `string` | `null` | -| `timeout` | Timeout for provisioning operations | `string` | `"15m"` | +| `timeout` | Timeout for provisioning operations | `string` | `"5m"` | | `accept_language` | Language code for API calls | `string` | `"en"` | | `ignore_errors` | Ignore errors during provisioning | `bool` | `false` | | `notification_arns` | SNS topic ARNs for notifications | `list(string)` | `[]` | @@ -216,7 +216,7 @@ No providers are used directly - all AWS resources are provisioned by the underl | `portfolio_id` | The ID of the portfolio used | | `product_id` | The ID of the product used | | `provisioning_artifact_id` | The ID of the provisioning artifact used | -| `bucket_name` | The actual bucket name (with account ID suffix) | +| `bucket_name` | The configured bucket name input parameter | ## Module Architecture @@ -258,6 +258,9 @@ Additional parameters can be passed via the `parameters` variable, which will ov ## Important Notes +Provide required Service Catalog identifiers: `portfolio_id` and `product_id`. +To discover IDs with AWS CLI, see [Discovering Service Catalog IDs With AWS CLI](../../README.md#discovering-service-catalog-ids-with-aws-cli). + ### Bucket Naming - The bucket name must be 3-50 characters long - Can only contain lowercase letters, numbers, and hyphens diff --git a/modules/s3/main.tf b/modules/s3/main.tf index 7116e06..0e6e2a7 100644 --- a/modules/s3/main.tf +++ b/modules/s3/main.tf @@ -9,6 +9,7 @@ module "s3" { # Service Catalog configuration portfolio_id = var.portfolio_id product_id = var.product_id + provisioning_artifact_id = var.provisioning_artifact_id path_id = var.path_id accept_language = var.accept_language timeout = var.timeout diff --git a/modules/s3/module_name.tf b/modules/s3/module_name.tf index 5e2c32c..e35560b 100644 --- a/modules/s3/module_name.tf +++ b/modules/s3/module_name.tf @@ -1,4 +1,4 @@ locals { module_name = "aws-servicecatalog/s3" - module_version = "0.0.0" + module_version = "1.0.0" } diff --git a/modules/s3/outputs.tf b/modules/s3/outputs.tf index da6e6dc..cc9b2ca 100644 --- a/modules/s3/outputs.tf +++ b/modules/s3/outputs.tf @@ -55,6 +55,6 @@ output "stack_outputs" { } output "bucket_name" { - description = "The actual bucket name (with account ID suffix)" + description = "The configured bucket name parameter" value = "${var.bucket_name}" } \ No newline at end of file diff --git a/modules/s3/variables.servicecatalog.tf b/modules/s3/variables.servicecatalog.tf index b33f532..906bfdb 100644 --- a/modules/s3/variables.servicecatalog.tf +++ b/modules/s3/variables.servicecatalog.tf @@ -1,17 +1,23 @@ variable "portfolio_id" { description = "Portfolio ID for the S3 product" type = string - default = "port-7qwegwxptuyl2" + default = null } variable "product_id" { description = "Product ID for the S3 product" type = string - default = "prod-tssqrgscxu7da" + default = null +} + +variable "provisioning_artifact_id" { + description = "Provisioning artifact ID (product version). If not provided, the latest active artifact is used" + type = string + default = null } variable "path_id" { - description = "Path identifier of the product. If not provided, will use the latest active artifact" + description = "Launch path identifier of the product" type = string default = null } diff --git a/version.tf b/version.tf index a0cd862..fa2705b 100644 --- a/version.tf +++ b/version.tf @@ -1,3 +1,3 @@ locals { - _module_version = "0.0.0" + _module_version = "1.0.0" }