- Terraform Policy
- v0.2.x (latest)
- v0.1.x (beta)
locals
Use the locals block to define values and reuse them in your policy. locals blocks allow you to share data with useful names between blocks. When defined at the root of the configuration, all policies in the configuration can access its values.
You can also define reusable values in locals blocks nested within any type of policy block, which limits the scope to its parent policy. Terraform policy raises an error if a policy-scoped local has the same name as a top-level locals block. For information about locals nested inside policy blocks, refer to the following topics:
localsargument inprovider_policyblockslocalsargument inresource_policyblockslocalsargument inmodule_policyblocks
Configuration model
The locals block supports the following configuration:
Complete configuration example
The following example demonstrates a top-level locals block, a locals block within an example policy block, and a policy block that references locals:
policies/example.policy.hcl
locals {
approved_regions = ["us-east-1", "us-west-2", "eu-west-1"]
version_constraint = ">= 5.0.0"
}
provider_policy "aws" "region_and_version" {
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
A locals block supports the following configuration.
name
Provide a value for the local with the given name. Refer to local values in your policies with local.<name>.
- Data type: Any
- Default: None
- Repeatable
Scope
Local blocks can be a the top level of a policy file, or within a policy block.
Top-level locals
Top-level locals are defined outside of any policy block. They are available to all policies in the policy file.
Policy-scoped locals
Policy-scoped locals are defined within a policy block. They are only available to the policy containing them.
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
The following examples demonstrate common locals block configuration patterns for specific use cases.
Top-level locals for shared values
In the following example, the top-level locals block defines shared values that are referenced by multiple policies throughout the file.
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
In the following example, the locals block within a resource policy uses the core::getresources function to fetch related resources and perform validation 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
In the following example, top-level locals define region lists while policy-scoped locals determine which list to use based on the environment tag.
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
In the following example, the locals block uses the core::semverconstraint function to evaluate provider version requirements.
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
In the following example, the locals block defines a complex nested data structure that maps environment types to their allowed instance configurations.
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
In the following example, the locals block uses the core::gethttprequest function to make an external API call and validate the response.
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}"
}
}