hashicorp / terraform-provider-template

Terraform template provider
https://www.terraform.io/docs/providers/template/
Mozilla Public License 2.0
131 stars 89 forks source link

Terraform template creating the wrong type of numbers #65

Closed Nmishin closed 5 years ago

Nmishin commented 5 years ago

Terraform Version

0.11.7 provider.template >= 2.0.0

Affected Resource(s)

template_file

Terraform Configuration Files

variable "ttl" {
    default = "31536000"
}

resource "null_resource" "test" {
  triggers {
    foo = "${format("%v", data.external.timestamp.result.seconds)}"
    bar = "${format("%v", (var.ttl / 2))}"
  }
}

data "template_file" "number" {
    template = "$${time / cycle}"
    vars {
        time  = "${data.external.timestamp.result.seconds}"
        cycle = "${var.ttl / 2}"
    }
}

Output

  + module.super_ca.null_resource.test
      id:                     <computed>
      triggers.%:             "2"
      triggers.bar:           "15768000"
      triggers.foo:           "1562944382"

Debug Output

Panic Output

Expected Behavior

data.template_file must generate the integer number which changed every half of year, example 97, 97, 99, etc

triggers.do_upload: "99" => "99"

Actual Behavior

data.template_file getting for input two integer numbers, but when template rendered math operation processed like with float numbers, and produce float number.

triggers.do_upload:            "99.10943226788432267884322678843226788432267884322678843226788432267884322678843226788432267884322678843226788432267884322678843226788432267884322678843227" => "99.10952708016235413495687468290208016235413495687468290208016235413495687468290208016235413495687468290208016235413495687468290208016235413495687468290208" (forces new resource)

Steps to Reproduce

Please list the steps required to reproduce the issue, for example:

  1. terraform init
  2. terraform apply

Important Factoids

This is works as expected with Terraform provider template version prior to 2.0.0.

References

Nmishin commented 5 years ago

This is interesting, it working wrong also if I provide two variables like this:

data "template_file" "serial" {
    template = "$${time / cycle}"
    vars {
        time  = "${var.timestamp}"
        cycle = "${var.ttl}"
    }
}

but works as expected if I do something like this:

data "template_file" "serial" {
    template = "${var.timestamp / var.ttl}"
}
apparentlymart commented 5 years ago

Hi @Nmishin,

The template provider from version 2.0.0 onwards uses the Terraform 0.12 template engine, which does not distinguish between integer and floating-point numbers: all numbers are arbitrary-precision floating point numbers.

To get an integer result, you can use the floor function to round down or the ceil function to round up, depending on which behavior is more appropriate. It sounds like you were previously relying on the behavior of Terraform 0.11's template engine of rounding the result down, in which case floor should give you the same result you wanted here.

data "template_file" "number" {
    template = "$${floor(time / cycle)}"
    vars {
        time  = "${data.external.timestamp.result.seconds}"
        cycle = "${var.ttl / 2}"
    }
}
Nmishin commented 5 years ago

Hi @apparentlymart,

Thank you for the fast reply. Yes, we are moving from Terraform 0.9.4 to 0.11.7. And seems this is what do we need. Thank you.

Out of my curiosity, why this construction works as expected:

data "template_file" "serial" {
    template = "${data.external.timestamp.result.seconds / var.ttl}"
}

seems because of this block work in the Terraform 0.11.7 block engine, not in the inside provider (and use Terraform 0.12 template engine) ?

apparentlymart commented 5 years ago

Yes, correct usage is to escape the ${ so that it can be interpreted by the template provider rather than by Terraform itself. If you don't escape it, then Terraform 0.11 will be the one to evaluate that expression, and the template engine will simply see the resulting digits as its input template.

For an expression this straightforward, the template provider is a lot of extra complexity... I assume you were doing that in Terraform 0.9 because there weren't many other options for factoring out an expression to be used multiple times. In Terraform 0.11 you can use the Local Values feature instead:

locals {
  serial = "${floor(data.external.timestamp.result.seconds / var.ttl)}"
}

resource "null_resource" "test" {
  triggers {
    serial = "${local.serial}"
  }
}

In this case, this too is evaluated by Terraform directly and not a provider, so you don't technically need floor but I would suggest adding it anyway to make it explicit to the reader that you are intending integer division and so that the configuration would work the same way if you subsequently upgrade to Terraform 0.12.

Nmishin commented 5 years ago

@apparentlymart thank you very much for your explanations! This is really useful. I'm closing the issue.