18/04/2024
Terraform is a powerful tool that helps you build and manage your digital infrastructure, kind of like how you use Lego bricks to build things. Instead of manually setting up servers, databases, and other resources, Terraform lets you write code to define what you want your infrastructure to look like. Let's see how Terraform can drive Fastly
How to install Terraform
On a Mac :
brew install hashicorp/tap/terraform
Verify Terraform is set up :
terraform -help
Create a the main terraform file
Let’s create our terraform project in his own folder :
mkdir terraform-tutorial && cd terraform-tutorial
Then let’s create our main.tf
touch main.tf
Add Fastly as a provider :
terraform {
required_providers {
fastly = {
source = "fastly/fastly"
version = ">= 5.7.3"
}
}
}
Add a resource :
In the following code you can personalize the demo_tutorial
part the name
and the domain name.
domain
For the domain name you can pick what ever you want if you want Fastly to create a temp domain name for you, but you have to follow the format : <name>.global.ssl.fastly.net
force_destroy = true
In Terraform, the force_destroy argument is used in conjunction with certain resources, typically those that manage persistent storage or long-lived infrastructure components. When force_destroy is set to true, it instructs Terraform to destroy the resource even if it has non-Terraform-managed changes or it’s not empty.
For example, if you have an AWS S3 bucket managed by Terraform and you want to delete it, but it still contains objects, Terraform will refuse to delete it by default because it wants to prevent accidental data loss. However, if you set force_destroy to true, Terraform will proceed with the deletion, permanently removing all data from the bucket and then destroying the bucket itself.
The backend
block is for the address of your origin :
resource "fastly_service_vcl" "demo_tutorial" {
name = "Super tutorial"
domain {
name = "antoine-super-tutorial.global.ssl.fastly.net"
}
backend {
address = "httpbin.org"
name = "Httbin dummy server"
}
force_destroy = true
}
Add an output
In Terraform, the output block is used to define values that should be exposed to the user after Terraform has executed its operations. These values can be useful for displaying important information or passing data between Terraform configurations.
output "active" {
value = fastly_service_vcl.demo_tutorial.active_version
}
Add the Fastly API Key
provider "fastly" {
api_key = "NyVYPuAb2Jb3nu_tsQblrMtmk-gw-oBd"
}
The complete main.tf
terraform {
required_providers {
fastly = {
source = "fastly/fastly"
version = ">= 5.7.3"
}
}
}
# Configure the Fastly Provider
provider "fastly" {
api_key = "NyVYPuAb2Jb3nu_tsQblrMtmk-gw-oBd"
}
resource "fastly_service_vcl" "demo_tutorial" {
name = "Super tutorial"
domain {
name = "antoine-super-tutorial.global.ssl.fastly.net"
}
backend {
address = "httpbin.org"
name = "Httbin dummy server"
}
force_destroy = true
}
output "active" {
value = fastly_service_vcl.demo_tutorial.active_version
}
Deploy the project on Fastly
terraform init
Terraform relies on plugins called providers to interact with various cloud platforms, APIs, and services. When you run terraform init, it downloads the necessary provider plugins specified in your configuration files and installs them locally. This ensures that Terraform has the required tools to manage your infrastructure.
Run :
terraform init
tarraform plan
The terraform plan command is used in Terraform to create an execution plan. It essentially simulates the execution of the configuration and displays what actions Terraform will take to achieve the desired state defined in the configuration files
terraform plan
tarraform apply
terraform apply is a command used in Terraform to apply the changes specified in your Terraform configuration to your infrastructure.Terraform executes the actions outlined in the execution plan. This may involve creating new resources, updating existing ones, or destroying resources that are no longer needed.
terraform apply
Add a custom VCL
In the resource fastly_service_vcl
we can specify a vcl block to add a custom vcl as specified in the doc
vcl {
name = "my_custom_main_vcl"
content = file("${path.module}/vcl/main.vcl")
main = true
}
name
: the name of your VCL
content
: The path to your vcl code. In my case my vcl is stored in a vcl
folder and in a main.vcl
file.
main
: is it the main vcl ?
The complete resource block :
resource "fastly_service_vcl" "test_service" {
name = "An Example Service"
domain {
name = "hello-antoine.global.ssl.fastly.net"
}
backend {
address = "httpbin.org"
name= "My Test Backend"
}
vcl {
name = "my_custom_main_vcl"
content = file("${path.module}/vcl/main.vcl")
main = true
}
force_destroy = true
}
The custom vcl
the vcl_recv
subroutine. This subroutine is executed whenever Varnish receives an incoming HTTP request.
if (req.url.path == "/anything/here") {
set req.http.X-CustomHeader = "example";
}
This if block checks if the request URL path is exactly /anything/here
. If it is, then it sets a custom HTTP header named « X-CustomHeader » with the value « example » in the request. This header modification allows for customization of the request handling based on the requested URL.
if (req.url.path == "/anything/not/found") {
error 600;
}
This if block checks if the request URL path is exactly « /anything/not/found ». If it is, then it generates a synthetic error with status code 600
sub vcl_error { }
This VCL (Varnish Configuration Language) code snippet defines a subroutine vcl_error, which is executed when an error occurs during the processing of a request
if (obj.status == 600) {
}
This block of code generates a synthetic response body using the synthetic keyword. It returns a simple HTML document containing an UTF-8 charset meta tag and a « Not Found » message in an <h1>
element. This synthetic response is sent to the client when the error condition specified in the if block is met.
Our complete VCL
// vcl/main.vcl
sub vcl_recv {
#FASTLY recv
if (req.url.path == "/anything/here") {
set req.http.X-CustomHeader = "example";
}
if (req.url.path == "/anything/not/found") {
error 600;
}
return(lookup);
}
sub vcl_error {
#FASTLY error
if (obj.status == 600) {
set obj.status = 404;
set obj.http.Content-Type = "text/html";
synthetic {"
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>Not Found 🤷♂️ </h1>
</body>
</html>
"};
return(deliver);
}
}
To deploy as said before :
terraform plan
And then
terraform apply
Add a VCL snippet
Instead of writing and importing a full VCL configuration in a separate file, it could make sense to split your logic, or parts of your logic, into multiple VCL snippets. Here’s how to proceed with Terraform:
snippet {
name = "custom-snippet" # Name of the snippet
type = "recv" # In which subroutine should the snippet reside
content = <<EOT
# Custom VCL logic
if (req.http.X-Custom-Header) {
set req.http.X-Response-Header = "Custom response";
}
EOT
priority = 10
}
Following our previous example the full Terraform file should look like this :
resource "fastly_service_vcl" "demo_tutorial" {
name = "Super tutorial"
domain {
name = "antoine-super-tutorial.global.ssl.fastly.net"
}
backend {
address = "httpbin.org"
name = "Httbin dummy server"
}
vcl {
name = "my_custom_main_vcl"
content = file("${path.module}/vcl/main.vcl")
main = true
}
# snippet START
snippet {
name = "custom-snippet"
type = "recv" # Indique où le snippet sera appliqué (recv, deliver, etc.)
content = <<EOT
# Custom VCL logic
if (req.http.X-Custom-Header) {
set req.http.X-Response-Header = "Custom response";
}
EOT
priority = 10
}
# snippet END
force_destroy = true
}
