» Import: tfconfig/v2

The tfconfig/v2 import provides access to a Terraform configuration.

The Terraform configuration is the set of *.tf files that are used to describe the desired infrastructure state. Policies using the tfconfig import can access all aspects of the configuration: providers, resources, data sources, modules, and variables.

Some use cases for tfconfig include:

  • Organizational naming conventions: requiring that configuration elements are named in a way that conforms to some organization-wide standard.
  • Required inputs and outputs: organizations may require a particular set of input variable names across all workspaces or may require a particular set of outputs for asset management purposes.
  • Enforcing particular modules: organizations may provide a number of "building block" modules and require that each workspace be built only from combinations of these modules.
  • Enforcing particular providers or resources: an organization may wish to require or prevent the use of providers and/or resources so that configuration authors cannot use alternative approaches to work around policy restrictions.

The data in the tfconfig/v2 import is sourced from the JSON configuration file that is generated by the terraform show -json command. For more information on the file format, see the JSON Output Format page.

» Import Overview

The tfconfig/v2 import is structured as a series of collections, keyed as a specific format, such as resource address, module address, or a specifically-formatted provider key.

tfconfig/v2
├── strip_index() (function)
├── providers
│   └── (indexed by [module_address:]provider[.alias])
│       ├── provider_config_key (string)
│       ├── name (string)
│       ├── alias (string)
│       ├── module_address (string)
│       ├── config (block expression representation)
│       └── version_constraint (string)
├── resources
│   └── (indexed by address)
│       ├── address (string)
│       ├── module_address (string)
│       ├── mode (string)
│       ├── type (string)
│       ├── name (string)
│       ├── provider_config_key (string)
│       ├── provisioners (list)
│       │   └── (ordered provisioners for this resource only)
│       ├── config (block expression representation)
│       ├── count (expression representation)
│       ├── for_each (expression representation)
│       └── depends_on (list of strings)
├── provisioners
│   └── (indexed by resource_address:index)
│       ├── resource_address (string)
│       ├── type (string)
│       ├── index (string)
│       └── config (block expression representation)
├── variables
│   └── (indexed by module_address:name)
│       ├── module_address (string)
│       ├── name (string)
│       ├── default (value)
│       └── description (string)
├── outputs
│   └── (indexed by module_address:name)
│       ├── module_address (string)
│       ├── name (string)
│       ├── sensitive (boolean)
│       ├── value (expression representation)
│       ├── description (string)
│       └── depends_on (list of strings)
└── module_calls
    └── (indexed by module_address:name)
        ├── module_address (string)
        ├── name (string)
        ├── source (string)
        ├── config (block expression representation)
        ├── count (expression representation)
        ├── depends_on (expression representation)
        ├── for_each (expression representation)
        └── version_constraint (string)

The collections are:

  • providers - The configuration for all provider instances across all modules in the configuration.
  • resources - The configuration of all resources across all modules in the configuration.
  • variables - The configuration of all variable definitions across all modules in the configuration.
  • outputs - The configuration of all output definitions across all modules in the configuration.
  • module_calls - The configuration of all module calls (individual module blocks) across all modules in the configuration.

These collections are specifically designed to be used with the filter quantifier expression in Sentinel, so that one can collect a list of resources to perform policy checks on without having to write complex module or configuration traversal. As an example, the following code will return all aws_instance resource types within the configuration, regardless of what module they are in:

all_aws_instances = filter tfconfig.resources as _, r {
    r.mode is "managed" and
        r.type is "aws_instance"
}

You can add specific attributes to the filter to narrow the search, such as the module address. The following code would return resources in a module named foo only:

all_aws_instances = filter tfconfig.resources as _, r {
    r.module_address is "module.foo" and
        r.mode is "managed" and
        r.type is "aws_instance"
}

» Address Differences Between tfconfig, tfplan, and tfstate

This import deals with configuration before it is expanded into a resource graph by Terraform. As such, it is not possible to compute an index as the import is building its collections and computing addresses for resources and modules.

As such, addresses found here may not always match the expanded addresses found in the tfplan/v2 and tfstate/v2 imports, specifically when count and for_each, are used.

As an example, consider a resource named null_resource.foo with a count of 2 located in a module named bar. While there will possibly be entries in the other imports for module.bar.null_resource.foo[0] and module.bar.null_resource.foo[1], in tfconfig/v2, there will only be a module.bar.null_resource.foo. As mentioned in the start of this section, this is because configuration actually defines this scaling, whereas expansion actually happens when the resource graph is built, which happens as a natural part of the refresh and planning process.

The strip_index helper function, found in this import, can assist in removing the indexes from addresses found in the tfplan/v2 and tfstate/v2 imports so that data from those imports can be used to reference data in this one.

» The strip_index Function

The strip_index helper function can be used to remove indexes from addresses found in tfplan/v2 and tfstate/v2, by removing the indexes from each resource.

This can be used to help facilitate cross-import lookups for data between plan, state, and config.

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan

main = rule {
    all filter tfplan.resource_changes as _, rc {
        rc.mode is "managed" and
            rc.type is "aws_instance"
    } as _, rc {
        tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345"
    }
}

» Expression Representations

Most collections in this import will have one of two kinds of expression representations. This is a verbose format for expressing a (parsed) configuration value independent of the configuration source code, which is not 100% available to a policy check in Terraform Cloud.

(expression representation)
├── constant_value (value)
└── references (list of strings)

There are two major parts to an expression representation:

  • Any strictly constant value is expressed as an expression with a constant_value field.
  • Any expression that requires some degree of evaluation to generate the final value - even if that value is known at plan time - is not expressed in configuration. Instead, any particular references that are made are added to the references field. More details on this field can be found in the expression representation section of the JSON output format documentation.

For example, to determine if an output is based on a particular resource value, one could do:

import "tfconfig/v2" as tfconfig

main = rule {
    tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"]
}

» Block Expression Representation

Expanding on the above, a multi-value expression representation (such as the kind found in a resources collection element) is similar, but the root value is a keyed map of expression representations. This is repeated until a "scalar" expression value is encountered, ie: a field that is not a block in the resource's schema.

(block expression representation)
└── (attribute key)
    ├── (child block expression representation)
    │   └── (...)
    ├── constant_value (value)
    └── references (list of strings)

As an example, one can validate expressions in an aws_instance resource using the following:

import "tfconfig/v2" as tfconfig

main = rule {
    tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345"
}

Note that nested blocks, sometimes known as sub-resources, will be nested in configuration as as list of blocks (reflecting their ultimate nature as a list of objects). An example would be the aws_instance resource's ebs_block_device block:

import "tfconfig/v2" as tfconfig

main = rule {
    tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10
}

» The providers Collection

The providers collection is a collection representing the configurations of all provider instances across all modules in the configuration.

This collection is indexed by an opaque key. This is currently module_address:provider.alias, the same value as found in the provider_config_key field. module_address and the colon delimiter are omitted for the root module.

The provider_config_key field is also found in the resources collection and can be used to locate a provider that belongs to a configured resource.

The fields in this collection are as follows:

» The resources Collection

The resources collection is a collection representing all of the resources found in all modules in the configuration.

This collection is indexed by the resource address.

The fields in this collection are as follows:

  • address - The resource address. This is the index of the collection.
  • module_address - The module address that this resource was found in.
  • mode - The resource mode, either managed (resources) or data (data sources).
  • type - The type of resource, ie: null_resource in null_resource.foo.
  • name - The name of the resource, ie: foo in null_resource.foo.
  • provider_config_key - The opaque configuration key that serves as the index of the providers collection.
  • provisioners - The ordered list of provisioners for this resource. The syntax of the provisioners matches those found in the provisioners collection, but is a list indexed by the order the provisioners show up in the resource.
  • config - The block expression representation of the configuration values found in the resource.
  • count - The expression data for the count value in the resource.
  • for_each - The expression data for the for_each value in the resource.
  • depends_on - The contents of the depends_on config directive, which declares explicit dependencies for this resource.

» The provisioners Collection

The provisioners collection is a collection of all of the provisioners found across all resources in the configuration.

While normally bound to a resource in an ordered fashion, this collection allows for the filtering of provisioners within a single expression.

This collection is indexed with a key following the format resource_address:index, with each field matching their respective field in the particular element below:

» The variables Collection

The variables collection is a collection of all variables across all modules in the configuration.

Note that this tracks variable definitions, not values. See the tfplan/v2 variables collection for variable values set within a plan.

This collection is indexed by the key format module_address:name, with each field matching their respective name below. module_address and the colon delimiter are omitted for the root module.

  • module_address - The address of the module the variable was found in.
  • name - The name of the variable.
  • default - The defined default value of the variable.
  • description - The description of the variable.

» The outputs Collection

The outputs collection is a collection of all outputs across all modules in the configuration.

Note that this tracks variable definitions, not values. See the tfstate/v2 outputs collection for the final values of outputs set within a state. The tfplan/v2 output_changes collection also contains a more complex collection of planned output changes.

This collection is indexed by the key format module_address:name, with each field matching their respective name below. module_address and the colon delimiter are omitted for the root module.

  • module_address - The address of the module the output was found in.
  • name - The name of the output.
  • sensitive - Indicates whether or not the output was marked as sensitive.
  • value - An expression representation for the output.
  • description - The description of the output.
  • depends_on: A list of resource names that the output depends on. These are the hard-defined output dependencies as defined in the depends_on field in an output declaration, not the dependencies that get derived from natural evaluation of the output expression (these can be found in the references field of the expression representation).

» The module_calls Collection

The module_calls collection is a collection of all module declarations at all levels within the configuration.

Note that this is the module stanza in any particular configuration, and not the module itself. Hence, a declaration for module.foo would actually be declared in the root module, which would be represented by a blank field in module_address.

This collection is indexed by the key format module_address:name, with each field matching their respective name below. module_address and the colon delimiter are omitted for the root module.