Using a downloaded image during Terraform apply fails due to plan-time validation
I'm trying to dynamically download a server image using Terraform's null_resource with the local-exec provisioner and then pass the downloaded image to the stackit_image resource via the local_file_path attribute.
However, this fails during the terraform plan phase because the image file doesn't exist yet. Terraform attempts to validate the file path during the plan phase, even though the file is only created during terraform apply.
My goal is to replicate the behavior of openstack_images_image_v2.web_download = true, where the image is downloaded during terraform apply and immediately used — without requiring the image to exist beforehand.
Question
Would it be possible to defer the validation of the file path in stackit_image.local_file_path until the apply phase?
Or alternatively, is there a supported approach to dynamically download an image during terraform apply and use it with the stackit_image resource—without requiring the image to be pre-downloaded?
Terraform Code
locals {
image_source_url = "https://pfsense.object.storage.eu01.onstackit.cloud/pfsense-ce-2.7.2-amd64-10-12-2024.qcow2"
image_file_name = basename(local.image_source_url)
image_local_path = "${path.root}/.terraform/image_cache/${local.image_file_name}"
}
resource "null_resource" "download_image" {
provisioner "local-exec" {
command = "curl -L -o \"${local.image_local_path}\" \"${local.image_source_url}\""
}
triggers = {
file_check = try(filebase64sha256(local.image_local_path), "")
}
}
resource "stackit_image" "this" {
project_id = var.project_id
labels = var.labels
name = local.image_file_name
disk_format = "qcow2"
local_file_path = local.image_local_path
config = {
# UEFI must be disabled for this image to boot correctly
uefi = false
}
depends_on = [null_resource.download_image]
}
Error Output
╷
│ Error: Invalid Attribute Value
│
│ with stackit_image.this,
│ on main.tf line 23, in resource "stackit_image" "this":
│ 23: local_file_path = local.image_local_path
│
│ Attribute local_file_path file must exist, got: ./.terraform/image_cache/pfsense-ce-2.7.2-amd64-10-12-2024.qcow2
╵
Any advice or workarounds would be greatly appreciated. Thank you!
Workaround
As a temporary solution, you can create an empty placeholder file before running terraform plan. This allows the validation to pass, even though the actual image content will be downloaded later during terraform apply.
Example:
mkdir -p .terraform/image_cache
touch .terraform/image_cache/pfsense-ce-2.7.2-amd64-10-12-2024.qcow2
Hello @rswrz, thanks for the report. I've just checked the code: The existance of the file is checked eagerly during pan time, as you've observed. This actually intentionally and conforms to the terraform philosophy, as all resource should check their respective preconditions, before any work actually starts. This avoid unnecessary consumption of API resources. Could be a more suitable work around be to separate your configuration into multiple modules? One for making sure that the image resource exists, and the other for actually creating the server?
Moin @bahkauv70, thanks for the quick reply!
Could a more suitable workaround be to separate your configuration into multiple modules? One for making sure that the image resource exists, and the other for actually creating the server?
Possibly — I'll explore that approach and will follow up here if I find a workable solution.
Another question:
Do you plan to support a feature similar to openstack_images_image_v2.web_download = true in the stackit_image resource in the near future?
I've also hit this issue. I think it's not Terraform philosophy to rely on big files being present locally as this causes headaches with pipelines - you either have to use a fixed runner where the file is present or you have to download the file during every pipeline run.
@bahkauv70 Separating the Terraform code that creates the image from the code that creates a server doesn't address this issue, it merely shifts it from one place to the other. I'd appreciate a solution where no local file needs to be present.
@rswrz I found a workaround. Not a pretty solution, but it works. Check in an empty placeholder file into your repository (in my case alpine.qcow2 and then use code like this:
locals {
alpine_image_url = "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.0-x86_64-uefi-cloudinit-r0.qcow2"
}
resource "terraform_data" "alpine_image" {
triggers_replace = local.alpine_image_url
# Download Alpine image, overwriting placeholder file.
provisioner "local-exec" {
when = create
command = "curl --clobber -o alpine.qcow2 ${local.alpine_image_url}"
}
}
resource "stackit_image" "alpine" {
project_id = stackit_resourcemanager_project.my_project.project_id
name = "alpine"
disk_format = "qcow2"
# local_file_path expects a file to be present at all times, therefore we use an
# empty placeholder file to still be able to download the image on the fly.
local_file_path = "alpine.qcow2"
min_disk_size = 1
min_ram = 128
# Truncate Alpine image to 0 bytes, making it a placeholder file again.
provisioner "local-exec" {
when = create
command = "truncate -s0 ${self.local_file_path}"
}
lifecycle {
replace_triggered_by = [terraform_data.alpine_image]
}
}