Skip to content

Module Testing Environment

Kim Ngo edited this page Jun 8, 2021 · 2 revisions

Testing Terraform is a challenge because it can create or change real infrastructure, and testing Terraform automation with Consul Terraform Sync introduces more complexity. A common approach to test a module for Consul Terraform Sync is to simplify the environment to just Terraform. This can be done by manually emulating how CTS would automate the module and execute Terraform.

Note: You do not need Consul Terraform Sysnc or a running Consul agent to test the integration, but does require familiarity with running Terraform.

  1. Download and install Terraform CLI version 0.13 or newer.
  2. Create root module files that would typically be generated by Consul Terraform Sync.
  3. Execute Terraform commands and verify the output is expected and the network infrastructure is updated accordingly.

Root Module

This section will guide you to create a Terraform root module that can be used to test a module for Consul Terraform Sync compatibility. You will need to create 3 Terraform configuration files that make up the root module. Create these files in a directory separate from the module you are developing.

Main

  1. Create a file named main.tf and copy the code block below to the file. This snippet of configuration includes a module block that calls your module in development as a child module.
    terraform {
      required_version = "~>0.13.0"
      required_providers {
        # <provider> = {
        #  source  = "<namespace>/<provider>"
        #  version = "1.0.0"
        # }
      }
    }
    
    # terraform_provider "<provider>" {
    #  attr = value
    # }
    
    module "<name>" {
      source   = "<module source>" # Set the module source to the location of your module for testing.
      version  = "X.Y.Z"
      services = var.services
    
      # Pass required or optional input variables as module arguments.
      # arg = 5
    }
  2. For any provider used by the module that is being tested, add the provider source and version in the terraform.required_providers block. This is a Terraform 0.13 feature.
  3. Include any provider configuration needed for the providers used by the module in terraform_provider blocks. Consul Terraform Sync relies on passing providers implicitly through inheritance to the child module.
  4. Replace placeholder values in the module block with information pertaining to the module to test, like name, module source, and version. The module source can be local or remote, and Terraform will either search on disk or download it.
  5. Note that the services input variable is passed to the module. This satisfies the module spec requirement for Consul Terraform Sync.
  6. Pass any arguments in the module block to set input variables for the module to test.

Consul Terraform Sync creates the main.tf file by templating all of the above values manually entered. The resulting file would resemble the main.tf below. Note that in this example, CTS sets each attribute from a variable instead of direct assignment. These variables are interpreted and generated based on user configuration for CTS. CTS will also place the corresponding variable declarations in a variables.tf file and set the values in terraform.tfvars. For manual testing purposes, you do not need to abstract variables this way and can set them directly in the main.tf.

main.tf
# This file is generated by Consul Terraform Sync.
#
# The HCL blocks, arguments, variables, and values are derived from the
# operator configuration for Sync. Any manual changes to this file
# may not be preserved and could be overwritten by a subsequent update.
#
# Task: <task name>
# Description: <task description>

terraform {
  required_version = "~>0.13.0"
  required_providers {
    testProvider = {
      source  = "namespace/testProvider"
      version = "1.0.0"
    }
  }
}

terraform_provider "testProvider" {
  attr  = var.testProvider.attr
  count = var.testProvider.count
}

module "test" {
  source   = "namespace/cts/test"
  version  = "0.0.0"
  services = var.services

  arg1 = var.arg1
  arg2 = var.arg2
}

Variables

  1. Create a file named variables.tf and copy the variable "services" {} code found in this variables.tf template file. It would resemble the variable below (this may be not up to date, please refer to the template file).
    variable "services" {
      description = "Consul services monitored by Consul Terraform Sync"
      type = map(
        object({
          id        = string
          name      = string
          kind      = string
          address   = string
          port      = number
          meta      = map(string)
          tags      = list(string)
          namespace = string
          status    = string
    
          node                  = string
          node_id               = string
          node_address          = string
          node_datacenter       = string
          node_tagged_addresses = map(string)
          node_meta             = map(string)
    
          cts_user_defined_meta = map(string)
        })
      )
    }
  2. This is the services variable declared at the root module that is expected to be compatible with the services variable declared in the module for testing.
  3. If there are any additional input variables referenced in main.tf specific to your test, you can add the variables declarations below var.services.

Continuing with the main.tf example that Consul Terraform Sync generates, this is the corresponding variables.tf that would be created:

variables.tf
# This file is generated by Consul Terraform Sync.
#
# The HCL blocks, arguments, variables, and values are derived from the
# operator configuration for Sync. Any manual changes to this file
# may not be preserved and could be overwritten by a subsequent update.
#
# Task: <task name>
# Description: <task description>

# Service definition protocol v0
variable "services" {
  description = "Consul services monitored by Consul Terraform Sync"
  type = map(
    object({
      id        = string
      name      = string
      kind      = string
      address   = string
      port      = number
      meta      = map(string)
      tags      = list(string)
      namespace = string
      status    = string

      node                  = string
      node_id               = string
      node_address          = string
      node_datacenter       = string
      node_tagged_addresses = map(string)
      node_meta             = map(string)

      cts_user_defined_meta = map(string)
    })
  )
}

variable "testProvider" {
  default     = null
  description = "Configuration object for testProvider"
  type = object({
    attr  = string
    count = number
  })
}

Input Variables

  1. Create a file named terraform.tfvars and copy the mock value for the services input variable. The mock value represents service information from the Consul Catalog for 2 services, named "api" and "web", with 2 instances of the service "web".
  2. You can modify the values to test different variants for your module as long as the changes retain the same variable structure.
    services = {
      "api" : {
        address = "172.17.0.1"
        id      = "api"
        name    = "api"
        kind    = ""
        port    = 80
        meta = {}
        tags            = []
        namespace       = ""
        status          = "passing"
        node_id         = "node_a"
        node            = "foobar"
        node_address    = "192.168.10.10"
        node_datacenter = "dc1"
        node_tagged_addresses = {
          lan = "192.168.10.10"
          wan = "10.0.10.10"
        }
        node_meta = {}
        cts_user_defined_meta = {}
      },
      "web_1" : {
        address = "172.17.0.3"
        id      = "web_1"
        name    = "web"
        kind    = ""
        port    = 5000
        meta = {
          foobar_meta_value = "baz"
        }
        tags            = ["tacos"]
        namespace       = ""
        status          = "passing"
        node_id         = "node_a"
        node            = "foobar"
        node_address    = "192.168.10.10"
        node_datacenter = "dc1"
        node_tagged_addresses = {
          lan = "192.168.10.10"
          wan = "10.0.10.10"
        }
        node_meta = {
          somekey = "somevalue"
        }
        cts_user_defined_meta = {}
      },
      "web_2" : {
        address = "172.17.0.3"
        id      = "web_2"
        name    = "web"
        kind    = ""
        port    = 5000
        meta = {
          foobar_meta_value = "baz"
        }
        tags            = ["burrito"]
        namespace       = ""
        status          = "passing"
        node_id         = "node_b"
        node            = "foobarbaz"
        node_address    = "192.168.10.11"
        node_datacenter = "dc1"
        node_tagged_addresses = {
          lan = "192.168.10.11"
          wan = "10.0.10.10"
        }
        node_meta = {}
        cts_user_defined_meta = {}
      }
    }

Execute Terraform

  1. Set any additional configuration that may be required for your setup and testing the module. This may include setting up real infrastructure to interface with and preparing configuration and authentication for the provider(s).
  2. Run terraform init to setup the local Terraform environment.
  3. Run terraform plan and terraform apply to execute changes to your network infrastructure.
  4. Verify that the infrastructure is updated with the services values.
  5. Update the services value in this file in between Terraform runs to emulate dynamic changes discovered from Consul Catalog by Consul Terraform Sync. This could be adding or removing a service or service instance.

Clone this wiki locally