Terraform

Terraform Plan Command – Check Your IaC Deployment

Terraform plan command

Infrastructure as Code (IaC) is a key attribute of enabling best practices in any organization practicing a DevOps culture. The ability to build up and tear down infrastructure via code has many benefits, like reducing cost, increasing deployment speed, and lowering risk through consistency. There is, however, a dark side to IaC. If not used cautiously and carefully, it is way too easy to cause harm to an environment.

What Does Terraform Plan Command Do

Applying infrastructure changes haphazardly can lead to unwanted changes to your environment. The Terraform plan command is available to bring visibility to your IaC deployments. The plan command reports on changes to infrastructure, but it does not apply any of the proposed changes. Instead, it creates a reviewable execution plan, which you can use to confirm that the proposed changes are expected.

The execution plan can be saved to a file, which can be applied later, or peer-reviewed to enforce quality control. The ability to review the proposed changes prior to applying them can save you from making unexpected infrastructure changes. After you have confirmed the changes as expected, use the Terraform apply command to update the remote objects.

The plan command does three things:

  • Ensures the state is up to date by reading the current state of any already-existing remote infrastructure.
  • Determines the deltas between the current configuration and the prior state data.
  • Proposes a series of changes that will make the remote infrastructure match the current configuration.

If there aren’t any deltas, the output will report that no actions need to be taken.

Usage Examples

Let’s look at the output resulting from executing plan with a simple Terraform configuration script. For this walkthrough, we’ll use a new Azure subscription that does not contain any resources. The listing for the configuration script is below.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.0.0"
    }
  }
}

provider "azurerm" {
  features { }
}

resource "azurerm_resource_group" "tf-plan" {
  name     = "rg-tf-plan-example-centralus"
  location = "centralus"
}

Run the following command on the command line to generate an execution plan.

Before doing so, be sure to set environment variables for your Azure Subscription Id and Tenant Id, using the subscription id and tenant id from your Azure subscription, respectively. For more details on the different ways, you can configure the AzureRM Terraform provider, see here.

terraform plan
Terraform plan - initial output

The output contains a wealth of information, which is highlighted in the image above. Terraform proposed a create action, as indicated by the green plus sign. Changes and deletions appear as yellow tilda and red minus sign, respectively. Terraform is also proposing to create a new resource group named “rg-tf-plan-example-centralus.” The execution plan includes a summary count of each action in the proposal. In this case, there is one add, zero change, and zero destroy, actions.

Apply the proposed changes by running the apply command (make sure you confirm the actions by typing yes when prompted):

terraform apply

In addition to creating the resource group, Terraform also updated the local state data. Terraform uses state data to remember the remote infrastructure configurations and create deltas when configurations change.

Make the following change to the sample Terraform script. Add the following line to the end of the script, just before the closing curly brace:

tags = {name: "test"}

The resource group resource should now look like this:

resource "azurerm_resource_group" "tf-plan" {
  name     = "rg-tf-plan-example-centralus"
  location = "centralus"
  tags = {name: "test"}
}

Re-run the plan command:

terraform plan

When the plan command executed, Terraform compared the stored state data for the rg-tf-plan-example-centralus resource to the changes made in the configuration script.

terraform plan delta output

Looking at the output, you can see Terraform determined an update was necessary. The output also shows the proposed changes, including the actual change itself: the addition of the name tag.

It’s also worth mentioning that with Spacelift you can use plan policies to make automated decisions based on Terraform plans. Plan policies are evaluated during a planning phase after a vendor-specific change preview command (e.g. terraform plan) executes successfully. The body of the change is exported to JSON, and parts of it are combined with Spacelift metadata to form the data input to the policy.

Planning Modes

The plan command can be run in three different modes, normal, which is the default and the mode we’ve used so far, destroy, and refresh-only. The modes are mutually exclusive. You cannot use more than one mode at a time.

Destroy mode creates a plan which shows you all the remote objects that will be removed. Running the command in destroy mode does not actually perform any actions. A great use case for destroy mode is to delete a temporary development environment.

You activate destroy mode by including the -destroy option on the command line:

terraform plan -destroy

There are times when you will intentionally change remote objects without the use of a Terraform script, i.e., directly via the Azure CLI. This can be necessary when troubleshooting a problem in the remote environment. In these cases, the state data will not match the remote object configuration and will need to be reconciled.

Executing plan in refresh-only mode creates a plan with the goal of showing you the deltas between the state data and the remote objects. You can activate refresh-only mode by including the -refresh-only option on the command line.

Note: the -refresh-only option is available only in Terraform v0.15.4 and later.

terraform plan -refresh-only

Planning Options

In addition to the alternate plan modes described above, there are a couple of other categories of options available, those affecting behavior and those affecting outputs.

Options Affecting How the Plan Command Behaves

Options that modify the plan command’s behavior are listed below.

Option Description
-refresh=false

 

Ignores external changes to remote objects, i.e., those made via the CLI. This option will not sync the state data with the remote objects before checking for configuration changes. While this improves performance by making less remote API requests, ignoring external changes could result in incomplete or incorrect plans.

 

-replace=ADDRESS

 

Forces the replacement of the resource instead of an update action or no change at all. This will replace the resource at the given address. It can be applied multiple times to replace many objects at once.

 

-var 'NAME=VALUE'

 

Applies a value for an input variable. It can be applied multiple times, once per variable.

Note: Errors will occur if a space appears before or after the equals sign (e.g., -var "region = centralus").

 

-var-file=FILENAME

 

Applies values for one or more input variables as defined in a “tfvars” file. It can be used multiple times, once for each “tfvars” file.

 

-lock-timeout=DURATION Instructs Terraform to retry acquiring a lock for a period of time before returning an error. The duration value must be entered using the format nt where n is a number and t is a letter representing a unit of time, e.g., 3m is 3 minutes.

 

Let’s turn the resource group’s location attribute into a variable. This adds flexibility and reusability to the script, allowing us to specify the location value at runtime. Modify the sample script as follows.

Add a variable to the sample script named “region” just before the provider section and modify the azurerm_resource_group resource so the value for the location attribute comes from a variable.

Note: for simplicity, we are using one “.tf” file to define the resources and the variables. In practice, variables are declared in a separate file, typically named “variables.tf”. The script below shows the result of making this change.

variable "region" {
  description = "Azure location for the resource group"
  type        = string
}

provider "azurerm" {
  features { }
}

resource "azurerm_resource_group" "tf-plan" {
  name     = "rg-tf-plan-example-centralus"
  location = var.region 
  tags = {name: "test"}
}

The “region” variable is declared as a string.

Re-run the Terraform plan command. This time you will be prompted for the location in Azure where the resource group resides.

terraform variable prompt

Run the plan command again. This time, use the -var option to provide a value for region on the command line and notice that you are no longer prompted to supply a value for the variable.

terraform plan -var='region="centralus"'

Options Related to Formatting and Output

There will be use cases where you will need to change the plan command’s output. The following options are available for those use-cases:

Option Description
-compact-warnings

 

Shows warnings as a summary unless the warnings include errors. If there are errors, the text will include useful information for troubleshooting.

 

-input=false

 

Disables prompts for input variables. that have not otherwise been assigned a value. This option is useful when automating Terraform scripts, like when run in a CI/CD pipeline.

 

-json

 

Returns output in JSON format.

 

-no-color

 

Removes color from the output.

 

-out=FILENAME Saves the plan to a file that can be passed to the Terraform apply command to execute the planned changes. Any filename is allowed, but the recommended naming convention is to name it “tfplan.” Do not use the “.tf” suffix, as this will lead Terraform to interpret the file as a configuration script and will fail.

Note: the file contains all the values associated with planned changes, including any sensitive data, in cleartext in the plan file. For this reason, you must consider impacts on security when saving plans to a file.

 

-parallelism=n By default, Terraform will run 10 operations concurrently, where possible. This option limits the number of concurrent operations to n.

 

-detailed-exitcode Includes detailed exit codes, which provide more granular information about the resulting plan. The list of codes is:

– 0 = Succeeded with empty diff (no changes)
– 1 = Error
– 2 = Succeeded with non-empty diff (changes present)

 

Resource Targeting

Resource targeting is like the -replace=ADDRESS option in that it tells Terraform to focus the plan only on resources that match the given address. Resource targeting includes all other objects that the selected resource(s) depend on, either directly or indirectly.

You can target resources using the -target=ADDRESS option.

Note: It is not recommended to use -target under normal circumstances.

Terraform enforces this point by including a warning message in the output. Run the following command to see the warning message embedded in the output:

terraform plan -var='region="centralus"' -target='azurerm_resource_group.tf-plan'
terraform plan target warning

Instead of using -target, split large configurations into several smaller configurations that can each be applied independently. Doing so allows a complicated configuration to be separated into more manageable parts.

Key Points

The plan command is very powerful despite not actually modifying any resources. Saving plans to output files enables automation and peer review. Supplying variable values on the command line or in files eliminates the need to hard code values, some of which may be sensitive. Using plan to show deltas between state data and remote configurations can help illuminate changes made directly to remote objects, which may need to be refreshed.

With Spacelift, once the workspace is prepared by the Initializing phase, planning runs a vendor-specific preview command, in this case, terraform plan, and interprets the results. The result of the planning phase is the collection of currently managed resources and outputs as well as planned changes. This is used as an input to plan policies (optional) and to calculate the delta – always. If you’re interested in finding out more about how to use Spacelift to manage and automate your Terraform deployments check out our Terraform documentation.

You should plan to run the plan command prior to applying your changes. This will help you avoid late-night troubleshooting sessions for changes that have gone awry. Remember, failing to plan is planning to fail.

Manage Terraform Better and Faster

If you are struggling with Terraform automation and management, check out Spacelift. It helps you manage Terraform state, build more complex workflows, and adds several must-have capabilities for end-to-end infrastructure management.

Start free trial