Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent conditional result types with locals templatization

I have this code, which is working if I remove version from msr code block. But if I add it - this error pop-ups. I've tried so far to interpolate conditional and to change types of the variables. No luck

  mke_launchpad_tmpl = {
    apiVersion = "API"
    kind       = "mke"
    spec = {
      mke = {
        version: var.mke_version
        adminUsername = "admin"
        adminPassword = var.admin_password
        installFlags : [
          "--default-node-orchestrator=kubernetes",
          "--san=${module.masters.lb_dns_name}",
        ]
        licenseFilePath: var.license_file_path
        upgradeFlags: [
          "--force-minimums",
          "--force-recent-backups",
        ]
      }
      mcr = {
        version: var.mcr_version
      }
      msr = {}
      hosts = concat(local.managers, local.workers, local.windows_workers)
    }
  }


  msr_launchpad_tmpl = {
    apiVersion = "API"
    kind       = "mke+msr"
    spec = {
      mke = {
        version: var.mke_version
        adminUsername = "admin"
        adminPassword = var.admin_password
        installFlags : [
          "--default-node-orchestrator=kubernetes",
          "--san=${module.masters.lb_dns_name}",
        ]
        licenseFilePath: var.license_file_path
        upgradeFlags: [
          "--force-minimums",
          "--force-recent-backups",
        ]
      }
      mcr = {
        version: var.mcr_version
      }
      msr = {
        version: var.msr_version
        installFlags : [
          "--ucp-insecure-tls",
          "--dtr-external-url ${module.msrs.lb_dns_name}",
        ]
      }
      hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
    }
  }

  launchpad_tmpl = var.msr_count > 0 ? local.msr_launchpad_tmpl : local.mke_launchpad_tmpl
}

Expected behaviour:

To normally run plan and apply it and get the output at the end to change it for the launchpad and install everything by versions from this output which I can pass in terraform.tfvars

Actual behaviour:

Error: Inconsistent conditional result types

  on main.tf line 179, in locals:
 179:   launchpad_tmpl = var.msr_count > 0 ? local.msr_launchpad_tmpl : local.mke_launchpad_tmpl
    |----------------
    | local.mke_launchpad_tmpl is object with 3 attributes
    | local.msr_launchpad_tmpl is object with 3 attributes

The true and false result expressions must have consistent types. The given
expressions are object and object, respectively.
like image 232
Lotarc Avatar asked May 08 '26 02:05

Lotarc


2 Answers

Unfortunately this is a situation where Terraform doesn't really know how to explain the problem fully because the difference between your two result types is in some details in deeply nested attributes.

However, what Terraform is referring to here is that your local.msr_launchpad_tmpl and local.make_launchpad_tmpl values have different object types, because an object type in Terraform is defined by the attribute names and associated types and your msr attributes are not consistent across both objects.

One way you could make this work is to explicitly add the msr attributes to local.msr_launchpad_tmpl but set them to null, so that the object types will be compatible but the unneeded attributes will still be left without a specific value:

      msr = {
        version = null
        installFlags = null
      }

This difference in msr's type was the only type difference I noticed between the two expressions, although I might have missed another example. If so, the general idea here is to make sure that both of the values have the same object structure, so that their types will be compatible with one another.


Terraform requires the true and false expressions in a conditional to have compatible types because it uses the common type as the return type for the conditional during type checking. However, in situations like this where you might intentionally want to use a different type for each case, you can use other language constructs that will allow Terraform to successfully complete type checking in other ways.

For example, if you combine both of those object values into a single object container then Terraform will be able to see that each of the two top-level attributes has a different type and see exactly what type each one has:

locals {
  launchpad_tmpls = 
    mke = {
      apiVersion = "API"
      kind       = "mke"
      spec = {
        mke = {
          version: var.mke_version
          adminUsername = "admin"
          adminPassword = var.admin_password
          installFlags : [
            "--default-node-orchestrator=kubernetes",
            "--san=${module.masters.lb_dns_name}",
          ]
          licenseFilePath: var.license_file_path
          upgradeFlags: [
            "--force-minimums",
            "--force-recent-backups",
          ]
        }
        mcr = {
          version: var.mcr_version
        }
        msr = {}
        hosts = concat(local.managers, local.workers, local.windows_workers)
      }
    }
    msr = {
      apiVersion = "API"
      kind       = "mke+msr"
      spec = {
        mke = {
          version: var.mke_version
          adminUsername = "admin"
          adminPassword = var.admin_password
          installFlags : [
            "--default-node-orchestrator=kubernetes",
            "--san=${module.masters.lb_dns_name}",
          ]
          licenseFilePath: var.license_file_path
          upgradeFlags: [
            "--force-minimums",
            "--force-recent-backups",
          ]
        }
        mcr = {
          version: var.mcr_version
        }
        msr = {
          version: var.msr_version
          installFlags : [
            "--ucp-insecure-tls",
            "--dtr-external-url ${module.msrs.lb_dns_name}",
          ]
        }
        hosts = concat(local.managers, local.msrs, local.workers, local.windows_workers)
      }
    }
  }

  launchpad_tmpl = local.launchpad_tmpl[var.msr_count > 0 ? "msr" : "mke"]
}

Because Terraform can see the exact types of both local.launchpad_tmpl["msr"] and local.launchpad_tmpl["mke"] it will be able to determine the exact object type for local.launchpad_tmpl in each case, even though the two have different types.

There is one exception to this: if var.msr_count is unknown during planning (that is, if you've computed it based on a resource attribute that won't be known until the apply step) then Terraform will be left in a situation where it can't infer a specific type for local.launchpad_tmpl, and so Terraform will treat it as an "unknown value of unknown type", which effectively means that any uses you make of it elsewhere in the configuration won't be type checked during planning and so might fail at apply time. However, this caveat won't apply as long as var.msr_count is set to a static value you've specified directly in your configuration.

like image 80
Martin Atkins Avatar answered May 11 '26 16:05

Martin Atkins


I ran into this issue with TF 0.14 while trying to conditionally set replication_configuration in a call to aws_s3_bucket:

replication_configuration = var.replication ? local.replication_configuration : {}

var.replication was defined as a bool, and local.replication_configuration looked something like this:

  replication_configuration = {
      role = "arn:aws:iam::${account}:role/${name}-s3-replication"
      rules = [
      {
        id     = "everything-without-filters"
        status = "Enabled" # Enabled or Disabled
        priority = 10
        delete_marker_replication_status = "Enabled"
        destination = {
          bucket        = "arn:aws:s3:::${name}-delete8-dr"
          storage_class = "STANDARD_IA"
        }
      }
    ]
}

Note: The contents of the json above are not real working code - they are provided only to illustrate the points below.

{} was not a close enough match to local.replication_configuration as it was defined, so the conditional failed, but the module for aws_s3_bucket errored when passed a null, so it was not possible to approach it this way, either.

Ultimately, I solved this by writing a conditional without using conditionals:

locals {
  repl_bool = {
    true = local.replication_configuration
    false = {}
  }
}

...

module "s3-bucket" {
...
replication_configuration = local.repl_bool[var.replication]
...
}

Writing code like the above really doesn't leave me with a good feeling. It looks awkward to me, and definitely has a hacky feel to it. But we needed to be able to write TF that only used one module, with or without replication, and this was a way to do that.

like image 24
Cognitiaclaeves Avatar answered May 11 '26 14:05

Cognitiaclaeves



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!