locals
Terraform policy supports locals {} blocks. These blocks behave like locals blocks within Terraform configuration language, allowing you to share data with useful names between blocks. You can include a locals block as a top-level block, in which case those values will be available to all of the policies you include in your configuration. You can also include a locals block nested within any policy block, in which case those local values will only be scoped to that policy.
Configuration model
locals {
<name> = <expression>
...
}
Complete configuration example
# Top-level locals available to all policies
locals {
approved_regions = ["us-east-1", "us-west-2", "eu-west-1"]
version_constraint = ">= 5.0.0"
}
provider_policy "aws" "region_and_version" {
# Policy-scoped locals only available within this policy
locals {
meets_version = core::semverconstraint(meta.version, local.version_constraint)
}
enforce {
condition = core::contains(local.approved_regions, attrs.region)
error_message = "AWS provider region must be one of: ${core::join(", ", local.approved_regions)}"
}
enforce {
condition = local.meets_version
error_message = "The AWS provider version must be at least ${local.version_constraint}."
}
}
Specification
Syntax
Each locals {} block contains one or more local value assignments. The name of each local value must be unique within its scope.
Scope
Top-level locals:
- Defined outside of any policy block
- Available to all policies in the file
- Referenced using
local.<name>
Policy-scoped locals:
- Defined within a specific policy block
- Only available within that policy block
- Can reference top-level locals
- Referenced using
local.<name>
Referencing locals
Reference local values using the local.<name> syntax:
locals {
max_size = 100
}
resource_policy "aws_ebs_volume" "size_check" {
enforce {
condition = attrs.size <= local.max_size
error_message = "EBS volume size must not exceed ${local.max_size} GB."
}
}
Examples
Top-level locals for shared values
locals {
approved_regions = ["us-east-1", "us-west-2"]
required_tags = ["owner", "project", "environment"]
}
provider_policy "aws" "region_check" {
enforce {
condition = core::contains(local.approved_regions, attrs.region)
error_message = "Region must be one of: ${core::join(", ", local.approved_regions)}"
}
}
resource_policy "aws_instance" "tag_check" {
locals {
has_all_tags = core::alltrue([
for tag in local.required_tags :
core::contains(core::keys(attrs.tags), tag)
])
}
enforce {
condition = local.has_all_tags
error_message = "Instance must have all required tags: ${core::join(", ", local.required_tags)}"
}
}
Policy-scoped locals for complex logic
resource_policy "aws_cloudtrail" "s3_bucket_private" {
locals {
s3_bucket_acl = core::getresources("aws_s3_bucket_acl", {
bucket = attrs.s3_bucket_name
})
is_private = length(local.s3_bucket_acl) > 0 && local.s3_bucket_acl[0].acl == "private"
}
enforce {
condition = local.is_private
error_message = "S3 bucket associated with CloudTrail must be private"
}
}
Combining top-level and policy-scoped locals
locals {
# Top-level: shared across all policies
production_regions = ["us-east-1", "eu-west-1"]
staging_regions = ["us-west-2", "eu-central-1"]
}
resource_policy "aws_instance" "environment_region_check" {
# Policy-scoped: only for this policy
locals {
is_production = attrs.tags.environment == "production"
allowed_regions = local.is_production ? local.production_regions : local.staging_regions
}
enforce {
condition = core::contains(local.allowed_regions, attrs.availability_zone)
error_message = "Instance in ${attrs.tags.environment} environment must be in allowed regions: ${core::join(", ", local.allowed_regions)}"
}
}
Using locals with functions
locals {
min_version = "5.0.0"
}
provider_policy "aws" "version_check" {
locals {
version_constraint = core::semverconstraint(meta.version, ">= ${local.min_version}")
}
enforce {
condition = local.version_constraint
error_message = "AWS provider version must be at least ${local.min_version}. Current version: ${meta.version}"
}
}
Complex data structures in locals
locals {
instance_type_limits = {
dev = {
allowed_types = ["t3.micro", "t3.small"]
max_count = 5
}
prod = {
allowed_types = ["t3.medium", "t3.large", "m5.large"]
max_count = 20
}
}
}
resource_policy "aws_instance" "type_and_count_check" {
locals {
env = attrs.tags.environment
limits = local.instance_type_limits[local.env]
instance_count = length(core::getresources("aws_instance", {
tags = { environment = local.env }
}))
}
enforce {
condition = core::contains(local.limits.allowed_types, attrs.instance_type)
error_message = "Instance type ${attrs.instance_type} not allowed in ${local.env}. Allowed: ${core::join(", ", local.limits.allowed_types)}"
}
enforce {
condition = local.instance_count < local.limits.max_count
error_message = "Instance count (${local.instance_count}) exceeds limit of ${local.limits.max_count} for ${local.env} environment."
}
}
Using locals for HTTP requests
resource_policy "aws_instance" "external_validation" {
locals {
validation_response = core::gethttprequest(
"https://api.example.com/validate-instance",
{
method = "GET"
headers = {
"Content-Type" = "application/json"
}
}
)
is_valid = local.validation_response.statusCode == 200
}
enforce {
condition = local.is_valid
error_message = "External validation failed with status: ${local.validation_response.statusCode}"
info_message = "Validation response: ${local.validation_response.body}"
}
}