Using data source in conditional expressions

I want to be able to choose route tables from either the vpc or subnets depending on input. Im using datasources to get the information, but it is not working. The temporary storing as a local is just empty when reading from vpc data… (but not from subnet data…)

I uploaded relevant code ( a test module and an example file that should be located in a subfolder) for this. But as .tf.txtfiles since apparently only image files and text os allowed… When running the example there is at first a problem with some of the data not populating, but after an extra refresh it at least populates.

The main lines causing issue is this:

vpc_rts_ids       = data.aws_route_tables.vpc_rts.ids
subnet_rts_ids = data.aws_route_table.subnet_rts[*].route_table_id
rts_ids               = var.subnets_ids == [] ? local.vpc_rts_ids : local.subnet_rts_ids

As can be seen when running the uploaded code; rts_ids (LOCAL_RTS_IDS in output) only gets a value when there are subnets given as input (var.subnet_rts_ids)
even though output of vpc_rts_ids shows a value.

Can somebody please explain or maybe confirm that this is due to some bug or something?
I do not understand what is wrong. (1.2 KB) (1.2 KB)

I have solved the issue!
The problem was in the condition checking for emtpy array:
Everything else worked fine, but the if case always seemed to return “false”
rts_ids = var.subnets_ids == [] ? should be:
rts_ids = length(var.subnets_ids) == 0

Hi @tomasbackman! I’m glad you found that answer; it was the same solution I was about to propose before I saw you’d already answered yourself. :slight_smile:

In case you’re curious as to what’s going on here: Terraform has two different ways to represent sequences: list types and tuple types. A list type is a sequence of any number of values that all have the same type, while a tuple type is a sequence of a fixed number of values that can all have their own types. When we’re writing these out as type constraints for variables they look like this:

  type = list(string)
  type = tuple([string, number, string])

It isn’t common to use a tuple type as a variable type constraint like that because that would make for a very unusual module interface, but Terraform has tuple types to allow representing mixed-typed sequences in contexts where they are possible, like in JSON.

Normally the list vs. tuple distinction isn’t super-visible because Terraform will automatically convert between them when used in a context that has an expected type: if you assign to a resource argument that expects list(string) then Terraform can see it needs to convert a given tuple to a list of strings.

However, the == operator doesn’t provide any context about what type is expected. Instead, it returns false if the two types don’t match. So in your case you were comparing a list of strings to an empty tuple value (list(string) to tuple([])) and so the result was false.

Using the length a good way to make it work with both sequence types, because the length function is defined for both sequence type kinds.

Thank you very much for the explanation! It makes a lot of sense.

Personally I find it pretty hard to distinguish and properly use the various collection types (when which is used, etc). Especially with all the implicit conversations that happens. Sometimes (too often for me) so that wrongly use and misunderstandings can still work ok. Like here, I thought I had a list but had a tuple. I find it pretty hard to see…
I guess it probably is due to me still being a beginner in terraform. But still, I feel that easier and clearer usages would be very helpful, and make learning faster. Maybe by enforcing, or at least allowing more explicit type/constraints definitions when handling variables and attributes. (Like for example instead of just writing what a variable expect to get in variable definition - type = string etc, to define what is actually given from the calls as well could be written out so that we do not have to check back and forth as much, which I find myself doing a lot when using output from one resource/module output as input to another…)
To add a (optional)? type attribute for outputs like in variable inputs would also go a long way and be great I think!
Maybe also adding a flag to terraform that disables all implicit conversations so that all conversations between various types must happen consciously with explicit commands.

But in any case I will try to have an extra eye out and to understand/follow the types better. In the end it is me that just have to learn it =)
Thank you for the help.

Hi @tomasbackman,

Indeed, the automatic conversions in the language are a tradeoff between automatically doing what a user expects in common cases, making some code more concise, vs. the risk of Terraform doing something surprising in some less-common situations. This tradeoff has to be made in every language, and Terraform errs more on the side of the sorts of decisions made by languages with dynamic type systems, though Terraform does still do some basic type inference and so is a sort of hybrid type system so we can keep track of the types of values that cannot be known until apply time.

The specifics of which types exist in the language today are themselves a compromise between what we needed to move forward with a proper type system in Terraform 0.12 and what needed to stick around to work with the existing ecosystem of providers and modules from earlier versions.

Although I don’t think there is likely to be a “strict mode” that requires type annotations (the language isn’t really designed to make room for the annotation syntax that would be required, anyway), you can be explicit about what you intend by making use of the type conversion functions like tolist, tomap, etc.

When I write Terraform modules I try to be explicit with type constraints or type conversions at the interface of the module (in input variables and in output values) because consistency there is helpful to the caller of the module. I tend to be more implicit in expressions within a module, but still sometimes include explicit type conversions on local values as a clue to the reader as to how they are intended to be used elsewhere in the module.

Hopefully as the Terraform language server gains more support for code analysis it’ll be able to offer features like hovering over parts of an expression to see what types they have, or similar, so that the type information tracked internally by the language runtime can be revealed to you when editing and debugging.