Parsing Bash Array in Yaml

Viewed 431

In ansible playbook, I have to read one yaml file which contains the data like:

john: stu001
bob: stu002
william: stu003

This data is dynamic, there can be number of students with their ids. I want to read this yaml in my ansible play and needs the output like :

---
- name: Students Data
  hosts: localhost
  tasks:
  - include_vars:
        file: tmp/stuData.yml
        name: stuName
  - name: "Students Details"
    uri:
      url: "{{ some_api_server }}"
      return_content: yes
      body_format: json
      method: POST
      body:
         matchers:
           - name: john
             id: stu001
             age: NA
           - name: bob
             id: stu002
             age: NA
           - name: william
             id: stu003
             age: NA
         startsAt: "{{ st_time }}"
         endsAt: "{{ en_time }}"
         createdBy: abc@example.com

I have tried the following code for it:

---
- name: Students Data
  hosts: localhost
  tasks:
  - include_vars:
        file: tmp/stuData.yml
        stuName: stuName
  - name: "Students Details"
    uri:
      url: "{{ some_api_server }}"
      return_content: yes
      body_format: json
      method: POST
      body:
         matchers:
         {% for k,v in stuName.items() %}
           - name: {{ k }}
             id: {{ v }}
             age: "NA"   
         {% endfor %}
         startsAt: "{{ st_time }}"
         endsAt: "{{ en_time }}"
         createdBy: abc@example.com

This is giving me syntax error in line after matchers. Can anyone please help me in writing the correct code.

2 Answers

Create a simple script that writes the hashes, e.g.

shell> cat /tmp/xyz.sh 
#!/bin/bash
declare -A animals=(["moo"]="cow" ["woof"]="dog")
for i in "${!animals[@]}"; do
    echo "$i: ${animals[$i]}"
done

gives

shell> /tmp/xyz.sh 
woof: dog
moo: cow

Then the task

    - set_fact:
        xyz: "{{ lookup('pipe', '/tmp/xyz.sh')|from_yaml }}"

creates the dictionary

  xyz:
    moo: cow
    woof: dog

Now you can format the data to any structure, e.g.

    - set_fact:
        matchers: "{{ xyz|dict2items }}"

gives the list matchers

  matchers:
  - key: woof
    value: dog
  - key: moo
    value: cow

You can change the names of the attributes, e.g.

    - set_fact:
        matchers: "{{ xyz|dict2items(key_name='name', value_name='value') }}"

gives

  matchers:
  - name: woof
    value: dog
  - name: moo
    value: cow

Then, you can add the attribute name and put it into a list, e.g.

    - set_fact:
        _list: "{{ [{'name': 'xyz', 'matchers': matchers}] }}"

gives the structure you're looking for

  _list:
  - matchers:
    - key: woof
      value: dog
    - key: moo
      value: cow
    name: xyz

Q: "Pass this animal variable to yaml file"

A: Use copy, e.g.

    - copy:
        dest: /tmp/xyz.yml
        content: |
          - name: xyz
            matchers:
          {% for k,v in xyz.items() %}
              - name: {{ k }}
                value: {{ v }}
          {% endfor %}

gives

shell> cat /tmp/xyz.yml 
- name: xyz
  matchers:
    - name: woof
      value: dog
    - name: moo
      value: cow

, or you can use the created list, e.g.

    - copy:
        dest: /tmp/xyz.yml
        content: "{{ _list|to_yaml }}"

gives

shell> cat /tmp/xyz.yml 
- matchers:
  - {key: woof, value: dog}
  - {key: moo, value: cow}
  name: xyz

, or you can improve the format by the filter to_nice_yaml, e.g.

    - copy:
        dest: /tmp/xyz.yml
        content: "{{ _list|to_nice_yaml }}"

gives

shell> cat /tmp/xyz.yml 
-   matchers:
    -   key: woof
        value: dog
    -   key: moo
        value: cow
    name: xyz

Q: "Flow of the code is like firstly sh file will be executed that will call play."

A: There is practically no difference. Read the file instead of running the command in the pipe lookup, e.g.

shell> /tmp/xyz.sh > /tmp/xyz.txt
shell> cat /tmp/xyz.txt
woof: dog
moo: cow

Then the task

    - set_fact:
        xyz: "{{ lookup('file', '/tmp/xyz.txt')|from_yaml }}"

creates the same dictionary

  xyz:
    moo: cow
    woof: dog

Here is a way to pass it "directly" from bash. I doubt it will really be of any use but it was fun to search

$ ansible localhost -m debug -a var=animals \
    -e "{\"animals\":{$(for k in ${!animals[@]}; do echo \"$k\":\"${animals[$k]}\",; done)}}"
localhost | SUCCESS => {
    "animals": {
        "moo": "cow",
        "woof": "dog"
    }
}

Note that you can probably create a bash script or function to ease that processing, but I didn't try myself and I'll let you do it if you want to walk that path.

Related