One of the things I’m currently playing with is a project to deploy some FortiGate Firewalls into cloud platforms. I have a couple of Evaluation Licenses I can use (as we’re a partner), but when it comes to automatically scaling, you need to use the PAYG license.
To try to keep my terraform files as reusable as possible, I came up with this work around. It’s likely to be useful in other places too. Enjoy!
This next block is stored in license.tf
and basically says “by default, you have no license.”
variable "license_file" {
default = ""
description = "Path to the license file to load, or leave blank to use a PAYG license."
}
We can either override this with a command line switch terraform apply -var 'license_file=mylicense.lic'
, or (more likely) the above override file named license_override.tf
(ignored in Git) which has this next block in it:
variable "license_file" {
default = "mylicense.lic"
}
This next block is also stored in license.tf
and says “If var.license is not empty, load that license file [var.license != "" ? var.license
] but if it is empty, check whether /dev/null
exists (*nix platforms) [fileexists("/dev/null")
] in which case, use /dev/null
, otherwise use the NUL:
device (Windows platforms).”
data "local_file" "license" {
filename = var.license_file != "" ? var.license_file : fileexists("/dev/null") ? "/dev/null" : "NUL:"
}
👉 Just as an aside, I’ve seen this “ternary” construct in a few languages. It basically looks like this: boolean_operation ? true_value : false_value
That check, logically, could have been written like this instead: "%{if boolean_operation}${true_value}%{else}${false_value}%{endif}"
By combining two of these together, while initially it looks far more messy and hard to parse, I’ve found that, especially in single-line statements, it’s much more compact and eventually easier to read than the alternative if/else/endif
structure.
So, this means that we can now refer to data.local_file.license
as our data source.
Next, I want to select either the PAYG (Pay As You Go) or BYOL (Bring Your Own License) licensed AMI in AWS (the same principle applies in Azure, GCP, etc), so in this block we provide a different value to the filter in the AMI Data Source, suggesting the string “FortiGate-VM64-AWS *x.y.z*
” if we have a value provided license, or “FortiGate-VM64-AWSONDEMAND *x.y.z*
” if we don’t.
data "aws_ami" "FortiGate" {
most_recent = true
filter {
name = "name"
values = ["FortiGate-VM64-AWS%{if data.local_file.license.content == ""}ONDEMAND%{endif} *${var.release}*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["679593333241"] # AWS
}
And the very last thing is to create the user-data
template (known as customdata
in Azure), using this block:
data "template_cloudinit_config" "config" {
gzip = false
base64_encode = false
part {
filename = "config"
content_type = "multipart/mixed"
content = templatefile(
"${path.module}/user_data.txt.tmpl",
{
hostname = "firewall"
}
)
}
part {
filename = "license"
content_type = "text/plain"
content = data.local_file.license.content
}
}
And so that is how I can elect to provide a license, or use a pre-licensed image from AWS, and these lessons can also be applied in an Azure or GCP environment too.
Featured image is “Tracking Methane Sources and Movement Around the Globe” by “NASA/Scientific Visualization Studio”
Thanks for sharing, I ran into this issue which Terraform complaints that “/dev/null” is not a regular file. I am running it in Docker container. Here is the error message:
Call to function “fileexists” failed: /dev/null is not a regular file, but “Dcrw-rw-rw-“.
Any idea?
Best regards,
Don
Hi Don, thanks for the question. Are you running Docker Desktop? If so, it might be a problem with Docker Desktop 3.3.0. If this does match your situation, it looks like upgrading to 3.3.1 or downgrading to 3.2.2 should fix your issue (according to this issue on the docker-for-mac repo). If not… um, I’m stumped :)