Terraform on Ubuntu 26.04 LTS - sudo-rs, APT Rollback, and Hardened Base Images
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
DevOps
Test Terraform code with Terratest. Write Go tests that deploy real infrastructure, validate it works, and tear it down. Includes unit tests
Terratest is a Go library for testing infrastructure code. It deploys real resources, validates they work, then destroys them:
Write test → terraform apply → validate → terraform destroyUnlike terraform validate (syntax only) or tfsec (static analysis), Terratest proves your infrastructure actually works by deploying it.
# Initialize Go module
mkdir test && cd test
go mod init github.com/myorg/myproject/test
# Add Terratest
go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/stretchr/testify/assert# modules/s3-bucket/main.tf
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
output "bucket_arn" {
value = aws_s3_bucket.this.arn
}
output "bucket_name" {
value = aws_s3_bucket.this.id
}// test/s3_bucket_test.go
package test
import (
"fmt"
"strings"
"testing"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestS3Bucket(t *testing.T) {
t.Parallel()
// Random name to avoid collisions
uniqueID := strings.ToLower(random.UniqueId())
bucketName := fmt.Sprintf("test-bucket-%s", uniqueID)
awsRegion := "us-east-1"
// Configure Terraform
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/s3-bucket",
Vars: map[string]interface{}{
"bucket_name": bucketName,
"environment": "test",
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": awsRegion,
},
})
// Destroy after test
defer terraform.Destroy(t, terraformOptions)
// Deploy
terraform.InitAndApply(t, terraformOptions)
// Validate outputs
outputBucketName := terraform.Output(t, terraformOptions, "bucket_name")
assert.Equal(t, bucketName, outputBucketName)
// Validate the bucket actually exists in AWS
aws.AssertS3BucketExists(t, awsRegion, bucketName)
}cd test
go test -v -timeout 30m=== RUN TestS3Bucket
TestS3Bucket: s3_bucket_test.go:30: Running terraform init...
TestS3Bucket: s3_bucket_test.go:33: Running terraform apply...
TestS3Bucket: s3_bucket_test.go:38: bucket_name = "test-bucket-abc123"
TestS3Bucket: s3_bucket_test.go:42: Bucket test-bucket-abc123 exists
TestS3Bucket: s3_bucket_test.go:45: Running terraform destroy...
--- PASS: TestS3Bucket (120.5s)func TestEC2Instance(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/ec2",
Vars: map[string]interface{}{
"instance_type": "t3.micro",
"environment": "test",
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Get the instance ID
instanceID := terraform.Output(t, terraformOptions, "instance_id")
// Verify instance is running
instance := aws.GetInstancesByIdE(t, instanceID, "us-east-1")
assert.Equal(t, "running", instance)
// Verify we can SSH
publicIP := terraform.Output(t, terraformOptions, "public_ip")
ssh.CheckSshCommand(t, publicIP, "ubuntu", keyPair, "echo hello")
}func TestVPC(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"vpc_cidr": "10.99.0.0/16",
"environment": "test",
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
publicSubnets := terraform.OutputList(t, terraformOptions, "public_subnet_ids")
privateSubnets := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
// Validate subnet counts
assert.Equal(t, 2, len(publicSubnets))
assert.Equal(t, 2, len(privateSubnets))
// Validate VPC exists
vpc := aws.GetVpcById(t, vpcID, "us-east-1")
assert.Equal(t, "10.99.0.0/16", vpc.CidrBlock)
}Terraform 1.6 added native testing without Go:
# tests/s3_bucket.tftest.hcl
run "create_bucket" {
command = apply
variables {
bucket_name = "test-bucket-tftest"
environment = "test"
}
assert {
condition = output.bucket_name == "test-bucket-tftest"
error_message = "Bucket name doesn't match"
}
assert {
condition = output.bucket_arn != ""
error_message = "Bucket ARN should not be empty"
}
}Run:
terraform test| Terratest (Go) | terraform test (native) | |
|---|---|---|
| Language | Go | HCL |
| Deploys real infra | Yes | Yes (or plan-only) |
| HTTP validation | Yes | No |
| SSH validation | Yes | No |
| Custom logic | Full Go stdlib | Limited to HCL |
| CI/CD integration | Standard Go testing | Built-in |
| Best for | Complex validation | Quick assertions |
test:
stage: test
image: golang:1.22
services:
- docker:dind
before_script:
- apt-get update && apt-get install -y unzip
- curl -LO https://releases.hashicorp.com/terraform/1.9.0/terraform_1.9.0_linux_amd64.zip
- unzip terraform_*.zip && mv terraform /usr/local/bin/
script:
- cd test
- go test -v -timeout 30m -count=1
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"t.Parallel() — run tests concurrentlygo test -timeout 30m (default 10m is too short)defer Destroy — prevent resource leaksWithDefaultRetryableErrors — handles transient AWS errorsproject/
├── modules/
│ ├── vpc/
│ ├── ec2/
│ └── rds/
├── test/
│ ├── go.mod
│ ├── go.sum
│ ├── vpc_test.go
│ ├── ec2_test.go
│ └── rds_test.go
└── tests/ # Native terraform test
└── basic.tftest.hclLearn by doing with interactive courses on CopyPasteLearn:
Use Terratest for complex validation (HTTP checks, SSH, custom logic) and terraform test for quick assertions. Both deploy real infrastructure and destroy it after. Always use t.Parallel(), random resource names, and defer Destroy to keep tests fast and clean. Run tests in CI on every merge request to catch infrastructure bugs before they reach production.
Install and run Terraform on Ubuntu 26.04 LTS Resolute Raccoon. Covers sudo-rs as default, APT 3.2 rollback, Kernel 7.0, Wayland-only, ROCm, and building...
Set up OCI Load Balancer with Terraform — backend sets, listeners, SSL certificates, and health checks. Step-by-step guide with code examples and best practi...
Configure OCI Object Storage buckets with Terraform — lifecycle policies, pre-authenticated requests, and replication. Step-by-step guide with code examples ...
Deploy Oracle Autonomous Database with Terraform — ATP and ADW instances, wallets, and private endpoints. Step-by-step guide with code examples and best prac...