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.42k stars 9.51k forks source link

Default not compatible with type constraint using object type #21838

Open alex-harvey-z3q opened 5 years ago

alex-harvey-z3q commented 5 years ago

Summary

When attempting to set an empty map as the default in a variable of type object(map), a message This default value is not compatible with the variable's type constraint is seen.

Terraform Version

▶ /usr/local/bin/terraform012 -v    
Terraform v0.12.2
+ provider.template v2.1.2

Steps to reproduce

Create the following content in a file test.tf and apply it:

variable "prod_vpc" {
  type = object({
    name          = string,
    region        = string,
    single_nat_gw = bool,
    create        = bool,
    supernet      = string
  })
  default = {}
}

locals {
  default_map_keys = {
    name          = "PROD"
    region        = "eu-central-1"
    single_nat_gw = true
    create        = false,
    supernet      = "0.0.0.0/0"
  }
  merged_map_keys = merge(local.default_map_keys, var.prod_vpc)
}

output "test" {
  value = local.merged_map_keys
}

Debug Output

Nothing interesting seen when setting TF_LOG.

Expected Behavior

I expected this to apply fine and output the merged has. I note a comment by Martin Atkins here that suggests this syntax should work.

Actual Behavior

▶ /usr/local/bin/terraform012 apply              

Error: Invalid default value for variable

  on test.tf line 9, in variable "prod_vpc":
   9:   default = {}

This default value is not compatible with the variable's type constraint:
attributes "create", "name", "region", "single_nat_gw", and "supernet" are
required.

References

See also docs here.

jbardin commented 5 years ago

Hi @alexharv074,

The first issue here is that all object keys are required in order to set a default. If you want each of those to be null, then you have to explicitly set them as such in the config.

You also have the option of setting the default as null (or not having a default, and requiring the caller set a value). In that case however, you need to avoid passing a null object into merge, which you can do using a condition or coalesce:

merged_map_keys = merge(local.default_map_keys, coalesce(var.prod_vpc, {}))

Using merge to assign default attributes to an object is an interesting idea, which merge wasn't really intended to do. We'll keep this in mind though as we gather more data about how users are building configuration with the new type system, as we may be able to build a nicer solution to this problem.

alex-harvey-z3q commented 5 years ago

@jbardin so, I am just reading the docs here and using merge like that appears to be the recommendation. Maybe docs need updating here?

jbardin commented 5 years ago

@alexharv074, I'm not sure I understand what you mean. Those docs only mention the merging of maps, but you're using an object type.

The merge function though is only documented to accept maps. That could probably be updated to match the actual implementation, though like I said, this wasn't exactly the use case we had in mind so we may want to consider whether the implementation needs to be updated as well.

rmalchow commented 5 years ago

so ... yeah. this question is open again. just to clarify my use case this entire object / map /merge kerfuffle:

i would like to be able to define an object (so that i'm type safe, and the available fields are all visible etc etc), and then fill everything with examples or "reasonable defaults", so they can be overridden at some other point. it would be nice if this was possible without repetition.

i have some more details (and alex's initial comments) here:

https://stackoverflow.com/questions/56688307/terraform-populate-an-object-with-reasonable-then-partially-override/56689254

zbutt-muvaki commented 5 years ago

i think we need a way to have default values get merged into objects... although i could see this being tricky with type safe checking

oba11 commented 5 years ago

@alekstorm faced the same issue and one way I got it working was to use type string and use jsondecode/jsonencode to substitute my variables

N.B: The method will only work if it is a module

module/main.tf

variable "prod_vpc" {
  default = ""
}

locals {
  prod_vpc = var.prod_vpc != "" ? jsondecode(var.prod_vpc) : {}
  default_map_keys = {
    name          = "PROD"
    region        = "eu-central-1"
    single_nat_gw = true
    create        = false,
    supernet      = "0.0.0.0/0"
  }
  merged_map_keys = merge(local.default_map_keys, local.prod_vpc)
}

output "test" {
  value = local.merged_map_keys
}

main.tf

module "test" {
  source = "./module"
}

output "test" {
  value = module.test
}

module "test2" {
  source = "./module"
  prod_vpc = jsonencode({"name" = "DEV", "region" = "eu-west-1"})
}

output "test2" {
  value = module.test2
}
Outputs:

test = {
  "test" = {
    "create" = false
    "name" = "PROD"
    "region" = "eu-central-1"
    "single_nat_gw" = true
    "supernet" = "0.0.0.0/0"
  }
}
test2 = {
  "test" = {
    "create" = false
    "name" = "DEV"
    "region" = "eu-west-1"
    "single_nat_gw" = true
    "supernet" = "0.0.0.0/0"
  }
}
jeevanashanthi commented 4 years ago

Invalid default value for variable

on variables.tf line 25, in variable "vpc_zone_identifier": 25: default = 2

This default value is not compatible with the variable's type constraint: list of string required. `im getting this error

`

antonbabenko commented 3 years ago

@jeevanashanthi vpc_zone_identifier should be a list, and not a number/string. Try setting default = [].

jeevanashanthi commented 3 years ago

okay tq