How can you improve the state of your infrastructure automation?

➡️ Take the Self-Assessment

Product

Ansible Dynamic Inventories And Spacelift

spacelift ansible dynamic inventories

Subscribe to our Newsletter

Mission Infrastructure newsletter is a monthly digest of the latest posts from our blog, curated to give you the insights you need to advance your infrastructure game.

Ansible can connect to your hosts and automatically configure them with ease. But what if your hosts are dynamic, and you don’t know their IP addresses or hostnames in advance?

You could potentially spin up new hosts in an autoscaling group or create and destroy new hosts with daily OpenTofu operations. Those hosts need to be configured using your Ansible playbooks. This is where dynamic inventories come in. Dynamic inventories are a way to build your inventory on the fly based on an external data source.

Some notable dynamic inventories include:

All the above inventory plugins will work in Spacelift, so you can use any one of the above dynamic inventories to configure your hosts. This blog post will go over a custom Spacelift dynamic inventory (tofusible) that is specifically built for OpenTofu and Ansible using our Stack Dependencies feature. However, you should be able to take these basic concepts and apply them to any dynamic inventory you want to use.

What is Tofusible?

Tofusible is a custom dynamic inventory that we built. It allows you to create hosts using OpenTofu and pass those hosts dynamically to Ansible for configuration.

In this blog post, we will walk you through setting up a custom dynamic inventory, configuring it, and using the Tofusible dynamic inventory in your Ansible playbooks.

Teach me more

Okay, so you made it this far. Before we set up the Tofusible dynamic inventory, we need to explain a few things.

A dynamic inventory is a script that collects some data and generates a JSON object representing your inventory. Things like the AWS, GCP, or Azure dynamic inventory plugins use their own APIs to gather data about your hosts. In the case of Tofusible, we are using OpenTofu to gather data about our hosts. But you could imagine a dynamic inventory that reads from a database or a dynamic inventory that reads from a CSV file.

The important concept to know here is that the source data can come from anywhere, and different dynamic inventories will have different sources of data. These are usually documented with the dynamic inventory plugin you’re using, so they are not all the same. You should read the documentation for your dynamic inventory to understand what it’s doing and how to use it.

The Tofusible dynamic inventory, which we will be moving onto shortly, is a custom dynamic inventory that we built specifically for OpenTofu and Ansible.

Its data source comes from OpenTofu itself. It uses the tofusible_host module to gather information about the hosts you created, which are then passed to Ansible for configuration.

How it works

From a high level, the Tofusible process is as follows:

  1. You create virtual machines with OpenTofu – using the provider natively. The examples in this blog post use aws_instance’s, but you are not limited to this provider.
  2. You use the tofusible_host module to gather information about the virtual machines you created.
  3. You output the tofusible_hosts as a list of hosts in OpenTofu (using native OpenTofu outputs).
  4. You use the Spacelift stack dependency feature to pass the output to an Ansible stack.
  5. The Ansible stack uses the tofusible dynamic inventory plugin to read that output and generate an inventory based on it.
  6. Ansible then uses that dynamic inventory to configure the virtual machines you created.

Let's get our hands dirty

Step 1: Create an OpenTofu and Ansible stack

First, we need to create new OpenTofu and Ansible stacks. The OpenTofu stack will be used to create the virtual machines in OpenTofu, and the Ansible stack will be used to configure those virtual machines. I won’t go into the details of how to set up the stack in this post, but you can read our Documentation on how to create a stack here.

We will add specific configurations to both stacks later, but for now, just create a vanilla OpenTofu stack and a vanilla Ansible stack.

Step 2: Create a virtual machine in OpenTofu

Now that we have our OpenTofu stack, we need to create a virtual machine.

In your OpenTofu stack, create a new aws_instance resource.

resource "aws_instance" "tofu_dev" {
  ami                    = data.aws_ami.this.id
  key_name               = var.aws_private_key_name
  instance_type          = "t2.micro"
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [var.vpc_security_group_id]
  tags = {
    Name = "tofu dev"
  }
}

You can configure this resource in any subnet, use any security group. We just need a virtual machine to configure with Ansible.

Step 3: Create a Tofusible host

Now, we need a way to tell our Ansible stack about the virtual machine we created.

When you create a virtual machine in OpenTofu, different providers will have different ways of outputting information about the virtual machine. In the case of AWS, its public IP is available as the attribute public_ip, but DigitalOcean uses ipv4_address.

So, we need a way to abstract this information and make it available to Ansible. In comes the tofusible_host module. In addition to the aws_instance resource, we will create a tofusible_host resource that will tell our dynamic inventory information about that host.

module "host_tofu_dev" {
   source  = "spacelift.io/spacelift-solutions/tofusible-host/spacelift"
   version = "1.0.0"

   host                 = aws_instance.tofu_dev.public_ip
   user                 = "ubuntu"
   ssh_private_key_file = var.private_key_path
   groups               = ["tofu", "dev", "example.dev"]

}

The two important attributes here are host and groups.

  • host will tell Ansible where to connect to the virtual machine. With Spacelift, you can use the public IP, the private IP, or whatever you want to use to connect to that host – as long as it is accessible from the Ansible stack. In this example, we are using the public IP of the virtual machine.
  • groups will group that host into the specified groups in the eventual inventory. You can have multiple tofusible_host module calls in your OpenTofu stack. (You can see a larger example calling the module more than once here.)

Say you have a web server and a db server; you can group them into web and db groups, respectively. You can also add both of those servers to the production group. Now, when you use the production group in your Ansible playbook, it will run on both the web and db servers.

Groups also have the ability to be nested. In the example above, the dev group is a child of the example group.

The tofusible_host module is fully documented, if you’d like to see more information about it.

Step 4: Output the Tofusible hosts

Now that we have our tofusible_host(s), we need to output that information so that our Ansible stack can read it.

output "inventory_tofu" {
  value = [
    module.host_tofu_dev.spec
  ]
  sensitive = true
}

The output inventory_tofu is a list of the tofusible_host(s), specifically pointed at the spec attribute. This attribute simply outputs all the necessary information in a format the dynamic inventory can read.

The output is also sensitive because, depending on how you handle your credentials, it could contain things like private keys. However, the current example does not have anything sensitive.

Step 5: Pass the output to the Ansible stack

Now that we have our tofusible_host information as an output, we need to pass that information to our Ansible stack. We can do this using the Spacelift stack dependency feature.

Again, I won’t go into the details of setting up a stack dependency. Please check out our docs for more information; there are only two important parts about this stack dependency.

  1. Your Ansible stack must be dependent on the OpenTofu stack. This way, when we trigger OpenTofu, it will automatically trigger the Ansible stack after it has created the virtual machine(s).
  2. The output of the OpenTofu stack is passed to the Ansible stack using an output reference.

You can see in the screenshot below that I’m selecting the inventory_tofu output from the OpenTofu stack and passing it to the Ansible stack as TOFUSIBLE_INVENTORY.

Step 6: Set up your Ansible playbook

Now that we have hosts created in OpenTofu and passing them into the Ansible stack, we need to set up our Ansible playbook to use the dynamic inventory. This requires a few configuration steps, but these are basically set-it-and-forget-it. You should only need to do this once.

Step 6.1: Install the Tofusible dynamic inventory plugin

Okay, install is a harsh word. It should be more like “copy the plugin to your project.”

In your Ansible project, create a new directory called inventory_plugins and download this file into that directory. That is the actual Tofusible Dynamic Inventory Plugin; it’s where the magic happens.

Step 6.2: Configure the dynamic inventory

Remember when I said at the beginning of this post that different dynamic inventories have different ways of configuring them? Well, the Tofusible dynamic inventory is no different.

In your project root, create a tofusible.yml file. Something to note here: dynamic inventories are usually configured in a yaml file, but the name of that file depends on the dynamic inventory you are using.

For the Tofusible dynamic inventory, it must be named tofusible.yml.

Inside the YAML file, provide the following configuration:

# This is the dynamic inventory plugin name, it should be tofusible always.
plugin: tofusible

# This is the name of the input reference you created in the stack dependency.
environment_variables:
  - TOFUSIBLE_INVENTORY

The plugin attribute should always be tofusible, every dynamic inventory will tell you what plugin name to use. The environment_variables attribute is the name of the input reference you created in the stack dependency. This configuration is specific to the Tofusible dynamic inventory plugin. You can also have multiple environment_variables if you have multiple input references in your stack dependency.

What this file is telling us is what environment variable(s) the dynamic inventory should read from to get its configuration data.

Step 6.3: Create your playbook

Now that we have our dynamic inventory setup, we can create our playbook.

In your project root, create a new file called playbook.yml.

---
- name: Copy my awesome file to my even more awesome servers

   # This playbook will run on all the hosts in the `tofu` group.
  hosts: tofu
  become: yes
  tasks:
     - name: Create File On Server
       ansible.builtin.copy:
          dest: /file.txt
          content: |
             Hello From Spacelift!
             If this file is present, the playbook ran successfully!
          mode: '0644'

This playbook is very simple. It will just copy a file onto a host for demonstrative purposes.

The hosts attribute tells Ansible what group of hosts you want to run this playbook against.

Remember the groups above (step 3) in our OpenTofu module? That’s what it’s keying off of. Anything you add to the tofu group in your OpenTofu stack will have this playbook run against it.

Step 6.4: Final touches

Now that we have all the OpenTofu and Ansible things configured, we need to make a few configurations on the Spacelift side to get the whole show up and running.

In your Ansible stack:

  1. Add the environment variable ANSIBLE_INVENTORY=tofusible.yml. This will tell Ansible to use the Tofusible dynamic inventory plugin.
  2. Add chmod 644 tofusible.yml as a hook both before init and before apply. This will make sure the tofusible.yml file is readable by Ansible.
  3. If you have any private SSH keys, they should also be chmod 600‘d in the same hooks. This will make sure SSH can use the private key.

Step 7: Run your playbook

Now that everything is set up, we can run our playbook. In your OpenTofu stack, click the Trigger button.

  1. You should see it create the virtual machine and then output that information in OpenTofu:
run OpenTofu stack
  1. Once the server is up and running, you should see the OpenTofu stack finish and then automatically trigger the Ansible stack. During this process, Spacelift will also pass the output from OpenTofu to the Ansible stack for you.
  2. You should see Ansible run the playbook, and during the Planning phase, you should see it uses the inventory plugin tofusible to process an inventory source (our dynamic inventory 🎉)
  1. You should see the Ansible run connect to all the virtual machines you have set up in the group you ran your playbook against.

And that’s it! You have successfully set up a dynamic inventory using OpenTofu and Ansible with Spacelift.

Key points

Dynamic inventories are a powerful tool in the Ansible arsenal. They allow you to configure hosts that are created on the fly without needing to know their hostname, ssh key, passwords, etc, in advance. This blog post went over how to set up a custom dynamic inventory using OpenTofu and Ansible with Spacelift, but you can take these concepts and apply them to any dynamic inventory you want to use.

Read more about Tofusible, how it works, and each of the components in its repository.

Manage Ansible Better with Spacelift

Managing large-scale playbook execution is hard. Spacelift enables you to automate Ansible playbook execution with visibility and control over resources, and seamlessly link provisioning and configuration workflows.

Learn more

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide