Terraform v0.12.6
variable "default_rules" {
default = []
}
output "test" {
value = var.default_rules != [] ? var.default_rules : [
{
a = "some string"
b = true
c = {t = "using map makes it fail"}
},
{
d = "some other string"
e = false
f = ["using list also makes it fail"]
}
]
}
Outputs:
test = [
{
"a" = "some string"
"b" = true
"c" = {
"t" = "using map makes it fail"
}
},
{
"d" = "some other string"
"e" = false
"f" = [
"using list also makes it fail"
]
},
]
Error: Inconsistent conditional result types
on out.tf line 6, in output "test":
6: value = var.default_rules != [] ? var.default_rules : [
7: {
8: a = "some string"
9: b = true
10: c = {t = "using map makes it fail"}
11: },
12: {
13: d = "some other string"
14: e = false
15: f = ["using list also makes it fail"]
16: }
17: ]
|----------------
| var.default_rules is empty tuple
The true and false result expressions must have consistent types. The given
expressions are tuple and tuple, respectively.
This issue is giving me an impossible headache. I am trying to set a default value for a variable(which can't be defined using variable block), and allow the user to override it.
maybe stop checking for deeper level type consistency ?
This issue is still happening in v 0.12.9
It's extremely frustrating:
Error: Inconsistent conditional result types
on ../../ec2/aws_autoscaling_group/main.tf line 35, in locals:
35: for template in lookup(policy, "launch_template", null) != null ? [policy.launch_template] : [{}]: {
|----------------
| policy.launch_template is object with 2 attributes
The true and false result expressions must have consistent types. The given
expressions are tuple and tuple, respectively.
The only way around this that I can see is to convert it to a map, list, or use the same number of values in the tuple in the default. But if you don't know what that will be, then using either object, or map might be the only possible workarounds. 😒
The only way around this that I can see is to convert it to a map, list, or use the same number of values in the tuple in the default. But if you don't know what that will be, then using either object, or map might be the only possible workarounds. 😒
You can't use map.. you'll eventually hit the same silly wall.
I am tired of this extremely bi-polar love/hate relation with Terraform :))
This could be "hacked" to work if I convert them to string like you did, but that would duplicate the code. I don't want to do that.
I think this could be easily fixed, that's why I opened the issue.
I just ran across this same problem in TF 0.12.9 but with objects instead of tuples.
What I'd like to do is build a large arbitrary object structure in Terraform, and then json_encode it. The catch is that some parts of my object structure might be omitted entirely depending on variables.
variable "tags" { default = { Name = "joe" } }
variable "LAN1_ip" { default = "192.168.1.1" }
variable "syslog_server" { default = "192.168.2.2" }
locals {
member_json = merge({
host_name = var.tags["Name"]
platform = "VNIOS"
config_addr_type = "IPV4"
vip_setting = {
address = var.LAN1_ip
}
use_dns_resolver_setting = true
},
var.syslog_server == "" ? {} : {
use_syslog_proxy_setting = true
external_syslog_server_enable = true
syslog_servers = [{
address = var.syslog_server
connection_type = "TCP"
port = 11006
severity = "INFO"
message_node_id = "HOSTNAME"
message_source = "ANY"
}]
},)
member_json_encoded = jsonencode(local.member_json)
}
output "json" { value = local.member_json_encoded }
I hoped the above would just work in TF 0.12, but it doesn't:
$ terraform-0.12.9 apply
Error: Inconsistent conditional result types
on main.tf line 15, in locals:
15: var.syslog_server == "" ? {} : {
16: use_syslog_proxy_setting = true
17: external_syslog_server_enable = true
18: syslog_servers = [{
19: address = var.syslog_server
20: connection_type = "TCP"
21: port = 11006
22: severity = "INFO"
23: message_node_id = "HOSTNAME"
24: message_source = "ANY"
25: }]
26: },)
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
It's possible to kludge around this by doing the string encoding before the conditional
member_json = {...}
member_syslog_json = {...}
member_json_encoded = var.syslog_server == "" ? jsonencode(local.member_json) : jsonencode(merge(local.member_json, local.member_syslog_json))
but that doesn't scale past one or two conditionals.
With regard to the new type system in general: I definitely see the value of being able to declare e.g. list(string) or map(string) in situations where that is desirable and appropriate, but forcing my ad-hoc objects and tuples to adhere to a consistent schema doesn't help me get work done efficiently.
still nothing? :(
This issue also affects Terraform v0.12.15
It affects all complex types, not just tuples.
This issue affects also Terraform v0.12.21
It's been more than 6 months. This bug is still existing.
Can't anyone look at it please? It's extremely frustrating.
Error: Inconsistent conditional result types
on outputs.tf line 9, in output "default":
9: value = length(google_project.default) > 0 ? google_project.default[0] : {}
|----------------
| google_project.default is tuple with 1 element
| google_project.default[0] is object with 11 attributes
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
@here A quick dirty fix for these kind of things is to replace this
output "default" {
value = length(google_project.default) > 0 ? google_project.default[0] : {}
}
with this
output "default" {
value = jsondecode(length(google_project.default) > 0 ? jsonencode(google_project.default[0]) : jsonencode({}))
}
I have workaround for this.
output "default" {
value = try(length(google_project.default) > 0 ? google_project.default[0] : tomap(false), {})
}
When condition = true, ?: evaluates normally. When condition = false, ?: goes to false condition, fails on tomap(false), then try() handles this and finally returns fallback which is empty object {}
I have workaround for this.
output "default" { value = try(length(google_project.default) > 0 ? google_project.default[0] : tomap(false), {}) }
When condition = true, ?: evaluates normally. When condition = false, ?: goes to false condition, fails on tomap(false), then try() handles this and finally returns fallback which is empty object {}
Love the workaround, but would this ever break in upcoming changes?
Just a FYI, this is how iterate over maps with if conditions.
locals { test_var ={} }
---
for_each = length(keys(local.test_var)) > 0 ? local.test_var : {}
name = each.key
path = each.value.path
description = each.value.description
policy = each.value.policy
I ran into this issue today
Error: Inconsistent conditional result types
on modules/gateway/outputs.tf line 3, in output "this":
3: value = var.enabled && module.gateway_lambda.this != null && length(module.gateway_lambda.this != null ? module.gateway_lambda.this : {}) > 0 ? module.gateway_lambda.this : null
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
@ibacalu thank you for reporting this. I apologize for how long the response has taken. I've reproduced this on 0.12.25 using the original reproduction case. I appreciate how clear of a case you put together. I'm putting this on our backlog.
@danieldreier do you think that this fix would probably be available in 0.13 only?
Can someone explain to me why the objects themselves have to exactly match instead of just the object type? Why does it matter if the different maps have 2 and 15 keys for a conditional to be evaluated?
I stumbled upon this too ... trying to conditionally merge complex structures in 0.13-beta3:
If i'm getting this right, most of these cases could be covered and solved by the DeepMerge PR #25032 ?
addresses = merge(
local.workspace.enable_bastion_host == "true" ? {
"bastion1-internal-ip" = {
project_id = module.projects["service1"].project_id
address_type = "INTERNAL"
subnetwork = module.vpc_net1.subnetworks["vpc-subnet1"].self_link
labels = local.workspace_maps.global_labels
},
"bastion1-external-ip" = {
project_id = module.projects["service1"].project_id
address_type = "EXTERNAL"
labels = local.workspace_maps.global_labels
}
} : {},
local.workspace.enable_classic_vpn == "true" ? {
"classic-vpn-ip" = {
project_id = module.projects["host1"].shared_vpc_host_project_id
address_type = "EXTERNAL"
labels = local.workspace_maps.global_labels
}
} : {}
)
Error: Inconsistent conditional result types
on main.tf line 240, in module "compute_addresses":
240: local.workspace.enable_bastion_host == "true" ? {
241: "bastion1-internal-ip" = {
242: project_id = module.projects["service1"].project_id
243: address_type = "INTERNAL"
244: subnetwork = module.vpc_net1.subnetworks["vpc-subnet1"].self_link
245: labels = local.workspace_maps.global_labels
246: },
247: "bastion1-external-ip" = {
248: project_id = module.projects["service1"].project_id
249: address_type = "EXTERNAL"
250: labels = local.workspace_maps.global_labels
251: }
252: } : {},
|----------------
| local.workspace.enable_bastion_host is true
| local.workspace_maps.global_labels is object with 1 attribute "managed-by"
| module.vpc_net1.subnetworks is object with 2 attributes
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
Happened to me too, while concatenating
locals {
bucket_policy_statements = (var.override_default_bucket_policy != true) ? concat([{...map2...},{...map2...}], var.list)
Gives:
The true and false result expressions must have consistent types. The given expressions are tuple and tuple, respectively.
Temporary workaround: remove the conditional...
Complete example
Fails:
locals {
bucket_policy_statements = (var.override_default_bucket_policy != true) ? concat(
var.bucket_policies,
[{
sid = "ForceSSLOnlyAccess-Default"
actions = ["s3:*"]
effect = "Deny"
resources = ["${aws_s3_bucket.this.arn}/*"]
principals = [{
type = "AWS"
identifiers = ["*"]
}]
condition = [{
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}]
},
{
sid = "DenyWriteUnlessBucketOwnerFullControl"
actions = ["s3:PutObject"]
effect = "Deny"
resources = ["${aws_s3_bucket.this.arn}/*"]
not_principals = [{
type = "AWS"
identifiers = ["*"]
}]
condition = [{
test = "StringNotEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}]
}
]
) : var.bucket_policies
}
Works:
locals {
bucket_policy_statements = concat(
var.bucket_policies,
[{
sid = "ForceSSLOnlyAccess-Default"
actions = ["s3:*"]
effect = "Deny"
resources = ["${aws_s3_bucket.this.arn}/*"]
principals = [{
type = "AWS"
identifiers = ["*"]
}]
condition = [{
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}]
},
{
sid = "DenyWriteUnlessBucketOwnerFullControl"
actions = ["s3:PutObject"]
effect = "Deny"
resources = ["${aws_s3_bucket.this.arn}/*"]
not_principals = [{
type = "AWS"
identifiers = ["*"]
}]
condition = [{
test = "StringNotEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}]
}
]
)
}
Happening in 0.12.29
too. As @rabidscorpio says, why do both sides of the object not only have to match structure but quantity too?
Is there a plan for when this is going to be fixed?
T.I.A.
Still happening in version 0.13.1
output "cw_alarm_created_all_info" {
value = var.status_check_monitoring == true ? aws_cloudwatch_metric_alarm.failed_status_alarms[0] : {}
}
and I have:
Error: Inconsistent conditional result types
on ../../../GLOBAL_MODULES/ec2_creation_private/outputs.tf line 8, in output "cw_alarm_created_all_info":
8: value = var.status_check_monitoring == true ? aws_cloudwatch_metric_alarm.failed_status_alarms[0] : {}
|----------------
| aws_cloudwatch_metric_alarm.failed_status_alarms[0] is object with 24 attributes
| var.status_check_monitoring is true
The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
I faced a similar issue using maps, i just fixed using tomap()
function instead of {}
:thinking:
variable deploy_charts {
type = bool
default = false
}
variable "charts" {
description = "A list of helm chart"
type = any
}
resource "helm_release" "chart" {
for_each = var.deploy_charts ? var.charts : tomap()
}
Hope it helps!
EDIT: This is not a fully working solution. It breaks when changing the boolean :(
Hi all! I'm sorry for this confusing behavior, but terraform is behaving correctly in this case. The problem is not the conditional, but the incomplete variable definition.
Terraform cannot convert the rules in your conditional statement to a list (which is what you are telling terraform with default = []
). You need to fully define the variable's type
OR fully define a default variable value, from which terraform can infer the type.
This example gets the result you are looking for. Note that we're now using null
as the default and in the conditional:
variable "default_rules" {
default = null // instead of []
type = tuple([object({
a = string,
b = bool,
c = map(string),
}),
object({
d = string,
e = bool,
f = list(string),
}),
])
}
// Note that while this variable doesn't have "type" set, the default is the exact same type as
// default_rules above
variable "other_rules" {
default = [
{
a = "some string"
b = true
c = {t = "using map makes it fail"}
},
{
d = "some other string"
e = false
f = ["using list also makes it fail"] // I've modified your example because [t = "something] is not a valid list
}
]
}
output "rules" {
value = var.default_rules == null ? var.other_rules : var.default_rules
}
Which you can see on terraform apply:
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
rules = [
{
"a" = "some string"
// etc
Since terraform is behaving as expected, I am going to close this issue. If you have further questions about this, I recommend that you ask in the community forum where there are far more people ready to help, whereas the GitHub issues here are generally monitored only by our few core maintainers.
@mildwonkey Please explain why Terraform cannot convert the rules in your conditional statement to a list
. The original configuration has default = []
as the first value and [ {...}, {...} ]
as the second value. Obviously both of those values are lists so why would terraform be trying to convert anything? You said OR fully define a default variable value, from which terraform can infer the type
, isn't default = []
defining a default variable value from which terraform would be able to infer the type of list
? What if I included type = list
in the variable declaration?
How specific does the variable definition need to be? Could I not also have:
variable "default_rules" {
default = null // instead of []
type = tuple([map(any),map(any)])
}
The only thing that makes sense here is if terraform is converting lists into tuples when comparing the values of a conditional. The docs say A list can only be converted to a tuple if it has exactly the required number of elements
and A map (or a larger object) can be converted to an object if it has at least the keys required by the object schema
so that would explain why the type comparison is failing.
If this is the case, why are lists converted to tuples? What if I want to have the values of a conditional to be lists with a different number of elements?
I'm sorry that I did not explain things properly, @rabidscorpio , I made a faulty statement. I said "list", but the variable in your example is a tuple, not a list. The empty tuple, and the 2 tuple, have different types and cannot be converted to match.
You are correct about the below example, which you supplied, working:
variable "default_rules" {
default = null // instead of []
type = tuple([map(any),map(any)])
}
I'm sorry for the confusion, and I also want to acknowledge that there are improvement we can (and should) make in the UX around the type system. I understand that terraform's error messages are frequently unclear, and it's something we are continually working on improving.
I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
Most helpful comment
I have workaround for this.
When condition = true, ?: evaluates normally. When condition = false, ?: goes to false condition, fails on tomap(false), then try() handles this and finally returns fallback which is empty object {}