{"id":2133,"date":"2026-02-21T01:47:28","date_gmt":"2026-02-20T12:47:28","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=2133"},"modified":"2026-02-21T01:47:28","modified_gmt":"2026-02-20T12:47:28","slug":"terraform-expressions-and-functions","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=2133","title":{"rendered":"Terraform Expressions and Functions"},"content":{"rendered":"<p>Terraform\u2019s expression and function system is the core \u201cthinking engine\u201d behind your configurations: expressions describe <em>what value<\/em> an argument should have, and functions are reusable tools you invoke inside those expressions to compute values dynamically.<\/p>\n<hr \/>\n<h2>1. What Is an Expression in Terraform?<\/h2>\n<p>An expression is any piece of HCL that Terraform can evaluate to a concrete value: a string, number, bool, list, map, or object. You use expressions in almost every place on the right-hand side of arguments, in <code>locals<\/code>, <code>count<\/code>, <code>for_each<\/code>, <code>dynamic<\/code> blocks, and more.<\/p>\n<p>Common expression forms:<\/p>\n<ul>\n<li>Literals: <code>&quot;hello&quot;<\/code>, <code>5<\/code>, <code>true<\/code>, <code>null<\/code>, <code>[&quot;a&quot;, &quot;b&quot;]<\/code>, <code>{ env = &quot;dev&quot; }<\/code>.<\/li>\n<li>References: <code>var.region<\/code>, <code>local.tags<\/code>, <code>aws_instance.app.id<\/code>, <code>module.vpc.vpc_id<\/code>.<\/li>\n<li>Operators: arithmetic (<code>+ - * \/ %<\/code>), comparisons (<code>&lt; &gt; &lt;= &gt;=<\/code>), equality (<code>== !=<\/code>), logical (<code>&amp;&amp; || !<\/code>).<\/li>\n<li>Conditionals: <code>condition ? value_if_true : value_if_false<\/code>.<\/li>\n<li><code>for<\/code> expressions: <code>[for s in var.subnets : upper(s)]<\/code> to transform collections.<\/li>\n<li>Splat expressions: <code>aws_instance.app[*].id<\/code> to project attributes out of collections.<\/li>\n<\/ul>\n<p><strong>Rationale:<\/strong> Terraform must stay declarative (you describe the desired state), but real infrastructure is not static; expressions give you a minimal \u201clanguage\u201d to derive values from other values without dropping into a general-purpose programming language.<\/p>\n<hr \/>\n<h2>2. What Is a Function?<\/h2>\n<p>A function is a built\u2011in helper you call <em>inside<\/em> expressions to transform or combine values. The syntax is a function name followed by comma\u2011separated arguments in parentheses, for example <code>max(5, 12, 9)<\/code>. Functions always return a value, so they can appear anywhere a normal expression is allowed.<\/p>\n<p>Key properties:<\/p>\n<ul>\n<li>Terraform ships many built\u2011in functions (string, numeric, collection, IP\/network, crypto, time, type conversion, etc.).<\/li>\n<li>You cannot define your own functions in HCL; you only use built\u2011ins, plus any provider-defined functions a provider may export.<\/li>\n<li>Provider-defined functions are namespaced like <code>provider::&lt;local-name&gt;::function_name(...)<\/code> when used.<\/li>\n<\/ul>\n<p>Examples of useful built\u2011in functions:<\/p>\n<ul>\n<li>String: <code>upper(&quot;dev&quot;)<\/code>, <code>lower()<\/code>, <code>format()<\/code>, <code>join(&quot;-&quot;, [&quot;app&quot;, &quot;dev&quot;])<\/code>.<\/li>\n<li>Numeric: <code>max(5, 12, 9)<\/code>, <code>min()<\/code>, <code>ceil()<\/code>, <code>floor()<\/code>.<\/li>\n<li>Collection: <code>length(var.subnets)<\/code>, <code>merge(local.tags, local.extra_tags)<\/code>, <code>flatten()<\/code>.<\/li>\n<\/ul>\n<p><strong>Rationale:<\/strong> Functions cover the common transformation needs (naming, list\/map manipulation, math) so that your Terraform remains expressive but compact, and you avoid copy\u2011pasting \u201cstring\u2011mangling\u201d logic everywhere.<\/p>\n<hr \/>\n<h2>3. How Expressions and Functions Work Together<\/h2>\n<p>Terraform\u2019s model is <em>expression\u2011centric<\/em>: on the right\u2011hand side of almost every argument, you write an expression, and <em>function calls are just one kind of expression<\/em>. You freely compose references, operators, conditionals, <code>for<\/code> expressions, and functions, as long as the input and output types match.<\/p>\n<p>Typical composition patterns:<\/p>\n<ul>\n<li>Use references (<code>var.*<\/code>, <code>local.*<\/code>, resource attributes) as the base inputs.<\/li>\n<li>Apply operators and conditional expressions to make decisions (<code>var.env == &quot;prod&quot; ? 3 : 1<\/code>).<\/li>\n<li>Use <code>for<\/code> expressions and collection functions to reshape data (<code>[for s in var.subnets : upper(s)]<\/code>).<\/li>\n<li>Use string functions to build consistent resource names (<code>format(&quot;%s-%s&quot;, var.app, var.env)<\/code>).<\/li>\n<\/ul>\n<p>From a mental-model perspective, a good way to think about this is: <em>\u201cEverything dynamic lives in expressions; functions are building blocks inside those expressions.\u201d<\/em><\/p>\n<hr \/>\n<h2>4. A Small Example<\/h2>\n<p>Below is a minimal Terraform configuration that showcases expressions and functions together, and that you can actually run to observe evaluation results.<\/p>\n<h2>Example configuration (<code>main.tf<\/code>)<\/h2>\n<pre><code class=\"language-bash\">terraform {\n  required_version = &quot;&gt;= 1.6&quot;\n}\n\nvariable &quot;environment&quot; {\n  type    = string\n  default = &quot;dev&quot;\n}\n\nvariable &quot;app_servers&quot; {\n  type    = list(string)\n  default = [&quot;app-1&quot;, &quot;app-2&quot;, &quot;app-3&quot;]\n}\n\nlocals {\n  # Expression: equality operator -&gt; bool\n  is_prod = var.environment == &quot;prod&quot;\n\n  # Literal map and reference\n  base_tags = {\n    app         = &quot;payments&quot;\n    environment = var.environment\n  }\n\n  # For expression + string function\n  uppercased_servers = [for s in var.app_servers : upper(s)]\n\n  # Merge and format functions to compute a name once\n  common_tags = merge(\n    local.base_tags,\n    {\n      name = format(\n        &quot;%s-%s-%02d&quot;,\n        local.base_tags.app,\n        local.base_tags.environment,\n        length(var.app_servers)\n      )\n    }\n  )\n}\n\noutput &quot;summary&quot; {\n  value = {\n    is_prod            = local.is_prod\n    uppercased_servers = local.uppercased_servers\n    common_tags        = local.common_tags\n  }\n}<\/code><\/pre>\n<p>What this demonstrates conceptually:<\/p>\n<ul>\n<li><strong>Expressions<\/strong>:\n<ul>\n<li><code>var.environment == &quot;prod&quot;<\/code> produces a bool for <code>local.is_prod<\/code>.<\/li>\n<li>The map in <code>local.base_tags<\/code> uses both literals and references.<\/li>\n<li>The <code>locals<\/code> block itself is a way to give names to intermediate expressions.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Functions<\/strong>:\n<ul>\n<li><code>upper(s)<\/code> transforms each server name to uppercase inside a <code>for<\/code> expression.<\/li>\n<li><code>length(var.app_servers)<\/code> computes the number of servers.<\/li>\n<li><code>format(&quot;%s-%s-%02d&quot;, ...)<\/code> builds a stable name string.<\/li>\n<li><code>merge(...)<\/code> combines two maps into a single tag map.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><strong>Rationale:<\/strong> This pattern\u2014variables + locals + expressions + functions\u2014is exactly how you avoid repetition and keep a production Terraform codebase readable as it grows.<\/p>\n<hr \/>\n<h2>5. How to Validate This Example<\/h2>\n<p>Terraform provides an expression console and standard workflow commands to validate that your expressions and functions behave as expected before they affect real infrastructure.<\/p>\n<h2>Option A: Run the configuration<\/h2>\n<ol>\n<li>Save the example as <code>main.tf<\/code> in an empty directory.<\/li>\n<li>Run:\n<ul>\n<li><code>terraform init<\/code> to set up the working directory.<\/li>\n<li><code>terraform apply -auto-approve<\/code> to evaluate and show outputs.<\/li>\n<\/ul>\n<\/li>\n<li>Observe the <code>summary<\/code> output:\n<ul>\n<li><code>is_prod<\/code> should be <code>false<\/code> (with the default environment <code>dev<\/code>).<\/li>\n<li><code>uppercased_servers<\/code> should be <code>[&quot;APP-1&quot;, &quot;APP-2&quot;, &quot;APP-3&quot;]<\/code>.<\/li>\n<li><code>common_tags.name<\/code> should be <code>payments-dev-03<\/code>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>To see how expressions react to different inputs, run again with a different environment:<\/p>\n<pre><code class=\"language-bash\">terraform apply -auto-approve -var &#039;environment=prod&#039;<\/code><\/pre>\n<p>Now <code>is_prod<\/code> will be <code>true<\/code>, and the computed <code>name<\/code> will switch to <code>payments-prod-03<\/code>, even though you haven\u2019t changed any resource definitions.<\/p>\n<h2>Option B: Experiment interactively with <code>terraform console<\/code><\/h2>\n<p>Terraform\u2019s console lets you test expressions and functions on the fly.<\/p>\n<p>From the same directory:<\/p>\n<pre><code class=\"language-bash\">terraform console<\/code><\/pre>\n<p>Then try:<\/p>\n<pre><code class=\"language-bash\">> 1 + 2 * 3\n> var.environment == &quot;prod&quot;\n> [for s in var.app_servers : upper(s)]\n> merge({a = 1}, {b = 2})\n> format(&quot;%s-%s&quot;, &quot;app&quot;, var.environment)<\/code><\/pre>\n<p>You will see the evaluated results immediately, which is ideal for teaching yourself how a particular expression or function behaves before embedding it into a real module.<\/p>\n<hr \/>\n<h2>6. Summary Table: Expressions vs Functions<\/h2>\n<table>\n<thead>\n<tr>\n<th>Aspect<\/th>\n<th>Expressions<\/th>\n<th>Functions<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Purpose<\/td>\n<td>Describe how to compute a value.<\/td>\n<td>Provide reusable operations used inside expressions.<\/td>\n<\/tr>\n<tr>\n<td>Examples<\/td>\n<td><code>var.env == &quot;prod&quot;<\/code>, <code>[for s in xs : x.id]<\/code>.<\/td>\n<td><code>length(var.subnets)<\/code>, <code>join(&quot;-&quot;, local.tags)<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>Defined by<\/td>\n<td>Terraform language syntax.<\/td>\n<td>Terraform\u2019s built\u2011in and provider-defined function library.<\/td>\n<\/tr>\n<tr>\n<td>Customization<\/td>\n<td>Composed via locals, variables, and blocks.<\/td>\n<td>No user-defined functions in HCL; only built\u2011ins\/providers.<\/td>\n<\/tr>\n<tr>\n<td>Typical usage domain<\/td>\n<td>Conditionals, loops, references, constructing structures.<\/td>\n<td>String formatting, math, collection manipulation, conversion.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n","protected":false},"excerpt":{"rendered":"<p>Terraform\u2019s expression and function system is the core \u201cthinking engine\u201d behind your configurations: expressions describe what value an argument should have, and functions are reusable tools you invoke inside those expressions to compute values dynamically. 1. What Is an Expression in Terraform? An expression is any piece of HCL that Terraform can evaluate to a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[94],"tags":[],"_links":{"self":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2133"}],"collection":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2133"}],"version-history":[{"count":1,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2133\/revisions"}],"predecessor-version":[{"id":2134,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/2133\/revisions\/2134"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}