Azure Citadel
  • Blogs

  • ARM
  • Azure Arc
    • Overview
    • Azure Arc-enabled Servers
      • Prereqs
      • Scenario
      • Hack Overview
      • Azure Landing Zone
      • Arc Pilot resource group
      • Azure Monitoring Agent
      • Additional policy assignments
      • Access your on prem VMs
      • Create onboarding scripts
      • Onboarding using scripts
      • Inventory
      • Monitoring
      • SSH
      • Windows Admin Center
      • Governance
      • Custom Script Extension
      • Key Vault Extension
      • Managed Identity
    • Azure Arc-enabled Kubernetes
      • Prereqs
      • Background
      • Deploy Cluster
      • Connect to Arc
      • Enable GitOps
      • Deploy Application
      • Enable Azure AD
      • Enforce Policy
      • Enable Monitoring
      • Enable Azure Defender
      • Enable Data Services
      • Enable Application Delivery
    • Useful Links
  • Azure CLI
    • Install
    • Get started
    • JMESPATH queries
    • Integrate with Bash
  • Azure Landing Zones
    • Prereqs
    • Day 1
      • Azure Baristas
      • Day 1 Challenge
    • Day 2
      • Example
      • Day 2 Challenge
    • Day 3
      • Day 3 Challenge
    • Useful Links
  • Azure Policy
    • Azure Policy Basics
      • Policy Basics in the Azure Portal
      • Creating Policy via the CLI
      • Deploy If Not Exists
      • Management Groups and Initiatives
    • Creating Custom Policies
      • Customer scenario
      • Policy Aliases
      • Determine the logic
      • Create the custom policy
      • Define, assign and test
  • Azure Stack HCI
    • Overview
    • Useful Links
    • Updates from Microsoft Ignite 2022
  • Marketplace
    • Introduction
      • Terminology
      • Offer Types
    • Partner Center
    • Offer Type
    • Publish a VM Offer HOL
      • Getting Started
      • Create VM Image
      • Test VM Image
      • VM Offer with SIG
      • VM Offer with SAS
      • Publish Offer
    • Other VM Resources
    • Publish a Solution Template HOL
      • Getting Started
      • Create ARM Template
      • Validate ARM Template
      • Create UI Definition
      • Package Assets
      • Publish Offer
    • Publish a Managed App HOL
      • Getting Started
      • Create ARM Template
      • Validate ARM Template
      • Create UI Definition
      • Package Assets
      • Publish Offer
    • Managed Apps with AKS HOL
    • Other Managed App Resources
    • SaaS Offer HOLs
    • SaaS Offer Video Series
      • Video 1 - SaaS Offer Overview
      • Video 2 - Purchasing a SaaS Offer
      • Video 3 - Purchasing a Private SaaS Plan
      • Video 4 - Publishing a SaaS Offer
      • Video 5 - Publishing a Private SaaS Plan
      • Video 6 - SaaS Offer Technical Overview
      • Video 7 - Azure AD Application Registrations
      • Video 8 - Using the SaaS Offer REST Fulfillment API
      • Video 9 - The SaaS Client Library for .NET
      • Video 10 - Building a Simple SaaS Landing Page in .NET
      • Video 11 - Building a Simple SaaS Publisher Portal in .NET
      • Video 12 - SaaS Webhook Overview
      • Video 13 - Implementing a Simple SaaS Webhook in .NET
      • Video 14 - Securing a Simple SaaS Webhook in .NET
      • Video 15 - SaaS Metered Billing Overview
      • Video 16 - The SaaS Metered Billing API with REST
  • Microsoft Fabric
    • Theory
    • Prereqs
    • Fabric Capacity
    • Set up a Remote State
    • Create a repo from a GitHub template
    • Configure an app reg for development
    • Initial Terraform workflow
    • Expanding your config
    • Configure a workload identity
    • GitHub Actions for Microsoft Fabric
    • GitLab pipeline for Microsoft Fabric
  • Packer & Ansible
    • Packer
    • Ansible
    • Dynamic Inventories
    • Playbooks & Roles
    • Custom Roles
    • Shared Image Gallery
  • Partner
    • Lighthouse and Partner Admin Link
      • Microsoft Cloud Partner Program
      • Combining Lighthouse and PAL
      • Minimal Lighthouse definition
      • Using service principals
      • Privileged Identity Management
    • Useful Links
  • REST API
    • REST API theory
    • Using az rest
  • Setup
  • Terraform
    • Fundamentals
      • Initialise
      • Format
      • Validate
      • Plan
      • Apply
      • Adding resources
      • Locals and outputs
      • Managing state
      • Importing resources
      • Destroy
    • Working Environments for Terraform
      • Cloud Shell
      • macOS
      • Windows with PowerShell
      • Windows with Ubuntu in WSL2
    • Using AzAPI
      • Using the REST API
      • azapi_resource
      • Removing azapi_resource
      • azapi_update_resource
      • Data sources and outputs
      • Removing azapi_update_resource
  • Virtual Machines
    • Azure Bastion with native tools & AAD
    • Managed Identities

  • About
  • Archive
  1. Home
  2. Terraform
  3. Fundamentals
  4. Locals and outputs

Table of Contents

  • Overview
  • Starting point
  • Functions
  • Terraform console
  • Locals
  • Outputs
    • ip_address
    • fqdn
  • Terraform workflow
  • Viewing outputs
  • Summary

Locals and outputs

Use locals and functions to generate a unique value, and add a couple of outputs.

Overview

In this lab you will

  • work with terraform console
  • use a local variable and functions
  • add outputs for the FQDN and public IP address

Starting point

Your files should look similar to this:

  • provider.tf

    terraform {
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>3.1"
        }
      }
    }
    
    provider "azurerm" {
      features {}
    
      storage_use_azuread = true
    }
    
  • variables.tf

    variable "resource_group_name" {
      description = "Name for the resource group"
      type        = string
      default     = "terraform-basics"
    }
    
    variable "location" {
      description = "Azure region"
      type        = string
      default     = "West Europe"
    }
    
    variable "container_group_name" {
      description = "Name of the container group"
      type        = string
      default     = "terraform-basics"
    }
    
    variable "prefix" {
      description = "Prefix string to ensure FQDNs are globally unique"
      type        = string
    }
    
  • main.tf

    resource "azurerm_resource_group" "basics" {
      name     = var.resource_group_name
      location = var.location
    }
    
    resource "azurerm_container_group" "example" {
      name                = var.container_group_name
      location            = azurerm_resource_group.basics.location
      resource_group_name = azurerm_resource_group.basics.name
      ip_address_type     = "Public"
      dns_name_label      = "${var.prefix}-${var.container_group_name}"
      os_type             = "Linux"
    
      container {
        name   = "inspectorgadget"
        image  = "jelledruyts/inspectorgadget:latest"
        cpu    = "0.5"
        memory = "1.0"
    
        ports {
          port     = 80
          protocol = "TCP"
        }
      }
    }
    
    
  • terraform.tfvars

    location = "UK South"
    prefix = "richeney"
    

    ⚠️ You should have specified a different value for prefix. You may have used a different value for location.

Functions

In this section we will remove the var.prefix and instead use a substring of a predictable hash. This will give us sufficient random characters to add to properties such as FQDNs and be confident that they will be globally unique.

Using a hash substring is closer in behaviour to the commonly used uniqueString() function in ARM templates. The resourceId of a resource group is commonly used as a seed. It contains the subscription ID, so meets the uniqueness requirement, yet will always produce the same result in your config even if you were to destroy the environment and start again, which is a good fit for idempotency.

If you wanted to have a newly generated random string each time then look at random_id, which is in the random provider available in the Terraform Registry.

OK, let’s work out how to find the right function and

  1. Search on “terraform functions” in your browser

    You should find the Terraform Built-in Functions page.

    Feel free to browse the available functions.

  2. Select the Hash and Crypto Functions

  3. Select the sha1 function

    The sha1 encryption is not the strongest from a security perspective, but we only need this to provide a hash from the given string.

The function call is sha1("string").

Terraform console

We now know the function and how to call it. Test it out in the Terraform console.

  1. List the objects in state

    terraform state list
    

    Example output:

    azurerm_container_group.example
    azurerm_resource_group.basics
    

    The correct Terraform identifier for the resource group is azurerm_resource_group.basics. You will need that later when working out how to get the resource ID for the resource group.

  2. Open the Terraform console

    terraform console
    

    This is an interactive console which is ideal for testing expressions and interpolation and for interrogating the state.

    Use CTRL+D at an empty prompt to exit the console. This is the end of file (EOF) character.

  3. Test the sha1 function

    sha1("This is a string")
    

    Expected hash:

    "f72017485fbf6423499baf9b240daa14f5f095a1"
    

    If you use the up arrow to recall the command you can see if will always return the same hash.

  4. Find the resource ID attribute for the resource group

    Remember when you looked at the documentation page for azurerm_resource_group that id was one of the attributes.

    azurerm_resource_group.basics
    

    Example response:

    {
      "id" = "/subscriptions/2ca40be1-7e80-4f2b-92f7-06b2123a68cc/resourceGroups/terraform-basics"
      "location" = "uksouth"
      "name" = "terraform-basics"
      "tags" = tomap({})
      "timeouts" = null /* object */
    }
    
  5. Drill down to the id attribute

    azurerm_resource_group.basics.id
    

    Example response:

    "/subscriptions/2ca40be1-7e80-4f2b-92f7-06b2123a68cc/resourceGroups/terraform-basics"
    

    Your response will contain a different subscription ID.

  6. Retest sha1 with the resourceId

    sha1(azurerm_resource_group.basics.id)
    

    Example output:

    "c3818179c2946e2352e5210e826239e65f5c3396"
    

    That is a little long. Eight characters should be sufficient to make it unique.

    Your response will be different as your resource group ID contains a different subscription ID.

  7. Find a suitable function

    💪 Mini challenge!

    Search for a Terraform function to take a substring.

    • What is the name of the function?
    • What are the three arguments?
  8. Extract the first eight characters from the hash

    Enter the following into the terraform console:

    substr(sha1(azurerm_resource_group.basics.id), 0, 8)
    

    Example output:

    "c3818179"
    

    Your output will be unique to you. It will also be entirely predictable. We have the right expression.

  9. Exit the console

    Use CTRL+d to leave the console prompt and go back to the shell.

The console is great for forming and testing expressions. We have settled on substr(sha1(azurerm_resource_group.basics.id), 0, 8) which will be used as a suffix. Let’s declare the local.

Locals

The variables we have been using so far are more accurately called input variables, i.e. var.<variable_name>. (Close to the parameters in ARM and Bicep templates.)

It is common to place more complex expression into locals so that the main resource and data source blocks are more readable. (Much like the variables in ARM and Bicep templates.)

We’ll define a local variable for the unique value, and call it uniq. Local variables are defined in a locals block.

  1. Add the following block to the top of your main.tf

    locals {
      uniq = substr(sha1(azurerm_resource_group.basics.id), 0, 8)
    }
    
  2. Modify value for dns_name_label

    Update the interpolation expression for dns_name_label in the azurerm_container_group resource:

      dns_name_label = "${var.container_group_name}-${local.uniq}"
    

    Note that locals are referenced as local.<local_name>.

  3. Delete the prefix variable

    This is no longer required. Remove it from variables.tf and terraform.tfvars.

Outputs

There are a couple of attributes whose values are unknown until they;ve been created. One is the public IPv4 address, and the other is the fully qualified domain name now that it is suffixed with the uniq local variable.

The convention with Terraform is to place all outputs in a file called outputs.tf. The lab will walk you through creating one and then you’ll be challenged to do the other.

ip_address

  1. Open the console

    terraform console
    
  2. Query the Azure Container Instance

    azurerm_container_group.example
    
  3. Use dot notation to get the public IPv4 address

    azurerm_container_group.example.ip_address
    

    This should return you container instance’s current public IP address.

  4. Create a file called outputs.tf and add the following block

    output "ip_address" {
      value = azurerm_container_group.example.ip_address
    }
    

fqdn

OK, time for another little challenge section.

💪 Challenge: Add the fqdn output.

  • Add another output, called fqdn
  • Set the value to the azurerm_container_group’s fqdn attribute, prefixed with http://.

Terraform workflow

Run through the terraform workflow.

  1. Format the files

    terraform fmt
    
  2. Validate the config

    terraform validate
    
  3. Check the plan

    terraform plan
    

    ⚠️ Note that the ACI will be deleted and recreated due to the name change.

  4. Apply the changes

    terraform apply
    

    ℹ️ Note that the two values are now shown as outputs.

Viewing outputs

  • Display all outputs

    terraform output
    
  • Display a single output

    terraform output fqdn
    
  • Set a shell variable to an output value

    Bash:

    ip_address=$(terraform output -raw ip_address)
    

    Powershell:

    $ip_address = (terraform output -raw ip_address)
    
  • Read all objects as JSON / Powershell object

    This is slightly more advanced.

    If you are dealing with more complex arrays and objects tjen you can read the whole output into a JSON string in Bash, or an object in PowerShell.

    You can then view the whole object, or drill in using jq and object dot notation respectively.

    Bash:

    outputs=$(terraform output -json)
    jq -Mc . <<< $outputs
    jq . <<< $outputs
    jq -r .ip_address.value <<< $outputs
    

    Powershell:

    $outputs = (terraform output -json | ConvertFrom-Json)
    $outputs
    $outputs | ConvertTo-Json
    ($outputs).ip_address.value
    

Summary

You have started to use the Terraform console, and made use of locals and outputs.

You will use both of these more and more as your Terraform code becomes more ambitious. Locals are important for readability as the expressions become more complex. Outputs are useful at this level, but become far more important when you start creating your own modules.

In the next section we will manipulate state a little using imports, refresh, renames and taints.

Adding resources Locals and outputs Managing state