In my previous article on configuring a Terraform module for an Azure Function App with a private endpoint and a storage account, I shown a section within the application_stack
block where I needed to implement function apps stacks. Here in this article I focusing on advanced variable validation for application stack block. These stacks include .NET, Java, Python, and Node.js, each with specific version requirements. For example, the Linux Function App service plan supports Python versions from 3.7 to 3.11. Similarly, there are defined version ranges for Java, .NET, and other stacks.
To address these requirements, I created multiple validation blocks to ensure that the versions specified for each stack are within the allowed ranges.
Additionally, for custom runtimes, which require a boolean value to indicate whether they are enabled, I included separate validation blocks with appropriate error messages to ensure correct boolean values (true or false).
A more complex validation scenario involved ensuring that only one stack in the application_stack
object had a non-null numerical version at any given time, while all others should be null or false. For instance, if Python is set to version 3.11, all other stacks must be null or false. Furthermore, since Python is not supported on Windows Function Apps, I added validation to handle this restriction as well.
Related article: Terraform module Azure function app with private endpoint and storage account
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
variable "os" { default = "Linux" } variable "application_stack" { type = object({ dotnet_version = optional(string) use_dotnet_isolated_runtime = optional(bool) java_version = optional(string) node_version = optional(string) python_version = optional(string) powershell_core_version = optional(string) use_custom_runtime = optional(bool) }) default = { dotnet_version = null use_dotnet_isolated_runtime = false #null java_version = null node_version = 12 python_version = null powershell_core_version = null use_custom_runtime = false } # Validation for .NET version validation { condition = ( var.application_stack.dotnet_version == null || ( (var.os == "Linux" && (var.application_stack.dotnet_version == null || can(regex("^(3\\.1|6\\.0|7\\.0|8\\.0)$", var.application_stack.dotnet_version)))) ) || ( (var.os == "Windows" && (var.application_stack.dotnet_version == null || can(regex("^(3\\.0|4\\.0|6\\.0|7\\.0|8\\.0)$", var.application_stack.dotnet_version)))) ) ) error_message = "If specified, .NET version must be one of 3.1, 6.0, 7.0, or 8.0 for Linux. Windows versions are possible values include v3.0, v4.0 v6.0, v7.0 and v8.0. Defaults to v4.0" } validation { condition = can(var.application_stack.use_dotnet_isolated_runtime) && (var.os == "Linux" || var.os == "Windows") error_message = "Should the DotNet process use an isolated runtime, The 'use_dotnet_isolated_runtime' must be a boolean value, accepted true or false. Defaults to false" } validation { condition = ( var.application_stack.java_version == null || ( var.os == "Linux" && can(regex("^(8|11|17)$", var.application_stack.java_version)) ) || ( var.os == "Windows" && can(regex("^(1\\.8|11|17)$", var.application_stack.java_version)) ) ) error_message = "The version of Java to use. Linux supported versions include 8, 11 & 17. For Windows supported versions include 1.8, 11 & 17 (In-Preview)." } validation { condition = ( var.application_stack.node_version == null || ( lower(var.os) == "linux" && can(regex("^(12|14|16|18|20)$", var.application_stack.node_version)) ) || ( lower(var.os) == "windows" && can(regex("^(~12|~14|~16|~18|~20)$", var.application_stack.node_version)) ) ) error_message = "The version of Node to run. Possible values include 12, 14, 16, 18, and 20 for Linux. Possible values include ~12, ~14, ~16, ~18, and ~20 for Windows." } validation { condition = var.application_stack.python_version == null || ( lower(var.os) == "Linux" && can(regex("^(3\\.12|3\\.11|3\\.10|3\\.9|3\\.8|3\\.7)$", var.application_stack.python_version)) ) error_message = var.os == "Windows" ? "Windows is not supported" : "The version of Python to run. Possible values for Linux are 3.12, 3.11, 3.10, 3.9, 3.8 and 3.7." } # validation { # condition = var.application_stack.python_version == null || ( # lower(var.os) == "linux" && can(regex("^(3\\.12|3\\.11|3\\.10|3\\.9|3\\.8|3\\.7)$", var.application_stack.python_version)) # ) # error_message = lower(var.os) == "windows" ? "Python versions are only supported on Linux. Please set the OS to Linux or remove the Python version." : "The Python version is invalid. Supported versions for Linux are 3.12, 3.11, 3.10, 3.9, 3.8, and 3.7." # } validation { condition = var.application_stack.powershell_core_version == null || ( lower(var.os) == "linux" && can(regex("^(7|7\\.2|7\\.4)$", var.application_stack.powershell_core_version)) ) || ( lower(var.os) == "windows" && can(regex("^(7|7\\.2|7\\.4)$", var.application_stack.powershell_core_version)) ) error_message = "he version of PowerShell Core to run. Windows and Linux Possible values are 7, 7.2, and 7.4" } validation { condition = can(var.application_stack.use_custom_runtime) && (var.os == "Linux" || var.os == "Windows") error_message = "Should the Linux Function App use a custom runtime?, The 'use_custom_runtime ' must be a boolean value, accepted true or false. Defaults to false" } validation { condition = ( ( ( length([for known_stack01 in [ var.application_stack.dotnet_version, var.application_stack.java_version, var.application_stack.node_version, var.application_stack.python_version, var.application_stack.powershell_core_version ] : known_stack01 if known_stack01 != null]) == 1 #<= 1 ) && ( length([for unknown_stack01 in [ var.application_stack.use_dotnet_isolated_runtime, var.application_stack.use_custom_runtime ] : unknown_stack01 if unknown_stack01 == true]) == 0 ) ) || ( ( length([for known_stack01 in [ var.application_stack.dotnet_version, var.application_stack.java_version, var.application_stack.node_version, var.application_stack.python_version, var.application_stack.powershell_core_version ] : known_stack01 if known_stack01 != null]) == 0 ) && ( length([for unknown_stack01 in [ var.application_stack.use_dotnet_isolated_runtime, var.application_stack.use_custom_runtime ] : unknown_stack01 if unknown_stack01 == true]) == 1 #<= 1 ) ) ) error_message = "Only one runtime version or isolated runtime option can be set at a time." } } |
Download this example code Terraform Variable multiple advanced validation block.tf here or it is also available on github.com.
To test In my first validation and apply in terraform is everything OK and all values are provided correctly I see no error. But as I change variable values for python (providing incorrect value) and assigning addition value in node_version. It shows the error message as expected.
Useful Articles
Terraform Azure function app with private endpoint and storage account
Hashicorp Terraform map and object inside module and variable example
Terraform one module deploy null or multiple resources based on input
Terraform A reference to a resource type must be followed by at least one attribute access, specifying the resource name
Terraform fore_each for loop filter with if condition example
Terraform remote-exec provisioner with ssh connection in null_resource
Terraform count vs for_each for examples with map of objects
Terraform one module deploy null or multiple resources based on input (nested for loop) Example of Terraform functions flatten() and coalesce()
Terraform Azure Create Private Endpoint to existing Storage Account with Custom Private DNS zone record link
Creating a Private Endpoint for Azure Storage Account with required sub services using Terraform Example Terraform functions of lookup() and lower()
Using element function with count meta argument example Terraform Azure subnets Example Terraform functions of element(), count() and sum()
Terraform create Azure Virtual Network subnets from map of object and show name in the header Header name change in the output
Creating a Private Endpoint for Azure Storage Account with Terraform example 2 Example of for_each with toset() function
Creating a Private Endpoint for Azure Storage Account with Terraform example 3