hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.11k stars 9.47k forks source link

Inconsistent conditional result types when branching different length of tuples after concat call #30699

Closed zogamorph closed 2 years ago

zogamorph commented 2 years ago

Terraform Version

Terraform v1.1.7
on windows_amd64

Terraform Configuration Files

locals {

  IsProductionBuild = "prod" == "prod" ? true : false

  DefaultRuleSet = [{
    "Name"                     = "Rule Name 1",
    "Description"              = "Rule Description",
    "Priority"                 = 4096,
    "Direction"                = "Inbound",
    "Access"                   = "Deny",
    "Protocol"                 = "*",
    "SourcePortRange"          = "*",
    "SourceAddressPrefix"      = "*",
    "DestinationPortRange"     = "*",
    "DestinationAddressPrefix" = "*"
    },
    {
      "Name"                     = "Rule Name 2",
      "Description"              = "Rule Description",
      "Priority"                 = 4096,
      "Direction"                = "OutBound",
      "Access"                   = "Deny",
      "Protocol"                 = "*",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "*",
      "DestinationPortRange"     = "*",
      "DestinationAddressPrefix" = "*"
    }
  ]

  RuleSet1 = [
    {
      "Name"                     = "Rule Name 3",
      "Description"              = "Rule Description",
      "Priority"                 = 4000,
      "Direction"                = "OutBound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "Network",
      "DestinationPortRanges"    = [666, 9090],
      "DestinationAddressPrefix" = "Internet"
    }
  ]

  MainRulesSet = [
    {
      "Name"                     = "Rule Name 4",
      "Description"              = "Rule Description",
      "Priority"                 = 200,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 9999,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 5",
      "Description"              = "Rule Description",
      "Priority"                 = 300,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 5",
      "Description"              = "Rule Description",
      "Priority"                 = 301,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 6",
      "Description"              = "Rule Description",
      "Priority"                 = 302,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 7",
      "Description"              = "Rule Description",
      "Priority"                 = 303,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 8",
      "Description"              = "Rule Description",
      "Priority"                 = 304,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    }
    ,
    {
      "Name"                     = "Rule Name 9",
      "Description"              = "Rule Description",
      "Priority"                 = 305,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 10",
      "Description"              = "Rule Description",
      "Priority"                 = 200,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 445,
      "DestinationAddressPrefix" = "Storage"
    },
    {
      "Name"                     = "Rule Name 12",
      "Description"              = "Rule Description",
      "Priority"                 = 210,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = "*",
      "DestinationAddressPrefix" = "Sql"
    }
    , {
      "Name"                     = "Rule Name 13",
      "Description"              = "Rule Description",
      "Priority"                 = 100,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 222,
      "DestinationAddressPrefix" = "Monitor"
    }
    ,
    {
      "Name"                     = "Rule Name 14",
      "Description"              = "Rule Description",
      "Priority"                 = 3900,
      "Direction"                = "OutBound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRanges"    = [999, 879],
      "DestinationAddressPrefix" = "Internet"
    },
    {
      "Name"                     = "Rule Name 15",
      "Description"              = "Rule Description",
      "Priority"                 = 110,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 987,
      "DestinationAddressPrefix" = "KeyVault"
    }
    ,
    {
      "Name"                     = "Rule Name 16",
      "Description"              = "Rule Description",
      "Priority"                 = 120,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = "443",
      "DestinationAddressPrefix" = "Storage"
    },
    {
      "Name"                     = "Rule Name 17",
      "Description"              = "Rule Description",
      "Priority"                 = 220,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 666,
      "DestinationAddressPrefix" = "Internet"
    },
    {
      "Name"                     = "Rule Name 18",
      "Description"              = "Rule Description",
      "Priority"                 = 306,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 19",
      "Description"              = "Rule Description",
      "Priority"                 = 307,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 20",
      "Description"              = "Rule Description",
      "Priority"                 = 308,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    },
    {
      "Name"                     = "Rule Name 21",
      "Description"              = "Rule Description",
      "Priority"                 = 105,
      "Direction"                = "Outbound",
      "Access"                   = "Allow",
      "Protocol"                 = "*",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRanges"    = [111, 222, 333, 444, 555, 666, 77777, 88888, 99999, 101010],
      "DestinationAddressPrefix" = "10.10.10.10"
    }
  ]

  RuleSet2 =  [{
      "Name"                     = "Rule Name 22",
      "Description"              = "Rule Description",
      "Priority"                 = 309,
      "Direction"                = "Inbound",
      "Access"                   = "Allow",
      "Protocol"                 = "TCP",
      "SourcePortRange"          = "*",
      "SourceAddressPrefix"      = "10.10.10.10",
      "DestinationPortRange"     = 6666,
      "DestinationAddressPrefix" = "10.10.10.10"
    }]

  FullDefaultRules = concat(local.DefaultRuleSet, local.MainRulesSet, local.RuleSet1)
  RuleSetToApply01   = local.IsProductionBuild ? concat(local.DefaultRuleSet, local.MainRulesSet, local.RuleSet1) : concat(local.DefaultRuleSet, local.MainRulesSet, local.RuleSet1, local.RuleSet2)
  RuleSetToApply02   = local.IsProductionBuild ? local.FullDefaultRules : concat(local.RuleSet2, local.FullDefaultRules)
}

Debug Output

https://gist.github.com/zogamorph/53d7d5072dcb4947da928c01fda36111

Expected Behavior

When IsProductionBuild is ture then expect the RuleSetToApply01 and RuleSetToApply02 to have all the object/maps found in DefaultRuleSet, MainRulesSet and RuleSet1

When IsProductionBuild is false then expect the RuleSetToApply01 and RuleSetToApply02 to have all the object/maps found in DefaultRuleSet, MainRulesSet and RuleSet1 and RuleSet2

Actual Behavior

╷ │ Error: Inconsistent conditional result types │ │ on test.tf line 283, in locals: │ 283: RuleSetToApply01 = local.IsProductionBuild ? concat(local.DefaultRuleSet, local.MainRulesSet, local.RuleSet1) : concat(local.DefaultRuleSet, local.MainRulesSet, local.RuleSet1, local.RuleSet2) │ ├──────────────── │ │ local.DefaultRuleSet is tuple with 2 elements │ │ local.IsProductionBuild is true │ │ local.MainRulesSet is tuple with 18 elements │ │ local.RuleSet1 is tuple with 1 element │ │ local.RuleSet2 is tuple with 1 element │ │ The true and false result expressions must have consistent types. The given expressions are tuple and tuple, respectively. ╵ ╷ │ Error: Inconsistent conditional result types │ │ on test.tf line 284, in locals: │ 284: RuleSetToApply02 = local.IsProductionBuild ? local.FullDefaultRules : concat(local.RuleSet2, local.FullDefaultRules) │ ├──────────────── │ │ local.FullDefaultRules is tuple with 21 elements │ │ local.IsProductionBuild is true │ │ local.RuleSet2 is tuple with 1 element │ │ The true and false result expressions must have consistent types. The given expressions are tuple and tuple, respectively.

Steps to Reproduce

  1. terraform init
  2. terraform plan

Additional Context

I currently have a module that use the results from concat(local.DefaultRuleSet, local.MainRulesSet, local.RuleSet1).
I now requirement that if we are not in production I need extra rule to applied.

Me and my colleges believe what I am trying to do make sense and we can't workout why it's not working.

References

jbardin commented 2 years ago

Hi @zogamorph,

A tuple's type includes the number of elements, so the error is technically correct, in that the conditional expression contains inconsistent types. The reason these are remaining as tuples is because the individual elements of the collections themselves are not consistently typed, and cannot be converted to a list.

The easiest solution here (though it would take some refactoring to deal with multiple ranges) would be to make each object a simple map(string), which is easier to dynamically populate and merge. If you want to retain the ability to have attributes of different types, it's probably better to declare the values as modules inputs so that explicit type constraints can be set, however that would make things a bit more verbose (at least until there is a solution for optional attributes in place).

Another thing to note here is that because of the mixed object types, some of the values shown here are going to be converted to map(string) converting the numbers to a strings in order to make the expression valid, which may be surprising later on when what is written as a numeric literal shows up as a string when assigned elsewhere.

We use GitHub issues for tracking bugs and enhancements, rather than for questions. While we can sometimes help with certain simple problems here, it's better to use the community forum where there are more people ready to help.

Thanks!

github-actions[bot] commented 2 years ago

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.