-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow {} to be coerced to object of any type #33303
Comments
Hi @Nuru! Thanks for sharing this use-case. Unfortunately I don't think exactly what you've proposed is an appropriate answer to the use-case: In the case you've described, I would expect to use obj = local.coin_flip ? null : local.datasource.obj
Alternatively, a list of zero or one elements if in a particular situation it's more convenient to think of this as being a bounded list rather than a single value that's either present or not: obj = local.coin_flip ? tolist([]) : tolist([local.datasource.obj]) Finally, if you are intending your obj = local.coin_flip ? tomap({}) : tomap(local.datasource.obj) However, the given example for Would any of these three solutions be suitable for what you are trying to achieve? If not, can you say more about why? Thanks! Another possible way to interpret this request would be to have some way to request an explicit type conversion to an object type with optional attributes. Here's a hypothetical syntax for that which we've prototyped in the past: # INVALID: This is just an example of a possible future syntax
convert({}, map(object({
enabled = optional(bool, false)
list = optional(list(string), [])
}))) Unfortunately the only reason we haven't already implemented something like this is legacy constraints: this would be the first time that a type constraint could appear anywhere other than the If we were able to complete this then it would have the same behavior as passing a value through in input variable with the same type constraint. (Note for future maintainers reading this: the HCL part of the |
@apparentlymart Thank you for your detailed response. Unfortunately, none of your suggestions help me. I want some kind of value I can put in the conditional obj = local.coin_flip ? <SOMETHING> : local.datasource.obj that will ensure enabled = { for k, v in local.obj : k => v if v.enabled } works regardless of the coin flip. This is because in the actual code, there are close to 30 lines of additional transformations performed on I understand your comment about obj = local.coin_flip ? [] : [local.datasource.obj] and Terraform does not complain about the empty tuple as being a different type. To me it is a parallel construction to write obj = local.coin_flip ? {} : local.datasource.obj I would NOT expect obj = local.coin_flip ? {foo = "bar"} : local.datasource.obj to work, but like I said, I think Side note: When the placeholder is a local (click to reveal)BTW, I understand the problem with the following ( placeholder = local.coin_flip ? {} : null
obj = local.coin_flip ? local.placeholder : local.datasource.obj
In my particular case, I do not know the full type that will be returned by the data source, so I could not cast it into an object type even if that feature were available. It is not a map, so I cannot cast it to a map. An alternative solution would be to allow |
In order for
Therefore to make that work would require Terraform to automatically set that attribute to either Therefore I don't think an automatic approach is appropriate here. You will always need to at least write out an With today's Terraform to meet your requirement I would define another local value that has the placeholder object to use to represent the absence of an object, and then use that in your expression: locals {
placeholder_something = {
enabled = false
list = tolist([]) # or null, if that seems better
}
obj = local.coin_flip ? local.datasource.obj : local.placeholder_something
} (I called this "placeholder something" just because the example we're discussing here is so contrived that I don't know what kind of thing Alternatively, you can make the conditional produce enabled = { for k, v in local.obj : k => v if try(v.enabled, false) } This is a more concise approach but it's also less precise, and would mask other errors such as |
@apparentlymart wrote:
No, the code works fine if I cannot flesh out I am looking for a more precise solution, otherwise I could solve this with |
Thanks for the extra context, @Nuru. I thought you were intending It isn't a goal of the Terraform language to help you write code that accepts arbitrary input regardless of type. We generally expect that you, as the module author, will decide exactly what type your module expects as input and return errors if the given input doesn't match that type. That seems in direct conflict with your goal of having "extraneous stuff that can vary". I would suggest choosing a different approach where you design your API explicitly, and so that every expression has a well-defined result type regardless of input. Consider the Terraform language as having a static type system, not a dynamic type system. |
@apparentlymart This is not about an input I can declare and control, this is about data returned by a data source, which is beyond my control, much like Terraform allows for JSON input. I think Hashicorp opened the door by allowing objects to be treated like maps in |
There is an inconsistency here which, regardless of the previous comments, needs to be considered and possibly addressed. Consider a module with a variable defined thus:
Note the type and the default. Now I want to be able to define 'anything' to have some value or not according to a flag:
Nope, not allowed. Ok, let me try this:
Also not allowed. Fair enough, the module that consumes 'anything' doesn't expect 'null', that's why it defines a default of {} (which has a type of 'any', right?). But, in my first attempt, am I not providing the same default value on the rhs of ternary expression? To my mind, if the variable defines a default value/type and the same default value/type is explicitly provided, there should be no issue whatsoever - Terraform should accept that because they are clearly identical, semantically. I suspect this would be the case if it wasn't for the use of the ternary operator. Ergo, the issue here is the ternary operator - it is making an optimistic evaluation which, in my view, it shouldn't and possibly doesn't need to do. Why not simply pass the appropriate value through and allow the consumer to determine whether it's valid or not? |
I hit something similar to this today but possibly even more odd. Note that the result in both cases is returning an object with 2 properties, but I am finding that if the types of one of the properties is an object and the other properties are not objects then I get the inconsistent type error.
Using the above I get an error:
However, if I comment out the Fails line and uncomment the Works line I get the below.
Something feels off. |
Hi @shaneholder! Sorry for the slow response. I no longer work on the Terraform team but I'm working my way through some old GitHub notifications that I didn't manage to deal with before I left. 🙃 For backward compatibility with now-very-old versions of Terraform, there is a special rule in the conditional operator where if the true and false expressions are not of the same type then it will try to find some third type that both of the results can convert to, and silently perform that conversion if possible. This is essentially emulating the Terraform v0.11-and-earlier situation where the language only supported strings, lists of strings, and maps of strings. The reason that Your first example failed because there is no type that both If the modern Terraform language were not constrained by older versions of Terraform then I expect that all three of these expressions would fail in the same way, but you've accidentally activated a backward-compatibility behavior that made this appear to work even though it wasn't doing exactly what you thought it was doing. I understand that this answer probably isn't very satisfying, but at least you no longer need to wonder why these examples behave differently from one another. |
Terraform Version
Use Cases
I would like to be able to provide an empty object value that can be used in code rather than have to use conditionals in multiple places.
Attempted Solutions
What I would like to work:
This is, of course, a simplified example. In practice, the objects are more complex, and
local.enabled
is further refined in additional statements.But it fails:
What I have had to resort to:
Proposal
Allow
{}
to be cast to map or any type of object.References
The text was updated successfully, but these errors were encountered: