slide

Migrating to open TOFU

Ned Bellavance
15 min read

Cover

Are you thinking about migrating from Terraform to OpenTofu? What are the possible benefits? What are the drawbacks? And can you migrate back? I’ll try to answer those questions in this blog post.

If you’d prefer your information in video format, check out the Terraform Tuesday video instead.

Why OpenTofu?

In August of 2023, HashiCorp decided to change the licensing for their core products from Mozilla Public License (MPL) 2.0 to Business Source License (BSL) 1.1. The change was introduced via a commit on each GitHub repository and took effect with the next subsequent release of each product. Terraform was included in the list of impacted products and some folks were nonplussed about the change.

I’m not here to re-litigate the whole thing. This is not a blog for a bunch of drama. If you want to hear people spilling the tea on HashiCorp and the state of open source in general, there’s plenty of folks happy to ramble at you. Just scoot on over to reddit or the bird site.

HashiCorp’s interpretation of BSL was simply that you could continue to use their community products for free if you weren’t creating a product that competed with their paid solutions. And the use had to be like for like. So you couldn’t use Vault community edition to create a competitor to HCP Vault or Vault Enterprise. And you couldn’t use Terraform community edition to create a competitor to Terraform Enterprise or HCP Terraform (previously known as Terraform Cloud). You could, however, use Terraform community edition to create a competitor to HCP Consul.

If you really want to dig into the details, you can check out their blog post and BSL FAQs. And if that isn’t adequate, there’s a licensing questions email address you can ping for your unique situation. For 99% of you, the license change doesn’t require any change from you. If licensing is your only concern, you’re probably in the clear (#notalawyer).

Terraform is by far HashiCorp’s most popular software, and many companies and projects have worked hard to fill in the gaps not covered by the community version of Terraform. Whether that was automation, state management, policy enforcement, or testing, there is a rich ecosystem of Terraform adjacent projects and products that suddenly felt under threat by the change to BSL.

At the same time, there were some open-source purists who rightly identified that BSL is not true open-source software (OSS), and now Terraform was under a source-available license. Organizations like the CNCF, that require their projects only use OSS tools and components found themselves in a jam.

A group of folks in the IaC ecosystem got together and wrote a manifesto demanding that HashiCorp revert back to an OSS license or they would fork Terraform. HashiCorp was disinclined to acquiesce to their request, and thus OpenTofu was born.

I am disinclined to acquiesce to your request… means no.

Like I said before, there is a non-zero amount of drama surrounding the birth of OpenTofu, HashiCorp’s response, and general internet sturm and drang. I am not going to get into it. I’ve said my piece elsewhere and I stand by it.

OpenTofu Now

OpenTofu was forked from the latest version of Terraform that still carried the previous MPL license, equating to roughly 1.5.something. The current version of OpenTofu as of this post is 1.7.3, so everything I’m going to mention is about that version. Likewise, the current version of Terraform right now is 1.9.2, so when I make comparisons between the two that is the version I’m referring to.

The folks at OpenTofu have been trying to keep feature parity with newer releases of Terraform, but that is starting to diverge a bit. Any configuration that was valid before Terraform 1.6, should work just fine with OpenTofu. If you’re on Terraform 1.6 or newer, then you may have to do some small amount of rework depending on what features you’ve leveraged in the newer versions.

Comparing OpenTofu and Terraform

I have a blog post I’ve tried to keep up to date as new minor versions of each project are released. If that’s tl;dr, here are the big highlights.

Both Terraform and OpenTofu have introduced a testing framework that makes use of run blocks, .tftest.hcl files, and a test command. Terraform includes mock data, which OpenTofu doesn’t yet. Other than that, the two are basically the same.

Both Terraform and OpenTofu introduced removed blocks, looping for import blocks, and provider defined functions. The removed block syntax is a little different, but otherwise I believe all of these features are identical.

OpenTofu released state and plan encryption with 1.7. This feature does not exist in Terraform, and I doubt it ever will. There’s an interesting WIP about ephemeral resources and values for Terraform that will allow you to omit certain values from state and plans, but I wouldn’t expect any implementation of that until 1.10 at the earliest.

Version 1.9 of Terraform introduce the ability to include other object values in an input variable validation block. As far as I can tell, this feature is not planned for OpenTofu at the moment.

Version 1.8 of OpenTofu will introduce support for .tofu files to enable a configuration to support both platforms while allowing you to take advantage of new features in either. The core idea is to have two files with the same name, but one ends in .tf and the other ends in .tofu. Terraform will ignore the .tofu file, and OpenTofu will ignore the .tf file. You can leverage new Terraform functionality in the .tf file and new OpenTofu stuff in the .tofu file. It’s a neat idea!

While there has been some divergence, OpenTofu and Terraform are largely the same from an operational standpoint. The code sitting behind it will be slightly different, but unless you’re a developer trying to contribute, that shouldn’t concern you too much.

That’s enough chit-chat. How about we check out a migration?

Migrating from Terraform to OpenTofu

The steps to perform the migration are as follows:

  1. Verify no pending changes
  2. Backup state data
  3. Remove incompatible features
  4. Change provider and module source addresses
  5. Run tofu init to switch providers and modules
  6. Run tofu plan to verify no changes
  7. Run tofu apply to update state data

The Example Configuration

I’m going to start with a simple project that deploys an Azure resource group and virtual network using Azure storage as the backend. Looking inside the configuration, we’ve got a terraform.tf file that defines the required providers and backend:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }

  backend "azurerm" {
    resource_group_name = "tacoTruck"
    storage_account_name = "opentofu1313"
    container_name = "tfstate"
    key = "terraform.tfstate"
  }
}

And there’s a main.tf that defines the azurerm provider, resource group, and uses a module from the public registry to deploy a Virtual Network.

provider "azurerm" {
  features {}
  
}

resource "azurerm_resource_group" "test" {
  name = "opentofu-test"
  location = "East US"
  tags = {
    managed_by = "Terraform"
  }
}

module "vnet" {
  source  = "Azure/vnet/azurerm"
  version = "4.1.0"
  
    resource_group_name = azurerm_resource_group.test.name
    use_for_each = true
    vnet_location = azurerm_resource_group.test.location
    address_space = ["10.0.0.0/16"]
    subnet_names = ["subnet1", "subnet2"]
    subnet_prefixes = ["10.0.0.0/24", "10.0.1.0/24"]

}

Let’s assume I’ve already deployed this configuration to Azure using Terraform. I can pull up the terminal and run terraform state list to see the resources under management.

$ terraform state list

azurerm_resource_group.test
module.vnet.azurerm_subnet.subnet_for_each["subnet1"]
module.vnet.azurerm_subnet.subnet_for_each["subnet2"]
module.vnet.azurerm_virtual_network.vnet

The good folks at OpenTofu have published a migration guide that walks you through the process depending on which version of Terraform you’re currently using. I’m on version 1.9.2, so I’m following those directions.

Verify No Pending Changes

The first step is to make sure there are no pending changes from the Terraform side. I’m going to run terraform plan to make sure.

$ terraform plan

azurerm_resource_group.test: Refreshing state...
...

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no 
changes are needed.

It comes back as no changes, so we’re good there. Had there been pending changes, we would want to apply those before attempting a migration. The goal is to switch to OpenTofu without impacting the target environment(s) in any way.

Backup Your State Data

Next up, I am going to back up my state file just in case something goes sideways. If you’re using Azure storage, make sure you have versioning enabled and maybe even take a snapshot. You can also run terraform state pull and direct it to a file for a quick and dirty backup, but I wouldn’t recommend it for production workloads where there might be sensitive information in the state data.

We can use this copy of the state data for comparison as well once we migrate to OpenTofu.

Remove incompatible features

You’re going to want to check your code for any features that OpenTofu doesn’t support. Here are the ones I know of at the moment:

  • Mock data in the testing framework
  • Provider defined functions for the built-in Terraform provider
  • Input variable validation referencing other objects

As always, you should check the release notes from the most recent version of OpenTofu. Version 1.8 is going to add support for mock data.

Change Provider and Module Source Addresses

OpenTofu had to set up their own registry for providers and modules because HashiCorp changed the licensing terms of their public registry to prevent non-Terraform binaries from accessing it. Again, I’m not here to point fingers or get catty. The practical upshot for you is that moving to OpenTofu means using their registry instead of the Terraform public registry.

The Terraform public registry is essentially an abstraction over the provider and module repositories on GitHub. It adds support for version constraints, supplies nicely formatted documentation, and allows for easy discovery. But the actual provider binaries and module files live on GitHub. The OpenTofu registry points at the exact same GitHub repositories without violating the Terraform registry license.

When you define a source for your provider or module, you can choose to use a shorthand like hashicorp/aws or the longer form registry.terraform.io/hashicorp/aws. When using the shorthand form, Terraform prepends registry.terraform.io automatically. OpenTofu does the same, but prepends registry.opentofu.org instead.

If any of the providers or modules in your configuration are using the longer form, you’ll need to switch to the short form. That includes modules and providers inside of child modules as well. You only need to do this for modules and providers from the Terraform public registry.

OpenTofu will still work if you pull providers from the Terraform public registry, but you might technically be violating the Terms and Conditions of the registry. And you’re not a rule breaker, are you?

Run tofu init to switch providers and modules

With that out of the way, it’s time to initialize the configuration with OpenTofu. From the terminal, I’ll run tofu init:

$ tofu init

Initializing the backend...
Initializing modules...
Downloading registry.opentofu.org/Azure/vnet/azurerm 4.1.0 for vnet...
- vnet in .terraform\modules\vnet

Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 3.0, >= 3.11.0, < 4.0.0"...
- Installing hashicorp/azurerm v3.113.0...
- Installed hashicorp/azurerm v3.113.0 (signed, key ID 0C0AF313E5FD9F80)

Providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://opentofu.org/docs/cli/plugins/signing/

OpenTofu has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

OpenTofu has been successfully initialized!

The output shows that the vnet module is being pulled from registry.opentofu.org/Azure/vnet/azurerm. Looking at the updated .terraform.lock.hcl file:

provider "registry.opentofu.org/hashicorp/azurerm" {
  version     = "3.113.0"
  constraints = "~> 3.0, >= 3.11.0, < 4.0.0"
  hashes = [
    "h1:beBFmLUcm/WPYeAX+E40sP3g6kn4Flpv2kO8DIwxj6c=",
    ...]
}

The azurerm provider is also pointing at the OpenTofu registry. Additionally, in the .terraform directory, there are now two folders for the azurerm provider:

$ tree .terraform/providers

.
├───registry.opentofu.org
│   └───hashicorp
│       └───azurerm
│           └───3.113.0
│               └───windows_amd64
└───registry.terraform.io
    └───hashicorp
        └───azurerm
            └───3.113.0
                └───windows_amd64

You may want to go back after the migration and clean out the provider plugins from the Terraform public registry if you’re cramped for space.

Let’s check out the state data too, I’ll run tofu state pull and that will print the state to the terminal.

$ tofu state pull

{
  "version": 4,
  "terraform_version": "1.7.3",
  "serial": 11,
  ...

Looking at the terraform_version, it says 1.7.3. However the source for the azurerm providers still says registry.terraform.io:

"resources": [
    {
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "test",
      "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
      ...

Once we run a successful tofu apply, that will update as well.

Run tofu plan To Verify No Changes

With the initialization is done, I’m going to run a tofu validate to make sure OpenTofu is cool with my config and then run tofu plan. If any issues occur it might come down to the slight differences in implementation of features.

$ tofu validate

Success! The configuration is valid.

My validate comes back clean, so I’m ready to run plan to see if any changes are listed. There shouldn’t be- we did check with Terraform before switching- but I want to make sure.

$ tofu plan

azurerm_resource_group.test: Refreshing state...
...

No changes. Your infrastructure matches the configuration.

OpenTofu has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.

My plan comes back with no changes, but if you do see changes, you may want to roll back to Terraform or try and troubleshoot what is different about the configuration. I’ll address rolling back later in the post.

Run tofu apply to update state data

Although OpenTofu did change the Terraform version logged in state data, it left it otherwise unchanged. There are no pending changes for our actual infrastructure, so all tofu apply will do is alter the registry references in state data.

$ tofu apply -auto-approve

tofu apply -auto-approve
azurerm_resource_group.test: Refreshing state...
...

No changes. Your infrastructure matches the configuration.

OpenTofu has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

As predicted, there are no changes to our resources, but there should be some changes in state. I’ll run tofu state pull to check:

{
  "version": 4,
  "terraform_version": "1.7.3",
  "serial": 12,
  "lineage": "a084a4f3-7aee-5943-dc21-613e4d1a88c1",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "azurerm_resource_group",
      "name": "test",
      "provider": "provider[\"registry.opentofu.org/hashicorp/azurerm\"]",

The serial has incremented from 11 to 12 and the provider reference now points to registry.opentofu.org. Otherwise, the actual resources are exactly the same.

If I want to test out a change, I can update the managed_by tag on the resource group from Terraform to OpenTofu and run an apply.

resource "azurerm_resource_group" "test" {
  name = "opentofu-test"
  location = "East US"
  tags = {
    managed_by = "OpenTofu"
  }
}

This will prove that OpenTofu is now managing our infrastructure.

$ tofu apply -auto-approve

azurerm_resource_group.test: Refreshing state...
...
...
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

After a few moments the apply is complete and now our infra is managed with OpenTofu.

Not too tough eh?

The YOLO Option

The above migration process is cautious and methodical. For anything production-ish, I’d recommend that approach. But if you’re moving development stuff that you don’t really care about, the whole thing can be boiled down to two steps:

  1. Run tofu init
  2. Run tofu apply -auto-approve

tofu apply -auto-approve YOLO

And that’s it. The rest of the steps are about protecting the state and preventing issues. While I can’t recommend the YOLO approach, it really can be that simple to migrate.

Migrating Back to Terraform

In case you were wondering, migrating back to Terraform is essentially the same process. Run terraform init, plan, and then apply and you’ll be back where you started. If you’ve started using new OpenTofu features, that might complicate things, especially the state data encryption. You’ll need to remove that before migrating back.

Your actual infrastructure in either case isn’t altered by the migration, and I think that’s the most important part. If you want to experiment with OpenTofu and then move back to Terraform, you can! No harm, no foul.

Should You Migrate?

This is a tougher question, and it’s one that I struggle to answer because honestly, and I know this is trite, IT DEPENDS™. So rather than giving you a straight answer, since I don’t think there’s a one size fits all response, instead here are some questions to consider:

  1. Are you running on HCP Terraform today? OpenTofu doesn’t work with HCP Terraform, so this is a non-starter.
  2. Are you running on a Terraform automation platform like Spacelift or env0? BSL versions of Terraform are not-available on these platforms, so if you want some of the new features introduced after the BSL change, you might want to move to OpenTofu.
  3. Are you working on a project that requires OSS tools? I guess you’re moving to OpenTofu!
  4. Are you building an HCP Terraform competitor for paid public consumption? Yeah, you’re going to need to move to OpenTofu.
  5. Are you using Terraform to build out your infrastructure and don’t really care about the whole OSS battle? I guess my follow-up question would be whether there is a killer feature of OpenTofu that you really want. Right now that’s just state data encryption. If that doesn’t move the needle for you, then I don’t see a reason to migrate.
  6. Has your legal team decided that the BSL is no good for your organization? Well I guess that’s out of your hands and above my pay-grade. To OpenTofu it is!

You can also opt to stay on Terraform pre-BSL changes and just excuse yourself from the whole debate. You won’t be getting any security patches- that ended on December 31st 2023- but my point is that you can sit on 1.5 and wait to see what happens with the whole IBM acquiring HashiCorp thing, which is another can of worms I don’t want to get into right now.

Final Thoughts

I hope in this post I’ve shown you how easy it is to move from Terraform to OpenTofu. While its not quite a drop-in replacement- you will need to do a little homework and analysis- it’s also a relatively low-risk and reversible operation. You could easily pilot OpenTofu on some development environments and see if it meets your needs.

That being said, my bottom line is that I would stick with the status quo unless there is some compelling reason to migrate. Most engineers and admins I know already have enough on their plate, and migrating to a new tool (even one as simple as Terraform to OpenTofu) is just one more thing to worry about. Unless there’s a clear business benefit or legal compulsion, I wouldn’t make the move.

That’s not to denigrate or cheapen what the OpenTofu folks have done. They’ve successfully forked an incredibly popular project and made the migration process as easy as possible. Kudos to everyone working on the project! As both projects evolve and inevitably diverge, I’d like to revisit the topic and see if my thinking has changed.