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.65k stars 9.55k forks source link

Problems referencing TypeList attributes (e.g. aws_route53_zone.name_servers) #2795

Closed dalehamel closed 9 years ago

dalehamel commented 9 years ago

I have the following code which attempts to build a zone with subzones, and set the nameserver records of the subzones to those defined in a delegation set.

variable "apex_domain" {
  default = "example.com"
}

variable "subdomains" {
  default = "foo,dev,staging"
}

variable "nameserver_ttl" {
  default = "300"
}

resource "aws_route53_delegation_set" "apex" {
  reference_name = "${var.apex_domain}"
}

// Build each subdomain
resource "aws_route53_zone" "apex" {
  name = "${var.apex_domain}"
  delegation_set_id = "${aws_route53_delegation_set.apex.id}"
}

// Build each subdomain
resource "aws_route53_zone" "sub" {
  count = "${length(split(",", var.subdomains))}"
  name = "${element(split(",", var.subdomains), count.index)}.${var.apex_domain}"
  delegation_set_id = "${element(aws_route53_delegation_set.sub.*.id, count.index)}"
}

// Build delegation sets for each subdomain
resource "aws_route53_delegation_set" "sub" {
  count = "${length(split(",", var.subdomains))}"
  reference_name = "${element(split(",", var.subdomains), count.index)}.${var.apex_domain}"
}

// Add NS records for each subdomain
resource "aws_route53_record" "sub-ns" {

  zone_id = "${element(aws_route53_zone.sub.*.id, count.index)}"

  count = "${length(split(",", var.subdomains))}"
  name = "${element(aws_route53_zone.sub.*.name, count.index)}"
  type = "NS"
  ttl = "${var.nameserver_ttl}"
  records = [
    "${element(aws_route53_delegation_set.sub.*.name_servers.0, count.index)}",
    "${element(aws_route53_delegation_set.sub.*.name_servers.1, count.index)}",
    "${element(aws_route53_delegation_set.sub.*.name_servers.2, count.index)}",
    "${element(aws_route53_delegation_set.sub.*.name_servers.3, count.index)}",
  ]
}

This is basically a count-based version of the sample code for route53, (https://terraform.io/docs/providers/aws/r/route53_zone.html). This code fails, giving messages like:

* Resource 'aws_route53_delegation_set.sub' does not have attribute 'name_servers.0' for variable 'aws_route53_delegation_set.sub.*.name_servers.0'
* Resource 'aws_route53_delegation_set.sub' does not have attribute 'name_servers.2' for variable 'aws_route53_delegation_set.sub.*.name_servers.2'
* Resource 'aws_route53_delegation_set.sub' does not have attribute 'name_servers.0' for variable 'aws_route53_delegation_set.sub.*.name_servers.0'

Interestingly, the output varies from run to run on which index it gets mad at, indicating possibly non-deterministic behavior (rerun plan and you'll see what I mean).

If i specify that it should use, for instance

"${element(aws_route53_delegation_set.sub.*.id, count.index)}"

That works totally fine, where as this does not:

"${element(aws_route53_delegation_set.sub.*.name_servers.0, count.index)}"

So it appears terraform is getting mad about the fact that name_servers is a list, or maybe that name_servers.0 does not yet exist.

Even more curiously, if I simply build the records block with invalid records (for instance, specifying to use sub.*.id), then go back and change the code later, it works happily. I assume this is because the delegation set resources exist, and it can actually find the value now.

So, I can successfully build it with:

variable "apex_domain" {
  default = "example.com"
}

variable "subdomains" {
  default = "foo,dev,staging"
}

variable "nameserver_ttl" {
  default = "300"
}

resource "aws_route53_delegation_set" "apex" {
  reference_name = "${var.apex_domain}"
}

// Build each subdomain
resource "aws_route53_zone" "apex" {
  name = "${var.apex_domain}"
  delegation_set_id = "${aws_route53_delegation_set.apex.id}"
}

// Build each subdomain
resource "aws_route53_zone" "sub" {
  count = "${length(split(",", var.subdomains))}"
  name = "${element(split(",", var.subdomains), count.index)}.${var.apex_domain}"
  delegation_set_id = "${element(aws_route53_delegation_set.sub.*.id, count.index)}"
}

// Build delegation sets for each subdomain
resource "aws_route53_delegation_set" "sub" {
  count = "${length(split(",", var.subdomains))}"
  reference_name = "${element(split(",", var.subdomains), count.index)}.${var.apex_domain}"
}

// Add NS records for each subdomain
resource "aws_route53_record" "sub-ns" {

  zone_id = "${element(aws_route53_zone.sub.*.id, count.index)}"

  count = "${length(split(",", var.subdomains))}"
  name = "${element(aws_route53_zone.sub.*.name, count.index)}"
  type = "NS"
  ttl = "${var.nameserver_ttl}"
  records = [ 
    "${element(aws_route53_delegation_set.sub.*.id, count.index)}",
    "${element(aws_route53_delegation_set.sub.*.id, count.index)}",
    "${element(aws_route53_delegation_set.sub.*.id, count.index)}",
    "${element(aws_route53_delegation_set.sub.*.id, count.index)}",
  ]
}

Which outputs this plan:


+ aws_route53_delegation_set.apex
    name_servers.#: "" => "<computed>"
    reference_name: "" => "example.com"

+ aws_route53_delegation_set.sub.0
    name_servers.#: "" => "<computed>"
    reference_name: "" => "foo.example.com"

+ aws_route53_delegation_set.sub.1
    name_servers.#: "" => "<computed>"
    reference_name: "" => "dev.example.com"

+ aws_route53_delegation_set.sub.2
    name_servers.#: "" => "<computed>"
    reference_name: "" => "staging.example.com"

+ aws_route53_record.sub-ns.0
    fqdn:      "" => "<computed>"
    name:      "" => "foo.example.com"
    records.#: "" => "<computed>"
    ttl:       "" => "300"
    type:      "" => "NS"
    zone_id:   "" => "${element(aws_route53_zone.sub.*.id, count.index)}"

+ aws_route53_record.sub-ns.1
    fqdn:      "" => "<computed>"
    name:      "" => "dev.example.com"
    records.#: "" => "<computed>"
    ttl:       "" => "300"
    type:      "" => "NS"
    zone_id:   "" => "${element(aws_route53_zone.sub.*.id, count.index)}"

+ aws_route53_record.sub-ns.2
    fqdn:      "" => "<computed>"
    name:      "" => "staging.example.com"
    records.#: "" => "<computed>"
    ttl:       "" => "300"
    type:      "" => "NS"
    zone_id:   "" => "${element(aws_route53_zone.sub.*.id, count.index)}"

+ aws_route53_zone.apex
    comment:           "" => "Managed by Terraform"
    delegation_set_id: "" => "${aws_route53_delegation_set.apex.id}"
    name:              "" => "example.com"
    name_servers.#:    "" => "<computed>"
    vpc_region:        "" => "<computed>"
    zone_id:           "" => "<computed>"

+ aws_route53_zone.sub.0
    comment:           "" => "Managed by Terraform"
    delegation_set_id: "" => "${element(aws_route53_delegation_set.sub.*.id, count.index)}"
    name:              "" => "foo.example.com"
    name_servers.#:    "" => "<computed>"
    vpc_region:        "" => "<computed>"
    zone_id:           "" => "<computed>"

+ aws_route53_zone.sub.1
    comment:           "" => "Managed by Terraform"
    delegation_set_id: "" => "${element(aws_route53_delegation_set.sub.*.id, count.index)}"
    name:              "" => "dev.example.com"
    name_servers.#:    "" => "<computed>"
    vpc_region:        "" => "<computed>"
    zone_id:           "" => "<computed>"

+ aws_route53_zone.sub.2
    comment:           "" => "Managed by Terraform"
    delegation_set_id: "" => "${element(aws_route53_delegation_set.sub.*.id, count.index)}"
    name:              "" => "staging.example.com"
    name_servers.#:    "" => "<computed>"
    vpc_region:        "" => "<computed>"
    zone_id:           "" => "<computed>"

Then if I change back to the original code (referencing name_servers.INDEX) it applies works totally fine, setting the name servers to the correct value.

My theory is that terraform doesn't like the list attributes when doing a splat, where the lists don't exist yet.

@mitchellh @phinze for :eyes:

dalehamel commented 9 years ago

similar to https://github.com/hashicorp/terraform/pull/2788

phinze commented 9 years ago

Thanks for the detailed report, @dalehamel.

I think the problem with name_servers here is actually even larger scoped. This simple test fails to output anything:

resource "aws_route53_zone" "prod" {
  name = "prod.mydomain.com"
}

output "ns" { value = "${join(",", aws_route53_zone.prod.name_servers)}" }

There's some general issue with referencing TypeList attributes that we'll need to get to the bottom of.

radeksimko commented 9 years ago

@phinze There's a PR which is solving exactly this: https://github.com/hashicorp/terraform/pull/2157 :wink:

phinze commented 9 years ago

Oh right! :heart: @radeksimko going to review that now

radeksimko commented 9 years ago

2157 was merged and will be available in the upcoming release.

Feel free to reopen if you still experience the issue in the upcoming version (or in build from master).

ghost commented 4 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.