Terraform v1.1: The Journey Continues

If you have been reading my previous blog posts on Terraform 0.13, 0.14 and 0.15 you might be wondering why there was no post for v1.0 and why we are just skipping to v1.1. The reason is quite simple and v1.0 was simply retagging and releasing v0.15 as a stable release with no other significant changes. So that means most of the work on v1.0 was about minor fixes and enhancements and v1.1 is not bringing some more changes in.

NOTE: Before we dive into what latest minor release brings us, I just want to start with a slight warning; if you have already updated to a v1.1.x release, please make sure you’re running at least v1.1.2 as it fixes a bug where a failure to construct the apply-time graph can cause Terraform to incorrectly report success and save an empty state, effectively “forgetting” all existing infrastructure!

HashiCorp Terraform v1.1

HashiCorp Terraform v1.1

So let’s have a look at what cool new features we can use with this release. The most notable are the ones I want to have a look at in this blog: moving Terraform Cloud backend configuration to a new cloud block, refactoring improvements with moved statements, increased verbosity using destroy annotations, nullable variables, and improved debugging using type console function.

A full list of changes is available in the changelog.

Cloud block and workspaces mapping

Terraform currently supports over 15 backends, which you can use to store and also share state with other people collaborating on Infrastructure as Code (IaC). This includes different kinds of backends, from local, s3, http, kubernetes and remote. Looking at those backends one can quickly spot an ‘odd’ one out. One of them is a bit different and does offer quite a bit more than just state persistence and locking. It also changes your workflow using Version Control System(VSC) driven runs, applying code policies, alerting, variable sets, remote operations, etc. So because remote stores whole configuration and static variables, HashiCorp introduced new cloud block in top level terraform configuration block, for example:

terraform {
  cloud {
    organization = "my-org"
    hostname     = "app.terraform.io" # Optional; defaults to app.terraform.io

    workspaces {
      tags = ["moduleX", "source:cli"]

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.65.0"

Using the remote backend for Terraform Cloud integration is still supported but it will eventually get deprecated and new features and arguments will be added to the new block, which makes TFC backend a 1st class citizen.

For example this is what we would migrate it from:

backend "remote" {
  organisation = "my-org"
  workspaces {
    name = "workspace"
    # prefix = "net-*" # alternatively we can use prefixes, which are mutually exclusive

Also looking at the example above, you’ve probably noticed that instead of using name parameter, we are setting tags. And this is also a preferred way to reference and migrate your current local workspaces to TFC workspaces, which are so much more than just separate state files (that’s what local workspaces literally are). It combines your whole IaC workflow and are basic building blocks belonging to the TFC organisation where you can also perform access control.

Refactoring from the configuration

Another big announcement and an exciting change in this minor release is how we can refactor our IaC using moved statements. So the way we could refactor our Terraform code until now was quite cumbersome and error prone by using state mv CLI commands. That presented a lot of different problems, especially when doing it in a shared Terraform configuration or with public modules. So in order to refactor or change a configuration you’d need to make a code change and reflect that in the state manually by moving resources around (what can go wrong when you start manually fiddling with the state, right? 🙂). So in order to address some of the pain points mentioned and make this part of the main plan/apply Terraform workflow, HashiCorp introduced moved statements. These make it possible to refactor from within your code directly.

This means that changing resource paths/addresses is part of the run execution and it will automate moving them for you. So in the following (really simple) example we don’t need to run any CLI commands and the move is automatically done for us during apply:

# Imagine that this is your code before the change
resource "random_pet" "first" {
  length = 2

And then when we change the name for example:

# You decided to rename the resource in your code.
resource "random_pet" "new" {
  length = 2

That would cause Terraform to plan a destroy and creation of a new resource! But if we know this is the same resource we want to keep, we can avoid the destroy by adding a hint to Terraform using the new moved statement:

moved {
  from = random_pet.first
  to   = random_pet.new

After the change is done, can we remove those statements? Well, it depends. If you are certain that resources were moved then there is no need to keep them around, but they can serve as helping pointers for collaborators (or future you!) to figure out if something moved. For example, if you’re using this in a module you could introduce the moved statements in one version and remove it in a later one, where users would be required to gradually migrate in order to avoid unwanted destructive actions due to moved resources.

The most common use-cases for moved statements are: moving resources to or from modules, splitting modules, renaming modules, and changing or enabling count/for_each on resources. You can also chain moved statements to track multiple moves and optionally keep them around to capture historical context.

There are a couple of things to keep in mind when using moved statements today. You need to be on Terraform v1.1 and it only allows you to move things around in your current ’local’ module context. Further, it doesn’t allow moving a resource to a remote registry module that you might be using. You can still use the state mv CLI command to move those. Attempting to move something outside of the ’local’ module context would result in the following error:

│ Error: Cross-package move statement
│   on main.tf line 55:
│   55: moved {
│ This statement declares a move to an object declared in external module package "registry.terraform.io/org/module/aws". Move statements can be only within a single module package

Other awesome features

Besides what we already mentioned there are few other exciting changes that made it into v1.1 and I want to briefly mention them here:

  • Terraform runs will produce more verbosity on errors and will try to be even more helpful.
  • Destroy annotations and better debugging changes. We will get better and more explicit messages when resources are removed, a count is changing or when for_each keys are removed.
  • Nullable variables will be aligning their behaviour with how Terraform handles resource null arguments elsewhere.
  • Debugging is further improved by adding a new type function in the Terraform CLI console. This can be particularly helpful in situations where we are trying to decipher those messages when Terraform run complains about incorrect or mismatching types. It can also help us to understand how types are handled under the hood.

Looking to the future

We probably all wish we had a crystal ball and could have a peek in the future. Unfortunately that is not an option, but what we currently know about upcoming Terraform release v1.2, and what I’ve heard from HashiCorp, they are focusing on the following areas:

  • Improving testing with assertions and building up confidence in asserting conditions when testing your IaC.
  • Expanding on moved blocks and new workflow improvements to bring all manual and edge cases in line with the main plan/apply Terraform workflow.

There seems to be an improvement for the dependency lock file coming up in v1.1.3.

What I also hope to see in the next releases is perhaps a long-awaited improvement on importing existing resources into Terraform state and some automation around that workflow. There is some existing 3rd party tooling in this space, such as terraformer. But having it as part of the Terraform workflow would improve user experience when ingesting existing resources and could accelerate adoption of IaC as well.

Further reading

We offer hands-on AWS training as part of our SaaS Growth subscription, to help your team make the best use of the AWS cloud. Book a free chat to find out more.

For some topics, you can also get the same training just on the topic you need - see our Terraform training and Kubernetes training pages.

This blog is written exclusively by The Scale Factory team. We do not accept external contributions.

Free Healthcheck

Get an expert review of your AWS platform, focused on your business priorities.

Book Now

Discover how we can help you.

Consulting packages

Advice, engineering, and training, solving common SaaS problems at a fixed price.

Learn more >

Growth solutions

Complete AWS solutions, tailored to the unique needs of your SaaS business.

Learn more >

Support services

An ongoing relationship, providing access to our AWS expertise at any time.

Learn more >