Vagrant Ansible Provisioner: working with the Ansible Inventory – addendum

Recently I wrote a post about one of my dream combinations, Ansible and Vagrant. After hitting the publish button I noticed that there might be a need for a part II – passing complex data types such as lists and dicts to Ansible via a Vagrantfile.

I wrote a similar post for when you are in a situation where you invoke an Ansible playbook directly from the command line. For this article the invocation of the Ansible playbook happens as part of a call to vagrant up or vagrant provision.

Setup

I’m going to reuse the Vagrantfile from the previous article:

Vagrant.configure("2") do |config|
  
  config.vm.box = "debianbase"
  config.vm.hostname = "debian"

  config.ssh.private_key_path = "/home/martin/.ssh/debianbase"

  config.vm.provider "virtualbox" do |vb|
    vb.vcpus = 2
    vb.memory = "1024"
    vb.name = "debian"
  end

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/example01.yml"
    ansible.verbose = "v"
    # ...
  end
end

The directory/file layout is also identical, repeated here for convenience:

$ tree provisioning/
provisioning/
├── example01.yml
├── example02.yml
├── group_vars
│   └── all.yml
└── roles
    └── role1
        └── tasks
            └── main.yml

I used Ubuntu 22.04, patched to 230306 with both Ansible and Vagrant versions as provided by the distribution:

  • Ansible 2.10.8
  • Vagrant 2.2.19

Passing lists to the Ansible playbook

This time however I’d like to pass a list to the playbook indicating which block devices to partition. The type of variable is a list, with either 1 or more elements. The Ansible code iterates over the list and performs the action on the current item. Here’s the code from the playbook example01.yml:

- hosts: default

  tasks: 
  - ansible.builtin.debug:
      var: blkdevs

  - name: print block devices to be partitioned
    ansible.builtin.debug:
      msg: If this was a call to community.general.parted I'd partition {{ item }} now
    loop: "{{ blkdevs }}"

The question is: how can I pass a list to the playbook? As with scalar data types I wrote about yesterday you use host_vars in the Vagrantfile:

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/example01.yml"
    ansible.verbose = "v"
    ansible.host_vars = {
      "default" => {
        "blkdevs" => '[ "/dev/sdb", "/dev/sdc" ]'
      }
    }
  end

Note the use of single and double quotes! Without quotes around the entire RHS expression Ansible will complain about a syntax error in the dynamically generated inventory. The provisioner does what it’s supposed to do:

PLAY [default] *****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [default]

TASK [ansible.builtin.debug] ***************************************************
ok: [default] => {
    "blkdevs": [
        "/dev/sdb",
        "/dev/sdc"
    ]
}

TASK [print block devices to be partitioned] ***********************************
ok: [default] => (item=/dev/sdb) => {
    "msg": "If this was a call to community.general.parted I'd partition /dev/sdb now"
}
ok: [default] => (item=/dev/sdc) => {
    "msg": "If this was a call to community.general.parted I'd partition /dev/sdc now"
}

PLAY RECAP *********************************************************************
default                    : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Passing Dicts to the Ansible playbook

Passing a dict works exactly the same way, which is why I feel like I can keep this section short. The Vagrantfile uses the same host_var, blkdevs, but this time it’s a dict with keys indicating the intended use of the block devices. Each key is associated with a list of values containing the actual block device(s). Lists are perfectly fine even if they only contain a single item ;)

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/example02.yml"
    ansible.verbose = "v"
    ansible.host_vars = {
      "default" => {
        "blkdevs" => 
          '{ "binaries": ["/dev/sdb"], "database": ["/dev/sdc", "/dev/sdd"], "fast_recovery_area": ["/dev/sde"] }'
      }
    }
  end

The playbook iterates over the list of block devices provided as the dict’s values:

- hosts: default
  become: true

  tasks: 
  - name: format block devices for Oracle binaries
    ansible.builtin.debug:
      msg: If this was a call to community.general.parted I'd partition {{ item }} now
    loop: "{{ blkdevs.binaries }}"
  
  - name: format block devices for Oracle database files
    ansible.builtin.debug:
      msg: If this was a call to community.general.parted I'd partition {{ item }} now
    loop: "{{ blkdevs.database }}"
  
  - name: format block devices for Oracle database Fast Recovery Area
    ansible.builtin.debug:
      msg: If this was a call to community.general.parted I'd partition {{ item }} now
    loop: "{{ blkdevs.fast_recovery_area }}"

Using lists as the dict’s values solves the problem of having to distinguish between a scalar variable like /dev/sdc and multiple block devices like /dev/sdc, /dev/sdd to be used.

Et voila! Here’s the result:

PLAY [default] *****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [default]

TASK [format block devices for Oracle binaries] ********************************
ok: [default] => (item=/dev/sdb) => {
    "msg": "If this was a call to community.general.parted I'd partition /dev/sdb now"
}

TASK [format block devices for Oracle database files] **************************
ok: [default] => (item=/dev/sdc) => {
    "msg": "If this was a call to community.general.parted I'd partition /dev/sdc now"
}
ok: [default] => (item=/dev/sdd) => {
    "msg": "If this was a call to community.general.parted I'd partition /dev/sdd now"
}

TASK [format block devices for Oracle database Fast Recovery Area] *************
ok: [default] => (item=/dev/sde) => {
    "msg": "If this was a call to community.general.parted I'd partition /dev/sde now"
}

PLAY RECAP *********************************************************************
default                    : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Happy automating!

Advertisement