Skip to content
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

Inconsistent conditional result types fails for complex types #22405

Closed
ibacalu opened this issue Aug 9, 2019 · 25 comments
Closed

Inconsistent conditional result types fails for complex types #22405

ibacalu opened this issue Aug 9, 2019 · 25 comments
Labels
config explained a Terraform Core team member has described the root cause of this issue in code v0.12 Issues (primarily bugs) reported against v0.12 releases

Comments

@ibacalu
Copy link

ibacalu commented Aug 9, 2019

Terraform Version

Terraform v0.12.6

Terraform Configuration Files

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"]
      }
    ]
}

Debug Output

Crash Output

Expected Behavior

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"
    ]
  },
]

Actual Behavior

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.

Steps to Reproduce

  1. terraform refresh

Additional Context

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.

References

#19180

@ibacalu
Copy link
Author

ibacalu commented Aug 9, 2019

maybe stop checking for deeper level type consistency ?

@ibacalu ibacalu changed the title Inconsistent conditional result types fails for tuples/lists Inconsistent conditional result types fails for comples types Aug 12, 2019
@hashibot hashibot added the v0.12 Issues (primarily bugs) reported against v0.12 releases label Aug 27, 2019
@ibacalu
Copy link
Author

ibacalu commented Sep 26, 2019

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.

@HighwayofLife
Copy link

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. 😒

@ibacalu
Copy link
Author

ibacalu commented Sep 30, 2019

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.

@dmrzzz
Copy link

dmrzzz commented Oct 12, 2019

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.

@ibacalu ibacalu changed the title Inconsistent conditional result types fails for comples types Inconsistent conditional result types fails for complex types Nov 20, 2019
@ibacalu
Copy link
Author

ibacalu commented Nov 20, 2019

still nothing? :(

@ibacalu
Copy link
Author

ibacalu commented Nov 20, 2019

This issue also affects Terraform v0.12.15
It affects all complex types, not just tuples.

Fixed the title (had a typo)

@ibacalu
Copy link
Author

ibacalu commented Feb 25, 2020

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.

@ibacalu
Copy link
Author

ibacalu commented Feb 25, 2020

@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({}))
}

@marlock9
Copy link

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 {}

@nikskiz
Copy link

nikskiz commented May 8, 2020

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?

@smitjainsj
Copy link

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

@rawrgulmuffins
Copy link

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.

@danieldreier
Copy link
Contributor

@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 danieldreier added the confirmed a Terraform Core team member has reproduced this issue label May 21, 2020
@galindro
Copy link

@danieldreier do you think that this fix would probably be available in 0.13 only?

@rabidscorpio
Copy link

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?

@pdemagny
Copy link

pdemagny commented Jul 7, 2020

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.

@siran
Copy link

siran commented Jul 14, 2020

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"]
      }]
    }
    ]
  )
}

@woter1832
Copy link

woter1832 commented Jul 26, 2020

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.

@carcuevas
Copy link

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.

@ma-caylent
Copy link

ma-caylent commented Sep 4, 2020

I faced a similar issue using maps, i just fixed using tomap() function instead of {} 🤔

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 :(

@mildwonkey mildwonkey added explained a Terraform Core team member has described the root cause of this issue in code and removed bug confirmed a Terraform Core team member has reproduced this issue labels Sep 17, 2020
@mildwonkey
Copy link
Contributor

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.

@rabidscorpio
Copy link

@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?

@mildwonkey
Copy link
Contributor

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.

@ghost
Copy link

ghost commented Oct 18, 2020

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.

@ghost ghost locked as resolved and limited conversation to collaborators Oct 18, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
config explained a Terraform Core team member has described the root cause of this issue in code v0.12 Issues (primarily bugs) reported against v0.12 releases
Projects
None yet
Development

No branches or pull requests