Overview of Scenario Flow

Basics

  • Terraform files contains the HCL that describes the infrastructure to be tested by terraform-compliance. Running terraform plan --out=plan.out creates a planfile (which is usally converted to a json planfile).
  • plan.out.json is the json file Terraform-Compliance uses to test your infrastructure. It resembles the terraform files closely, but there may be some differences depending on how used providers behave on terraform plan. Note that the plan file is a big nested dictionary.
  • Sometimes it’s a good idea to search this file if you’re unsure which words to use in your steps while writing scenarios. The context we refer to will usually be in configuration/root_module within the planfile

Given and Context

Each step has a context. (i.e. list of resources, data this step will consider while deciding to pass or not)

GIVEN sets the context, while WHEN and THEN narrows the context from the step above.

To begin a scenario, a GIVEN step must be used to set the initial context. Although it’s less common, GIVEN can also be used to reset the context.

# Example 
#	if I have AWS VPC
#		then aws_flow_logs' must have traffic_type set to ALL

Scenario: VPC implies flowlogs' traffic_type is "ALL"
		Given I have aws_vpc defined
		Given I have aws_flow_log defined
		Then it must contain traffic_type
		And its value must be ALL

Here, I want to check a property of aws_flow_log resources, given I have aws_vpc. Therefore, I’m resetting the context to a new set of resources (aws_flow_log) instead of aws_vpc.

Most of the time, using preconditions or backgrounds are more suitable for cases using more than one Given.

While the the common Scenario structure looks like

    Given
    When
    ...
    Then
    ...

Given, When, Then steps could be used in any order.

When

WHEN steps evaluate their condition and decide to pass or not. If the condition doesn’t pass, it skips instead of failing.

Assume we had a plan of the following terraform file:

resource "azurerm_postgresql_server" "John" {
  ...
  auto_grow_enabled            = true
  ...
}
resource "azurerm_postgresql_server" "Douglas" {
  ...
  auto_grow_enabled            = false
  ...
}

The following steps would pass

Given I have azurerm_postgresql_server defined
When its auto_grow_enabled is true

When filters

WHEN directive filters the context passed from the above step. Meaning if I have five elements in the context and only three of them satisfy the condition, I filter out (remove) the other two from the context and pass the remaining three elements unmodified.

In the above example, the context past the GIVEN step would be a list of both postgresql servers, while the context past the WHEN step would only be the first server.

If I didn’t have any elements in the context that satisfied the condition, I would skip the scenario instead.

If both servers had auto grow disabled, When its auto_grow_enabled is true would not be able to pass anything to the next step and would skip. Given I have azurerm_postgresql_server defined, its auto_grow_enabled is never set to true.

Please note, using WHEN, I can: filter a list of resource passed by GIVEN, filter an already filtered list of resource, filter a list of drilled down resources.)

Then

Similar to other steps, then also evaluates its condition and decides to pass or not. However if the condition doesn’t pass, the scenario is failed. If then step is reached within a scenario, it is expected to pass. (Which usually is the very thing you’re testing)

For example, the following scenario would fail on a plan where I have one aws_s3_bucket and one aws_flow_log defined.

Scenario: Example scenario
    Given I have any resource defined
	Then its type must be aws_flow_log

The first step would set the context to be a list of two resources: aws_s3_bucket and aws_flow_log. Since the type of s3 bucket is not aws_flow_log, the step fails.

Then drills down

THEN steps drill down the current context.

A resource (or a provider, variable, etc.) is simply a nested dictionary. Drilling down changes the scope of that dictionary.

Let the following be one of the resources in the context and I want to check a value within the access_logs

{
	'data1': {
		...
	}
	'data2': {
		...
	}
	'access_logs': {
		bucket        = "foo"
	    bucket_prefix = "bar"
	    interval      = 60
	}

}

If I write Then it has access_logs, to the next step I will pass

{
	bucket        = "foo"
    bucket_prefix = "bar"
    interval      = 60
}

instead of the entire resource, as I “drilled down” into access_logs. If had used something like When it has access_logs I would pass the entire resource to the next step instead.

If the next step were to be Then it has bucket

I would drill down to:

"foo"

If I had a list of 5 resources similar to one above as my context and the THEN step passes, the context I’ll be passing to the next step would be a list of 5 access_logs (the thing I drilled down to.)

The THEN directive fails the step if there’s any element in the context that fails the condition (It can’t drill down to one of the given elements in the context). So to pass a THEN step, all resources (whatever were drilled down to) must passs the step.


terraform-compliance made with . Distributed by an MIT license.