33

I'm using Ansible to automate some configuration steps for my application VM, but having difficult to insert a new key-value to an existing json file on the remote host.

Say I have this json file:

{
  "foo": "bar"
}

And I want to insert a new key value pair to make the file become:

{
  "foo": "bar",
  "hello": "world"
}

Since json format is not line based, I'm excluding lineinfile module from my options. Also, I would prefer not to use any external modules. Google keeps giving me examples to show how to read json file, but nothing about change json values and write them back to file. Really appreciate your help please!

fengye87
  • 2,433
  • 4
  • 24
  • 41

4 Answers4

46

since the file is of json format, you could import the file to a variable, append the extra key:value pairs you want, and then write back to the filesystem.

here is a way to do it:

---
- hosts: localhost
  connection: local
  gather_facts: false
  vars:

  tasks:
  - name: load var from file
    include_vars:
      file: /tmp/var.json
      name: imported_var

  - debug:
      var: imported_var

  - name: append more key/values
    set_fact:
      imported_var: "{{ imported_var | default([]) | combine({ 'hello': 'world' }) }}"

  - debug:
      var: imported_var

  - name: write var to file
    copy: 
      content: "{{ imported_var | to_nice_json }}" 
      dest: /tmp/final.json

UPDATE:

as OP updated, the code should work towards remote host, in this case we cant use included_vars or lookups. We could use the slurp module.

NEW code for remote hosts:

---
- hosts: greenhat
  # connection: local
  gather_facts: false
  vars:

  tasks:
  - name: load var from file
    slurp:
      src: /tmp/var.json
    register: imported_var

  - debug:
      msg: "{{ imported_var.content|b64decode|from_json }}"

  - name: append more key/values
    set_fact:
      imported_var: "{{ imported_var.content|b64decode|from_json | default([]) | combine({ 'hello': 'world' }) }}"

  - debug:
      var: imported_var

  - name: write var to file
    copy: 
      content: "{{ imported_var | to_nice_json }}" 
      dest: /tmp/final.json

hope it helps

ilias-sp
  • 6,135
  • 4
  • 28
  • 41
  • My bad, I should have mentioned that the json file is on a remote host – fengye87 Jun 11 '18 at 11:52
  • you can run the play towards a remote host too. just remove the `connection: local` line from my playbook and replace the host with your own. also copy the actual var file to `/tmp/var.json` just for this test. file will be created as `/tmp/final.json` – ilias-sp Jun 11 '18 at 12:01
  • 1
    looks like ansible still look for the local path after removing `connection: local`, it says `fatal: [192.168.63.181]: FAILED! => {"ansible_facts": {"imported_var": {}}, "ansible_included_var_files": [], "changed": false, "message": "Could not find or access '/tmp/var.json'"}` – fengye87 Jun 11 '18 at 12:35
  • you are right, i overlooked that because the file existed on my localhost as well when i tested the playbook towards remote host. please see updated answer, with `slurp` module – ilias-sp Jun 11 '18 at 12:49
  • Can this be done after a specific line? – user2066480 Mar 11 '22 at 23:53
  • @user2066480 havent investigated it much, but since dicts are unordered i would look first into manipulating the file itself – ilias-sp Mar 12 '22 at 11:14
3

ilias-sp's solution is great!

In my case, it lacked the case when we may have to create a base json file. So I had to add this task in the beginning of the play:

- name: Ensure json file exists
copy:
  content: "{}"
  dest: /tmp/var.json
  force: false
Herve Nicol
  • 101
  • 6
3

For people who are OK with custom ansible modules: https://github.com/ParticleDecay/ansible-jsonpatch works great!

With this you can simply do:

- name: append key/values
  json_patch:
    src: /tmp/var.json
    operations:
      - op: add
        path: "/hello"
        value: "world"
    pretty: yes
    create: yes
juge
  • 31
  • 2
0
 - name: update log
    copy:
      content: "{{ log | to_nice_json}}"
      dest: "{{ log_file }}"
    vars:
      log: "{{ (lookup('file', log_file) | from_json) + ([{'job': (build_id if build_id != '' else 'dev'), 'keystore': ks, 'timestamp': ansible_date_time.iso8601}]) }}"
      log_file: log/log.json
      build_id: "{{ lookup('ENV', 'BUILD_ID') }}"
    tags: log