
terraform-syntax
by Luscii
Re-usable configuration of Parsers and Filter for Fluentbit
SKILL.md
name: terraform-syntax description: 'Master Terraform HCL syntax including blocks, arguments, expressions, operators, templates, and formatting conventions. Use when asked about "Terraform syntax", "HCL syntax", "write Terraform code", "expression syntax", "template syntax", or when writing, reviewing, or understanding Terraform configuration. Covers structural language, expression language, template language, and style guidelines.'
Terraform HCL Syntax
Comprehensive guide to Terraform's HashiCorp Configuration Language (HCL) syntax, covering structural elements, expressions, templates, operators, and style conventions.
When to Use This Skill
- User asks about "Terraform syntax", "HCL syntax", "how to write Terraform"
- Writing new Terraform configuration files
- Reviewing or understanding existing Terraform code
- Questions about expressions, operators, or templates
- Formatting and style questions
- Syntax errors or validation issues
- Learning Terraform language fundamentals
Language Components
Terraform's configuration language consists of three integrated sub-languages:
- Structural Language - Defines hierarchical configuration (bodies, blocks, attributes)
- Expression Language - Specifies values (literals, operations, functions)
- Template Language - Composes strings (interpolation, directives)
Structural Language
Configuration Files
A configuration file is a UTF-8 encoded text file whose top-level is interpreted as a Body.
File Extensions:
.tf- Native Terraform syntax.tf.json- JSON syntax (for programmatic generation)
Character Encoding:
- Must be UTF-8
- Unix-style line endings (LF) preferred
- Windows-style (CRLF) accepted but may be auto-converted
Bodies
A body is a collection of associated attributes and blocks.
# Body contains attributes and blocks
resource "aws_instance" "example" {
# This is a body - contains attributes and nested blocks
ami = "abc123"
instance_type = "t2.micro"
tags = {
Name = "example"
}
}
Attributes (Arguments)
An attribute assigns a value to a name. Each distinct attribute name may be defined no more than once within a single body.
Syntax:
Attribute = Identifier "=" Expression Newline
Examples:
# Simple attribute
ami = "abc123"
# Attribute with expression
instance_type = var.instance_type
# Attribute with complex expression
count = var.enabled ? 1 : 0
# Object attribute
tags = {
Name = "example"
Environment = var.environment
}
Formatting:
# ✅ Good - Align equals signs
ami = "abc123"
instance_type = "t2.micro"
subnet_id = var.subnet_id
# ❌ Bad - Unaligned
ami = "abc123"
instance_type = "t2.micro"
subnet_id = var.subnet_id
Blocks
A block creates a child body annotated with a type and optional labels.
Syntax:
Block = Identifier (StringLit|Identifier)* "{" Newline Body "}" Newline
Structure:
block_type "label1" "label2" {
# Block body - can contain attributes and nested blocks
argument = value
nested_block {
# Nested block body
}
}
Examples:
# Resource block (2 labels: type, name)
resource "aws_instance" "web" {
ami = "abc123"
}
# Variable block (1 label: name)
variable "instance_count" {
type = number
default = 1
}
# Nested blocks
resource "aws_instance" "web" {
ami = "abc123"
# Nested block
root_block_device {
volume_size = 20
}
# Another nested block
network_interface {
device_index = 0
}
}
One-Line Blocks:
# Short blocks can be on one line
lifecycle { create_before_destroy = true }
Top-Level Blocks
Terraform uses a limited number of top-level block types:
terraform {
required_version = ">= 1.3"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "abc123"
}
data "aws_ami" "ubuntu" {
most_recent = true
}
variable "region" {
type = string
}
output "instance_id" {
value = aws_instance.web.id
}
module "vpc" {
source = "./modules/vpc"
}
locals {
common_tags = {
Project = "example"
}
}
Identifiers
Identifiers name entities (blocks, attributes, variables).
Rules:
- Can contain: letters, digits, underscores (
_), hyphens (-) - Must start with: letter or underscore (not a digit)
- Follows Unicode identifier syntax (UAX #31)
- Dash
-is allowed (Terraform extension)
Examples:
# ✅ Valid identifiers
my_variable
aws_instance
web-server
resource_1
_private
# ❌ Invalid identifiers
1st_resource # Starts with digit
my variable # Contains space
my@resource # Contains @
Conventions:
# Use snake_case for multi-word identifiers
instance_type
security_group
vpc_cidr_block
# Use snake_case for labels (when appropriate)
resource "aws_instance" "web_server" {
# ...
}
Comments
Three comment syntaxes:
# Single-line comment (preferred)
// Single-line comment (alternative)
/*
Multi-line comment
spans multiple lines
*/
Best Practice:
# ✅ Preferred - Use # for single and multi-line comments
# This is a comment
# explaining the resource
# ❌ Avoid - // style
// This comment style is less idiomatic
Expression Language
Expressions specify values for attributes.
Literal Values
Numbers:
42 # Integer
3.14159 # Decimal
1.5e10 # Exponent notation
Booleans:
true
false
Null:
null
Strings:
# Strings use template syntax
"hello"
"hello ${var.name}"
# Heredoc strings
<<EOT
Multi-line
string content
EOT
# Indented heredoc (strips leading whitespace)
<<-EOT
Indented content
will have leading spaces stripped
EOT
Collection Values
Tuples (ordered lists):
["a", "b", "c"]
[
"item1",
"item2",
"item3",
]
# Trailing comma allowed
[1, 2, 3,]
Objects (maps with named attributes):
{
name = "example"
age = 42
}
# Keys can be expressions (use parentheses)
{
(var.key_name) = "value"
}
# Colon syntax also allowed
{
name: "example",
age: 42
}
# Mixed newlines and commas
{
name = "example",
age = 42
city = "Boston"
}
Ambiguity Resolution:
# ❌ Syntax error - for expression vs collection
[for, foo, baz]
# ✅ Use parentheses
[(for), foo, baz]
# ❌ Syntax error - for as key
{for = 1, baz = 2}
# ✅ Quote the key
{"for" = 1, baz = 2}
# ✅ Or reorder
{baz = 2, for = 1}
# ✅ Or use parentheses
{(for) = 1, baz = 2}
Variables
Access variables using identifiers:
var.instance_type
var.region
local.common_tags
Functions
Call functions with parentheses:
# Basic function call
length(var.list)
# Multiple arguments
merge(local.tags, var.additional_tags)
# Nested function calls
upper(substr(var.name, 0, 3))
# Spread operator (...)
concat(var.list1, var.list2...)
Operators
Arithmetic:
a + b # Addition
a - b # Subtraction
a * b # Multiplication
a / b # Division
a % b # Modulo
-a # Negation
Comparison:
a == b # Equal
a != b # Not equal
a < b # Less than
a <= b # Less than or equal
a > b # Greater than
a >= b # Greater than or equal
Logical:
a && b # AND
a || b # OR
!a # NOT
Operator Precedence (highest to lowest):
1. -, ! (unary)
2. *, /, %
3. +, -
4. >, >=, <, <=
5. ==, !=
6. &&
7. ||
Examples:
# Arithmetic
var.count * 2
(var.price * var.quantity) + var.tax
# Comparisons
var.age >= 18
var.status == "active"
# Logical
var.enabled && var.count > 0
var.env == "prod" || var.env == "staging"
# Combined
(var.count > 0) && (var.enabled || var.force)
Conditional Expressions
Ternary operator:
condition ? true_val : false_val
Examples:
# Simple conditional
var.enabled ? 1 : 0
# With complex expressions
var.env == "prod" ? "m5.large" : "t2.micro"
# Nested conditionals
var.env == "prod" ? "m5.large" : var.env == "staging" ? "t3.medium" : "t2.micro"
# Safe indexing
length(var.list) > 0 ? var.list[0] : "default"
For Expressions
Transform collections:
Tuple For:
# Basic syntax
[for item in collection : transform_expression]
# With index
[for i, v in collection : expression]
# With condition
[for v in collection : v if condition]
# Examples
[for s in var.list : upper(s)]
[for i, v in var.list : "${i}: ${v}"]
[for s in var.list : s if length(s) > 3]
Object For:
# Basic syntax
{for item in collection : key_expr => value_expr}
# With grouping (...)
{for item in collection : key_expr => value_expr...}
# Examples
{for k, v in var.map : k => upper(v)}
{for s in var.list : s => length(s)}
# Grouping mode
{for i, v in ["a", "a", "b"] : v => i...}
# Result: {a = [0, 1], b = [2]}
Splat Expressions
Convenient attribute/element access:
Attribute-Only Splat:
# Syntax
collection.*.attribute
# Example
var.instances.*.id
# Equivalent to: [for i in var.instances : i.id]
# Chained attributes
var.instances.*.tags.*.Name
Full Splat:
# Syntax
collection[*].attribute_or_index
# Examples
var.instances[*].id
var.instances[*].tags["Name"]
var.instances[*].network_interfaces[0]
Auto-Wrapping:
# If value is not a list, it's wrapped
single_object.*.id
# Equivalent to: [single_object.id]
# Null becomes empty list
null_value.*.id
# Result: []
Index Operator
Access collection elements:
# Tuple/list indexing (zero-based)
var.list[0]
var.list[var.index]
# Object/map indexing (by key)
var.map["key"]
var.map[var.dynamic_key]
# Legacy syntax (compatibility only)
var.list.0 # Don't use in new code
Attribute Access
Access object attributes:
# Syntax
object.attribute
# Examples
var.config.name
aws_instance.web.public_ip
module.vpc.vpc_id
data.aws_ami.ubuntu.id
# Chained access
var.config.network.vpc.id
Template Language
Templates compose values into strings.
Template Expressions
Quoted Template:
"Hello, ${var.name}!"
"The result is ${var.a + var.b}"
Heredoc Template:
<<EOT
Hello, ${var.name}!
Your instance is ${aws_instance.web.id}
EOT
# Indented heredoc (strips leading spaces)
<<-EOT
Line 1
Line 2
EOT
Escape Sequences
In quoted templates:
"Line 1\nLine 2" # Newline
"Column 1\tColumn 2" # Tab
"She said \"Hello\"" # Quote
"Path: C:\\Users\\name" # Backslash
"Unicode: \u0041" # Unicode (A)
"Emoji: \U0001F600" # Unicode emoji
Escaping Template Sequences:
# Escape interpolation
"Dollar sign: $${var.name}" # Outputs: Dollar sign: ${var.name}
# Escape directive
"Percent: %%{if true}text%{endif}" # Outputs: Percent: %{if true}text%{endif}
Interpolation
Embed expressions in strings:
# Basic interpolation
"Hello, ${var.name}!"
# Expression interpolation
"Total: ${var.price * var.quantity}"
# Function calls
"Uppercase: ${upper(var.name)}"
# Conditional
"Status: ${var.enabled ? "active" : "inactive"}"
Strip Markers
Remove whitespace with ~:
# Strip after opening
"hello ${~ "world" }"
# Result: "helloworld"
# Strip before closing
"hello${ "world" ~} test"
# Result: "hellotest"
# Strip both
"%{ if true ~} hello %{~ endif }"
# Result: "hello"
Template Directives
If Directive:
%{ if condition }
true_content
%{ else }
false_content
%{ endif }
# Example
<<EOT
%{ if var.enabled }
Service is enabled
%{ else }
Service is disabled
%{ endif }
EOT
For Directive:
%{ for item in collection }
${item}
%{ endfor }
# Example with index
<<EOT
%{ for i, name in var.names }
${i + 1}. ${name}
%{ endfor }
EOT
# Result:
# 1. Alice
# 2. Bob
# 3. Carol
Template Unwrapping
A template with only a single interpolation is "unwrapped":
# Unwrapped - returns boolean
"${true}"
# Result: true (boolean, not string)
# Not unwrapped - returns string
"hello ${true}"
# Result: "hello true"
# Double unwrapping
"${"${true}"}"
# Result: true (boolean)
Common Patterns
Meta-Arguments
Meta-arguments modify resource behavior:
resource "aws_instance" "web" {
# Meta-arguments first
count = var.instance_count
for_each = toset(var.availability_zones)
depends_on = [aws_security_group.web]
provider = aws.west
# Regular arguments
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
# Lifecycle block last
lifecycle {
create_before_destroy = true
prevent_destroy = true
}
}
Dynamic Blocks
Generate nested blocks:
resource "aws_security_group" "example" {
name = "example"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
For comprehensive examples and best practices, see the terraform-resources skill.
Type Constraints
Specify variable types:
variable "instance_type" {
type = string
}
variable "instance_count" {
type = number
}
variable "enabled" {
type = bool
}
variable "tags" {
type = map(string)
}
variable "subnets" {
type = list(string)
}
variable "config" {
type = object({
name = string
size = number
enabled = bool
})
}
variable "instances" {
type = list(object({
name = string
type = string
}))
}
# Optional attributes
variable "server" {
type = object({
name = string
port = optional(number, 80)
enabled = optional(bool, true)
})
}
Style Guidelines
Formatting
Indentation:
# ✅ Use 2 spaces per nesting level
resource "aws_instance" "web" {
ami = "abc123"
network_interface {
device_index = 0
}
}
Alignment:
# ✅ Align equals signs
ami = "abc123"
instance_type = "t2.micro"
subnet_id = var.subnet_id
Blank Lines:
# ✅ Separate top-level blocks with blank line
resource "aws_instance" "web" {
ami = "abc123"
}
resource "aws_security_group" "web" {
name = "web"
}
Block Ordering:
resource "aws_instance" "example" {
# 1. Meta-arguments first
count = 2
# 2. Required arguments
ami = "abc123"
instance_type = "t2.micro"
# 3. Optional arguments
monitoring = true
# 4. Nested blocks
root_block_device {
volume_size = 20
}
# 5. Lifecycle block last
lifecycle {
create_before_destroy = true
}
}
Naming Conventions
Resources:
# ✅ Use descriptive nouns, snake_case
resource "aws_instance" "web_server" {}
resource "aws_security_group" "database" {}
# ❌ Avoid redundant naming
resource "aws_instance" "aws_instance_web" {}
Variables and Outputs:
# ✅ Descriptive, snake_case
variable "instance_count" {}
output "vpc_id" {}
# Order: type, description, default, sensitive, validation
variable "db_password" {
type = string
description = "Database password"
sensitive = true
validation {
condition = length(var.db_password) >= 8
error_message = "Password must be at least 8 characters"
}
}
File Organization
Standard Files:
main.tf # Primary resources
variables.tf # Input variables (alphabetical)
outputs.tf # Output values (alphabetical)
versions.tf # Version constraints
providers.tf # Provider configurations
locals.tf # Local values
backend.tf # Backend configuration
Resource-Specific Files:
network.tf # Networking resources
security.tf # Security groups, NACLs
iam.tf # IAM roles and policies
compute.tf # Compute resources
Validation and Formatting
terraform fmt
Format code to standard style:
# Format current directory
terraform fmt
# Format recursively
terraform fmt -recursive
# Check formatting without changes
terraform fmt -check
terraform validate
Validate syntax and internal consistency:
terraform validate
What it checks:
- Syntax correctness
- Type consistency
- Argument requirements
- Variable references
- Does NOT check: Provider-specific validation
Common Syntax Errors
Missing Equals Sign
# ❌ Error
ami "abc123"
# ✅ Correct
ami = "abc123"
Unclosed Blocks
# ❌ Error - missing closing brace
resource "aws_instance" "web" {
ami = "abc123"
# ✅ Correct
resource "aws_instance" "web" {
ami = "abc123"
}
Invalid Identifiers
# ❌ Error - starts with digit
variable "1st_instance" {}
# ✅ Correct
variable "first_instance" {}
Type Mismatches
# ❌ Error - string where number expected
variable "count" {
type = number
default = "five"
}
# ✅ Correct
variable "count" {
type = number
default = 5
}
Duplicate Attributes
# ❌ Error - duplicate attribute
resource "aws_instance" "web" {
ami = "abc123"
ami = "def456"
}
# ✅ Correct - use one value
resource "aws_instance" "web" {
ami = "abc123"
}
Best Practices Summary
Do's
✅ Use terraform fmt before committing ✅ Use 2-space indentation ✅ Align equals signs for readability ✅ Use # for comments (not //) ✅ Use snake_case for identifiers ✅ Separate top-level blocks with blank lines ✅ Put meta-arguments first ✅ Put lifecycle blocks last ✅ Use descriptive, clear names ✅ Add descriptions to variables and outputs ✅ Use type constraints on variables
Don'ts
❌ Don't use // for comments ❌ Don't start identifiers with digits ❌ Don't duplicate attribute names ❌ Don't use CRLF line endings ❌ Don't include resource type in resource name ❌ Don't skip terraform fmt ❌ Don't use legacy syntax (.0 indexing) ❌ Don't hardcode values that should be variables
Quick Reference
Attribute:
name = value
Block:
type "label" {
attribute = value
}
Expression:
value
var.name
function(arg)
condition ? true_val : false_val
[for x in list : transform]
{for k, v in map : k => v}
Template:
"String with ${interpolation}"
<<EOT
Heredoc with ${interpolation}
EOT
Operators:
+ - * / % # Arithmetic
== != < > <= >= # Comparison
&& || ! # Logical
Comments:
# Preferred style
// Alternative
/* Multi-line */
References
- HCL Spec: https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md
- Terraform Configuration: https://developer.hashicorp.com/terraform/language/syntax/configuration
- Terraform Style Guide: https://developer.hashicorp.com/terraform/language/style
- Expressions: https://developer.hashicorp.com/terraform/language/expressions
- Functions: https://developer.hashicorp.com/terraform/language/functions
- terraform-refactoring skill for safe code restructuring
- terraform.instructions.md for complete coding guidelines
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です