Provision a JSON file with Ansible while keeping indentation - json

I want to provision a JSON file with Ansible. The content of this file is a variable in my Ansible's playbook.
And very important for my usecase: I need the indentation & line breaks to be exactly the same as in my variable.
The variable looks like this :
my_ansible_var:
{
"foobar": {
"foo": "bar"
},
"barfoo": {
"bar": "foo"
}
}
And it's use like this in my playbook :
- name: drop the gitlab-secrets.json file
copy:
content: "{{ my_ansible_var }}"
dest: "/some/where/file.json"
Problem: when this tasks is played, my file is provisionned but as a "one-line" file:
{ "foobar": { "foo": "bar" }, "barfoo": { "bar": "foo" } }
I tried several other ways:
Retrieve the base64 value of my JSON content, and use content: "{{ my_ansible_var | b64decode }}" : same problem at the end
I tried playing with YAML block indicator : none of the block indicators helped me with that problem
I tried adding some filters like to_json, to_nice_json(indent=2) : no more luck here
Question:
How in Ansible can I provison a JSON file while keeping the exact indentation I want ?

In your example my_ansible_var is a dict. If you don't need to access its keys in your playbook (e.g. my_ansible_var.foobar.foo) and just want it as JSON string for your copy task, force it to be a string.
There is type-detection feature in Ansible template engine, so if you feed it with dict-like or list-like string, it will be evaluated into object. See some details here.
This construction will work ok for your case:
---
- hosts: localhost
gather_facts: no
vars:
my_ansible_var: |
{
"foobar": {
"foo": "bar"
},
"barfoo": {
"bar": "foo"
}
}
tasks:
- copy:
content: "{{ my_ansible_var | string }}"
dest: /tmp/out.json
Note vertical bar in my_ansible_var definition and | string filter in content expression.

Related

Combining JSON items using JMESPath and/or Ansible

I have an Ansible playbook that queries a device inventory API and gets back a JSON result that contains a lot of records following this format:
{
"service_level": "Test",
"tags": [
"Application:MyApp1"
],
"fqdn": "matestsvcapp1.vipcustomers.com",
"ip": "172.20.11.237",
"name": "matestsvcapp1.vipcustomers.com"
}
I then loop through these ansible tasks to query the JSON result for each of the IP addresses I care about:
- name: Set JMESQuery
set_fact:
jmesquery: "Devices[?ip_addresses[?ip.contains(#,'{{ ip_to_query }}' )]].{ip: '{{ ip_to_query }}', tags: tags[], , service_level: service_level }"
- name: Store values
set_fact:
inven_results: "{{ (inven_results| default([])) + (existing_device_info.json | to_json | from_json | json_query(jmesquery)) }}"
I then go on to do other tasks in ansible, pushing this data into other systems, and everything works fine.
However, I just got a request from management that they would like to see the 'service level' represented as a tag in some of the systems I push this data into. Therefore I need to combine the 'tags' and 'service_level' items resulting in something that looks like this:
{
"tags": [
"Application:MyApp1",
"service_level:Test"
],
"fqdn": "matestsvcapp1.vipcustomers.com",
"ip": "172.20.11.237",
"name": "matestsvcapp1.vipcustomers.com"
}
I've tried modifying the JMESPath query to join the results together using the join function, and tried doing it the 'ansible' way, using the combine or map, but I couldn't get either of those to work either.
Any thoughts on the correct way to handle this? Thanks in advance!
Note: 'tags' is a list of strings, and even though it's written in key:value format, it's really just a string.
to add two arrays you use the + operator like this:
ansible localhost -m debug -a 'msg="{{ b + ["String3"] }}"' -e '{"b":["String1", "String2"]}'
result:
localhost | SUCCESS => {
"msg": [
"String1",
"String2",
"String3"
]
}
So if i take your json code as test.json you could run
ansible localhost -m debug -a 'msg="{{ tags + ["service_level:" ~ service_level ] }}"' -e #test.json
Result:
localhost | SUCCESS => {
"msg": [
"Application:MyApp1",
"service_level:Test"
]
}
With this knowledge you can use set_fact to put this new array in a variable for later use.

How to convert dict or list to string in ansible

play:
- set_fact:
irules: "{{ rule | json_query('[*].definition') }}"
- debug:
msg: "{{ irules }}"
output:
"msg": [
"when HTTP_REQUEST {\n switch -glob [HTTP::uri] {\n \"*HAC*\" { pool char.hr.cal.ed.ABC.pool }\n \n }\n}"
]
}
I'm expecting output to be in below format.
Expected output:
"when HTTP_REQUEST {
switch -glob [HTTP::uri] {
"*HAC*" { pool char.hr.cal.ed.ABC.pool }
}
I have tried parsing "from_json" to the above play and ended up with the error:
"({{ rule | json_query('[*].definition') | from_json }}): the JSON object must be str, bytes or ``bytearray, not 'list'"}"
is there a way to convert list to string in ansibe?
if not, any other suggestions would be appreciated to achieve "Expected output"`
Module debug of Ansible cannot print carriage return. It always escape them (converting them into \n). That's why you have many \n in your debug print msg.
The only option I know to output multiline variable is to use the pause module :
- pause:
seconds: 1
prompt: "{{ irules[0] }}"
More detailled answer here can help.

transform values of yaml hash into keys of json hash in Ansible

I'm trying to get Ansible to convert an array of hashes, into to a list of key value pairs with the keys being one of the values from the first hash and the values being a different value from the first hash.
An example will help.
I want to convert :-
TASK [k8s_cluster : Cluster create | debug result of private ec2_vpc_subnet_facts] ***
ok: [localhost] => {
"result": {
"subnets": [
{
"availability_zone": "eu-west-1c",
"subnet_id": "subnet-cccccccc",
},
{
"availability_zone": "eu-west-1a",
"subnet_id": "subnet-aaaaaaaa",
},
{
"availability_zone": "eu-west-1b",
"subnet_id": "subnet-bbbbbbbb",
}
]
}
}
into
eu-west-1a: subnet-aaaaaaaa
eu-west-1b: subnet-bbbbbbbb
eu-west-1c: subnet-cccccccc
I've tried result.subnets | map('subnet.availability_zone': 'subnets.subnet_id') (which doesn't work at all) and json_query('subnets[*].subnet_id' which simply pickes out the subnet_id values and puts them into a list.
I think I could do this with Zip and Hash in Ruby but I don't know how to make this work in Ansible, or more specifically in Jmespath.
I have generated the below list I will add a new line to the generated list(thought to share this first)
---
- name: play
hosts: localhost
tasks:
- name: play
include_vars: vars.yml
- name: debug
debug:
msg: "{% for each in subnets %}{{ each.availability_zone }}:{{ each.subnet_id }}{% raw %},{% endraw %}{% endfor %}"
output --->
ok: [localhost] => {
"msg": "eu-west-1c:subnet-cccccccc,eu-west-1a:subnet-aaaaaaaa,eu-west-1b:subnet-bbbbbbbb,"
}
Jmespath does not allow to use dynamic names in multi select hashes. I have found an extension to jmespath allowing to do such thing by using key references, but it is not part of the plain jmespath implementation nor ansible.
To do this in plain ansible, you will have to create a new variable and populate it with a loop. There might be other ways using other filters but this is the solution I came up with:
- name: Create the expected hash
set_fact:
my_hash: >-
{{
my_hash
| default({})
| combine({ item.availability_zone: item.subnet_id })
}}
loop: "{{ subnets }}"
- name: Print result
debug:
var: my_hash

Ansible: Create variables from json string

In Ansible, is there a way to convert a dynamic list of key/value pairs that are located in a JSON variable into variable names/values that can be accessed in a Playbook without using the filesystem?
IE - If I have the following JSON in a variable (in my case, already imported from a URI call):
{
"ansible_facts": {
"list_of_passwords": {
"ansible_password": "abc123",
"ansible_user": "user123",
"blue_server_password": "def456",
"blue_server_user": "user456"
}
}
Is there a way to convert that JSON variable into the equivelant of:
vars:
ansible_password: abc123
ansible_user: user123
blue_server_password: def456
blue_server_user: user456
Normally, I'd write the variable to a file, then import it using vars_files:. Our goal is to not write the secrets to the filesystem.
You can use uri module to make a call and then register response to variable:
For example:
- uri:
url: http://www.mocky.io/v2/59667604110000040ec8f5c6
body_format: json
register: response
- debug:
msg: "{{response.json}}"
- set_fact: {"{{ item.key }}":"{{ item.val }}"}
with_dict: "{{response.json.ansible_facts.list_of_passwords}}"

Using jq to append output generated from Ansible template

I am writing Ansible play, one of the tasks is to append entry to a JSON document. E.g.
JSON document staff.json:
{
"staff":[
{
"john":[
{
"position":"techwriter"
},
{
"sex":"male"
}
]
}
]
}
I need to append this entry to staff:
{
"staff":[
{
"john":[
{
"position":"techwriter"
},
{
"sex":"male"
}
]
},
{
"jane":[
{
"position":"admin"
},
{
"sex":"female"
}
]
}
]
}
The entry would be generated from Ansible template, something like this:
{
"{{ staff_name }}":[
{
"position":"{{ staff_position }}"
},
{
"sex":"{{ staff_sex }}"
}
]
}
I've learned to use jq to append entry to the JSON document, as seen in "Add json array element with jq (cmdline)". However, I do not know how I can implement this in Ansible, as template would output to a file.
I need a solution of something like this:
cat staff.json | jq '.staff |= .+ ["OUTPUT_FROM_TEMPLATE"]'
Any ideas welcome.
You may use template lookup plugin:
- shell: cat staff.json | jq '.staff |= . + [{{ item | to_json }}]' > staff.json
with_template: person.j2
vars:
staff_name: jane
staff_position: admin
staff_sex: female
Note that you need to use to_json filter with item, because Ansible template engine converts json strings that it can evaluate into dict.