Skip to content

infrablocks/rspec-terraform

Repository files navigation

RSpec::Terraform

An RSpec extension for verifying Terraform configurations, with support for:

  • unit testing;
  • integration testing;
  • end-to-end testing; and
  • change auditing.

Installation

Add this line to your application's Gemfile:

gem 'rspec-terraform'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rspec-terraform

Usage

To use RSpec::Terraform, require it in your spec_helper.rb file:

require 'rspec/terraform'

When required, RSpec::Terraform automatically configures itself against RSpec by:

  • adding helper methods to interact with Terraform;
  • adding matchers to verify Terraform plans; and
  • adding settings to control RSpec::Terraform's behaviour.

The sections below provide further details on each of these additions.

Helper methods

RSpec::Terraform adds helper methods to the RSpec DSL for planning, applying, and destroying Terraform configurations, as well as accessing variables to and outputs from Terraform configurations.

Each helper method takes a hash of parameters used to identify the configuration against which to act and to provide as options to the Terraform command being executed. Additionally, RSpec::Terraform includes a flexible approach to resolving these parameters allowing them to be sourced from a variety of locations. See the Configuration Providers section for more details.

When executing helper methods, RSpec::Terraform provides two execution modes, :in_place and :isolated. By default, RSpec::Terraform works against a Terraform configuration in place, i.e., it executes commands against the Terraform configuration directly, in the location specified. RSpec::Terraform can also operate in an isolated manner, wherein it initialises the configuration into an isolated directory before executing commands. See the Execution Mode section for more details.

plan

The plan helper produces a Terraform plan for a configuration, reads it into a Ruby representation and returns it.

plan requires a :configuration_directory parameter, representing the path to the configuration to plan and is typically invoked in a before(:context) hook, with the resulting plan stored for use in expectations:

before(:context) do
  @plan = plan(
    configuration_directory: 'path/to/configuration/directory'
  )
end

If the configuration has input variables, a :vars parameter can be provided as a hash:

before(:context) do
  @plan = plan(
    configuration_directory: 'path/to/configuration/directory',
    vars: {
      region: 'uk',
      zones: ['uk-a', 'uk-b'],
      tags: {
        name: 'important-thing',
        role: 'persistence'
      }
    }
  )
end

or within a block:

before(:context) do
  @plan = plan(
    configuration_directory: 'path/to/configuration/directory'
  ) do |vars|
    vars.region = 'uk'
    vars.zones = ['uk-a', 'uk-b']
    vars.tags = {
      name: 'important-thing',
      role: 'persistence'
    }
  end
end

plan accepts an optional :state_file parameter with the path to where the current state file for the configuration is located, useful when checking the incremental change that applying the configuration would have after a previous apply.

Internally, plan:

  • calls terraform init to initialise the configuration directory;
  • calls terraform plan to produce a plan file;
  • calls terraform show to read the contents of the plan file into a Ruby representation; and
  • deletes the plan file.

Any additional parameters passed to plan are passed on to the underlying Terraform invocations.

apply

destroy

output

var

Plan Matchers

Settings

Binary Location

Logging and Standard Streams

Execution Mode

The benefit of isolated execution is that nothing is carried over between test runs and providers and modules are fetched into a clean configuration directory every time. The downside is additional test run time.

Configuration Providers

Frequently Asked Questions

Development

To install dependencies and run the build, run the pre-commit build:

./go

This runs all unit tests and other checks including coverage and code linting / formatting.

To run only the unit tests, including coverage:

./go test:unit

To attempt to fix any code linting / formatting issues:

./go library:fix

To check for code linting / formatting issues without fixing:

./go library:check

You can also run bin/console for an interactive prompt that will allow you to experiment.

Managing CircleCI keys

To encrypt a GPG key for use by CircleCI:

openssl aes-256-cbc \
  -e \
  -md sha1 \
  -in ./config/secrets/ci/gpg.private \
  -out ./.circleci/gpg.private.enc \
  -k "<passphrase>"

To check decryption is working correctly:

openssl aes-256-cbc \
  -d \
  -md sha1 \
  -in ./.circleci/gpg.private.enc \
  -k "<passphrase>"

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/infrablocks/rspec-terraform. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.