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!