Recently, I wanted to automate the upload of a HTML email template plus a logo (PNG) to an Azure storage account. These files are used by an Azure Function to send emails via SMTP. In order to be able to make adjustments quickly (without redeploying the Azure function) – especially in the test environment – I decided to upload these files via Terraform to a storage account instead of deploying them together with the Azure Function.
Versions
- Terraform:
1.10.4 - hashicorp/azurerm provider:
3.117.0
I put the files into a folder which is in the same directory as the terraform config (.tf files).
iac/
├── templates-and-logos/
│ ├── arbitrary-logo.png
│ └── arbitrary-template.html
├── main.tf
└── …
To upload all files in folder templates-and-logos to an Azure storage account container (storage account and container already defined in main.tf), I added the following resource to main.tf.
NOTE: the files are uploaded to arbitraryFolder/arbitrarySubFolder within the specified container.
resource "azurerm_storage_blob" "templates_and_logos" {
for_each = fileset(path.module, "templates-and-logos/*")
name = "arbitraryFolder/arbitrarySubFolder${trimprefix(each.key, "templates-and-logos")}"
storage_account_name = azurerm_storage_account.sa.name
storage_container_name = azurerm_storage_container.arbitrary.name
type = "Block"
source = each.key
}
The for_each Meta-Argument creates one instance for each member of the fileset which results in the following plan output.
# azurerm_storage_blob.templates_and_logos["templates-and-logos/arbitrary-template.html"] will be created
+ resource "azurerm_storage_blob" "templates_and_logos" {
+ access_tier = (known after apply)
+ content_type = "application/octet-stream"
+ id = (known after apply)
+ metadata = (known after apply)
+ name = "arbitraryFolder/arbitrarySubFolder/arbitrary-template.html"
+ parallelism = 8
+ size = 0
+ source = "templates-and-logos/arbitrary-template.html"
+ storage_account_name = "arbitrarysa"
+ storage_container_name = "arbitrarycontainer"
+ type = "Block"
+ url = (known after apply)
}
# azurerm_storage_blob.templates_and_logos["templates-and-logos/arbitrary-logo.png"] will be created
+ resource "azurerm_storage_blob" "templates_and_logos" {
+ access_tier = (known after apply)
+ content_type = "application/octet-stream"
+ id = (known after apply)
+ metadata = (known after apply)
+ name = "arbitraryFolder/arbitrarySubFolder/arbitrary-logo.png"
+ parallelism = 8
+ size = 0
+ source = "templates-and-logos/arbitrary-logo.png"
+ storage_account_name = "arbitrarysa"
+ storage_container_name = "arbitrarycontainer"
+ type = "Block"
+ url = (known after apply)
}
To ensure that always the most recent version of the files is uploaded, extend the IaC config as follows.
resource "null_resource" "run_always" {
triggers = {
timestamp = "${timestamp()}"
}
}
resource "azurerm_storage_blob" "templates_and_logos" {
for_each = fileset(path.module, "templates-and-logos/*")
name = "arbitraryFolder/arbitrarySubFolder${trimprefix(each.key, "templates-and-logos")}"
storage_account_name = azurerm_storage_account.sa.name
storage_container_name = azurerm_storage_container.arbitrary.name
type = "Block"
source = each.key
lifecycle {
replace_triggered_by = [
null_resource.run_always
]
}
}

Leave a comment