Terraform: Migrate from template_file Data Sources

Reading Time: 3 minutes

Overview

I stumbled across this problem when using a new MacBook Pro M1 CPU. I had some legacy code that used aws_template for a user data script. The template function is now deprecated, and the only way around this is to use the templatefile function that was introduced in Terraform 0.12.

This is simple for the new code, but if, like me, you have legacy code that is still embedded into production you will have issues.

This is the error I was getting:

template v2.2.0 does not have a package available for your current platform, darwin_arm64

There are many suggested fixes online, but they just fix the issue locally. It doesn’t fix the issue in the code and the terraform state files (mine are located in S3)

Migrating

Locate current usage

To begin, you can check to see what requires this provider in your current code – in the below example you can see that the template provider is required by both resources in module.jenkins and the state:

Please note that I use AWS-Vault to access my cloud resources.

aws-vault exec <my-aws-profile> --no-session -- terraform providers

Providers required by configuration: . ├── provider[registry.terraform.io/hashicorp/aws] ├── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── provider[terraform.io/builtin/terraform] └── module.jenkins ├── provider[registry.terraform.io/hashicorp/random] ├── provider[registry.terraform.io/hashicorp/template] ├── provider[registry.terraform.io/hashicorp/aws] ├── module.codebuild_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.iam_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.s3_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.security_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.slaves_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.network_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.ec2_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.lb_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.log_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.api_integration_autoscaling │ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── module.jenkins_alert_policy │ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── module.certs_label │ └── provider[registry.terraform.io/hashicorp/aws] └── module.api_integration_cloudwatch └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 Providers required by state: provider[registry.terraform.io/opsgenie/opsgenie] provider[registry.terraform.io/hashicorp/aws] provider[registry.terraform.io/hashicorp/random] provider[registry.terraform.io/hashicorp/template] provider[terraform.io/builtin/terraform]

Removing Current usage

Search for all instances of the template_file data resource and replace it with a template file function – for example, this CloudWatch dashboard changes

From This:

data "template_file" "dashboard" { 
template = file("${path.module}/templates/dashboard.template")
} 
resource "aws_cloudwatch_dashboard" "jenkins" { 
dashboard_name = "Jenkins" dashboard_body = data.template_file.dashboard.rendered 
}

To This:

resource "aws_cloudwatch_dashboard" "jenkins" { 
dashboard_name = "Jenkins" 
dashboard_body = templatefile("${path.module}/templates/dashboard.template", {}) 
}

Full details of how to use the function, and pass variables, can be found here

Now, if you check the providers again, you should see that the modules are no longer listing template as a required provider – however, the state does:

aws-vault exec<my-aws-profile> --no-session -- terraform providers

Providers required by configuration: . ├── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── provider[registry.terraform.io/hashicorp/aws] ├── provider[terraform.io/builtin/terraform] └── module.jenkins ├── provider[registry.terraform.io/hashicorp/aws] ├── provider[registry.terraform.io/hashicorp/random] ├── module.network_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.certs_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.codebuild_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.ec2_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.iam_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.api_integration_autoscaling │ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── module.api_integration_cloudwatch │ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── module.slaves_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.jenkins_alert_policy │ └── provider[registry.terraform.io/opsgenie/opsgenie] 0.6.10 ├── module.s3_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.security_label │ └── provider[registry.terraform.io/hashicorp/aws] ├── module.log_label │ └── provider[registry.terraform.io/hashicorp/aws] └── module.lb_label └── provider[registry.terraform.io/hashicorp/aws] Providers required by state: provider[registry.terraform.io/opsgenie/opsgenie] provider[registry.terraform.io/hashicorp/aws] provider[registry.terraform.io/hashicorp/random] provider[registry.terraform.io/hashicorp/template] provider[terraform.io/builtin/terraform]

Update State

Apply the Terraform and (assuming you got your code correct!) you should see that there were no changes to apply – this is because the template should be the same, just the method of generating it has changed:

\aws-vault exec<my-aws-profile> --no-session -- terraform apply ... No changes.

Your infrastructure matches the configuration. Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a refresh-only plan: terraform apply -refresh-only Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

After you successfully apply, you should refresh the state:

aws-vault exec<my-aws-profile> --no-session -- terraform apply -refresh-only .... No changes. Your infrastructure still matches the configuration. Terraform has checked that the real remote objects still match the result of your most recent changes, and found no differences. Would you like to update the Terraform state to reflect these detected changes? Terraform will write these changes to the state without modifying any real infrastructure. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Now when you check, you will see that the provider is no longer needed by the state either:

Providers required by state: provider[terraform.io/builtin/terraform] provider[registry.terraform.io/opsgenie/opsgenie] provider[registry.terraform.io/hashicorp/aws] provider[registry.terraform.io/hashicorp/random]

At this point, you can remove the .terraform directory and re-init the code (terraform init -reconfigure) – all the modules and providers (now excluding the template provider) will be installed and the .terraform.lock.hcl will have its entry for the template provider removed.

At this point, you can commit your code back to git.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *