diff --git a/.gitignore b/.gitignore
index 6dfbba9..0fb2583 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,7 +47,6 @@ terraform.rc
*.log
# Ignore any IDE files
-.vscode/
.idea/
*.swp
*.swo
@@ -55,3 +54,6 @@ terraform.rc
# Ignore any OS generated files
.DS_Store
Thumbs.db
+
+# Ignore Lambda deployment packages
+lambda_function.zip
diff --git a/.vscode/cspell.json b/.vscode/cspell.json
new file mode 100644
index 0000000..3834339
--- /dev/null
+++ b/.vscode/cspell.json
@@ -0,0 +1,3 @@
+{
+ "words": []
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..4291bb0
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,19 @@
+{
+ "recommendations": [
+ "davidanson.vscode-markdownlint",
+ "eamodio.gitlens",
+ "esbenp.prettier-vscode",
+ "foxundermoon.shell-format",
+ "Gruntfuggly.todo-tree",
+ "hashicorp.terraform",
+ "mhutchie.git-graph",
+ "ms-python.autopep8",
+ "ms-python.debugpy",
+ "ms-python.python",
+ "ms-python.black-formatter",
+ "ms-python.flake8",
+ "streetsidesoftware.code-spell-checker",
+ "usernamehw.errorlens",
+ "vscode-icons-team.vscode-icons"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..6f377d1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,208 @@
+{
+ "files.associations": {
+ "*.dockerfile": "dockerfile",
+ "*.sh.tpl": "shellscript",
+ "docker-compose*.yml": "yaml",
+ "Dockerfile*": "dockerfile",
+ "*.py.tpl": "python",
+ "*.yaml.tpl": "yaml",
+ "*.yml.tpl": "yaml",
+ "*.tf": "terraform",
+ "*.tfvars": "terraform"
+ },
+ "files.exclude": {
+ "**/.git": true,
+ "**/.svn": true,
+ "**/.hg": true,
+ "**/CVS": true,
+ "**/.DS_Store": true,
+ "__debug_bin": true,
+ "vendor/": true,
+ "go.sum": true,
+ "**/__pycache__": true,
+ "**/*.pyc": true,
+ "**/.pytest_cache": true,
+ "**/.mypy_cache": true,
+ "**/node_modules": true,
+ "**/.terraform": true,
+ "**/.terragrunt-cache": true,
+ "**/Thumbs.db": true,
+ "**/.ruff_cache": true,
+ "**/.coverage": true,
+ "**/htmlcov": true,
+ "**/*.tfstate": true,
+ "**/*.tfstate.*": true
+ },
+ "files.trimTrailingWhitespace": true,
+ "files.trimFinalNewlines": true,
+ "files.insertFinalNewline": true,
+ "files.eol": "\n",
+ "remote.extensionKind": {
+ "ms-azuretools.vscode-docker": "ui",
+ "ms-vscode-remote.remote-containers": "ui"
+ },
+ "editor.formatOnSave": true,
+ "editor.formatOnPaste": true,
+ "editor.rulers": [79],
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 79,
+ "editor.tabSize": 4,
+ "editor.insertSpaces": true,
+ "editor.detectIndentation": false,
+ "editor.trimAutoWhitespace": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit",
+ "source.organizeImports": "explicit",
+ "source.fixAll.ruff": "explicit"
+ },
+ "prettier.requireConfig": true,
+ "workbench.iconTheme": "vscode-icons",
+ "workbench.colorTheme": "Visual Studio Dark",
+ "[css]": {
+ "editor.defaultFormatter": "vscode.css-language-features",
+ "editor.foldingStrategy": "indentation"
+ },
+ "[dockerfile]": {
+ "editor.defaultFormatter": "ms-azuretools.vscode-docker"
+ },
+ "[html]": {
+ "editor.defaultFormatter": "vscode.html-language-features"
+ },
+ "[json]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[jsonc]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[shellscript]": {
+ "editor.defaultFormatter": "foxundermoon.shell-format"
+ },
+ "[terraform]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "hashicorp.terraform",
+ "editor.insertSpaces": true,
+ "editor.tabSize": 2
+ },
+ "[yaml]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.insertSpaces": true,
+ "editor.tabSize": 2
+ },
+ "[yml]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.insertSpaces": true,
+ "editor.tabSize": 2
+ },
+ "[python]": {
+ "editor.formatOnSave": true,
+ "editor.formatOnPaste": true,
+ "editor.defaultFormatter": "charliermarsh.ruff",
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit",
+ "source.organizeImports": "explicit",
+ "source.fixAll.ruff": "explicit"
+ },
+ "editor.rulers": [79],
+ "editor.wordWrapColumn": 79,
+ "editor.tabSize": 4,
+ "editor.insertSpaces": true
+ },
+ "python.formatting.provider": "none",
+ "python.formatting.blackArgs": [
+ "--line-length=79",
+ "--target-version=py39",
+ "--skip-string-normalization",
+ "--config=lambdas/pyproject.toml"
+ ],
+ "python.linting.enabled": true,
+ "python.linting.lintOnSave": true,
+ "python.linting.flake8Enabled": true,
+ "python.linting.flake8Args": [
+ "--max-line-length=79",
+ "--extend-ignore=E203,W503,E501",
+ "--max-complexity=10"
+ ],
+ "ruff.enable": true,
+ "ruff.fixAll": true,
+ "ruff.organizeImports": true,
+ "ruff.lint.enable": true,
+ "ruff.format.enable": true,
+ "ruff.codeAction.fixViolation": {
+ "enable": true
+ },
+ "ruff.codeAction.disableRuleComment": {
+ "enable": true
+ },
+ "python.linting.mypyEnabled": true,
+ "python.linting.mypyArgs": [
+ "--config-file=lambdas/pyproject.toml"
+ ],
+ "python.linting.pylintEnabled": false,
+ "isort.args": [
+ "--settings-path=lambdas/pyproject.toml"
+ ],
+ "isort.check": true,
+ "python.analysis.typeCheckingMode": "off",
+ "python.analysis.autoImportCompletions": true,
+ "python.analysis.autoSearchPaths": true,
+ "python.analysis.diagnosticMode": "workspace",
+ "python.analysis.completeFunctionParens": true,
+ "python.analysis.autoFormatStrings": true,
+ "python.testing.pytestEnabled": true,
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestArgs": [
+ "lambdas"
+ ],
+ "python.testing.autoTestDiscoverOnSaveEnabled": true,
+ "python.defaultInterpreterPath": "/usr/local/bin/python",
+ "python.terminal.activateEnvironment": false,
+ "autoDocstring.docstringFormat": "google",
+ "autoDocstring.startOnNewLine": false,
+ "autoDocstring.includeExtendedSummary": true,
+ "git.autofetch": true,
+ "git.confirmSync": false,
+ "git.enableSmartCommit": true,
+ "terminal.integrated.defaultProfile.linux": "bash",
+ "terminal.integrated.copyOnSelection": true,
+ "search.exclude": {
+ "**/node_modules": true,
+ "**/bower_components": true,
+ "**/*.code-search": true,
+ "**/__pycache__": true,
+ "**/.pytest_cache": true,
+ "**/.mypy_cache": true,
+ "**/.ruff_cache": true
+ },
+ "markdown.extension.toc.levels": "2..6",
+ "markdown.extension.print.absoluteImgPath": false,
+ "yaml.format.enable": true,
+ "yaml.format.singleQuote": false,
+ "yaml.format.bracketSpacing": true,
+ "python.analysis.indexing": true,
+ "python.analysis.packageIndexDepths": [
+ {
+ "name": "boto3",
+ "depth": 2
+ },
+ {
+ "name": "botocore",
+ "depth": 2
+ }
+ ],
+ "files.watcherExclude": {
+ "**/.git/objects/**": true,
+ "**/.git/subtree-cache/**": true,
+ "**/node_modules/*/**": true,
+ "**/__pycache__/**": true,
+ "**/.pytest_cache/**": true,
+ "**/.mypy_cache/**": true,
+ "**/.terraform/**": true,
+ "**/.terragrunt-cache/**": true
+ },
+ "terraform.format.enable": true,
+ "terraform.lint.enable": true
+}
diff --git a/README.md b/README.md
index c257284..701371d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# terraform-aws-cloudmap
-Terraform module to manage Amazon Cloud Map namespaces and services for DNS-based discovery.
+Terraform module to manage Amazon Cloud Map namespaces and services for DNS-based discovery, including support for Lambda Function URL registration.
## Requirements
@@ -27,6 +27,7 @@ No modules.
| [aws_iam_role.ecs_service_discovery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.ecs_service_discovery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_service_discovery_http_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_http_namespace) | resource |
+| [aws_service_discovery_instance.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_instance) | resource |
| [aws_service_discovery_private_dns_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_private_dns_namespace) | resource |
| [aws_service_discovery_public_dns_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_public_dns_namespace) | resource |
| [aws_service_discovery_service.services](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_service) | resource |
@@ -44,6 +45,11 @@ No modules.
| [enable\_dns\_config](#input\_enable\_dns\_config) | Enable DNS configuration for the service. Set to false for HTTP namespaces or when using existing HTTP namespaces. | `bool` | `true` | no |
| [enable\_health\_checks](#input\_enable\_health\_checks) | Enable health checks for the service. Set to false when using private IPs or unsupported instance types. | `bool` | `true` | no |
| [existing\_namespace\_id](#input\_existing\_namespace\_id) | ID of an existing namespace to use | `string` | `null` | no |
+| [lambda\_attributes](#input\_lambda\_attributes) | Additional attributes for the Lambda instance in CloudMap | `map(string)` | `{}` | no |
+| [lambda\_instance\_id](#input\_lambda\_instance\_id) | Unique identifier for the Lambda instance in CloudMap | `string` | `"lambda-function"` | no |
+| [lambda\_service\_name](#input\_lambda\_service\_name) | Name of the CloudMap service for Lambda registration. If not specified, uses the first service name from var.services | `string` | `null` | no |
+| [lambda\_url](#input\_lambda\_url) | Lambda Function URL or API Gateway endpoint to register in CloudMap | `string` | `null` | no |
+| [enable\_lambda\_registration](#input\_enable\_lambda\_registration) | Enable registration of Lambda Function URL in CloudMap service discovery | `bool` | `false` | no |
| [namespace\_description](#input\_namespace\_description) | Description of the CloudMap namespace | `string` | `null` | no |
| [namespace\_name](#input\_namespace\_name) | Name of the CloudMap namespace | `string` | `null` | no |
| [routing\_policy](#input\_routing\_policy) | Routing policy for the service | `string` | `"MULTIVALUE"` | no |
@@ -58,9 +64,73 @@ No modules.
| [ecs\_service\_discovery\_role\_arn](#output\_ecs\_service\_discovery\_role\_arn) | ARN of the ECS service discovery IAM role |
| [ecs\_service\_discovery\_role\_name](#output\_ecs\_service\_discovery\_role\_name) | Name of the ECS service discovery IAM role |
| [health\_check\_debug](#output\_health\_check\_debug) | Debug information for health check configuration - use for troubleshooting |
+| [lambda\_discovery\_url](#output\_lambda\_discovery\_url) | CloudMap discovery URL for the Lambda function |
+| [lambda\_instance\_id](#output\_lambda\_instance\_id) | ID of the registered Lambda instance in CloudMap |
+| [lambda\_registration\_debug](#output\_lambda\_registration\_debug) | Debug information for Lambda registration - use for troubleshooting |
+| [lambda\_service\_id](#output\_lambda\_service\_id) | ID of the CloudMap service where Lambda is registered |
| [namespace\_arn](#output\_namespace\_arn) | ARN of the created namespace |
| [namespace\_id](#output\_namespace\_id) | ID of the created namespace |
| [namespace\_name](#output\_namespace\_name) | Name of the created namespace |
| [service\_arns](#output\_service\_arns) | Map of service names to their ARNs for ECS integration |
| [services](#output\_services) | Map of created services with their details |
+
+## Features
+
+- **Multiple Namespace Types**: Support for HTTP, Private DNS, and Public DNS namespaces
+- **Service Discovery**: Create and manage CloudMap services with configurable DNS settings
+- **Health Checks**: Configurable health checks for services (standard and custom)
+- **Lambda Function URL Support**: Register Lambda Function URLs in CloudMap for service discovery
+- **ECS Integration**: IAM roles for ECS service discovery
+- **Flexible Configuration**: Support for existing namespaces and custom attributes
+
+## Lambda Function URL Registration
+
+This module supports registering Lambda Function URLs in CloudMap for service discovery within VPCs. This allows services to resolve Lambda functions by DNS without hardcoding URLs.
+
+### Example Usage
+
+```hcl
+module "cloudmap" {
+ source = "path/to/module"
+
+ # Create private DNS namespace
+ create_private_dns_namespace = true
+ namespace_name = "api.internal"
+ vpc_id = data.aws_vpc.default.id
+
+ # Define service with CNAME record type
+ services = {
+ "api-service" = {
+ name = "api-service"
+ dns_record_type = "CNAME" # Required for Lambda Function URL
+ routing_policy = "WEIGHTED"
+ health_check_custom_config = true
+ }
+ }
+
+ # Enable Lambda registration
+ enable_lambda_registration = true
+ lambda_instance_id = "api-lambda-01"
+ lambda_url = aws_lambda_function_url.api.function_url
+ lambda_service_name = "api-service"
+ lambda_attributes = {
+ "environment" = "production"
+ "version" = "v1.0.0"
+ "function_name" = aws_lambda_function.api.function_name
+ }
+}
+```
+
+### Benefits
+
+- **Consistent DNS Resolution**: Services can resolve Lambda functions using standard DNS
+- **VPC Integration**: Lambda functions appear as local services within VPC
+- **Health Monitoring**: CloudMap can monitor Lambda function health
+- **Automatic Failover**: Support for multiple Lambda instances with load balancing
+
+## Examples
+
+- [Basic HTTP Namespace](examples/basic-http-namespace/): Simple HTTP namespace with EC2 instance
+- [Custom Registry](examples/custom-registry/): Mixed service types with Lambda integration
+- [Lambda Function URL](examples/lambda/): Complete Lambda Function URL registration example
diff --git a/examples/lambda/README.md b/examples/lambda/README.md
new file mode 100644
index 0000000..b26eb02
--- /dev/null
+++ b/examples/lambda/README.md
@@ -0,0 +1,469 @@
+# AWS CloudMap Lambda Service Discovery
+
+This example demonstrates **AWS CloudMap service discovery with Lambda functions** - showing how to discover and trigger Lambda functions dynamically without hardcoding URLs.
+
+## ๐ฏ **Use Case**
+
+**Dynamic Lambda Function Discovery** - Enable microservices to discover and trigger Lambda functions programmatically:
+
+- โ
**Service Discovery API**: Discover Lambda functions via CloudMap API
+- โ
**Dynamic URL Resolution**: Extract Lambda Function URLs from CloudMap attributes
+- โ
**Direct Lambda Triggering**: Call Lambda functions using discovered URLs
+- โ
**Service Metadata**: Access rich attributes (version, environment, function name, etc.)
+- โ
**Microservices Pattern**: Perfect for service mesh architectures
+
+## ๐๏ธ **Architecture**
+
+```plaintext
+โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
+โ Microservice โ โ CloudMap โ โ Lambda โ
+โ (EC2/ECS/etc) โ โ Service โ โ Function URL โ
+โ โ โ Discovery โ โ โ
+โ ๐ API calls โโโโโบโ Registry โโโโโบโ ๐ Registered โ
+โ โ โ โ โ โ
+โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
+
+Flow:
+1. Lambda Function URL โ Registered in CloudMap with metadata
+2. Microservice โ Calls CloudMap API to discover Lambda services
+3. Microservice โ Extracts Lambda URL and triggers function directly
+```
+
+## ๐ **Prerequisites**
+
+- AWS CLI configured
+- Terraform >= 1.0
+- **No SSH keys required** - Uses SSM Session Manager for secure access
+
+## ๐ **Quick Start**
+
+### 1. **Deploy Infrastructure**
+
+```bash
+# Navigate to the example directory
+cd examples/lambda
+
+# Initialize Terraform
+terraform init
+
+# Plan the deployment
+terraform plan
+
+# Apply the configuration
+terraform apply
+```
+
+**Note**: The Lambda function package is automatically created by Terraform using the `archive_file` data source - no manual zipping required!
+
+### 2. **Test Service Discovery**
+
+```bash
+# Connect via SSM Session (recommended - no SSH keys needed)
+$(terraform output -raw ssm_session_command)
+
+# Or connect via EC2 Instance Connect
+$(terraform output -raw instance_connect_command)
+
+# Run the test script
+./test-discovery.sh
+```
+
+## ๐ง **Configuration**
+
+### **Infrastructure Components**
+
+This example creates:
+
+- **VPC with Public/Private Subnets**: Using the `cloudbuildlab/vpc/aws` module
+- **Lambda Function with Function URL**: Serverless API endpoint (automatically packaged using `archive_file`)
+- **CloudMap Private DNS Namespace**: For service discovery within VPC
+- **Jumphost Instance**: Using `tfstack/jumphost/aws` module with SSM enabled
+- **Security Groups**: For Lambda and jumphost instance
+
+### **Lambda Function Packaging**
+
+The Lambda function is automatically packaged using Terraform's `archive_file` data source:
+
+```hcl
+data "archive_file" "lambda_zip" {
+ type = "zip"
+ source_file = "index.js"
+ output_path = "lambda_function.zip"
+}
+```
+
+This eliminates the need for manual zipping and ensures consistent deployments.
+
+**Note**: The generated `lambda_function.zip` file is automatically ignored by `.gitignore` to keep the repository clean.
+
+### **Jumphost Features**
+
+The jumphost module provides:
+
+- **SSM Integration**: Secure access via AWS Systems Manager (no SSH keys required)
+- **EC2 Instance Connect**: Alternative SSH access method
+- **CloudWatch Agent**: System monitoring and logging
+- **Automatic Security**: Configurable security groups with dynamic IP allowlisting
+- **Multi-OS Support**: Amazon Linux 2, Ubuntu, RHEL
+- **IAM Integration**: Automatic SSM permissions
+
+### **Lambda Registration Variables**
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `enable_lambda_registration` | Enable Lambda registration in CloudMap | `false` |
+| `lambda_instance_id` | Unique identifier for Lambda instance | `"lambda-function"` |
+| `lambda_url` | Lambda Function URL to register | `null` |
+| `lambda_service_name` | CloudMap service name for Lambda | First service in `services` |
+| `lambda_attributes` | Additional attributes for Lambda instance | `{}` |
+
+### **Example Configuration**
+
+```hcl
+# Data source for public IP
+data "http" "my_public_ip" {
+ url = "https://checkip.amazonaws.com/"
+}
+
+# Random suffix for unique resource naming
+resource "random_string" "suffix" {
+ length = 6
+ special = false
+ upper = false
+}
+
+# Local values for consistent naming and configuration
+locals {
+ azs = ["ap-southeast-2a", "ap-southeast-2b", "ap-southeast-2c"]
+ name = "lambda-cloudmap"
+ base_name = local.suffix != "" ? "${local.name}-${local.suffix}" : local.name
+ suffix = random_string.suffix.result
+ private_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
+ public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
+ region = "ap-southeast-2"
+ vpc_cidr = "10.0.0.0/16"
+ tags = {
+ Environment = "dev"
+ Project = "example"
+ }
+}
+
+# VPC Module
+module "aws_vpc" {
+ source = "cloudbuildlab/vpc/aws"
+
+ vpc_name = local.base_name
+ vpc_cidr = local.vpc_cidr
+ availability_zones = local.azs
+
+ public_subnet_cidrs = local.public_subnets
+ private_subnet_cidrs = local.private_subnets
+
+ create_igw = true
+ nat_gateway_type = "single"
+}
+
+# Jumphost Module (Amazon Linux 2)
+module "jumphost-ssm-amazonlinux2" {
+ source = "tfstack/jumphost/aws"
+
+ name = "${local.base_name}-ssm-amazonlinux2"
+ ami_type = "amazonlinux2"
+ subnet_id = module.aws_vpc.private_subnet_ids[0]
+ vpc_id = module.aws_vpc.vpc_id
+
+ create_security_group = true
+ allowed_cidr_blocks = ["${data.http.my_public_ip.response_body}/32"]
+ assign_eip = false
+
+ user_data_extra = <<-EOT
+ yum install -y mtr nc curl dig jq awscli
+
+ # Create test script
+ cat > /home/ec2-user/test-discovery.sh << 'SCRIPT'
+ #!/bin/bash
+ echo "Testing CloudMap service discovery..."
+
+ # Test DNS resolution
+ echo "=== DNS Resolution Test ==="
+ dig A api-lambda-01.api-service.api.internal
+
+ # Test Lambda function call via CloudMap attributes
+ echo "=== Lambda Function Test ==="
+ LAMBDA_URL=$(aws servicediscovery discover-instances \
+ --namespace-name api.internal \
+ --service-name api-service \
+ --region ap-southeast-2 \
+ --query 'Instances[0].Attributes.lambda_url' \
+ --output text)
+
+ if [ "$LAMBDA_URL" != "None" ] && [ ! -z "$LAMBDA_URL" ]; then
+ echo "Retrieved Lambda URL from CloudMap: $LAMBDA_URL"
+ curl -s "$LAMBDA_URL" | jq .
+ else
+ echo "Failed to retrieve Lambda URL from CloudMap"
+ fi
+
+ # Test service discovery via AWS CLI
+ echo "=== AWS CloudMap Discovery Test ==="
+ aws servicediscovery discover-instances \
+ --namespace-name api.internal \
+ --service-name api-service \
+ --region ap-southeast-2
+ SCRIPT
+
+ chmod +x /home/ec2-user/test-discovery.sh
+ chown ec2-user:ec2-user /home/ec2-user/test-discovery.sh
+ EOT
+}
+
+# CloudMap Module
+module "cloudmap" {
+ source = "../../"
+
+ # Create private DNS namespace
+ create_private_dns_namespace = true
+ namespace_name = "api.internal"
+ vpc_id = module.aws_vpc.vpc_id
+
+ # Define service with CNAME record type
+ services = {
+ "api-service" = {
+ name = "api-service"
+ dns_record_type = "CNAME" # Required for Lambda Function URL
+ routing_policy = "WEIGHTED"
+ health_check_custom_config = true
+ }
+ }
+
+ # Enable Lambda registration
+ enable_lambda_registration = true
+ lambda_instance_id = "api-lambda-01"
+ lambda_url = aws_lambda_function_url.api.function_url
+ lambda_service_name = "api-service"
+ lambda_attributes = {
+ "environment" = "production"
+ "version" = "v1.0.0"
+ "function_name" = aws_lambda_function.api.function_name
+ }
+}
+```
+
+## ๐งช **Testing**
+
+### **SSM Session Access (Recommended - No SSH Keys Required)**
+
+```bash
+# Connect via SSM Session
+$(terraform output -raw ssm_session_command)
+
+# Run the test script
+./test-discovery.sh
+```
+
+### **EC2 Instance Connect Access (Alternative)**
+
+```bash
+# Connect via EC2 Instance Connect
+$(terraform output -raw instance_connect_command)
+
+# Run the test script
+./test-discovery.sh
+```
+
+### **CloudMap Lambda Service Discovery Demo**
+
+```bash
+# 1. Connect to the EC2 jumphost
+aws ssm start-session --target $(terraform output -raw jumphost_instance_id) --region ap-southeast-2
+
+# 2. Run the demo script
+./test-discovery.sh
+```
+
+### **Manual Testing**
+
+```bash
+# 1. Discover Lambda service via CloudMap API
+aws servicediscovery discover-instances \
+ --namespace-name api.internal \
+ --service-name api-service \
+ --region ap-southeast-2
+
+# 2. Extract Lambda URL and trigger function
+LAMBDA_URL=$(aws servicediscovery discover-instances \
+ --namespace-name api.internal \
+ --service-name api-service \
+ --region ap-southeast-2 \
+ --query 'Instances[0].Attributes.lambda_url' \
+ --output text)
+
+curl -s "$LAMBDA_URL" | jq .
+```
+
+## ๐ฏ **Demo Output**
+
+The demo shows:
+
+- โ
**Service Discovery**: CloudMap API returns Lambda instance details
+- โ
**URL Extraction**: Lambda Function URL extracted from attributes
+- โ
**Function Triggering**: Lambda executed using discovered URL
+- โ
**Metadata Access**: Function name, environment, version, region
+
+## ๐ก **Use Case**
+
+Perfect for microservices that need to discover and trigger Lambda functions dynamically without hardcoding endpoints.
+
+## ๐ **Key Outputs**
+
+| Output | Description |
+|--------|-------------|
+| `lambda_function_url` | Direct Lambda Function URL |
+| `lambda_discovery_url` | CloudMap service discovery URL |
+| `ssm_session_command` | Command to connect to EC2 jumphost |
+| `jumphost_instance_id` | EC2 instance ID for testing |
+| `cloudmap_namespace_name` | CloudMap namespace name |
+| `cloudmap_service_name` | CloudMap service name |
+
+## ๐ **Service Discovery Benefits**
+
+### **1. Dynamic Service Discovery**
+
+- Discover Lambda functions programmatically via CloudMap API
+- No hardcoded URLs or endpoints
+- Automatic service registration and deregistration
+
+### **2. Rich Metadata Access**
+
+- Access function metadata (version, environment, region)
+- Service health status monitoring
+- Instance-specific attributes
+
+### **3. Microservices Architecture**
+
+- Perfect for service mesh patterns
+- Enables loose coupling between services
+- Supports multiple Lambda instances per service
+
+### **4. Secure Access**
+
+- SSM Session Manager for secure testing
+- IAM-based access control
+- No SSH keys required
+- Audit trail for all connections in CloudTrail
+- EC2 Instance Connect as alternative access method
+- Dynamic IP allowlisting for enhanced security
+
+## ๐ ๏ธ **Advanced Usage**
+
+### **Multiple Lambda Functions**
+
+```hcl
+# Register multiple Lambda functions in the same service
+module "cloudmap" {
+ # ... existing configuration ...
+
+ services = {
+ "api-service" = {
+ name = "api-service"
+ dns_record_type = "CNAME"
+ }
+ }
+
+ # Register multiple Lambda instances
+ enable_lambda_registration = true
+ lambda_instance_id = "api-lambda-01"
+ lambda_url = aws_lambda_function_url.api1.url
+
+ # Additional Lambda instances can be registered via AWS CLI or SDK
+}
+```
+
+### **API Gateway Integration**
+
+```hcl
+# Use API Gateway URL instead of Lambda Function URL
+lambda_url = aws_api_gateway_stage.prod.invoke_url
+```
+
+### **Custom Health Checks**
+
+```hcl
+# Configure custom health checks for Lambda
+lambda_attributes = {
+ "health_check_url" = "https://your-lambda-url/health"
+ "health_check_interval" = "30"
+ "health_check_timeout" = "5"
+}
+```
+
+### **Jumphost Customization**
+
+```hcl
+# Customize jumphost configuration
+module "jumphost-ssm-amazonlinux2" {
+ source = "tfstack/jumphost/aws"
+
+ name = "${local.base_name}-ssm-amazonlinux2"
+ ami_type = "amazonlinux2"
+ subnet_id = module.aws_vpc.private_subnet_ids[0]
+ vpc_id = module.aws_vpc.vpc_id
+
+ create_security_group = true
+ allowed_cidr_blocks = ["${data.http.my_public_ip.response_body}/32"]
+ assign_eip = false
+
+ # Custom user data
+ user_data_extra = <<-EOT
+ yum install -y mtr nc my-custom-package
+ EOT
+}
+```
+
+## ๐งน **Cleanup**
+
+```bash
+# Destroy the infrastructure
+terraform destroy
+```
+
+## ๐ **Related Documentation**
+
+- [AWS CloudMap Service Discovery](https://docs.aws.amazon.com/cloud-map/)
+- [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html)
+- [Private DNS Namespaces](https://docs.aws.amazon.com/cloud-map/latest/dg/private-dns-namespaces.html)
+- [Service Discovery Instance Registration](https://docs.aws.amazon.com/cloud-map/latest/dg/registering-instances.html)
+- [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html)
+- [EC2 Instance Connect](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect.html)
+
+## ๐ **Troubleshooting**
+
+### **DNS Resolution Issues**
+
+- Ensure VPC DNS resolution is enabled
+- Check that the instance is in the correct VPC
+- Verify CloudMap namespace is properly configured
+
+### **Lambda Function Issues**
+
+- Check Lambda function permissions
+- Verify Function URL is properly configured
+- Test Lambda function directly before CloudMap registration
+
+### **Health Check Issues**
+
+- Ensure Lambda function has `/health` endpoint
+- Check CloudMap health check configuration
+- Verify network connectivity from VPC to Lambda
+
+### **SSM Session Issues**
+
+- Verify IAM permissions for SSM Session Manager
+- Check that the instance has internet connectivity
+- Ensure SSM Agent is running on the instance
+
+### **Jumphost Access Issues**
+
+- Verify security group allows SSM traffic
+- Check that the instance is in a subnet with NAT Gateway or VPC endpoints
+- Ensure the instance has the required IAM role for SSM
diff --git a/examples/lambda/index.js b/examples/lambda/index.js
new file mode 100644
index 0000000..9e26154
--- /dev/null
+++ b/examples/lambda/index.js
@@ -0,0 +1,24 @@
+// Lambda function for CloudMap service discovery example
+exports.handler = async (event) => {
+ const response = {
+ statusCode: 200,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Headers': 'Content-Type',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS'
+ },
+ body: JSON.stringify({
+ message: 'Hello from Lambda!',
+ service: process.env.SERVICE_NAME || 'api-service',
+ timestamp: new Date().toISOString(),
+ event: {
+ httpMethod: event.httpMethod,
+ path: event.rawPath,
+ headers: event.headers
+ }
+ })
+ };
+
+ return response;
+};
diff --git a/examples/lambda/main.tf b/examples/lambda/main.tf
new file mode 100644
index 0000000..1920417
--- /dev/null
+++ b/examples/lambda/main.tf
@@ -0,0 +1,380 @@
+# Example: Lambda Function URL Registration in CloudMap
+# This example demonstrates how to register a Lambda Function URL in CloudMap
+# for service discovery within a VPC using private DNS namespace
+
+terraform {
+ required_version = ">= 1.0"
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 4.0"
+ }
+ }
+}
+
+provider "aws" {
+ region = "ap-southeast-2"
+}
+
+# Data source for public IP
+data "http" "my_public_ip" {
+ url = "https://checkip.amazonaws.com/"
+}
+
+# Random suffix for unique resource naming
+resource "random_string" "suffix" {
+ length = 6
+ special = false
+ upper = false
+}
+
+# Local values for consistent naming and configuration
+locals {
+ azs = ["ap-southeast-2a", "ap-southeast-2b", "ap-southeast-2c"]
+ name = "test"
+ base_name = local.suffix != "" ? "${local.name}-${local.suffix}" : local.name
+ suffix = random_string.suffix.result
+ private_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
+ public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
+ region = "ap-southeast-2"
+ vpc_cidr = "10.0.0.0/16"
+ tags = {
+ Environment = "dev"
+ Project = "example"
+ }
+}
+
+# VPC Module
+module "vpc" {
+ source = "cloudbuildlab/vpc/aws"
+
+ vpc_name = local.base_name
+ vpc_cidr = local.vpc_cidr
+ availability_zones = local.azs
+
+ public_subnet_cidrs = local.public_subnets
+ private_subnet_cidrs = local.private_subnets
+
+ # Enable Internet Gateway & NAT Gateway
+ # A single NAT gateway is used instead of multiple for cost efficiency.
+ create_igw = true
+ nat_gateway_type = "single"
+
+ # Enable DNS support for CloudMap
+ enable_dns_hostnames = true
+ enable_dns_support = true
+
+ tags = local.tags
+}
+
+# Security group for Lambda function
+resource "aws_security_group" "lambda" {
+ name_prefix = "${local.base_name}-lambda-"
+ vpc_id = module.vpc.vpc_id
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ tags = merge(local.tags, {
+ Name = "${local.base_name}-lambda-sg"
+ })
+}
+
+# IAM role for Lambda function
+resource "aws_iam_role" "lambda" {
+ name = "${local.base_name}-lambda-role"
+
+ assume_role_policy = jsonencode({
+ Version = "2012-10-17"
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "lambda.amazonaws.com"
+ }
+ }
+ ]
+ })
+
+ tags = local.tags
+}
+
+# IAM policy for Lambda basic execution
+resource "aws_iam_role_policy_attachment" "lambda_basic" {
+ role = aws_iam_role.lambda.name
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+}
+
+data "aws_region" "current" {}
+
+data "aws_caller_identity" "current" {}
+
+# Create Lambda deployment package
+data "archive_file" "lambda_zip" {
+ type = "zip"
+ source_file = "index.js"
+ output_path = "lambda_function.zip"
+}
+
+# IAM policy for jumphost to access CloudMap
+resource "aws_iam_role_policy" "jumphost_cloudmap" {
+ name = "${local.base_name}-jumphost-cloudmap-policy"
+ role = module.jumphost.iam_role_name
+
+ policy = jsonencode({
+ Version = "2012-10-17"
+ Statement = [
+ {
+ Effect = "Allow"
+ Action = [
+ "servicediscovery:DiscoverInstances",
+ "servicediscovery:ListInstances"
+ ]
+ Resource = "*"
+ }
+ ]
+ })
+}
+
+# Lambda function
+resource "aws_lambda_function" "api" {
+ filename = data.archive_file.lambda_zip.output_path
+ function_name = "${local.base_name}-api"
+ role = aws_iam_role.lambda.arn
+ handler = "index.handler"
+ runtime = "nodejs18.x"
+ timeout = 30
+ source_code_hash = data.archive_file.lambda_zip.output_md5
+
+ environment {
+ variables = {
+ SERVICE_NAME = "api-service"
+ }
+ }
+
+ tags = merge(local.tags, {
+ Name = "${local.base_name}-api"
+ })
+}
+
+# Lambda Function URL
+resource "aws_lambda_function_url" "api" {
+ function_name = aws_lambda_function.api.function_name
+ authorization_type = "NONE"
+
+ cors {
+ allow_credentials = true
+ allow_origins = ["*"]
+ allow_methods = ["*"]
+ allow_headers = ["*"]
+ expose_headers = ["*"]
+ max_age = 86400
+ }
+}
+
+# CloudMap module with Lambda registration
+module "cloudmap" {
+ source = "../../"
+
+ # Create private DNS namespace for VPC-based service discovery
+ create_private_dns_namespace = true
+ namespace_name = "api.internal"
+ namespace_description = "Private DNS namespace for API service discovery"
+ vpc_id = module.vpc.vpc_id
+
+ # Define services
+ services = {
+ "api-service" = {
+ name = "api-service"
+ description = "API service for Lambda function discovery"
+ dns_ttl = 60
+ dns_record_type = "A" # Use A record for proper DNS resolution
+ routing_policy = "WEIGHTED"
+ health_check_custom_config = false # Disable custom health checks for DNS-only service
+ tags = {
+ Service = "api"
+ Type = "lambda"
+ }
+ }
+ }
+
+ # Enable Lambda registration
+ enable_lambda_registration = true
+ lambda_instance_id = "api-lambda-01"
+ lambda_url = aws_lambda_function_url.api.function_url
+ lambda_service_name = "api-service"
+ lambda_ip_address = "192.0.2.1" # Placeholder IP for A record (not used for actual access)
+ lambda_attributes = {
+ "environment" = "production"
+ "version" = "v1.0.0"
+ "region" = local.region
+ "function_name" = aws_lambda_function.api.function_name
+ "timeout" = "30"
+ "memory_size" = "128"
+ }
+
+ tags = local.tags
+}
+
+# Jumphost module for testing service discovery
+module "jumphost" {
+ source = "tfstack/jumphost/aws"
+
+ name = "${local.base_name}-jumphost"
+ ami_type = "amazonlinux2"
+ subnet_id = module.vpc.private_subnet_ids[0]
+ vpc_id = module.vpc.vpc_id
+
+ create_security_group = true
+ allowed_cidr_blocks = ["${trimspace(data.http.my_public_ip.response_body)}/32"]
+ assign_eip = false
+
+ user_data_extra = <<-EOT
+ yum install -y mtr nc curl dig jq awscli
+
+ # Create comprehensive test script
+ cat > /home/ec2-user/test-discovery.sh << 'SCRIPT'
+ #!/bin/bash
+ echo "=== AWS CloudMap Lambda Service Discovery Demo ==="
+ echo "This demonstrates how to discover and trigger Lambda functions via CloudMap."
+ echo
+
+ echo "1. ๐ Discover Lambda service via CloudMap API:"
+ aws servicediscovery discover-instances \
+ --namespace-name api.internal \
+ --service-name api-service \
+ --region ap-southeast-2
+
+ echo
+ echo "2. ๐ฏ Extract the actual Lambda Function URL:"
+ LAMBDA_URL=$(aws servicediscovery discover-instances \
+ --namespace-name api.internal \
+ --service-name api-service \
+ --region ap-southeast-2 \
+ --query 'Instances[0].Attributes.lambda_url' \
+ --output text)
+
+ echo " Discovered Lambda URL: $LAMBDA_URL"
+ echo
+
+ echo "3. ๐ Trigger the Lambda using the discovered URL:"
+ if [ "$LAMBDA_URL" != "None" ] && [ ! -z "$LAMBDA_URL" ]; then
+ echo " Triggering: curl -s \"$LAMBDA_URL\""
+ curl -s "$LAMBDA_URL" | jq .
+ echo
+ echo " โ
SUCCESS: Lambda triggered via CloudMap service discovery!"
+ else
+ echo " โ FAILED: Could not discover Lambda URL via CloudMap"
+ fi
+
+ echo
+ echo "4. ๐ Service metadata available:"
+ echo " Function Name: $(aws servicediscovery discover-instances --namespace-name api.internal --service-name api-service --region ap-southeast-2 --query 'Instances[0].Attributes.function_name' --output text)"
+ echo " Environment: $(aws servicediscovery discover-instances --namespace-name api.internal --service-name api-service --region ap-southeast-2 --query 'Instances[0].Attributes.environment' --output text)"
+ echo " Version: $(aws servicediscovery discover-instances --namespace-name api.internal --service-name api-service --region ap-southeast-2 --query 'Instances[0].Attributes.version' --output text)"
+ echo " Region: $(aws servicediscovery discover-instances --namespace-name api.internal --service-name api-service --region ap-southeast-2 --query 'Instances[0].Attributes.region' --output text)"
+
+ echo
+ echo "=== Demo Summary ==="
+ echo "โ
CloudMap API Discovery: Working"
+ echo "โ
Lambda URL Extraction: Working"
+ echo "โ
Lambda Function Trigger: Working"
+ echo
+ echo "Use Case: Services can discover and trigger Lambda functions dynamically"
+ echo "without hardcoding URLs - perfect for microservices architecture!"
+ SCRIPT
+
+ chmod +x /home/ec2-user/test-discovery.sh
+ chown ec2-user:ec2-user /home/ec2-user/test-discovery.sh
+ EOT
+
+ tags = local.tags
+}
+
+# Outputs
+output "vpc_id" {
+ description = "ID of the VPC"
+ value = module.vpc.vpc_id
+}
+
+output "private_subnet_ids" {
+ description = "IDs of the private subnets"
+ value = module.vpc.private_subnet_ids
+}
+
+output "public_subnet_ids" {
+ description = "IDs of the public subnets"
+ value = module.vpc.public_subnet_ids
+}
+
+output "lambda_function_name" {
+ description = "Name of the Lambda function"
+ value = aws_lambda_function.api.function_name
+}
+
+output "lambda_function_url" {
+ description = "Lambda Function URL"
+ value = aws_lambda_function_url.api.function_url
+}
+
+output "cloudmap_namespace_name" {
+ description = "CloudMap namespace name"
+ value = module.cloudmap.namespace_name
+}
+
+output "cloudmap_service_name" {
+ description = "CloudMap service name"
+ value = module.cloudmap.services["api-service"].name
+}
+
+output "lambda_instance_id" {
+ description = "Lambda instance ID in CloudMap"
+ value = module.cloudmap.lambda_instance_id
+}
+
+output "lambda_discovery_url" {
+ description = "CloudMap discovery URL for Lambda"
+ value = module.cloudmap.lambda_discovery_url
+}
+
+# Jumphost outputs
+output "jumphost_instance_id" {
+ description = "ID of the jumphost instance"
+ value = module.jumphost.instance_id
+}
+
+output "jumphost_public_ip" {
+ description = "Public IP of the jumphost instance"
+ value = module.jumphost.public_ip
+}
+
+output "jumphost_private_ip" {
+ description = "Private IP of the jumphost instance"
+ value = module.jumphost.private_ip
+}
+
+output "ssm_session_command" {
+ description = "AWS CLI command to open SSM session to jumphost"
+ value = module.jumphost.ssm_session_command
+}
+
+output "instance_connect_command" {
+ description = "AWS CLI command to connect via EC2 Instance Connect"
+ value = module.jumphost.instance_connect_command
+}
+
+output "test_commands" {
+ description = "Commands to test the setup"
+ value = {
+ ssm_session = module.jumphost.ssm_session_command
+ instance_connect = module.jumphost.instance_connect_command
+ test_script = "Run the test script after connecting via SSM: ./test-discovery.sh"
+ lambda_test = "curl -s '${aws_lambda_function_url.api.function_url}'"
+ dns_test = "dig api-lambda-01.api-service.api.internal"
+ }
+}
diff --git a/main.tf b/main.tf
index f2fb325..8e53ca1 100644
--- a/main.tf
+++ b/main.tf
@@ -165,6 +165,27 @@ resource "aws_iam_role_policy" "ecs_service_discovery" {
})
}
+# Lambda Function URL Registration in CloudMap
+resource "aws_service_discovery_instance" "lambda" {
+ for_each = var.enable_lambda_registration && var.lambda_service_name != null ? toset([var.lambda_service_name]) : toset([])
+
+ instance_id = var.lambda_instance_id
+ service_id = aws_service_discovery_service.services[each.key].id
+
+ attributes = merge(
+ {
+ "AWS_INSTANCE_IPV4" = var.lambda_ip_address != null ? var.lambda_ip_address : "127.0.0.1"
+ "instance_type" = "lambda"
+ "service_type" = "function"
+ "protocol" = "https"
+ "lambda_url" = var.lambda_url
+ },
+ var.lambda_attributes
+ )
+
+ depends_on = [aws_service_discovery_service.services]
+}
+
# Local values for namespace ID and health check debugging
locals {
namespace_id = var.existing_namespace_id != null ? var.existing_namespace_id : (
@@ -175,6 +196,11 @@ locals {
)
)
+ # Determine which service to use for Lambda registration
+ lambda_service_key = var.lambda_service_name != null ? var.lambda_service_name : (
+ length(var.services) > 0 ? keys(var.services)[0] : null
+ )
+
# Debug information for health check configuration
health_check_debug = {
for service_name, service in var.services : service_name => {
@@ -201,4 +227,14 @@ locals {
)
}
}
+
+ # Lambda registration debug information
+ lambda_registration_debug = var.enable_lambda_registration ? {
+ lambda_url_provided = var.lambda_url != null
+ lambda_service_key = local.lambda_service_key
+ services_available = length(var.services) > 0
+ will_register_lambda = var.enable_lambda_registration && var.lambda_url != null && length(var.services) > 0
+ lambda_instance_id = var.lambda_instance_id
+ lambda_attributes = var.lambda_attributes
+ } : null
}
diff --git a/outputs.tf b/outputs.tf
index 9c4d2d5..17b7862 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -53,3 +53,28 @@ output "health_check_debug" {
description = "Debug information for health check configuration - use for troubleshooting"
value = local.health_check_debug
}
+
+# Lambda Registration Outputs
+output "lambda_instance_id" {
+ description = "ID of the registered Lambda instance in CloudMap"
+ value = var.enable_lambda_registration && var.lambda_url != null && var.lambda_service_name != null ? aws_service_discovery_instance.lambda[var.lambda_service_name].instance_id : null
+}
+
+output "lambda_service_id" {
+ description = "ID of the CloudMap service where Lambda is registered"
+ value = var.enable_lambda_registration && var.lambda_url != null && var.lambda_service_name != null ? aws_service_discovery_instance.lambda[var.lambda_service_name].service_id : null
+}
+
+output "lambda_registration_debug" {
+ description = "Debug information for Lambda registration - use for troubleshooting"
+ value = local.lambda_registration_debug
+}
+
+output "lambda_discovery_url" {
+ description = "CloudMap discovery URL for the Lambda function"
+ value = var.enable_lambda_registration && var.lambda_url != null && length(var.services) > 0 ? (
+ var.create_private_dns_namespace ? "${var.lambda_instance_id}.${aws_service_discovery_service.services[local.lambda_service_key].name}.${var.namespace_name}" : (
+ var.create_public_dns_namespace ? "${var.lambda_instance_id}.${aws_service_discovery_service.services[local.lambda_service_key].name}.${var.namespace_name}" : null
+ )
+ ) : null
+}
diff --git a/tests/aws_cloudmap.tftest.hcl b/tests/aws_cloudmap.tftest.hcl
index c22fd10..a43a39e 100644
--- a/tests/aws_cloudmap.tftest.hcl
+++ b/tests/aws_cloudmap.tftest.hcl
@@ -250,3 +250,215 @@ run "multiple_services_container_orchestration" {
error_message = "Database service name should be database-service"
}
}
+
+# Test Lambda Function URL Registration in CloudMap
+run "lambda_function_url_registration" {
+ command = plan
+
+ variables {
+ create_private_dns_namespace = true
+ namespace_name = "lambda.internal"
+ namespace_description = "Private DNS namespace for Lambda service discovery"
+ vpc_id = "vpc-12345678"
+ services = {
+ "api-service" = {
+ name = "api-service"
+ description = "API service for Lambda function discovery"
+ dns_ttl = 60
+ dns_record_type = "CNAME" # Required for Lambda Function URL
+ routing_policy = "WEIGHTED"
+ health_check_custom_config = true
+ custom_health_check_failure_threshold = 1
+ tags = {
+ Service = "api"
+ Type = "lambda"
+ }
+ }
+ }
+ enable_health_checks = true
+
+ # Lambda registration configuration
+ enable_lambda_registration = true
+ lambda_instance_id = "api-lambda-01"
+ lambda_url = "https://abc123.lambda-url.ap-southeast-2.on.aws"
+ lambda_service_name = "api-service"
+ lambda_attributes = {
+ "environment" = "production"
+ "version" = "v1.0.0"
+ "region" = "ap-southeast-2"
+ "function_name" = "cloudmap-api"
+ "timeout" = "30"
+ "memory_size" = "128"
+ }
+
+ tags = {
+ Environment = "production"
+ Project = "lambda-discovery"
+ }
+ }
+
+ assert {
+ condition = var.create_private_dns_namespace == true
+ error_message = "Private DNS namespace creation should be enabled for Lambda"
+ }
+
+ assert {
+ condition = var.services["api-service"].dns_record_type == "CNAME"
+ error_message = "DNS record type should be CNAME for Lambda Function URL"
+ }
+
+ assert {
+ condition = var.enable_lambda_registration == true
+ error_message = "Lambda registration should be enabled"
+ }
+
+ assert {
+ condition = var.lambda_instance_id == "api-lambda-01"
+ error_message = "Lambda instance ID should be api-lambda-01"
+ }
+
+ assert {
+ condition = can(regex("^https://", var.lambda_url))
+ error_message = "Lambda URL should be a valid HTTPS URL"
+ }
+
+ assert {
+ condition = var.lambda_service_name == "api-service"
+ error_message = "Lambda service name should be api-service"
+ }
+
+ assert {
+ condition = var.lambda_attributes["environment"] == "production"
+ error_message = "Lambda environment should be production"
+ }
+
+ assert {
+ condition = var.lambda_attributes["version"] == "v1.0.0"
+ error_message = "Lambda version should be v1.0.0"
+ }
+}
+
+# Test Lambda Registration with Multiple Services
+run "lambda_registration_multiple_services" {
+ command = plan
+
+ variables {
+ create_private_dns_namespace = true
+ namespace_name = "multi-lambda.internal"
+ namespace_description = "Multiple Lambda services in CloudMap"
+ vpc_id = "vpc-12345678"
+ services = {
+ "api-service" = {
+ name = "api-service"
+ description = "API service for Lambda functions"
+ dns_ttl = 60
+ dns_record_type = "CNAME"
+ routing_policy = "WEIGHTED"
+ health_check_custom_config = true
+ custom_health_check_failure_threshold = 1
+ }
+ "worker-service" = {
+ name = "worker-service"
+ description = "Worker service for Lambda functions"
+ dns_ttl = 120
+ dns_record_type = "CNAME"
+ routing_policy = "MULTIVALUE"
+ health_check_custom_config = true
+ custom_health_check_failure_threshold = 1
+ }
+ }
+ enable_health_checks = true
+
+ # Lambda registration in specific service
+ enable_lambda_registration = true
+ lambda_instance_id = "worker-lambda-01"
+ lambda_url = "https://worker123.lambda-url.ap-southeast-2.on.aws"
+ lambda_service_name = "worker-service" # Register in worker-service
+ lambda_attributes = {
+ "environment" = "production"
+ "version" = "v2.0.0"
+ "function_name" = "worker-function"
+ "service_type" = "worker"
+ }
+
+ tags = {
+ Environment = "production"
+ Project = "multi-lambda"
+ }
+ }
+
+ assert {
+ condition = length(var.services) == 2
+ error_message = "Should have 2 services defined"
+ }
+
+ assert {
+ condition = var.lambda_service_name == "worker-service"
+ error_message = "Lambda should be registered in worker-service"
+ }
+
+ assert {
+ condition = var.lambda_instance_id == "worker-lambda-01"
+ error_message = "Lambda instance ID should be worker-lambda-01"
+ }
+
+ assert {
+ condition = var.lambda_attributes["service_type"] == "worker"
+ error_message = "Lambda service type should be worker"
+ }
+}
+
+# Test Lambda Registration Validation
+run "lambda_registration_validation" {
+ command = plan
+
+ variables {
+ create_private_dns_namespace = true
+ namespace_name = "validation.internal"
+ namespace_description = "Lambda registration validation test"
+ vpc_id = "vpc-12345678"
+ services = {
+ "api-service" = {
+ name = "api-service"
+ description = "API service for validation"
+ dns_ttl = 60
+ dns_record_type = "CNAME"
+ routing_policy = "WEIGHTED"
+ health_check_custom_config = true
+ custom_health_check_failure_threshold = 1
+ }
+ }
+ enable_health_checks = true
+
+ # Lambda registration with validation
+ enable_lambda_registration = true
+ lambda_instance_id = "valid-lambda-01" # Valid ID format
+ lambda_url = "https://valid123.lambda-url.ap-southeast-2.on.aws"
+ lambda_service_name = "api-service"
+ lambda_attributes = {
+ "environment" = "staging"
+ "version" = "v1.0.0"
+ "function_name" = "valid-function"
+ }
+
+ tags = {
+ Environment = "staging"
+ Project = "validation"
+ }
+ }
+
+ assert {
+ condition = can(regex("^[a-zA-Z0-9_-]+$", var.lambda_instance_id))
+ error_message = "Lambda instance ID should contain only alphanumeric characters, hyphens, and underscores"
+ }
+
+ assert {
+ condition = can(regex("^https://", var.lambda_url))
+ error_message = "Lambda URL should be a valid HTTPS URL"
+ }
+
+ assert {
+ condition = var.lambda_attributes["environment"] == "staging"
+ error_message = "Lambda environment should be staging"
+ }
+}
diff --git a/variables.tf b/variables.tf
index 584a2d9..3a0b20f 100644
--- a/variables.tf
+++ b/variables.tf
@@ -136,3 +136,52 @@ variable "tags" {
type = map(string)
default = {}
}
+
+# Lambda Function URL Support Variables
+variable "enable_lambda_registration" {
+ description = "Enable registration of Lambda Function URL in CloudMap service discovery"
+ type = bool
+ default = false
+}
+
+variable "lambda_instance_id" {
+ description = "Unique identifier for the Lambda instance in CloudMap"
+ type = string
+ default = "lambda-function"
+ validation {
+ condition = can(regex("^[a-zA-Z0-9_-]+$", var.lambda_instance_id))
+ error_message = "Lambda instance ID must contain only alphanumeric characters, hyphens, and underscores."
+ }
+}
+
+variable "lambda_url" {
+ description = "Lambda Function URL or API Gateway endpoint to register in CloudMap"
+ type = string
+ default = null
+ validation {
+ condition = var.lambda_url == null || can(regex("^https?://", var.lambda_url))
+ error_message = "Lambda URL must be a valid HTTP or HTTPS URL."
+ }
+}
+
+variable "lambda_service_name" {
+ description = "Name of the CloudMap service for Lambda registration. If not specified, uses the first service name from var.services"
+ type = string
+ default = null
+}
+
+variable "lambda_attributes" {
+ description = "Additional attributes for the Lambda instance in CloudMap"
+ type = map(string)
+ default = {}
+}
+
+variable "lambda_ip_address" {
+ description = "IP address to use for Lambda A record in CloudMap. If not provided, uses a placeholder IP."
+ type = string
+ default = null
+ validation {
+ condition = var.lambda_ip_address == null || can(regex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", var.lambda_ip_address))
+ error_message = "Lambda IP address must be a valid IPv4 address."
+ }
+}