TrademarkTrademark
Features
Documentation

Terraform Check - New Terraform Feature

Learn all about the new Terraform Check feature
Brendan ThompsonJune 22, 2023
Terraform Check - New Terraform Feature
Key takeaways
  • Terraform v1.5 introduced the check block, which lets you assert things about provisioned resources.
  • Unlike precondition and postcondition, a check is non-blocking: it warns the engineer when an assertion fails rather than stopping the Terraform run.
  • A check block can contain a data block and one or more assert blocks, each with a condition returning a boolean and an error_message shown on failure.
  • A current limitation is that a check block is limited to a single data block.
  • Useful real-world checks include validating a firewall rule for a public IP, confirming a route exists, verifying an API is private, or checking group membership.

Terraform v1.5 added a handful of new features to the language. Today we're looking at one of them, the check block, which lets us assert things about our provisioned resources. Unlike precondition and postcondition, a check is non-blocking. It won't stop your Terraform run. It just warns the engineer when the assertion fails.

First off let's look at a very basic example.

resource "scratch_string" "this" {
  in = "meow"
}
 
resource "scratch_string" "that" {
  in = "woof"
}
 
check "this" {
  data "http" "this" {
    url = "https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat"
  }
 
  assert {
    condition     = scratch_string.this.in == data.http.this.response_body
    error_message = "Err: the data source doesn't match the resource."
  }
}
 
check "that" {
  data "http" "that" {
    url = "https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat"
  }
 
  assert {
    condition     = scratch_string.that.in == data.http.that.response_body
    error_message = "Err: the data source doesn't match the resource."
  }
}

Firstly we are creating two resources, scratch_string.this and scratch_string.that, and will run a check against a remote key-value store to ensure that they both match what is in the KV store with check. As part of the check block we are able to define a data block and one or more assert blocks. The assert block takes the same form that we are used to in precondition and postcondition checks with the condition attribute that is required to return a boolean and the error_message attribute that is shown when the check fails.

We can make our error_message more useful with string interpolation. That lets us pull in information from the data block or other parts of the code, so the error has some context to it. Below is an example using a fake resource.

assert {
  condition = ...
  error_message = format("Err: User (%s) no it group.", some_resource.user.name)
}

Our result looks like:

data.http.that: Reading...
data.http.this: Reading...
data.http.this: Read complete after 0s [id=https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat]
data.http.that: Read complete after 0s [id=https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat]
 
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)
 
Terraform will perform the following actions:
 
  # data.http.that will be read during apply
  # (config will be reloaded to verify a check block)
 <= data "http" "that" {
      + body             = "meow"
      + id               = "https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat"
      + response_body    = "meow"
      + response_headers = {
          ...
        }
      + status_code      = 200
      + url              = "https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat"
    }
 
  # data.http.this will be read during apply
  # (config will be reloaded to verify a check block)
 <= data "http" "this" {
      + body             = "meow"
      + id               = "https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat"
      + response_body    = "meow"
      + response_headers = {
          ...
        }
      + status_code      = 200
      + url              = "https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat"
    }
 
  # scratch_string.that will be created
  + resource "scratch_string" "that" {
      + id = (known after apply)
      + in = "woof"
    }
 
  # scratch_string.this will be created
  + resource "scratch_string" "this" {
      + id = (known after apply)
      + in = "meow"
    }
 
Plan: 2 to add, 0 to change, 0 to destroy.
scratch_string.this: Creating...
scratch_string.that: Creating...
scratch_string.this: Creation complete after 0s [id=7d2182f0-0f0d-11ee-b7ef-aacb117da4fc]
data.http.that: Reading...
data.http.this: Reading...
scratch_string.that: Creation complete after 0s [id=7d21889a-0f0d-11ee-b7ef-aacb117da4fc]
data.http.that: Read complete after 0s [id=https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat]
data.http.this: Read complete after 0s [id=https://kvdb.io/QmQfhGhDgNTVEANY6vXfFk/sounds/cat]

│ Warning: Check block assertion failed

│   on main.tf line 35, in check "that":
35:     condition     = scratch_string.that.in == data.http.that.response_body
│     ├────────────────
│     │ data.http.that.response_body is "meow"
│     │ scratch_string.that.in is "woof"

│ Err: the data source doesn't match the resource.

 
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

The first check, for the scratch_string.this resource, passes cleanly: no warnings in the apply output. The second one doesn't. We get a Warning: Check block assertion failed. We expected the in attribute to match the KV store value of meow, but it actually holds woof. This check would yield the same result during the Plan phase.

Limitations

The biggest limitation I've hit so far is that you can only have a single data block in your check. There are scenarios where it would make sense to check against several things at once, but that isn't possible right now.

Real-world Scenarios

The example above is easy to follow, but it isn't what I'd call real-world. Here are a few cases where the check block would actually be useful.

  • Validating a firewall rule exists for your public IP
  • Ensure there is a route present to allow for routing of your newly provisioned service
  • Validating an API is private
  • User/Identity is in a particular group/role

Closing Out

That's the check block, new in Terraform v1.5. It gives us a way to test our infrastructure and confirm it's in the state we expect. With it, reaching for a tool like Terratest to validate the resulting infrastructure (think Unit Tests) becomes less necessary. I still think it's a strong option for Integration Testing, though.

Try out the Check feature using Scalr today!

About the author
Brendan Thompsonsolutions engineer at Scalr
Brendan Thompson is a solutions engineer at Scalr, specializing in Terraform and cloud infrastructure.