Please note that this post, first published over a year ago, may now be out of date.
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!
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 whenfor_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 CLIconsole
. 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 mainplan
/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
- Terraform Cloud Settings
- Migrate State from S3 to Terraform Cloud
- Use Configuration to Move Resources
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.