Skip to content

Instantly share code, notes, and snippets.

@omarismail
Created July 21, 2023 15:10
Show Gist options
  • Save omarismail/c466f02853a7109234aef78dbf61e015 to your computer and use it in GitHub Desktop.
Save omarismail/c466f02853a7109234aef78dbf61e015 to your computer and use it in GitHub Desktop.
terraform test gpt
import openai
import os
import glob
from dotenv import load_dotenv
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
system_message = """
You are a Terraform expert and understand testing in programming languages.
"""
intro= """
I want to teach you a new testing framework developed in Terraform. Lets do this
in three phases.
Phase one: I will define a set of rules for this test frameowrk.
Phase two: I will give you a sample module and sample tests.
Phase three: I will ask you to write tests for a real module.
Ready?
"""
rules = """
1. Each Terraform test is contained within a test file.
2. Each test file has a ".tftest" file extension.
3. Each test file contains the following root level attributes and
blocks:
3a. One to many run blocks.
3b. Zero to one variables block.
3c. Zero to many provider blocks.
4. The run blocks are executed in order, simulating a series of Terraform
commands being executed directly within the configuration directory
5. The order of the variables and provider blocks doesn't matter, all values
within these blocks are processed once at the beginning of the test operation. A
well laid out test file has the variables and provider blocks defined first, at
the beginning of the file.
6. Each run block has zero to one command attribute, which is either apply or
plan and defaults to apply
7. Each run block has zero to one plan_options block, which contains:
7a. Zero to one mode attribute, which is either normal or refresh-only and defaults to normal.
7b. Zero to one boolean refresh attribute, which defaults to true.
7c. Zero to one replace attribute, which contains a list of resource addresses referencing resources within the configuration under test.
7d. Zero to one target attribute, which contains a list of resource addresses referencing resources within the configuration under test.
8. Each run block contains zero to one variables block. Variables should not
have commas after each one.
9. Each run block contains zero to one module block.
10. Each run block contains zero to one providers attribute.
11. Each run block contains zero to many assert blocks.
12. Each run block contains zero to one expect_failures attribute.
13. The command attribute and plan_options block tell Terraform which command
and options to execute for each run block. The default operation, if neither the
command attribute nor the plan_options block is specified is normal Terraform
apply operation.
14. Terraform run block assertions are Custom Conditions, made up of a condition
and an error message.
"""
samples = """
I am going to give you some sample terraform and sample test.
I will put the sample between a starting --- and an ending ---. The terraform code will
start with a # and a .tf file name, and the test code will start with # and a
.tftest file name.
Sample 1
---
# main.tf
variable "bucket_prefix" {
type = string
}
resource "aws_s3_bucket" "bucket" {
name = "${var.bucket_prefix}_bucket"
}
output "bucket_name" {
value = "${var.bucket_prefix}_bucket"
}
# valid_string_concat.tftest
variables {
bucket_prefix = "test"
}
run "valid_string_concat" {
command = apply
assert {
condition = aws_s3_bucket.bucket.name == "test_bucket"
error_message = "S3 bucket name did not match expected"
}
}
# variable_precedence.tftest
variables {
bucket_prefix = "test"
}
run "uses_root_level_value" {
command = plan
assert {
condition = aws_s3_bucket.bucket.name == "test_bucket"
error_message = "S3 bucket name did not match expected"
}
}
run "overrides_root_level_value" {
command = plan
variables {
bucket_prefix = "other"
}
assert {
condition = aws_s3_bucket.bucket.name == "other_bucket"
error_message = "S3 bucket name did not match expected"
}
}
# customised_provider.tftest
provider "aws" {
region = "us-east-1"
}
run "valid_string_concat" {
command = apply
assert {
condition = aws_s3_bucket.bucket.name == "test_bucket"
error_message = "S3 bucket name did not match expected"
}
}
---
Sample 2
---
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
configuration_aliases = [us-east-1, eu-west-1]
}
}
}
variable "bucket_prefix" {
default = "test"
type = string
}
resource "aws_s3_bucket" "us-east-1_bucket" {
provider = aws.us-east-1
name = "${var.bucket_prefix}_us-east-1_bucket"
}
resource "aws_s3_bucket" "eu-west-1_bucket" {
provider = aws.eu-west-1
name = "${var.bucket_prefix}_eu-west-1_bucket"
}
# customised_providers.tftest
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "eu-west-1"
region = "eu-west-1"
}
run "providers" {
providers = {
aws.us-east-1 = aws
aws.eu-west-1 = aws.eu-west-1
}
assert {
condition = aws_s3_bucket.eu-west-1_bucket.name == "test_eu-west-1_bucket"
error_message = "invalid value for eu-west-1 S3 bucket"
}
assert {
condition = aws_s3_bucket.us-east-1_bucket.name == "test_us-east-1_bucket"
error_message = "invalid value for us-east-1 S3 bucket"
}
}
---
Sample 3
---
# main.tf
var "count" {
type = number
validation {
condition = var.count % 2 == 0
error_message = "must by even number"
}
}
# input_validation.tftest
variables {
count = 0
}
run "zero" {
# The variable defined above is even, so we expect the validation to pass.
command = plan
}
run "one" {
# This time we set the variable is odd, so we expect the validation to fail.
command = plan
variables {
count = 1
}
expect_failures = [
input.count,
]
}
---
"""
# Read Terraform files from a local directory
terraform_files = glob.glob("./terraform-null-label/*.tf")
module_code = ""
for file_path in terraform_files:
with open(file_path, "r") as file:
module_code += file.read()
write_tests = f"""
Can you write tests for this module?
{module_code}
"""
# Create a conversation with the Terraform expert
conversation = [
{"role": "system", "content": "You are a Terraform expert and understand testing in programming languages"},
{"role": "user", "content": intro},
{"role": "user", "content": rules},
{"role": "user", "content": samples},
{"role": "user", "content": write_tests}
]
response = openai.ChatCompletion.create(
model="gpt-4",
messages=conversation,
max_tokens=1000,
n=1,
stop=None,
temperature=0.5
)
summary = response.choices[0].message["content"].strip()
# Open the file in write mode ('w')
with open('my_test.tftest', 'w') as f:
f.write(summary)
print("Done!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment