This example demonstrates how to use Terraform data sources and preconditions to enable multiple teams to run the same Infrastructure as Code (IaC) while ensuring resources are only created once.
This pattern is useful when:
- Multiple teams need to deploy shared infrastructure
- You want to prevent duplicate resource creation
- Resources should be identified by specific tags
- Teams should be able to detect existing resources before attempting creation
-
Team 1 (team-alpha) runs the Terraform code first
- No matching resources exist
- Precondition passes
- S3 bucket is created with required tags
-
Team 2 (team-beta) runs the same Terraform code
- Data source queries all S3 buckets
- Filters buckets by required tags
- Finds the bucket created by Team 1
- Precondition detects existing resource
- No new bucket is created
- Outputs confirm the existing resource was found
┌─────────────────────────────────────────────────────────────┐
│ Terraform Execution │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Data Source: aws_s3_buckets │
│ └─> Lists all S3 buckets in the account │
│ │
│ 2. Data Source: aws_s3_bucket (for_each) │
│ └─> Retrieves tags for each bucket │
│ │
│ 3. Local Value: matching_buckets │
│ └─> Filters buckets by required_tags │
│ │
│ 4. Lifecycle Precondition │
│ └─> Checks if matching_buckets is empty │
│ ├─ Empty: Create resource │
│ └─ Not Empty: Skip creation, output existing │
│ │
└─────────────────────────────────────────────────────────────┘
.
├── main.tf # Main Terraform configuration with data sources and resources
├── variables.tf # Input variables for customization
├── outputs.tf # Outputs showing creation status and found resources
├── team1.tfvars # Configuration for Team 1 (team-alpha)
├── team2.tfvars # Configuration for Team 2 (team-beta)
└── README.md # This file
- Terraform >= 1.5.0
- AWS CLI configured with appropriate credentials
- AWS permissions to:
- List S3 buckets
- Read S3 bucket tags
- Create S3 buckets (for Team 1)
# Initialize Terraform
terraform init
# Plan the deployment
terraform plan -var-file="team1.tfvars"
# Apply the configuration
terraform apply -var-file="team1.tfvars"Expected Output:
bucket_created = true
bucket_name = "shared-resource-dev-team-alpha"
existing_buckets_found = []
resource_exists = false
summary = "Team 'team-alpha' created new bucket: shared-resource-dev-team-alpha"
# Initialize Terraform (if not already done)
terraform init
# Plan the deployment
terraform plan -var-file="team2.tfvars"
# Apply the configuration
terraform apply -var-file="team2.tfvars"Expected Output:
bucket_created = false
bucket_name = "shared-resource-dev-team-beta"
existing_buckets_found = ["shared-resource-dev-team-alpha"]
resource_exists = true
summary = "Team 'team-beta' found existing bucket(s): shared-resource-dev-team-alpha. No new resource created."
aws_s3_buckets: Lists all S3 buckets in the AWS account
data "aws_s3_buckets" "tagged" {
count = var.check_existing ? 1 : 0
}aws_s3_bucket: Retrieves detailed information including tags for each bucket
data "aws_s3_bucket" "check_tags" {
for_each = var.check_existing ? toset(try(data.aws_s3_buckets.tagged[0].ids, [])) : []
bucket = each.value
}locals {
matching_buckets = var.check_existing ? [
for bucket_name, bucket_data in data.aws_s3_bucket.check_tags :
bucket_name
if alltrue([
for tag_key, tag_value in var.required_tags :
try(bucket_data.tags[tag_key], "") == tag_value
])
] : []
resource_exists = length(local.matching_buckets) > 0
should_create = var.check_existing ? !local.resource_exists : true
}lifecycle {
precondition {
condition = !var.check_existing || !local.resource_exists
error_message = <<-EOT
Resource already exists with matching tags!
Found bucket(s): ${join(", ", local.matching_buckets)}
Team ${var.team_name} should not create this resource as it already exists.
EOT
}
}| Variable | Description | Default | Required |
|---|---|---|---|
team_name |
Name of the team running the code | - | Yes |
project_name |
Name of the project | shared-resource |
No |
environment |
Environment name (dev, staging, prod) | dev |
No |
aws_region |
AWS region for resources | us-east-1 |
No |
check_existing |
Enable existence checking | true |
No |
required_tags |
Tags to match for resource identification | See below | No |
{
Project = "shared-resource"
Environment = "dev"
Purpose = "multi-team-example"
}| Output | Description |
|---|---|
bucket_created |
Boolean indicating if a new bucket was created |
bucket_name |
Name of the bucket (created or would-be) |
bucket_id |
ID of the created bucket (empty if not created) |
bucket_arn |
ARN of the created bucket (empty if not created) |
existing_buckets_found |
List of existing buckets matching required tags |
resource_exists |
Boolean indicating if matching resource exists |
team_name |
Name of the team that ran the configuration |
summary |
Human-readable summary of the operation |
# Team 1 creates the resource
terraform apply -var-file="team1.tfvars" -auto-approve
# Verify bucket was created
aws s3 ls | grep shared-resource-dev-team-alpha# Team 2 attempts to create (should detect existing)
terraform apply -var-file="team2.tfvars" -auto-approve
# Verify no duplicate bucket
aws s3 ls | grep shared-resource-dev-team-beta # Should not exist# Force creation without checking (not recommended)
terraform apply -var-file="team2.tfvars" -var="check_existing=false" -auto-approve# Team 1 destroys their resources
terraform destroy -var-file="team1.tfvars" -auto-approve
# If Team 2 created resources with check_existing=false
terraform destroy -var-file="team2.tfvars" -auto-approve- Consistent Tagging: Ensure all teams use the same
required_tagsvalues - Tag Strategy: Use meaningful tags that uniquely identify shared resources
- State Management: Use remote state backends (S3 + DynamoDB) for team collaboration
- Permissions: Implement least-privilege IAM policies
- Documentation: Keep team-specific tfvars files well-documented
This pattern can be adapted for other AWS resources:
- EC2 Instances: Filter by instance tags
- RDS Databases: Check for existing DB instances by tags
- VPCs: Identify shared network infrastructure
- IAM Roles: Prevent duplicate role creation
- Lambda Functions: Check for existing functions by tags
data "aws_instances" "existing" {
filter {
name = "tag:Project"
values = [var.required_tags["Project"]]
}
filter {
name = "tag:Environment"
values = [var.required_tags["Environment"]]
}
instance_state_names = ["running", "stopped"]
}
locals {
instance_exists = length(data.aws_instances.existing.ids) > 0
}Solution: Another team or process created a bucket with the same name but different tags. Update bucket_name logic or tags.
Solution: Verify that:
- AWS credentials have permission to list buckets and read tags
required_tagsmatch exactly (case-sensitive)check_existingis set totrue
Solution: Use remote state backend and state locking:
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "shared-resource/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}- Encryption: All buckets are encrypted with AES256 by default
- Public Access: Public access is blocked on all created buckets
- Versioning: Bucket versioning is enabled for data protection
- IAM: Follow least-privilege principle for AWS credentials
- Tags: Avoid including sensitive information in tags
This example is provided as-is for educational purposes.
Contributions and improvements are welcome! Please ensure:
- Code follows Terraform best practices
- Documentation is updated
- Examples are tested