Closed prehor closed 2 years ago
TypeScript has a good way of declaring a property on an interface as optional
interface MyObject {
id: string;
name?: string;
}
The ?
make the name
property optional. Maybe simple way to declare this for HCL could be
object({
id = string
name? = string
})
What about adding support for "configuration blocks" in modules just like we can do for resources.
In a resouce, I can do this:
resource "aws_security_group" "allow_tls" {
name = "allow_tls"
vpc_id = aws_vpc.main.id
ingress {
description = "TLS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 22
to_port = 22
protocol = "ssh"
cidr_blocks = [aws_vpc.main.cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
And each block can optionally be specified multiple times, each can have optional params, and each on the backend can interact with or leverage the defaults of the underlying config.
Is there any plans to move modules in this direction as well?
(Returning again to this issue after a while, as it is becoming another development roadblock as I migrate everything to TF 0.13 and as we can now have modules-calling-a-dynamic-count-of-modules.)
Whether implemented by 'objects' or something else on the backend, the challenge (at least the one I keep running into) is to "have many of a thing" in a module while still only needing "just enough" config to get the job done.
Please watch and vote for this related issue if it would resolve your use case: https://github.com/hashicorp/terraform/issues/26051
In short:
variable "ingress" {
type = block
# Nested variables okay within 'block' variable type
variable "from_port" {
type = int
}
variable "to_port" {
type = int
}
variable "description" {
type = string
default = null # optional variables work just as they do today
}
}
Which would function exactly how configuration blocks already do today for resources (but not for modules):
module "mymodule" {
source = "..."
ingress {
from_port = 880
to_port = 880
}
ingress {
description = "Totally optional."
from_port = 22
to_port = 22
}
}
Terraform would still output a list of strongly-typed objects, but because terraform is parsing the defaults block-by-block, each value in can be optional while still having the deterministic schema needed by objects today.
Inspired in part by @wdec and @michelzanini 's comments above.
Could something like this work?
variable "helm_parameters" {
type = list(object({
name: string,
value: any,
force_string: bool,
},{
name: "default_name_string_or_null",
value: "default_name_string_or_null",
force_string: false
}))
description = "Parameters that will override helm_values"
default = []
}
Will this still be addressed ?
+1 it's been more than 2 years, this prevents creating complex variable types with Terraform (e.g: list of maps with optional fields, or maps with optional fields and more than 1 type within the map ...)
I've found a workaround for this, tested in v0.12.x but I don't see why it wouldn't work for v0.11.x also and probably v0.13x. This is EXTREMELY ugly depending on the complexity of your input, but it works and I'm using it so that I can specify only the data I need in an input variable somewhere instead of having to fill out every last field. It works for nested objects as well, but it gets uglier for each layer. The trick is that Terraform is happy to accept "any" as a type, and then the merge() function works both with maps and with objects. Imagine I have a module called "account" that has an input called "users" defined as such:
# account/variables.tf
variable "users" {
type = map(object({
email = string, # I want this to be required and get an error if it's missing
readonly = bool, # I want this to default to false
enabled = bool # I want this to default to true
}))
}
Now for each user, I don't want to specify anything besides the email (the key will be their username) unless I need to, so my input in my inputs.auto.tfvars file would look like this:
users = { jsnuffy = {email = "jsnuffy@example.com},
joeyjojo = {email = "joeyjojojr@example.com", readonly = true}
}
Ok, now here's the fun part. I need to "fix up" the missing stuff with some defaults from somewhere so that the module gets the correct types and doesn't complain and/or crash Terraform (I've found a few dozen ways to crash it in working on this). My root module looks like this:
# main.tf
variable "users" {
type = any, # this is really critical or it'll try to coerce your input into something that you don't want
default = {} # this is optional; require the value or don't.
}
# hold my beer
locals {
# where's the default for the input? Let's just put it here!
defaultUser = {
readonly = false,
enabled = true
}
# THE ORDER HERE MATTERS! the default comes first in the merge() function
actual_users = { for k, v in var.users:
k => merge(local.defaultUser, v)
}
}
# now, local.actual_users has the right fields since if they were missing, the defaultUser variable had them and anything
# overwritten in whatever happens to be in the var.users input will be overwritten from the merge() function here.
# This is what I can pass to the module:
module "accounts" {
src = "path/to/accounts"
# this has been fixed up, so everything will match the type
# UNLESS the email is missing since we didn't include that with the default object that we passed to merge()
users = local.actual_users
}
That's all there is to it. If you have a nested value, you do the same thing but introduce two more intermediary variables. The first has the defaults for everything that is not nested. The second takes the keys for all the inputs and declares ONLY the nested module, then does another loop calling merge() on the key/values in the nested input (use lookup() if you want that itself to be optional). Now you have an intermediary local with all the fields that aren't nested, another with only the nested field, and then you can call merge() on these two locals to get your final local to pass in to the module. You could probably abstract the locals by using a data-only module to fix up the inputs and make this slightly more clean, but the basic idea is to declare the input you actually want and then declare the "default" values elsewhere, then merge the input you get with the default values to construct a valid input that is then passed in to whatever you need to use. Hope this helps someone; it took me a long time to find a way to make this work.
Guys I just found a working workaround for having great control over variables and their default values for composite type for Terraform 0.13.x. The solution uses the new variable validation approach. The workaround fixes 2 major limitations of Terraform:
Here is a brief description:
Before we start with the code, here is some context. I want to define ingress rules for an AWS security_group module. The internal aws_security_group
resource has 3 options for the source of the ingress rules of a security group - cidr_blocks = list(string)
, source_security_group_id = string
and self = bool
. Now I want to have a unified structure on how to supply all the ingress rules from the module. It should look something like this:
module "security_group" "test" {
source = "/path/to/module"
vpc_id = "<vpc_id>"
name = "test"
ingress = [
{
protocol = "tcp"
from_port = 0
to_port = 65535
description = "test sec group"
source = {
cidr_blocks = ["0.0.0.0/0"]
}
},
{
protocol = "tcp"
from_port = 0
to_port = 65535
description = "test sec group"
source = {
source_security_group_id = "sg-<id>"
}
},
{
protocol = "tcp"
from_port = 0
to_port = 65535
description = "test sec group"
source = {
self = true
}
}
]
}
Now if I tried to define the input variable of the module like this:
variable "ingress" {
type = string(object{
protocol = string
from_port = number
to_port = number
description = string
source = any
})
}
In theory, this should work. BUTTT it doesn't. The Terraform requires all objects within a list to look exactly the same. So, source = any
gives us the flexibility, but the list limits us to have either all elements of the list to be with source = { cidr_blocks = [...] }
or source = { self = true|false }
and the same for source_security_group_id
.
Can we do any better?
To bypass the list limitation we can use type = any
for the whole input variable. But then we will lose the type checks during plan
time which can result in misconfigurations during apply
time.
So, can we do any better?
HOLD MY BEER!
# variables.tf
# This is a variable for a list of ingress rules for an AWS security_group module
variable "ingress" {
type = any # we need this because Terraform requires objects to be absolutely equal for lists
default = []
# we will use the new validation feature in 0.13 to do type check during plan time
validation = {
# The ternary operator returns true if the list is empty, because we want to support egress only security groups
condition = length(var.ingress) > 0 ? length([
for x in var.ingress : x
if lookup(x, "protocol", null) != null &&
if lookup(x, "from_port", null) != null &&
if lookup(x, "source", null) != null
]) == length(var.ingress) : true
error_message = "Ingress rules validation error!"
}
}
So let's see how this works. First, we define the type of ingress
variable to be any
. This removes the list limitation of object structure equality. But we want to have a loose type of validation. So what we do:
A side effect of this approach is that we can still have type validation, but validate only the required fields and omit validation of optional fields like the description of the rules in our case. As you can see we don't lookup
for the description
field which means that we can provide it optionally.
I hope this can be of help until Terraform provides a more robust mechanism for type definition and type reuse.
cc @jefftucker @ocervell
I don't have time to try it myself, but instead of trying workarounds and all, you guys should just try out v0.14.0-rc1. They've released this new experimental feature:
module_variable_optional_attrs
: When declaring an input variable for a module whose type constraint (type argument) contains an object type constraint, the type expressions for the attributes can be annotated with the experimental optional(...) modifier. Marking an attribute as "optional" changes the type conversion behavior for that type constraint so that if the given value is a map or object that has no attribute of that name then Terraform will silently give that attribute the value null, rather than returning an error saying that it is required. The resulting value still conforms to the type constraint in that the attribute is considered to be present, but references to it in the recieving module will find a null value and can act on that accordingly.
This sounds interesting, I will check it out. But still, the list limitation for object structural equality is not resolved
This is really good. I think optional function should take a second parameter for default value.
For exemple I have this variable :
variable "ports" {
type = list(object({
name = string
network_id = string
subnet_id = string
admin_state_up = optional(bool)
security_group_ids = optional(list(string))
ip_address = optional(string)
}))
description = <<EOF
The ports list
EOF
default = []
}
For a list object it could be interesting to have a default value for optional attributes without a locals and defaults function like here
Something like this:
variable "ports" {
type = list(object({
name = string
network_id = string
subnet_id = string
admin_state_up = optional(bool, true)
security_group_ids = optional(list(string), ["general", "ssh"])
ip_address = optional(string, null)
}))
description = <<EOF
The ports list
EOF
default = []
}
I found this documentation with the experimental function defaults
being used with optional
.
https://www.terraform.io/docs/configuration/functions/defaults.html
This is not the best implementation, because you need to control default values at locals., but we can set default values for modules, We just need to write good documentation.
Just for: 0.15+
I just tested this, I have an issue with optional values being set to null, is there a way to get them omited ?
My use case is this, on a majority of my module I use this pattern :
aws-load-balancer-controller = merge(
local.helm_defaults,
{
name = "aws-load-balancer-controller"
namespace = "aws-load-balancer-controller"
chart = "aws-load-balancer-controller"
repository = "https://aws.github.io/eks-charts"
service_account_name = "aws-load-balancer-controller"
create_iam_resources_irsa = true
enabled = false
chart_version = "1.0.5"
version = "v2.0.0"
iam_policy_override = null
default_network_policy = true
allowed_cidrs = ["0.0.0.0/0"]
},
var.aws-load-balancer-controller
For now my variable aws-load-balancer-controller
is type any
with default to {}
. This allow the module to work with just:
aws-load-balancer-controller = {
enabled = true
}
Without further customization, if I set a validation like that:
variable "aws-ebs-csi-driver" {
+ description = "Customize aws-ebs-csi-driver helm chart, see `aws-ebs-csi-driver.tf`"
+ type = object({
+ atomic = optional(bool)
+ cleanup_on_fail = optional(bool)
+ dependency_update = optional(bool)
+ disable_crd_hooks = optional(bool)
+ disable_webhooks = optional(bool)
+ force_update = optional(bool)
+ recreate_pods = optional(bool)
+ render_subchart_notes = optional(bool)
+ replace = optional(bool)
+ reset_values = optional(bool)
+ reuse_values = optional(bool)
+ skip_crds = optional(bool)
+ timeout = optional(number)
+ verify = optional(bool)
+ wait = optional(bool)
+ extra_values = optional(string)
+ name = optional(string)
+ namespace = optional(string)
+ chart = optional(string)
+ repository = optional(string)
+ service_account_names = optional(object({
+ controller = optional(string)
+ snapshot = optional(string)
+ }))
+ create_iam_resources_irsa = optional(bool)
+ create_storage_class = optional(bool)
+ storage_class_name = optional(string)
+ is_default_class = optional(bool)
+ enabled = optional(bool)
+ chart_version = optional(string)
+ version = optional(string)
+ iam_policy_override = optional(string)
+ default_network_policy = optional(bool)
+ })
+ default = {}
+ }
It remplace the variable with null
and the merge use null
as a value as the merge is done from local to the vars in that order.
I don't really need to enforce validation I'm trying to use this feature only for documentation but maybe there is some other way
One solution I found was to use YAML as source for variables. For example, for a list of servers:
servers.yml
server: &default
cpu: 8
memory: 8192
ips: []
servers:
server1:
<<: *default
cpu: 2
ips: ["10.2.8.124"]
server2:
<<: *default
cpu: 4
main.tf
locals {
servers = yamldecode(file("servers.yml")).servers
}
Each server in variable servers will contain the 3 attributes defined, either the default or the overridden ones. Again, maybe not as good as implemented directly in terraform I reckon, but at least it works for us
I'm try to use optionals()
and defaults()
on object type but return error:
$ terraform -v
Terraform v0.14.6
+ provider registry.terraform.io/hashicorp/aws v3.29.0
variables.tf
variable "cert" {
description = "ACM Certificate Attributes"
type = object({
domain_name = string
subject_alternative_names = list(string)
validation_method = string
tags = map(string)
options = optional(object({
certificate_transparency_logging_preference = optional(string)
}))
})
}
main.tf
locals {
cert = defaults(var.cert, {
options = {
certificate_transparency_logging_preference = ""
}
})
}
resource "aws_acm_certificate" "cert" {
domain_name = local.cert.domain_name
validation_method = local.cert.validation_method
subject_alternative_names = local.cert.subject_alternative_names
dynamic "options" {
for_each = local.cert.options.certificate_transparency_logging_preference == "" ? [] : [var.cert.options]
content {
certificate_transparency_logging_preference = options.value.certificate_transparency_logging_preference
}
}
tags = local.cert.tags
}
module.tf
module "my-cert" {
source = "../../../../tf-module-certificate-manager"
cert = {
domain_name = "example.com"
validation_method = "DNS"
subject_alternative_names = ["*.example.com"]
tags = {
Name = "example.com"
}
# options = {
# certificate_transparency_logging_preference = "ENABLED"
# }
}
}
This configuration return the follow error:
Error: Error in the function call
on ../../../../tf-module-certificate-manager/main.tf line 37, in locals:
37: cert = defaults(var.cert, {
38: options = {
39: certificate_transparency_logging_preference = ""
40: }
41: })
|----------------
| var.cert is object with 5 attributes
Call to function "defaults" failed: panic in function implementation:
interface conversion: interface {} is nil, not map[string]interface {}
goroutine 10177 [running]:
runtime/debug.Stack(0xc004349268, 0x24f9dc0, 0xc004358a80)
/usr/local/go/src/runtime/debug/stack.go:24 +0x9f
github.com/zclconf/go-cty/cty/function.errorForPanic(...)
/go/pkg/mod/github.com/zclconf/go-cty@v1.7.1/cty/function/error.go:44
github.com/zclconf/go-cty/cty/function.Function.Call.func1(0xc00434a2f0,
0xc00434a310)
/go/pkg/mod/github.com/zclconf/go-cty@v1.7.1/cty/function/function.go:291
+0x95
panic(0x24f9dc0, 0xc004358a80)
/usr/local/go/src/runtime/panic.go:969 +0x1b9
github.com/zclconf/go-cty/cty.Value.GetAttr(0x2ce7960, 0xc001852b50, 0x0, 0x0,
0xc00098f5f0, 0x2b, 0x0, 0x0, 0x2ce7860, 0xc0000503d9)
/go/pkg/mod/github.com/zclconf/go-cty@v1.7.1/cty/value_ops.go:755 +0x40b
github.com/hashicorp/terraform/lang/funcs.defaultsApply(0x2ce7960,
0xc001852b50, 0x0, 0x0, 0x2ce7960, 0xc0025df9e0, 0x24ba300, 0xc004358750,
0x24ba300, 0xc004358750, ...)
/home/circleci/project/project/lang/funcs/defaults.go:102 +0x385
github.com/hashicorp/terraform/lang/funcs.defaultsApply(0x2ce7960,
0xc0025df910, 0x24ba300, 0xc004358360, 0x2ce7960, 0xc0025df9f0, 0x24ba300,
0xc0043587b0, 0x0, 0xc00434a0d8, ...)
/home/circleci/project/project/lang/funcs/defaults.go:107 +0x45c
github.com/hashicorp/terraform/lang/funcs.glob..func29(0xc004304a40, 0x2, 0x2,
0x2ce7960, 0xc0025df910, 0xc0025dfb90, 0x24ba300, 0xc0043589c0, 0xc004358a20,
0xc00434a190, ...)
/home/circleci/project/project/lang/funcs/defaults.go:65 +0xb3
github.com/zclconf/go-cty/cty/function.Function.Call(0xc00021a990,
0xc004304a40, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
/go/pkg/mod/github.com/zclconf/go-cty@v1.7.1/cty/function/function.go:295
+0x51a
github.com/hashicorp/hcl/v2/hclsyntax.(*FunctionCallExpr).Value(0xc00104b0e0,
0xc0019ff0a0, 0x0, 0xc00434b800, 0x1, 0x1, 0x0, 0x0, 0x0)
/go/pkg/mod/github.com/hashicorp/hcl/v2@v2.8.2/hclsyntax/expression.go:442
+0x10c5
github.com/hashicorp/terraform/lang.(*Scope).EvalExpr(0xc003f4efa0, 0x2ce63e0,
0xc00104b0e0, 0x2ce78a0, 0x3dfc420, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
/home/circleci/project/project/lang/eval.go:171 +0x1b7
github.com/hashicorp/terraform/terraform.(*BuiltinEvalContext).EvaluateExpr(0xc003ea9ee0,
0x2ce63e0, 0xc00104b0e0, 0x2ce78a0, 0x3dfc420, 0x0, 0x0, 0x0, 0x200000003,
0xc0015b2000, ...)
/home/circleci/project/project/terraform/eval_context_builtin.go:287 +0xbb
github.com/hashicorp/terraform/terraform.(*NodeLocal).Execute(0xc003eccab0,
0x2d23580, 0xc003ea9ee0, 0xc0035a0002, 0x2509020, 0x2690320)
/home/circleci/project/project/terraform/node_local.go:156 +0x71d
github.com/hashicorp/terraform/terraform.(*ContextGraphWalker).Execute(0xc0031a2c30,
0x2d23580, 0xc003ea9ee0, 0x7fe67a2bd428, 0xc003eccab0, 0x0, 0x0, 0x0)
/home/circleci/project/project/terraform/graph_walk_context.go:127 +0xbc
github.com/hashicorp/terraform/terraform.(*Graph).walk.func1(0x2690320,
0xc003eccab0, 0x0, 0x0, 0x0)
/home/circleci/project/project/terraform/graph.go:59 +0x962
github.com/hashicorp/terraform/dag.(*Walker).walkVertex(0xc000d91140,
0x2690320, 0xc003eccab0, 0xc002cc9e40)
/home/circleci/project/project/dag/walk.go:387 +0x375
created by github.com/hashicorp/terraform/dag.(*Walker).Update
/home/circleci/project/project/dag/walk.go:309 +0x1246
.
Releasing state lock. This may take a few moments...
Someone could help me?
I'm try to use
optionals()
anddefaults()
on object type but return error:$ terraform -v Terraform v0.14.6 + provider registry.terraform.io/hashicorp/aws v3.29.0
Someone could help me?
@thiagolsfortunato According to the doc, defaults
is only available in 0.15+
https://www.terraform.io/docs/language/functions/defaults.html
I'm try to use
optionals()
anddefaults()
on object type but return error:$ terraform -v Terraform v0.14.6 + provider registry.terraform.io/hashicorp/aws v3.29.0
Someone could help me?
@thiagolsfortunato According to the doc,
defaults
is only available in 0.15+https://www.terraform.io/docs/language/functions/defaults.html
@ktmorgan but it's available as experimental feature.
terraform {
experiments = [module_variable_optional_attrs]
}
The docs are not very clear on this, but I tried pretty much everything and I believe that you can't have optional structural types due to how the defaults
functions treats those. It will basically try to get to the deepest level of your data structure before applying defaults.
What I ended up with was having very shallow objects and instead of defaults I'm using coalesce on each key/property individually. That works with optional objects/lists/etc at least.
Does there exist any timeline as to when "module_variable_optional_attrs" - and defaults - won't be experimental anymore?
Does there exist any timeline as to when "module_variable_optional_attrs" - and defaults - won't be experimental anymore?
@DenWin when launching the new release 0.15.
Does there exist any timeline as to when "module_variable_optional_attrs" - and defaults - won't be experimental anymore?
@DenWin when launching the new release 0.15.
Sorry, but they still seem to be experimental.
Does there exist any timeline as to when "module_variable_optional_attrs" - and defaults - won't be experimental anymore?
@DenWin when launching the new release 0.15.
Sorry, but they still seem to be experimental.
Any update on this ? really need this feature :)
At this moment even if optional would be added that would be a huge step, there are other ways to handle default.
Maybe someone could provide a likelihood this experiment will go to production? Really need it, and we are wondering about the risk using this experimental feature in production.
I'm using it in production in a couple modules. You're get the annoying warning, but that's not too troublesome. They'll promote it I'm sure.
Same as @grimm26 here. It's proving very useful, although the try
function seems to not handle the optional properties too well. Or it could just be me not using it correctly
Yep, optional
is very useful. It simplifies our code a lot.
The optional
function works well. It also would be nice to have an additional parameter to define a default value instead of null.
Adding my 👍🏻 on optional
and the module_variable_optional_attrs
flag. We've been using it for a while in our modules and I'm getting anxious to see this promoted to a real non-experimental feature. A rollback/removal of the feature would make me pretty sad...
Same as @grimm26 here. It's proving very useful, although the
try
function seems to not handle the optional properties too well. Or it could just be me not using it correctly
It works well for me.
Optional is a great feature that simplify a lot the code but as others already said, alongside default null
behavior, the ability to define a custom default
:
argumentN optional(bool, true)
will really complete "optional" feature greatly improving DRY and allowing a single point for variables/arguments declaretion instead of duplication in variables + locals.
Optional is a great feature that simplify a lot the code but as others already said, alongside
default null
behavior, the ability to define acustom default
:argumentN optional(bool, true)
will really complete "optional" feature greatly improving DRY and allowing a single point for variables/arguments declaretion instead of duplication in variables + locals.
It's more than a matter of duplication. Having the default declared inline as part of the variable declaration allows documentation to be generated automatically without the risk of the default and locals getting out of sync.
indispensable and fantastic functionality! as they have implemented it now! I hope that from the next version the warning of the experimental functionality will disappear and become stable! I use it in every module! I can’t help it! totally in agreement with @prehor
From what I can tell, this feature has been in the "experimental" phase for approaching a year now. When will this be promoted to a non-experimental feature?
optional()
is potentially very useful for what I want to do, but I can't actually use it because my organization is (understandably) wary of relying on experimental features in a production environment. The fact that it has apparently been working fine as an experiment for a long time but nobody has bothered to make it "live" is, frankly, rather frustrating at this point.
I would also personally support the idea of adding a second parameter to the optional()
syntax to specify a default value other than null (I think the defaults()
stuff is overly cumbersome for most cases and unnecessarily separates default values from the rest of the schema definition, TBH), but frankly I'd be perfectly happy if the current behavior was just made non-experimental as-is.
Just give us something we can actually justify using in real applications (without all the scary warning messages), please!
This is really useful functionality, will love to see it promoted too.
I think the defaults() stuff is overly cumbersome for most cases and unnecessarily separates default values from the rest of the schema definition, TBH), but frankly I'd be perfectly happy if the current behavior was just made non-experimental as-is. @foogod
I guess they are different features at the end, I see use for both, but totally agree that trying to use defaults
to cover the optional
behavior is just... ugly.
@hashicorp Take 1.5+k votes into account during next feature planning plz
I wanted to set optional attributes so I landed on that page, so one more comment for asking an implementation of module_variable_optional_attrs
no more experimental :)
To be honest I use module_variable_optional_attrs
so extensively now that if it's removed I'll just have to stay on my current version of TF forever :) - it's been a godsend. Agree that an optional second parameter for a default value would make it perfect. The current defaults
function doesn't behave intuitively for me when dealing with maps and lists.
optional
+ defaults
has great potential for module developers to express complex & nested inputs that's possible in many yaml/json based templating engines(helm etc).
Hey Guys! I'm facing similar issue when creating optional variables inside a list of objects. Is this Issue related to that or did I missing something in the comments for workarounds.
terraform version: 0.13.7
variable "foo" { type = list(object({ bar= optional(bool), hello= optional(bool), bye= optional(bool), name = string, description = string, birthyear = optional(number), age = optional(number), text = string, records= list(object({ name = string description = string alias = string })) })) }
Issue: Keyword "optional" is not a valid type constructor.
Hey Guys! I'm facing similar issue when creating optional variables inside a list of objects. Is this Issue related to that or did I missing something in the comments for workarounds.
terraform version: 0.13.7
variable "foo" { type = list(object({ bar= optional(bool), hello= optional(bool), bye= optional(bool), name = string, description = string, birthyear = optional(number), age = optional(number), text = string, records= list(object({ name = string description = string alias = string })) })) }
Issue:
Keyword "optional" is not a valid type constructor.
You need to be on at least v0.14
Another 2022 bump on this, would definitely need it in my map of object setup where I don't want advanced options to be mandatory.
I find the separate declaration of the types and the defaults a bit cumbersome and the "optional" keyword superfluous.
I think this has already been suggested, but the syntax for declaring the attributes of a variable is already defined, so it would be consistent to just make them recursive, eg.
variable "simple1" {
default = "blah"
description = "A simple variable"
type = string
}
variable "complex1" {
default = null
description = "A complex variable"
type = object({
mystring1 = {
default = "blah"
description = "A string in an object variable (optional)"
type = string
}
mystring2 = {
description = "A string in an object variable (required)"
sensitive = true
type = string
}
mylist1 = {
default = []
description = "A list in an object variable (optional)"
type = list(string)
}
mymap1 = {
default = {}
description = "A map in an object variable (optional)"
type = map(string)
}
myobject1 = {
default = null
description = "An object within an object variable (optional)"
type = object({
....
)}
}
mystring3 = { description = "Another string in an object variable (required)", type = string }
})
}
I was surprised that when specifying an object as a type it filters out all inputs that aren't declared. I love all the ideas about specifying optional variables and stuff... but this has been open for 4 years. Maybe just don't kill things not declared in the type, then give us the awesome later?
tfvars:
test = {
map0 = {
required = "string"
}
map1 = {
required = "string"
optional = "string"
}
}
variable:
variable "test" {
type = map(
object(
{
required = string
}
)
)
}
outputs:
test = tomap({
"map0" = {
"required" = "string"
}
"map1" = {
"required" = "string"
}
})
In my opinion this should either error or let optional
go through. I'm 100% in favor of making this declared explicitly, but it's current behavior is very surprising.
I was surprised that when specifying an object as a type it filters out all inputs that aren't declared. I love all the ideas about specifying optional variables and stuff... but this has been open for 4 years. Maybe just don't kill things not declared in the type, then give us the awesome later?
I prefer to have them declared explicitly. This way you can look at the type declaration and see which fields are supported.
I prefer to have them declared explicitly. This way you can look at the type declaration and see which fields are supported.
It also means that the language server (terraform-ls
/terraform-lsp
) can introspect the modules and provide much more accurate auto-complete suggestions...
I prefer to have them declared explicitly. This way you can look at the type declaration and see which fields are supported.
Me too... but the five stages of grief are "denial, anger, bargaining, depression and acceptance" and right now I'm at the bargaining stage. If I can't have something perfect, can I at least have something less nice?
I'm at the bargaining phase myself. Simply getting rid of the "experimental" warning would please me.
For me this has been experimental for how long, I just would like to know what is the reason, what is causing issues, and is it really something that should be a blocker versus a hey every FYI this WILL break if you do this you know it will break.
Current Terraform Version
Proposal
I like the
object
variable type and it would be nice to be able to define optional arguments which can carrynull
value too, to use:instead of: