» Import: tfplan

The tfplan import provides access to a Terraform plan. A Terraform plan is the file created as a result of terraform plan and is the input to terraform apply. The plan represents the changes that Terraform needs to make to infrastructure to reach the desired state represented by the configuration.

In addition to the diff data available in the plan, there is an applied state available that merges the plan with the state to create the planned state after apply.

Finally, this import also allows you to access the configuration files and the Terraform state at the time the plan was run. See the section on accessing a plan's state and configuration data for more information.

» The Namespace

The following is a tree view of the import namespace. For more detail on a particular part of the namespace, see below.

tfplan
├── module() (function)
│   └── (module namespace)
│       ├── path ([]string)
│       ├── data
│       │   └── TYPE.NAME[NUMBER]
│       │       ├── applied (map of keys)
│       │       └── diff
│       │           └── KEY
│       │               ├── computed (bool)
│       │               ├── new (string)
│       │               └── old (string)
│       └── resources
│           └── TYPE.NAME[NUMBER]
│               ├── applied (map of keys)
│               └── diff
│                   └── KEY
│                       ├── computed (bool)
│                       ├── new (string)
│                       └── old (string)
├── module_paths ([][]string)
├── terraform_version (string)
├── variables (map of keys)
│
├── data (root module alias)
├── path (root module alias)
├── resources (root module alias)
│
├── config (tfconfig namespace alias)
└── state (tfstate import alias)

» Namespace: Root

The root-level namespace consists of the values and functions documented below.

In addition to this, the root-level data, path, and resources keys alias to their corresponding namespaces or values within the module namespace.

» Accessing a Plan's State and Configuration Data

The config and state keys alias to the tfconfig and tfstate namespaces, respectively, with the data sourced from the Terraform plan (as opposed to actual configuration and state).

» Function: module()

module = func(ADDR)

The module() function in the root namespace returns the module namespace for a particular module address.

The address must be a list and is the module address, split on the period (.), excluding the root module.

Hence, a module with an address of simply foo (or root.foo) would be ["foo"], and a module within that (so address foo.bar) would be read as ["foo", "bar"].

null is returned if a module address is invalid, or if the module is not present in the diff.

As an example, given the following module block:

module "foo" {
  # ...
}

If the module contained the following content:

resource "null_resource" "foo" {
  triggers = {
    foo = "bar"
  }
}

The following policy would evaluate to true:

import "tfplan"

main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" }

» Value: module_paths

  • Value Type: List of a list of strings.

The module_paths value within the root namespace is a list of all of the modules within the Terraform diff for the current plan.

Modules not present in the diff will not be present here, even if they are present in the configuration or state.

This data is represented as a list of a list of strings, with the inner list being the module address, split on the period (.).

The root module is included in this list, represented as an empty inner list, as long as there are changes.

As an example, if the following module block was present within a Terraform configuration:

module "foo" {
  # ...
}

The value of module_paths would be:

[
    [],
    ["foo"],
]

And the following policy would evaluate to true:

import "tfplan"

main = rule { tfplan.module_paths contains ["foo"] }

» Iterating through modules

Iterating through all modules to find particular resources can be useful. This example shows how to use module_paths with the module() function to retrieve all resources of a particular type from all modules (in this case, the azurerm_virtual_machine resource). Note the use of else [] in case some modules don't have any resources; this is necessary to avoid the function returning undefined.

Remember again that this will only locate modules (and hence resources) that have pending changes.

import "tfplan"

get_vms = func() {
    vms = []
    for tfplan.module_paths as path {
        vms += values(tfplan.module(path).resources.azurerm_virtual_machine) else []
    }
    return vms
}

» Value: terraform_version

  • Value Type: String.

The terraform_version value within the root namespace represents the version of Terraform used to create the plan. This can be used to enforce a specific version of Terraform in a policy check.

As an example, the following policy would evaluate to true, as long as the plan was made with a version of Terraform in the 0.11.x series, excluding any pre-release versions (example: -beta1 or -rc1):

import "tfplan"

main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" }

» Value: variables

  • Value Type: A string-keyed map of values.

The variables value within the root namespace represents all of the variables that were set when creating the plan. This will only contain variables set for the root module.

Note that unlike the default value in the tfconfig variables namespace, primitive values here are stringified, and type conversion will need to be performed to perform comparison for int, float, or boolean values. This only applies to variables that are primitives themselves and not primitives within maps and lists, which will be their original types.

If a default was accepted for the particular variable, the default value will be populated here.

As an example, given the following variable blocks:

variable "foo" {
  default = "bar"
}

variable "number" {
  default = 42
}

variable "map" {
  default = {
    foo    = "bar"
    number = 42
  }
}

The following policy would evaluate to true, if no values were entered to change these variables:

import "tfplan"

default_foo = rule { tfplan.variables.foo is "bar" }
default_number = rule { tfplan.variables.number is "42" }
default_map_string = rule { tfplan.variables.map["foo"] is "bar" }
default_map_int = rule { tfplan.variables.map["number"] is 42 }

main = rule { default_foo and default_number and default_map_string and default_map_int }

» Namespace: Module

The module namespace can be loaded by calling module() for a particular module.

It can be used to load the following child namespaces, in addition to the values documented below:

» Root Namespace Aliases

The root-level data and resources keys both alias to their corresponding namespaces within the module namespace, loaded for the root module. They are the equivalent of running module([]).KEY.

» Value: path

  • Value Type: List of strings.

The path value within the module namespace contains the path of the module that the namespace represents. This is represented as a list of strings.

As an example, if the following module block was present within a Terraform configuration:

module "foo" {
  # ...
}

The following policy would evaluate to true only if the diff had changes for that module:

import "tfplan"

main = rule { tfplan.module(["foo"]).path contains "foo" }

» Namespace: Resources/Data Sources

The resource namespace is a namespace type that applies to both resources (accessed by using the resources namespace key) and data sources (accessed using the data namespace key).

Accessing an individual resource or data source within each respective namespace can be accomplished by specifying the type, name, and resource number (as if the resource or data source had a count value in it) in the syntax [resources|data].TYPE.NAME[NUMBER]. Note that NUMBER is always needed, even if you did not use count in the resource.

In addition, each of these namespace levels is a map, allowing you to filter based on type and name.

Some examples of multi-level access are below:

  • To fetch all aws_instance.foo resource instances within the root module, you can specify tfplan.resources.aws_instance.foo. This would then be indexed by resource count index (0, 1, 2, and so on). Note that as mentioned above, these elements must be accessed using square-bracket map notation (so [0], [1], [2], and so on) instead of dotted notation.
  • To fetch all aws_instance resources within the root module, you can specify tfplan.resources.aws_instance. This would be indexed off of the names of each resource (foo, bar, and so on), with each of those maps containing instances indexed by resource count index as per above.
  • To fetch all resources within the root module, irrespective of type, use tfplan.resources. This is indexed by type, as shown above with tfplan.resources.aws_instance, with names being the next level down, and so on.

Further explanation of the namespace will be in the context of resources. As mentioned, when operating on data sources, use the same syntax, except with data in place of resources.

» Value: applied

  • Value Type: A string-keyed map of values.

The applied value within the resource namespace contains a "predicted" representation of the resource's state post-apply. It's created by merging the pending resource's diff on top of the existing data from the resource's state (if any).

The map is a complex representation of these values with data going as far down as needed to represent any state values such as maps, lists, and sets.

Note that some values will not be available in the applied state because they cannot be known until the plan is actually applied. These values are represented by a placeholder (the UUID value 74D93920-ED26-11E3-AC10-0800200C9A66). This is not a stable API and should not be relied on. Instead, use the computed key within the diff namespace to determine if a value is known or not.

As an example, given the following resource:

resource "null_resource" "foo" {
  triggers = {
    foo = "bar"
  }
}

The following policy would evaluate to true if the resource was in the diff:

import "tfplan"

main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" }

» Value: diff

The diff value within the resource namespace contains the diff for a particular resource. Each key within the map links to a diff namespace for that particular key.

Note that unlike the applied value, this map is not complex; the map is only 1 level deep with each key possibly representing a diff for a particular complex value within the resource.

See the below section for more details on the diff namespace, in addition to usage examples.

» Namespace: Resource Diff

The diff namespace is a namespace that represents the diff for a specific attribute within a resource. For details on reading a particular attribute, see the diff value in the resource namespace.

» Value: computed

  • Value Type: Boolean.

The computed value within the diff namespace is true if the resource key in question depends on another value that isn't yet known. Typically, that means the value it depends on belongs to a resource that either doesn't exist yet, or is changing state in such a way as to affect the dependent value so that it can't be known until the apply is complete.

As an example, given the following resource:

resource "null_resource" "foo" {
  triggers = {
    foo = "bar"
  }
}

resource "null_resource" "bar" {
  triggers = {
    foo_id = "${null_resource.foo.id}"
  }
}

The following policy would evaluate to true, if the id of null_resource.foo was currently not known, such as when the resource is pending creation, or is being deleted and re-created:

import "tfplan"

main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed }

» Value: new

  • Value Type: String.

The new value within the diff namespace contains the new value of a changing attribute, if the value is known at plan time.

Note that this value is always a string, regardless of the actual type of the value changing. Type conversion within policy may be necessary to achieve the comparison needed.

As an example, given the following resource:

resource "null_resource" "foo" {
  triggers = {
    foo = "bar"
  }
}

The following policy would evaluate to true, if the resource was in the diff and each of the concerned keys were changing to new values:

import "tfplan"

main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" }

» Value: old

  • Value Type: String.

The old value within the diff namespace contains the old value of a changing attribute.

Note that this value is always a string, regardless of the actual type of the value changing. Type conversion within policy may be necessary to achieve the comparison needed.

If the value did not exist in the previous state, old will always be an empty string.

As an example, given the following resource:

resource "null_resource" "foo" {
  triggers = {
    foo = "baz"
  }
}

If that resource was previously in config as:

resource "null_resource" "foo" {
  triggers = {
    foo = "bar"
  }
}

The following policy would evaluate to true:

import "tfplan"

main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" }