OpenTofu 1.8 Is Now Available!

Spacelift team - Jul 31 - - Dev Community

As a contributor to OpenTofu, we're always excited to see how it is developing and improving as an open-source, community-driven infrastructure-as-code tool. Today, with the release of OpenTofu 1.8.0, it's reached a new level, with some highly anticipated features that make it even easier to move from Terraform. 

We've analyzed the release and broken down some of the key features that we're excited about, including:  

  • Early variable/locals evaluation
  • .tofu file extensions
  • Enhanced testing capabilities

So let's dive in and explore about what you can do with 1.8.0!

Early variable/locals evaluation

This is by far the most highly anticipated feature of the release. Seven years ago, one of the issues with Terraform was that you couldn't add variables to the terraform block. There were some hacky workarounds that you could use, but they never felt like the right approach.

terraform {
 required_providers {
   aws = {
     version = ">= 5.0.0"
     source  = "hashicorp/aws"
   }
 }
 backend "s3" {
   bucket = "my-s3-state-bucket"
   key    = "spacelift/tofu_release/tofu18.tfstate"
   region = "eu-west-1"
 }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, if you wanted to add variables before OpenTofu 1.8, this simply didn't work:

terraform {
 backend "s3" {
   bucket = "my-s3-state-bucket"
   key    = "spacelift/tofu_release/tofu18.tfstate"
   region = var.region
 }
}

variable "region" {
 default = "eu-west-1"
}
Enter fullscreen mode Exit fullscreen mode
tofu -v
OpenTofu v1.7.0
on darwin_arm64

tofu init
Initializing the backend...
╷
│ Error: Variables not allowed
│
│   on main.tf line 11, in terraform:
│   11:     region = var.region
│
│ Variables may not be used here.

Enter fullscreen mode Exit fullscreen mode

Now, with OpenTofu 1.8, this is easy:

tofu18 -v
OpenTofu v1.8.0
on darwin_arm64

tofu18 init
Initializing the backend...
Initializing provider plugins...
OpenTofu has been successfully initialized!

Enter fullscreen mode Exit fullscreen mode

Another great addition is the ability to use variables or locals when using module sources and versions. Previously, changing the module versions was difficult from an automation standpoint because you always needed to use different workarounds to map to the module source and tag.

module "module1" {
 source = var.my_source
}

variable "my_source" {
 default = "./module1"
}
Enter fullscreen mode Exit fullscreen mode
.
╷
│ Error: Variables not allowed
│
│   on main.tf line 14, in module "module1":
│   14:   source = var.my_source
│
│ Variables may not be used here.

Enter fullscreen mode Exit fullscreen mode

Even if you used a module directly from a Terraform/OpenTofu registry, the same behavior occurred if you wanted to add variables to the version parameter:

module "vpc" {
 source  = "terraform-aws-modules/vpc/aws"
 version = var.vers
}

variable "vers" {
 default = "5.9.0"
}
Enter fullscreen mode Exit fullscreen mode
│ Error: Variables not allowed
│
│   on main.tf line 20, in module "vpc":
│   20:   version = var.vers
│
│ Variables may not be used here.

Enter fullscreen mode Exit fullscreen mode

But now, OpenTofu 1.8 allows this:

Initializing the backend...

Successfully configured the backend "s3"! OpenTofu will automatically
use this backend unless the backend configuration changes.
Initializing modules...
Downloading registry.opentofu.org/terraform-aws-modules/vpc/aws 5.9.0 for vpc...
- vpc in .terraform/modules/vpc

OpenTofu has been successfully initialized!

Enter fullscreen mode Exit fullscreen mode

It is also easy to add interpolations in these parameters:

locals {
 aks_version = "v1.0.12"
}

module "aks" {
 source = "git::https://github.com/flavius-dinu/terraform-az-aks.git?ref=${local.aks_version}"
}
Enter fullscreen mode Exit fullscreen mode
Initializing modules...
Downloading git::https://github.com/flavius-dinu/terraform-az-aks.git?ref=v1.0.12 for aks...
- aks in .terraform/modules/aks

OpenTofu has been successfully initialized!

Enter fullscreen mode Exit fullscreen mode

You can add values to the variables as you normally would, via default values, tfvars file, and even by using the "-var" and "-var-file" options.

.tofu file extensions

In OpenTofu 1.8, if you have two files with the same names but different extensions (e.g. main.tf and main.tofu), OpenTofu will prioritize the .tofu file and ignore the .tf file. As OpenTofu grows and adds new features, the OpenTofu community wants to ensure developers can write a single configuration file for both OpenTofu and Terraform by adding a new file rather than forcing them to create an entirely new configuration just to enable a feature. This feature is intended mainly for module authors, but it may have some other use cases.

For example, OpenTofu may release a feature in one version, and Terraform may release the same feature in a different one --- as happened with the provider-defined functions (OpenTofu 1.7, Terraform 1.8) --- so this addresses that issue. Additionally, in cases where OpenTofu supports a feature that Terraform doesn't, module authors don't need to keep two different configurations for their modules.

Let's look into a simple example to better understand the behavior:

main.tf:

locals {
 odd_numbers = [for i in var.my_number_list : i if i % 2 == 1]
}

output "odd_numbers" {
 value = local.odd_numbers
}
Enter fullscreen mode Exit fullscreen mode

main.tofu

locals {
 even_numbers = [for i in var.my_number_list: i if i % 2 == 0]
}

output "even_numbers" {
 value = local.even_numbers
}
Enter fullscreen mode Exit fullscreen mode

variables.tf

variable "my_number_list" {
 default = [1, 2, 3, 4]
}
Enter fullscreen mode Exit fullscreen mode

In the main.tf file, we will show only the odd numbers from the list, and in the main.tofu file we will show only the even numbers from the list, and both configurations will load the same variable:

terraform apply

Changes to Outputs:
 + odd_numbers = [
     + 1,
     + 3,
   ]
Enter fullscreen mode Exit fullscreen mode
tofu apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

even_numbers = [
 2,
 4,
]
Enter fullscreen mode Exit fullscreen mode

The above example is designed to help you better understand how this works, but you'll probably never do something like this in your configuration.

OpenTofu has also accepted the variable deprecation feature request, and when this feature is implemented, *.tofu *files will play a pivotal role in how module authors will develop their modules. To use this feature while still maintaining compatibility with Terraform, module authors will need to create two different files --- one for the Terraform variables and the other for their OpenTofu variables. Let's look at an example of how this would work:

resource "aws_instance" "this" {
 ami           = var.ami_id
 instance_type = var.instance_type

 tags = merge({
   Name = var.instance_name
 }, var.tags)
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we have an EC2 instance resource that has four variables declared. Let's look at how these variables will look using Terraform, and on OpenTofu if we plan to deprecate the tags one:

# Variables.tofu

variable "ami_id" {}

variable "instance_type" {}

variable "instance_name" {}

variable "tags" {
 type = map(string)
 default = {
   "Environment" : "dev"
 }
 deprecation = "This variable is deprecated, and will be removed in the next minor release of the module. Please use aws_tags variable instead"
}
Enter fullscreen mode Exit fullscreen mode
# Variables.tf

variable "ami_id" {}

variable "instance_type" {}

variable "instance_name" {}

variable "tags" {
 type = map(string)
 default = {
   "Environment" : "dev"
 }
}
Enter fullscreen mode Exit fullscreen mode

We haven't yet implemented the logic or declared the new variable that replaces the tags one in the OpenTofu variables.tofu file, but we are already letting our module consumers know this will change.

With this feature, you can gradually switch from Terraform to OpenTofu by enabling OpenTofu features and keeping the ability to switch back to Terraform if you'd like.

Enhanced testing capabilities

In traditional software testing, mocking is an approach to unit testing that enables simulating the behavior of real objects to test specific interactions. OpenTofu has the capability of mocking data and can now override resources, data sources, and even modules from your tests.

In the test_name.tftest.hcl, you can override these components by using the following:

  • override_data - this will override a data source
  • override_resource - this will override a resource
  • override_module - this will override a module

Depending on your objectives, you can use the override_component directly in a run block or independently.

When you override one of these components, you can select the kind of fields you want to override.

Let's look at an example:

provider "aws" {
 region = "us-east-1"
}

resource "aws_vpc" "main" {
 cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "subnet" {
 vpc_id     = aws_vpc.main.id
 cidr_block = "10.0.1.0/24"
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we are creating a vpc and a subnet.

Now, let's write a test that verifies if the subnet is created in a specific vpc_id. For that, I will first override the vpc resource's id and set it to a mock value:

override_resource {
 target = aws_vpc.main
 values = {
   id = "vpc-12345"
 }
}
Enter fullscreen mode Exit fullscreen mode

Now, if I override the subnet resource to specify the same vpc_id, I can create an assert block to verify if the vpc_id matches the id of the vpc set on the subnet level like so:

run "test_subnet_vpc_id" {
 override_resource {
   target = aws_subnet.subnet
   values = {
     vpc_id = "vpc-12345"
   }
 }

 assert {
   condition     = aws_subnet.subnet.vpc_id == "vpc-12345"
   error_message = "Incorrect VPC ID for aws_subnet.subnet: ${aws_subnet.subnet.vpc_id}"
 }
}
Enter fullscreen mode Exit fullscreen mode

In the end, the test file will look like this:

provider "aws" {}

override_resource {
 target = aws_vpc.main
 values = {
   id = "vpc-12345"
 }
}

run "test_subnet_vpc_id" {
 override_resource {
   target = aws_subnet.subnet
   values = {
     vpc_id = "vpc-12345"
   }
 }

 assert {
   condition     = aws_subnet.subnet.vpc_id == "vpc-12345"
   error_message = "Incorrect VPC ID for aws_subnet.subnet: ${aws_subnet.subnet.vpc_id}"
 }
}
Enter fullscreen mode Exit fullscreen mode

How to leverage OpenTofu 1.8 with Spacelift

Spacelift supports OpenTofu 1.8 natively, so all you need to do is select OpenTofu 1.8 when you create or update your stack. This makes it easy to leverage these new features, and by combining them with Spacelift's features, you can build seamless workflows.

For early variable evaluation, you can leverage a context and define environment variables inside it or directly add environment variables at the stack level. Based on these variables, you can easily create multiple stacks based on the same configuration and modify these variables only.

You can use .tofu files directly from your VCS repository, but if a new feature emerges that you want to test without making changes to your VCS repo, you can easily mount a file to your stack or your context.

Leveraging stack dependencies, policies, cloud integrations, and the OpenTofu provider for Spacelift will certainly take your infrastructure management to the next level.

If you want to learn more about this release, check out the release notes here, and if you're ready to get started with OpenTofu you can download the latest version here.

Key points

OpenTofu 1.8 comes packed with features that engineers expected from Terraform for many years. It works natively in Spacelift and you can easily take advantage of all Spacelift features in it. If you want to take your OpenTofu workflow to the next level with Spacelift, create a free account today or book a demo with one of our engineers.

Written by Flavius Dinu

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .