DEV Community

Eliise
Eliise

Posted on • Edited on

9 4

Testing terraform with Pester

In this post I'd like show how you can create integrations tests for terraform with Pester, a powershell testing framework.

In this example I'll be using the Azure DevOps terraform provider along with the Azure Devops CLI, but these are just examples that can be replaced as needed for your tests.

All code snippets can be found in terraform-pester-devcontainer-example repository.

Environment

Terraform under test

The terraform below will be creating a project, a repository and a variable group

main.tf

provider "azuredevops" {
  version = ">= 0.0.1"
}

resource "azuredevops_project" "test" {
  project_name = "test-project"
}

resource "azuredevops_git_repository" "test" {
  project_id = azuredevops_project.test.id
  name       = "test-repo"
  initialization {
    init_type = "Clean"
  }
}

resource "azuredevops_variable_group" "test" {
  project_id   = azuredevops_project.test.id
  name         = "test-vg"
  allow_access = true

  variable {
    name  = "example_var_name"
    value = "example_var_value"
  }
}

Pester tests

The tests will:

  1. apply terraform
  2. check validity of state against az devops CLI
  3. tear down the deployed infrastructure

tfIntegration.tests.ps1

function Get-ResourceState($address) {
    # Get state and resources after `terraform apply`
    $tfState = terraform show -json | ConvertFrom-Json
    $resources = $tfState.values.root_module.resources

    return $resources | Where-Object { $_.address -eq $address }
}

function Get-Project {
    return Get-ResourceState "azuredevops_project.test"
}

function Get-Repository {
    return Get-ResourceState "azuredevops_git_repository.test"
}

function Get-VariableGroup {
    return Get-ResourceState "azuredevops_variable_group.test"
}

Describe "Terraform Deployment" -Tag 'Deploy' { 
    Context "clean tfstate" {

        It "remove tfstate" {
            $tfStatePath = "./terraform.tfstate"
            if (Test-Path $tfStatePath -PathType leaf) {
                Remove-Item $tfStatePath -Force
            }
        }

        It "terraform initialize" {
            terraform init
        }

        It "terraform apply" {
            $tfResult = terraform apply -auto-approve
            $LASTEXITCODE | Should -Be 0
            Write-Host $tfResult
        }

        It "returns state for all resources" {
            Get-Project | Should -Not -BeNullOrEmpty
            Get-Repository | Should -Not -BeNullOrEmpty
            Get-VariableGroup | Should -Not -BeNullOrEmpty
        }

        It "returns a valid repostiory for the repository ID" {
            $repository = Get-Repository
            $repository.values.id | Should -Not -BeNullOrEmpty
            az repos show -r $repository.values.id -p $repository.values.project_id --org ${env:AZDO_ORG_SERVICE_URL}
            $LASTEXITCODE | Should -Be 0
        }

        It "returns a valid variableGroup for the variableGroup ID" {
            $variableGroup = Get-VariableGroup
            $variableGroup.values.id | Should -Not -BeNullOrEmpty
            az pipelines variable-group show --id $variableGroup.values.id -p $variableGroup.values.project_id --org ${env:AZDO_ORG_SERVICE_URL}
            $LASTEXITCODE | Should -Be 0
        }

        It "returns an empty plan when re-run" {
            # Run a terraform plan and check no changes are detected
            # `-detailed-exitcode` will cause the command to exit with 0 exit code
            # only if there are no diffs in the plan 
            # https://www.terraform.io/docs/commands/plan.html#detailed-exitcode
            #
            # If this test fails it shows an issue with the `read` command returning different data between calls.

            terraform plan -out plan.tfstate -detailed-exitcode

            if ($LASTEXITCODE -ne 0) {
                Write-Host "Detected terraform changes:"
                terraform show plan.tfstate
            }

            $LASTEXITCODE | Should -Be 0 -Because "plan should show no changes"
        }
    }
}

Describe "Terraform Destroy" -Tag 'Destroy' { 
    Context "existing tfstate" {

        It "ensure we have an existing terraform deployment" {
            "./terraform.tfstate" | Should -Exist
        }

        It "terraform destroy" {
            $tfResult = terraform destroy -auto-approve
            $LASTEXITCODE | Should -Be 0
            Write-Host $tfResult

        }

        It "clean up terraform files" {
            Remove-Item ./terraform.tfstate -Force
            Remove-Item ./terraform.tfstate.backup -Force
            Remove-Item ./plan.tfstate -Force
        }
    }
}

Run with

Invoke-Pester
# OR
./tfIntegration.tests.ps1

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Billboard image

Try REST API Generation for Snowflake

DevOps for Private APIs. Automate the building, securing, and documenting of internal/private REST APIs with built-in enterprise security on bare-metal, VMs, or containers.

  • Auto-generated live APIs mapped from Snowflake database schema
  • Interactive Swagger API documentation
  • Scripting engine to customize your API
  • Built-in role-based access control

Learn more