hashicorp / terraform-provider-local

Utility provider used to manage local resources, such as creating files.
https://registry.terraform.io/providers/hashicorp/local/latest
Mozilla Public License 2.0
209 stars 67 forks source link

local_file #335

Closed magzim21 closed 1 day ago

magzim21 commented 2 months ago

Terraform CLI and Provider Versions

terraform version ;terraform providers Terraform v1.8.2 on darwin_arm64

Your version of Terraform is out of date! The latest version is 1.8.5. You can update by downloading from https://www.terraform.io/downloads.html

Providers required by configuration: . ├── provider[registry.terraform.io/hashicorp/helm] ~> 2.13 ├── provider[registry.terraform.io/hashicorp/kubernetes] ~> 2.29 ├── provider[registry.terraform.io/hashicorp/local] ├── provider[registry.terraform.io/integrations/github] ~> 6.0 ├── provider[registry.terraform.io/hashicorp/tfe] ~> 0.55.0 ├── provider[registry.terraform.io/hashicorp/tls] ~> 3.0 └── provider[registry.terraform.io/hashicorp/aws] ~> 5.0

Providers required by state:

provider[registry.terraform.io/hashicorp/aws]

provider[registry.terraform.io/hashicorp/local]

provider[registry.terraform.io/hashicorp/tfe]

provider[registry.terraform.io/hashicorp/tls]

provider[registry.terraform.io/integrations/github]

Terraform Configuration

### START ### Update helm-generic-chart url
data "github_repository_file" "template_helm_chart" {
  repository          = github_repository.template.name
  branch              = github_branch_default.template.branch
  file                = "helm/Chart.yaml.tftpl"
}

resource "local_file" "template_helm_chart" {
  content  = data.github_repository_file.template_helm_chart.content
  filename = "${path.module}/tmp/Chart.yaml.tftpl"
}

resource "github_repository_file" "template_helm_chart" {
  repository          = github_repository.template.name
  branch              = "main"
  file                = "helm/Chart.yaml"
  content             = templatefile(local_file.template_helm_chart.filename, { ecr_repo_url = "oci://${aws_ecr_repository.this["helm-generic-chart"].registry_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com"  })
  commit_email   = "terraform@codelaw.pro" # TODO / create this with Terraform
  commit_author  = "Terraform"
  commit_message = "chore: update helm/Chart.yaml.tftpl"
  overwrite_on_create = true
}

### END ### Update helm-generic-chart url

Expected Behavior

It works

Actual Behavior

templatefile exepects the file to be present. It does not identify a dependency.

╷
│ Error: Invalid function argument
│
│   on gh.tf line 81, in resource "github_repository_file" "template_helm_chart":
│   81:   content             = templatefile(local_file.template_helm_chart.filename, { ecr_repo_url = "oci://${aws_ecr_repository.this["helm-generic-chart"].registry_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com"  })
│     ├────────────────
│     │ while calling templatefile(path, vars)
│     │ local_file.template_helm_chart.filename is "./tmp/Chart.yaml.tftpl"
│
│ Invalid value for "path" parameter: no file exists at
│ "./tmp/Chart.yaml.tftpl"; this function works only with files that are
│ distributed as part of the configuration source code, so if this file will
│ be created by a resource in this configuration you must instead obtain this
│ result from an attribute of that resource.
╵

Steps to Reproduce

  1. terraform apply
  2. get the error

How much impact is this issue causing?

Low

Logs

No response

Additional Information

No response

Code of Conduct

austinvalle commented 1 month ago

Hey there @magzim21 👋🏻, thanks for reporting the issue and sorry you're running into trouble here.

Functions, like templatefile, are executed as expressions, which means that your configuration would attempt to run templatefile during commands like terraform plan, despite the file not actually existing yet. The only way to avoid this execution is to supply that function with an unknown value, so Terraform knows it can't execute templatefile until the file path is known.

In your example local_file.template_helm_chart.filename is known during plan (since it's a hardcoded config value), so it attempts to execute the templatefile function, despite the file not existing yet.

I recreated this behavior with a gist template:

data "http" "remote_template" {
  url = "https://gist.githubusercontent.com/austinvalle/2637d4ecf217b85515edadc9c1e6cce3/raw/7bf13430334e00df19fbdb19d18bb7c5130f16d6/local_file_example.txt"
}

resource "local_file" "test_file" {
  content  = data.http.remote_template.response_body
  filename = "${path.module}/test_file.txt"
}

output "test" {
  value = templatefile(
    # Terraform already knows the value of `local_file.test_file.filename`, so running terraform plan will produce an error!
    local_file.test_file.filename, {
      msg = "hello world!"
    }
  )
}

Running terraform plan with this config will give you an error:

│ Error: Invalid function argument
│ 
│   on main.tf line 13, in output "test":
│   11:   value = templatefile(
│   12:     # Terraform already knows the value of `local_file.test_file.filename`, so running terraform plan will produce an error!
│   13:     local_file.test_file.filename, {
│   14:       msg = "hello world!"
│   15:     }
│   16:   )
│     ├────────────────
│     │ while calling templatefile(path, vars)
│     │ local_file.test_file.filename is "./test_file.txt"
│ 
│ Invalid value for "path" parameter: no file exists at "./test_file.txt"; this function works only with files that are
│ distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you
│ must instead obtain this result from an attribute of that resource.

One potential solution would be to work with the file contents directly, rather than attempting to write a file. Terraform v1.9 actually released a templatestring function that I believe could achieve what you're looking for.

Here is a rewrite of my previous failing configuration example:

data "http" "remote_template" {
  url = "https://gist.githubusercontent.com/austinvalle/2637d4ecf217b85515edadc9c1e6cce3/raw/7bf13430334e00df19fbdb19d18bb7c5130f16d6/local_file_example.txt"
}

output "test" {
  value = templatestring(
    data.http.remote_template.response_body, {
      msg = "hello world!"
    }
  )
}

Running terraform plan

data.http.remote_template: Reading...
data.http.remote_template: Read complete after 0s [id=https://gist.githubusercontent.com/austinvalle/2637d4ecf217b85515edadc9c1e6cce3/raw/7bf13430334e00df19fbdb19d18bb7c5130f16d6/local_file_example.txt]

Changes to Outputs:
  + test = "hello world!"

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run
"terraform apply" now.

Let me know if that's helpful or if you have any other questions!

magzim21 commented 1 day ago

So the answer is templatestring. Thanks man austinvalle!