Terraform tips’n’tricks: debugging data sources and testing interpolations

I have previously blogged about the use of Terraform data sources to fetch information concerning Oracle Cloud Infrastructure (OCI) resources. The documentation is pretty good, but sometimes you may want to know more about the data returned. This post describes a potential way to debug output of a data source and to evaluate interpolations.

Do you know Data::Dumper?

Perl is one of the programming languages I have worked with in the past. When I did (it really was a long time ago) there wasn’t a proper IDE allowing me to have nice breakpoints and inspect variables so I resorted to the good, old Data::Dumper. It worked pretty much everywhere and showed me the contents of complex data structures when I was a bit at a loss. For example:

 #!/usr/bin/env perl

use strict;
use warnings;

use Data::Dumper qw(Dumper);

my %dataStructure = (
    key1 => {
        a => "b",
        c => "d",
    },
    key2 => {
        e => "f",
        g => "h"
    }
);

# ---
# main

print Dumper(\%dataStructure); 

When executed, the last line would print the contents of the data structure:

$ perl debuggingDataStructure.pl 
$VAR1 = {
          'key2' => {
                      'g' => 'h',
                      'e' => 'f'
                    },
          'key1' => {
                      'a' => 'b',
                      'c' => 'd'
                    }
        };
 

Who needs JSON if you can have hashes of hashes (and other data structures) in perl ;) More seriously though, why is this code relevant to the post? Please read on, there is a feature remarkably similar yet more powerful in Terraform.

Terraform console

When I first read about the Terraform console in the most excellent Terraform Up and Running, I didn’t pay too much attention to it. This proved to be wrong in hindsight, the console can do a lot more than I thought it could.

This example demonstrates how I can debug data structures in Terraform using the console. I take the example I blogged about recently: fetching an Oracle Cloud ID (OCID) for an Oracle-provided Linux 7 image. Please refer back to the post for more details. Let’s assume I have already run terraform apply.

Starting the console

That’s as simple as typing terraform console

$ terraform console
> help
The Terraform console allows you to experiment with Terraform interpolations.
You may access resources in the state (if you have one) just as you would
from a configuration. For example: "aws_instance.foo.id" would evaluate
to the ID of "aws_instance.foo" if it exists in your state.

Type in the interpolation to test and hit  to see the result.

To exit the console, type "exit" and hit , or use Control-C or
Control-D.
>   

A useful help message for starters :)

Debugging my data source

My Terraform code uses the following data source:

 data "oci_core_images" "ol7_latest" {
        compartment_id = var.compartment_ocid

        operating_system = "Oracle Linux"
        operating_system_version = "7.9"
        shape = "VM.Standard.E2.1.Micro"
}

As per the OCI provider’s documentation, the data source returns an object of type images (a list of images). Let’s see if I can dump any of that. I am using the full path to the images object as in data.data_source_type.data_source_name.images. Note that the output is shortened to the relevant information

$ terraform console
> data.oci_core_images.ol7_latest.images
tolist([
  {
    ...
    "display_name" = "Oracle-Linux-7.9-2021.01.12-0"
    "id" = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaa5w2lrmsn6wpjn7fbqv55curiarwsryqhoj4dw5hsixrl37hrinja"
    ...
    "operating_system" = "Oracle Linux"
    "operating_system_version" = "7.9"
    "size_in_mbs" = "47694"
    "state" = "AVAILABLE"
    "time_created" = "2021-01-11 19:05:21.301 +0000 UTC"
  },
  {
    ...
    "display_name" = "Oracle-Linux-7.9-2020.11.10-1"
    "id" = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaaf6gm7xvn7rhll36kwlotl4chm25ykgsje7zt2b4w6gae4yqfdfwa"
    ...
    "operating_system" = "Oracle Linux"
    "operating_system_version" = "7.9"
    "size_in_mbs" = "47694"
    "state" = "AVAILABLE"
    "time_created" = "2020-11-11 06:18:05.628 +0000 UTC"
  },
  {
    ...
    "display_name" = "Oracle-Linux-7.9-2020.10.26-0"
    ...
    "id" = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaaoryxmxvdjbbkuexacfomno4vb5zdoxuecx4wb3mbj5diisuui5ia"
    ...
    "operating_system" = "Oracle Linux"
    "operating_system_version" = "7.9"
    "size_in_mbs" = "47694"
    "state" = "AVAILABLE"
    "time_created" = "2020-10-27 06:33:38.068 +0000 UTC"
  },
])
> 

Right, so, the data source returns a list of 3 potential Oracle Linux 7.9 images I could choose from for my always-free VM. That’s nice to know! Although it doesn’t change my approach of taking the latest :)

Inspecting cloud resources

The console extrapolation isn’t limited to data sources. You can also look at cloud resources, such as this VCN:

resource "oci_core_vcn" "dummy_vcn" {
        compartment_id = var.compartment_ocid

        cidr_block = "10.0.0.0/16"
        display_name = "iAmaDemoVCN"
}

Note that you always get a “default security list” when you create a VCN. Please don’t use it as it opens up SSH from everywhere. Please see my previous post on the topic for an example.

Let’s create the VCN:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # oci_core_vcn.dummy_vcn will be created
  + resource "oci_core_vcn" "dummy_vcn" {
      + cidr_block               = "10.0.0.0/16"
      + cidr_blocks              = (known after apply)
      + compartment_id           = "ocid1.compartment.oc1..aaaaa..."
      + default_dhcp_options_id  = (known after apply)
      + default_route_table_id   = (known after apply)
      + default_security_list_id = (known after apply)
      + defined_tags             = (known after apply)
      + display_name             = "iAmaDemoVCN"
      + dns_label                = (known after apply)
      + freeform_tags            = (known after apply)
      + id                       = (known after apply)
      + ipv6cidr_block           = (known after apply)
      + ipv6public_cidr_block    = (known after apply)
      + is_ipv6enabled           = (known after apply)
      + state                    = (known after apply)
      + time_created             = (known after apply)
      + vcn_domain_name          = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

oci_core_vcn.dummy_vcn: Creating...
oci_core_vcn.dummy_vcn: Creation complete after 0s [id=ocid1.vcn.oc1.eu-frankfurt-1.a..]

After Terraform finished creating the VCN I can display the “known after apply” values in the console:

$ terraform console
> oci_core_vcn.dummy_vcn
{
  "cidr_block" = "10.0.0.0/16"
  "cidr_blocks" = tolist([
    "10.0.0.0/16",
  ])
  "compartment_id" = "ocid1.compartment.oc1..aaa..."
  "default_dhcp_options_id" = "ocid1.dhcpoptions.oc1.eu-frankfurt-1.aaa..."
  "default_route_table_id" = "ocid1.routetable.oc1.eu-frankfurt-1.aaa..."
  "default_security_list_id" = "ocid1.securitylist.oc1.eu-frankfurt-1.aaa..."
  "defined_tags" = tomap({
    "Oracle-Tags.CreatedBy" = "someuser"
    "Oracle-Tags.CreatedOn" = "2021-02-01T19:08:00.774Z"
  })
  "display_name" = "iAmaDemoVCN"
  "dns_label" = tostring(null)
  "freeform_tags" = tomap({})
  "id" = "ocid1.vcn.oc1.eu-frankfurt-1.a..."
  "ipv6cidr_block" = tostring(null)
  "ipv6public_cidr_block" = tostring(null)
  "is_ipv6enabled" = tobool(null)
  "state" = "AVAILABLE"
  "time_created" = "2021-02-01T19:08:00.774Z"
  "timeouts" = null /* object */
  "vcn_domain_name" = tostring(null)
} 

Note that unlike with data sources I didn’t have to specify “resource” at the beginning. Doing so would result in an error telling you that a managed resource “resource” “resource_type” has not been declared in the root module.

Summary

Terraform’s console is a very useful tool for debugging your Terraform resources. Remember that when you want to inspect data sources, you need the “data” prefix. When looking at resources, you simply use the resource_type.resource_name syntax.