Best way currently to create an ansible inventory from terraform

Viewed 30243

I have a long list of machines, all of which are a little different in functionality in a system. I'd like to organize these machines and add to a hosts inventory file automatically so I can run ansible and manage inventory. Are there good solutions out there for this?

I think ansible hosts should looks something like...

[webservers]
someip
someip
[integration]
someip
someip

etc..

After asking the question, I currently am researching output vars and using those to render a template from a file.

7 Answers

Since Terraform 0.12+ there is templatefile function that comes handy, especially if you need to populate host groups:

# generate inventory file for Ansible
resource "local_file" "hosts_cfg" {
  content = templatefile("${path.module}/templates/hosts.tpl",
    {
      kafka_processors = aws_instance.kafka_processor.*.public_ip
      test_clients = aws_instance.test_client.*.public_ip
    }
  )
  filename = "../ansible/inventory/hosts.cfg"
}

Where hosts.tpl template file (future Ansible inventory) may look like this:

[kafka_broker_hosts]
%{ for ip in kafka_processors ~}
${ip}
%{ endfor ~}

[test_client_hosts]
%{ for ip in test_clients ~}
${ip}
%{ endfor ~}

End result:

[kafka_broker_hosts]
18.224.140.239
18.224.140.234

[test_client_hosts]
3.21.134.83

For multi severs:

File inventory.tf

data  "template_file" "k8s" {
    template = "${file("./templates/k8s.tpl")}"
    vars {
        k8s_master_name = "${join("\n", azurerm_virtual_machine.k8s-master.*.name)}"
    }
}

resource "local_file" "k8s_file" {
  content  = "${data.template_file.k8s.rendered}"
  filename = "./inventory/k8s-host"
}

File k8s.tpl

[kube-master]
${k8s_master_name}

final result

[kube-master]
k8s-master-01
k8s-master-02
k8s-master-03

My approach: from template to inventory file, use template_file to render the content and use local_file to output a file.

template file:

## file inventory.tpl

[frontend]
${bastion_pub_ip}

[all:vars]
ansible_ssh_private_key_file = ${key_path}
ansible_ssh_user = ubuntu

render and output:

## file inventory.tf

data "template_file" "inventory" {
    template = "${file("./test/inventory.tpl")}"

    vars {
       bastion_pub_ip = "${element(azurerm_public_ip.bastion.*.ip_address, count.index)}"
       key_path = "~/.ssh/id_rsa"
    }
}

resource "local_file" "save_inventory" {
  content  = "${data.template_file.inventory.rendered}"
  filename = "./myhost"
}

It works for single server, if you have a list, I don't find a proper way to do it.

This worked for me:

data "template_file" "ansible_inventory" {
  template = "${file("${path.module}/hosts.tmpl")}"

  vars = {
    public_ips = "${join("\n", aws_instance.public_instance.*.public_ip)}"
  }
}

resource "local_file" "hosts" {
  filename = "${path.module}/hosts"

  content = data.template_file.ansible_inventory.rendered
}

This worked for me on aws ec2:

main.tf:

resource "aws_instance" "instance" {
  for_each      = toset(["ingress-01", "node-01", "node-02", "master-01" ])
  ami           = "ami-0c239ecd40dcc174c"
  instance_type = "t2.micro"

  tags = {
    Name = "${each.key}"
  }
}

resource "local_file" "inventory" {
  content = templatefile("inventory.tmpl", { content = tomap({
    for instance in aws_instance.instance:
      instance.tags.Name => instance.public_dns
    })
  })
  filename = format("%s/%s", abspath(path.root), "inventory.yaml")
}

template (inventory.tmpl):

all:
  children:
    ingress:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("ingress", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}
    master:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("master", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}
    nodes:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("node", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}

cat inventory.yaml

all:
  children:
    ingress:
      hosts:
        ingress-01:
          ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
    master:
      hosts:
        master-01:
          ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
    nodes:
      hosts:
        node-01:
           ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
        node-02:
           ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
Related