
This post is part of a series on Enforcing Policy as Code in Terraform: A Comprehensive Guide.
With the release of Terraform v1.5 we get some pretty incredible features added to the language. Today we are going to be talking about one of them, the check block. This allows us to assert things about our provisioned resources. Unlike the precondition and postcondition however a check is non-blocking, this means that it will not prevent your Terraform code from running it will simply warn the engineer that the assertion has failed.
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 actually make our error_message more useful by using string interpolation, this allows us to bring in information from either the data block or other parts of the code so that our errors are more contextual and meaningful. 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.As can be seen the first check for the scratch_string.this resource is all hunky-dory as there are no warnings shown in our apply output, with the second one however we were not so lucky. You can see we have a Warning: Check block assertion failed we expected our in attribute to have the value of meow like the KV store however it actually has the value of woof. This check would also yield the same result on the Plan phase as well.
The biggest limitation I have found thus far is that you're limited to only having a single data block in your check, there might be scenarios where it makes sense to have multiple items to check against however this is not currently possible.
The example I have used is one that provides easy-to-understand context of how the check block works however it isn't what I would call real-world, below are a few examples of where the check block would be useful.
We have gone through the amazing new feature that is the check block that came out with Terraform v1.5 this opens up a whole new world of possibilities when it comes to testing our infrastructure and ensuring that it exists in the state we expect it to. This means that using technologies such as Terratest for validating our resultant infrastructure (think Unit Tests) becomes less likely to be required. I still think however it is a fantastic option for powerful Integration Testing.
Try out the Check feature using Scalr today!
