I just had to work with JSON in Ansible, and iterate through some data. It took me a few efforts of debugs before I got it, so putting it here for posterity, in hopes to help someone else.
Eventually, my Ansible playbook will be making a RESTful call to pull the data. So, to start, I put the example JSON data the call would get into a file. I called the file “data.json” and put it in the same directory as my playbook.
.
├── data.json
└── json_iter.yml
0 directories, 2 files
My example data.json looks like this:
{"webInfLib":[{"contextRoot":"Application1"},{"contextRoot":"Application2"},{"contextRoot":"Application3"}]}
So, I wanted to iterate the data for each “contextRoot” and do something with each application. First, I loaded the JSON into a var using the ‘lookup’ file plugin and used the “from_json” filter to bring it in as a workable object:
tmpdata: "{{ lookup('file','data.json')|from_json }}"
Then, using that variable, I iterated each one using the “with_items” loop directive:
debug: var=item.contextRoot
with_items: "{{ tmpdata.webInfLib }}"
The full test playbook looks like this:
---
- hosts: localhost
vars:
tmpdata: "{{ lookup('file','data.json')|from_json }}"
tasks:
- name: Iterate JSON
debug: var=item.contextRoot
with_items: "{{ tmpdata.webInfLib }}"
So, now that we have our list of data, we will want to store it for future use later, right? I mean, what good is getting this data if we don’t actually do anything with it? So, let’s add this list of items to a fact. We will be using the set_fact module, as well as using a couple of filters. Notably, the “map” and “list” filters.
So, let’s go back to our playbook, and instead of debugging we will be adding it to a fact.
tasks:
- name: Iterate JSON
set_fact:
app_item: "{{ item.contextRoot }}"
with_items: "{{ tmpdata.webInfLib }}"
Now, if you actually run this, you will notice a minor inconvenience! We are replacing the previous item in the “app_item” fact variable, with the last iteration of item! Well, that’s not good! We need a list of items in a single fact! So, let’s do that. The first thing we will want to do, is grab the output of the entire set_fact module (which includes each iteration). We will do that in a "“register”" directive.
- name: Iterate JSON
set_fact:
app_item: "{{ item.contextRoot }}"
with_items: "{{ tmpdata.webInfLib }}"
register: app_result
Excellent! Now, let’s move on to the next step. We have the output of all the iterations for set_fact. If you don’t believe me, go ahead and debug the variable “app_result” to see what it includes. Go ahead. It will help you understand what I’m doing below.
Now, comes the really cool part with filters. I hope you can follow along, as it can kinda get complex if you’ve never used them. First off, we are going to take the output from set_fact, and pull out only the attribute “app_item”, since that’s where the data we want resides. If you count, using my example json, you will see there are 3 iterations of “app_item” under the key “ansible_facts”
So, I’m going to extract the data following the attribute “app_item” from the results object of the registered variable “app_result”.
- name: Create Fact List
set_fact:
apps: "{{ app_result.results | map(attribute='ansible_facts.app_item') | list }}"
So, what I’m doing is pulling out the data which the attribute “ansible_facts.app_item” is located inside the registered variable “app_result.results”. Now that I have all of the data, I’m sending it to the “list” filter, to create a list, and put THAT in the apps fact.
Finally, the playbook task:
- name: Print Fact
debug: var=apps
Will give me a list of objects, to be used in a module:
TASK [Print Fact] **************************************************************
ok: [localhost] => {
"apps": [
"Application1",
"Application2",
"Application3"
]
}
Ansiblized! Happy Automating!