Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify JSON in Ansible

I have a management system where we define maintenance data to control virtual environment, and one of the options is VM shutdown timeframe for different teams. Now when new VM is created user should select from the available list of timeframes when his/her VM can be shut down without interruption to work shift. I need to be able to sync this list of timeframes from my database to job template survey. I'm stuck in modifying the JSON survey. I've tried this post best way to modify json in ansible but getting an error:

"exception": " File \"/tmp/ansible_1qa8eR/ansible_module_json_modify.py\", line 38, in main\n res = jsonpointer.resolve_pointer(data, pointer)\n File \"/usr/lib/python2.7/site-packages/jsonpointer.py\", line 126, in resolve_pointer\n return pointer.resolve(doc, default)\n File \"/usr/lib/python2.7/site-packages/jsonpointer.py\", line 204, in resolve\n doc = self.walk(doc, part)\n File \"/usr/lib/python2.7/site-packages/jsonpointer.py\", line 279, in walk\n raise JsonPointerException(\"member '%s' not found in %s\" % (part, doc))\n", "msg": "member 'spec' not found in {'stderr_lines': [], 'changed': True, 'end'

Here is my JSON that I'm trying to modify:

{
  "spec": [
    {
      "question_description": "", 
      "min": 0, 
      "default": "Test text", 
      "max": 4096, 
      "required": true, 
      "choices": "", 
      "variable": "_t", 
      "question_name": "Note", 
      "type": "textarea"
    }, 
    {
      "required": true, 
      "min": null, 
      "default": "", 
      "max": null, 
      "question_description": "appliance id", 
      "choices": "Unconfigured\n600,qvmProcessor/applianceexemptions,all", 
      "new_question": true, 
      "variable": "appid", 
      "question_name": "Appliance ID", 
      "type": "multiplechoice"
    }, 
    {
      "required": true, 
      "min": null, 
      "default": "", 
      "max": null, 
      "question_description": "Select version", 
      "choices": "1.2.3\n1.2.4\n1.2.5", 
      "new_question": true, 
      "variable": "version", 
      "question_name": "App Version", 
      "type": "multiplechoice"
    }, 
    {
      "required": true, 
      "min": 0, 
      "default": "", 
      "max": 1024, 
      "question_description": "", 
      "choices": "", 
      "new_question": true, 
      "variable": "newVMIP", 
      "question_name": "IP for new VM", 
      "type": "text"
    }, 
    {
      "required": true, 
      "min": 0, 
      "default": "", 
      "max": 1024, 
      "question_description": "", 
      "choices": "", 
      "new_question": true, 
      "variable": "requesterEmail", 
      "question_name": "Requester's email", 
      "type": "text"
    }, 
    {
      "required": true, 
      "min": null, 
      "default": "", 
      "max": null, 
      "question_description": "Select the timeframe for automatic VM shutdown. ***NOTE*** EST Time is in 24 hour format", 
      "choices": "23:00-02:00\n02:00-04:00\n04:00-06:00\n00:00-02:00", 
      "new_question": true, 
      "variable": "powerOFF_TimeFrame", 
      "question_name": "Power OFF window", 
      "type": "multiplechoice"
    }, 
    {
      "required": true, 
      "min": 0, 
      "default": 5, 
      "max": 30, 
      "question_description": "The VM will be deleted after # of days specified (default=5).", 
      "choices": "", 
      "new_question": true, 
      "variable": "vmNumReservedDays", 
      "question_name": "Keep VM for # of days", 
      "type": "integer"
    }
  ], 
  "description": "", 
  "name": ""
}

I have to update timeframes (the one before last) choices:

"choices": "23:00-02:00\n02:00-04:00\n04:00-06:00\n00:00-02:00",

Here is my code. I could read directly to variable but for now I'm just saving JSON to the file:

- name: Sync Power Schedules From Database to Survey Spec
  hosts: awxroot
  gather_facts: no

  vars:
    new_choices: {}

  tasks:

    - name: Set shared directory name
      set_fact: 
        sharedDataPath: /var/tmp/survey

    - name: Set shared file path name
      set_fact: 
        sharedDataPathFile: "{{sharedDataPath}}/s.json"

    - name: Create directory to share data
      file:
        path: "{{ sharedDataPath }}"
        state: directory

    - name: Load Survey Spec to file
      shell: 'tower-cli job_template survey 70 > "{{ sharedDataPathFile }}"'



    - name: Make sure the survey spec file exists
      stat:
        path: "{{ sharedDataPathFile }}"
      register: isFileExists

    - name: Fail if file is not there
      fail:
        msg: "Cannot find survey spec exported file"
      when: isFileExists == False

    - name: Read exception file to a variable
      command: cat "{{ sharedDataPathFile }}"
      register: surveySpec
      when: isFileExists.stat.exists == True


    - name: Setting key
      set_fact:
        choices_key: "choices"

    - name: Setting new values
      set_fact:
        choices_value: "23:00-02:00\n02:00-04:00\n04:00-06:00\n00:00-04:00"

    - name: Create dictionary
      set_fact:
        new_choices: "{{ new_choices | combine({choices_key: choices_value}) }}"


    - json_modify:
        data: "{{ surveySpec }}"
        pointer: "/spec/6/choices"
        action: update
        update: "{{new_choices}}"
      register: result

    - debug:
        var: result.result
like image 961
larrybg Avatar asked Nov 29 '18 15:11

larrybg


2 Answers

Not a direct answer to your question about the error with modify_json but a working solution.

I would go with jq for that. jq is a lightweight and flexible command-line JSON processor and available for nearly every Linux distribution. When not, use the prebuilt binaries that come without dependencies.

As the website states:

jq is like sed for JSON data - you can use it to slice and filter and map and transform structured data with the same ease that sed, awk, grep and friends let you play with text.

I shrinked your play to a minimal working solution with the same result. The jq executable must be in the PATH of the system it is running on. Feel free to customize it to your needs.

---
- name: Sync Power Schedules From Database to Survey Spec
  hosts: localhost
  gather_facts: no

  vars:
    choices_key: ".spec[6].choices"
    choices_value: "23:00-02:00\n02:00-04:00\n04:00-06:00\n00:00-04:00"
    json_file: "{{playbook_dir}}/s.json"

  tasks:

  - name: "modify json"
    command: >
      jq "{{choices_key}}=\"{{choices_value}}\"" "{{json_file}}"
    register: json

  - debug:
      var: json.stdout

I think this is more elegant as the solution with the extra json_modify.py module. For more information on jq please see the manual page.

like image 152
JGK Avatar answered Nov 19 '22 13:11

JGK


There's just a couple of minor issues in your playbook

  1. You are loading the json data from the file as a string, rather than json/dict. Instead of your command/cat task, use this to load the data in:

     - set_fact:
         surveySpec: "{{ lookup('file', sharedDataPathFile) | from_json }}"
    
  2. The other problem you would have hit is you are attempting to update just the choices value (i.e. a string), rather than the choices dict item. Just a small change required in your json_modify pointer:

     - json_modify:
         data: "{{ surveySpec }}"
         pointer: "/spec/5"
         action: update
         update: "{{ new_choices }}"
       register: result
    

One other thing .. the array index is 5, not 6

like image 40
Matt P Avatar answered Nov 19 '22 12:11

Matt P