Skip to main content

Checks

The check block can validate your infrastructure outside the usual resource lifecycle. Check blocks address a gap between post-apply and functional validation of infrastructure.

Check blocks allow you to define custom conditions that execute on every OpenTF plan or apply operation without affecting the overall status of an operation. Check blocks execute as the last step of a plan or apply after OpenTF has planned or provisioned your infrastructure.

Syntax

You can declare a check block with a local name, zero-to-one scoped data sources, and one-to-many assertions.

The following example loads the website and validates that it returns the expected status code 200.

check "health_check" {
data "http" "placeholderplaceholderplaceholder_io" {
url = "https://www.placeholderplaceholderplaceholder.io"
}

assert {
condition = data.http.placeholderplaceholderplaceholder_io.status_code == 200
error_message = "${data.http.placeholderplaceholderplaceholder_io.url} returned an unhealthy status code"
}
}

Scoped data sources

You can use any data source from any provider as a scoped data source within a check block.

A check block can optionally contain a nested (a.k.a. scoped) data source. This data block behaves like an external data source, except you can not reference it outside its enclosing check block. Additionally, if a scoped data source's provider raises any errors, they are masked as warnings and do not prevent OpenTF from continuing operation execution.

You can use a scoped data source to validate the status of a piece of infrastructure outside of the usual OpenTF resource lifecycle. In the above example, if the placeholderplaceholderplaceholder_io data source fails to load, you receive a warning instead of a blocking error, which would occur if you declared this data source outside of a check block.

Meta-Arguments

Scoped data sources support the depends_on and provider meta-arguments. Scoped data sources do not support the count orfor_each meta-arguments.

depends_on

The depends_on meta-argument can be particularly powerful when used within scoped data sources.

The first time OpenTF creates the initial plan for our previous example, the plan fails because OpenTF has not applied its configuration yet. Meaning this test fails because OpenTF must still create the resources to make this website exist. Therefore, the first time OpenTF runs this check, it always throws a potentially distracting error message.

You can fix this by adding depends_on to your scoped data source, ensuring it depends on an essential piece of your site's infrastructure, such as the load balancer. The check returns known after apply until that crucial piece of your website is ready. This strategy avoids producing unnecessary warnings during setup, and the check executes during subsequent plans and applies.

One problem with this strategy is that if the resource your scoped data source depends_on changes, the check block returns known after apply until OpenTF has updated that resource. Depending on your use case, this behavior could be acceptable or problematic.

We recommend implementing the depends_on meta-argument if your scoped data source depends on the existence of another resource without referencing it directly.

Assertions

Check blocks validate your custom assertions using assert blocks. Each check block must have at least one, but potentially many, assert blocks. Each assert block has a condition attribute and an error_message attribute.

Unlike other custom conditions, assertions do not affect OpenTF's execution of an operation. A failed assertion reports a warning without halting the ongoing operation. This contrasts with other custom conditions, such as a postcondition, where OpenTF produces an error immediately, halting the operation and blocking the application or planning of future resources.

Condition arguments within assert blocks can refer to scoped data sources within the enclosing check block and any variables, resources, data sources, or module outputs within the current module.

Learn more about assertions.

Meta-Arguments

Check blocks do not currently support meta-arguments. We are still collecting feedback on this feature, so if your use case would benefit from check blocks supporting meta-arguments, please let us know.

Continuous validation in TACOS (TF Automation and Collaboration Software)

TACOS (TF Automation and Collaboration Software) can automatically validate whether checks in a workspace’s configuration continue to pass after OpenTF provisions new infrastructure.

Choosing Checks or other Custom Conditions

Check blocks offer the most flexible validation solution within OpenTF. You can reference outputs, variables, resources, and data sources within check assertions. You can also use checks to model every alternate Custom Condition. However, that does not mean you should replace all your custom conditions with check blocks.

There are major behavioral differences between check block assertions and other custom conditions, the main one being that check blocks do not affect OpenTF's execution of an operation. You can use this non-blocking behavior to decide the best type of validation for your use case.

Outputs and variables

Output postconditions and variable validations both make assertions around inputs and outputs.

This is one of the cases where you might want OpenTF to block further execution.

For example, it is not helpful for OpenTF to warn that an input variable is invalid after it applies an entire configuration with that input variable. In this case, a check block would warn of the invalid input variable without interrupting the operation. A validation block for the same input variable would alert you of the invalid variable and halt the plan or apply operation.

Resource Preconditions and Postconditions

The difference between preconditions and postconditions and check blocks is more nuanced.

Preconditions are unique amongst the custom conditions in that they execute before a resource change is applied or planned. Choosing Between Preconditions and Postconditions offers guidance on choosing between a precondition and a postcondition, and the same topics also apply to choosing between a precondition and a check block.

You can often use postconditions interchangeably with check blocks to validate resources and data sources.

For example, you can rewrite the above check block example to use a postcondition instead. The below code uses a postcondition block to validate that the website returns the expected status code of 200.

data "http" "placeholderplaceholderplaceholder_io" {
url = "https://www.placeholderplaceholderplaceholder.io"

lifecycle {
postcondition {
condition = self.status_code == 200
error_message = "${self.url} returned an unhealthy status code"
}
}
}

Both the check and postcondition block examples validate that the website returns a 200 status code during a plan or an apply operation. The difference between the two blocks is how each handles failure.

If a postcondition block fails, it blocks OpenTF from executing the current operation. If a check block fails, it does not block OpenTF from executing an operation.

If the above example's postcondition fails, it is impossible to recover from. OpenTF blocks any future plan or apply operations if your postcondition is unsatisfied during the planning stage. This problem occurs because the postcondition does not directly depend on OpenTF configuration, but instead on the complex interactions between multiple resources.

We recommend using check blocks to validate the status of infrastructure as a whole. We only recommend using postconditions when you want a guarantee on a single resource based on that resource's configuration.