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!
You must be logged in to post a comment.