Search code examples
listdictionaryansiblekey-value

How to create a dictionary list of key: value items and append it to the original list in Ansible?


I want to create a new dictionary list, 'path: value' to be looped for each user and appended under each item in the user list. The end result would look something like below:

        "userdata": [
            {
                "ClassType": "Full Time",
                "FirstName": "Grace",
                "LastName": "Higgins",
                "path": "OU=fulltime,OU=Test OU,DC=localdemo,DC=local"
            },
            {
                "ClassType": "Part Time",
                "FirstName": "Robert",
                "LastName": "Miller",
                "path": "OU=parttime,OU=Test OU,DC=localdemo,DC=local"
            },
            {
                "ClassType": "Flexi",
                "FirstName": "Jeffrey",
                "LastName": "Keller",
                "path": "OU=flexi,OU=Test OU,DC=localdemo,DC=local"
            }
        ]

The values for path is dependent on the value of the ClassType.

However, when I tried to append the new path: value dictionary list to the original userdata list, the 'path: item.path' is in the wrong layout and also has the wrong value:

TASK [Read_csv_creation : Append to original userdata list] *******************************************************************************************
ok: [localhost] => (item={'FirstName': '}) => {
    "ansible_facts": {
        "userdata": [
            {
                "ClassType": "Full Time",
                "FirstName": "Grace",
                "LastName": "Higgins"
            },
            {
                "ClassType": "Part Time",
                "FirstName": "Robert",
                "LastName": "Miller"
            },
            {
                "ClassType": "Flexi",
                "FirstName": "Jeffrey",
                "LastName": "Keller"
            },
            "path: item.path"
        ]
    },

How can I get the final output as shown below?

        "userdata": [
            {
                "ClassType": "Full Time",
                "FirstName": "Grace",
                "LastName": "Higgins",
                "path": "OU=fulltime,OU=Test OU,DC=localdemo,DC=local"
            },
            {
                "ClassType": "Part Time",
                "FirstName": "Robert",
                "LastName": "Miller",
                "path": "OU=parttime,OU=Test OU,DC=localdemo,DC=local"
            },
            {
                "ClassType": "Flexi",
                "FirstName": "Jeffrey",
                "LastName": "Keller",
                "path": "OU=flexi,OU=Test OU,DC=localdemo,DC=local"
            }
        ]

Below is my tasks file. The variable 'data_list' would be my original list.

    - name: Read input file
      read_csv:
        path: /var/lib/awx/projects/file/creation.csv
        key: FirstName 
        fieldnames: FirstName,LastName,ClassType
        delimiter: ','
      register: userdata

    - name: Initialise data list
      set_fact:
        data_list: []

    - name: Extract the list
      set_fact:
        data_list: "{{ data_list + [{ 'FirstName': item.FirstName, 'LastName': item.LastName, 'ClassType': item.ClassType }] }}"
      loop: "{{ userdata | community.general.json_query('dict.[*][0]') }}"

    - name: Set fact for data_list
      set_fact: 
        data_list: "{{ data_list[1:] }}"

    - name: If class is full time
      set_fact:
        path: "OU=fulltime,OU=Test OU,DC=localdemo,DC=local"
      when: item.ClassType == "Full Time"  
      loop: "{{ data_list  }}"   

    - name: If employment type is part time
      set_fact:
        path: "OU=parttime,OU=Test OU,DC=localdemo,DC=local"
      when: item.ClassType == "Part Time"  
      loop: "{{ data_list  }}"   

    - name: If employment type is flexi
      set_fact:
        path: "OU=flexi,OU=Test OU,DC=localdemo,DC=local"
      when: item.ClassType == "Flexi"  
      loop: "{{ data_list  }}"   

    - name: Append to original userdata list
      set_fact:
        userdata: "{{ data_list + ['path: item.path'] }}"
      loop: "{{ data_list  }}" 

The job run output is as below. I have omitted some of it as it is very lengthy:

TASK [Read_csv_creation : Initialise data list] *******************************************************************************************************
ok: [localhost] => {
    "ansible_facts": {
        "data_list": []
    },
    "changed": false
}

TASK [Read_csv_creation : Read input file] ************************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "dict": {
        " FirstName ": {
            "ClassType": " Employment Type ",
            "FirstName": " FirstName ",
            "LastName": " Last Name "
        },
        "Grace": {
            "ClassType": "Sub Contract",
            "FirstName": "Grace",
            "LastName": "Higgins"
        },
        "Robert": {
            "ClassType": "Contract",
            "FirstName": "Robert",
            "LastName": "Miller"
        },
        "Jeffrey": {
            "ClassType": "Full Time",
            "FirstName": "Jeffrey",
            "LastName": "Keller"
        }
    },
    "invocation": {
        "module_args": {
            "delimiter": ",",
            "dialect": "excel",
            "fieldnames": [
                "FirstName",
                "LastName",
                "ClassType"
            ],
            "key": "FirstName",
            "path": "/var/lib/awx/projects/file/creation.csv",
            "skipinitialspace": null,
            "strict": null,
            "unique": true
        }
    },
    "list": []
}

TASK [Read_csv_creation : Extract the list] ***********************************************************************************************************
ok: [localhost] => (item={'FirstName': ' FirstName ', 'LastName': ' Last Name ', 'ClassType': ' Class Type '}) => {
    "ansible_facts": {
        "data_list": [
            {
                "ClassType": " Class Type ",
                "FirstName": " FirstName ",
                "LastName": " Last Name "
            }
        ]
    },
    "ansible_loop_var": "item",
    "changed": false,
    "item": {
        "ClassType": " Class Type ",
        "FirstName": " FirstName ",
        "LastName": " Last Name ",
    }
}
ok: [localhost] => (item={'FirstName': '}) => {
    "ansible_facts": {
        "data_list": [
            {
                "ClassType": " Class Type ",
                "FirstName": " FirstName ",
                "LastName": " Last Name ",

            }
        ]
    },
    "ansible_loop_var": "item",
    "changed": false,
    "item": {
        "ClassType": "Class Time",
        "FirstName": "Grace",
        "LastName": "Higgins"
    }
}


TASK [Read_csv_creation : Set fact for data_list] *****************************************************************************************************
ok: [localhost] => {
    "ansible_facts": {
        "data_list": [
            {
                "ClassType": "Full Time",
                "FirstName": "Grace",
                "LastName": "Higgins"
            },
            {
                "ClassType": "Part Time",
                "FirstName": "Robert",
                "LastName": "Miller"
            },
            {
                "EmploymentType": "Flexi",
                "FirstName": "Jeffrey",
                "LastName": "Keller"
            }
        ]
    },
    "changed": false
}


TASK [Read_csv_creation : If class type is full time] *******************************************************************************************
ok: [localhost] => (item={'FirstName': 'Grace', 'LastName': 'Higgins', 'ClassType': 'Full Time'}) => {
    "ansible_facts": {
        "path": "OU=fulltime,OU=Test OU,DC=localdemo,DC=local"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "item": {
        "ClassType": "Full Time",
        "FirstName": "Grace",
        "LastName": "Higgins"
    }
}
skipping: [localhost] => (item={'FirstName': 'Robert', 'LastName': 'Miller', 'ClassType': 'Part Time'})  => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": {
        "EmploymentType": "Contract",
        "FirstName": "Robert",
        "LastName": "Miller"
    },
    "skip_reason": "Conditional result was False"
}
skipping: [localhost] => (item={'FirstName': .....
    "ansible_loop_var": "item",
    "changed": false,
    "item": {
        .......
        "ClassType": "Flexi"
    },
    "skip_reason": "Conditional result was False"
}

Solution

  • Given the file for mre testing

    shell> cat /tmp/creation.csv 
    Grace,Higgins,Full Time
    Robert,Miller,Part Time
    Jeffrey,Keller,Flexi
    

    read the CSV file

        - read_csv:
            path: /tmp/creation.csv
            # key: FirstName 
            fieldnames: FirstName,LastName,ClassType
            delimiter: ','
          register: userdata
    

    When you omit the parameter key you get the list without 'extracting'

      userdata.list:
      - ClassType: Full Time
        FirstName: Grace
        LastName: Higgins
      - ClassType: Part Time
        FirstName: Robert
        LastName: Miller
      - ClassType: Flexi
        FirstName: Jeffrey
        LastName: Keller
    

    To add the attribute path create the dictionary class_path

      class_path:
        Full Time: "OU=fulltime,OU=Test OU,DC=localdemo,DC=local"
        Part Time: "OU=parttime,OU=Test OU,DC=localdemo,DC=local"
        Flexi: "OU=flexi,OU=Test OU,DC=localdemo,DC=local"
    

    From the list map the attribute ClassType, extract the path, and create the list of the hashes

      path: "{{ userdata.list|map(attribute='ClassType')|
                              map('extract', class_path)|
                              map('community.general.dict_kv', 'path') }}"
    

    gives

      path:
      - path: OU=fulltime,OU=Test OU,DC=localdemo,DC=local
      - path: OU=parttime,OU=Test OU,DC=localdemo,DC=local
      - path: OU=flexi,OU=Test OU,DC=localdemo,DC=local
    

    zip the lists and combine the items. You have to use set_facts because the variable userdata has already been used in registered vars

        - set_fact:
            userdata: "{{ userdata.list|zip(path)|map('combine') }}"
    

    gives what you want

      userdata:
      - ClassType: Full Time
        FirstName: Grace
        LastName: Higgins
        path: OU=fulltime,OU=Test OU,DC=localdemo,DC=local
      - ClassType: Part Time
        FirstName: Robert
        LastName: Miller
        path: OU=parttime,OU=Test OU,DC=localdemo,DC=local
      - ClassType: Flexi
        FirstName: Jeffrey
        LastName: Keller
        path: OU=flexi,OU=Test OU,DC=localdemo,DC=local
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        class_path:
          Full Time: "OU=fulltime,OU=Test OU,DC=localdemo,DC=local"
          Part Time: "OU=parttime,OU=Test OU,DC=localdemo,DC=local"
          Flexi: "OU=flexi,OU=Test OU,DC=localdemo,DC=local"
    
        path: "{{ userdata.list|map(attribute='ClassType')|
                                map('extract', class_path)|
                                map('community.general.dict_kv', 'path') }}"
    
      tasks:
    
        - read_csv:
            path: /tmp/creation.csv
            # key: FirstName 
            fieldnames: FirstName,LastName,ClassType
            delimiter: ','
          register: userdata
    
        - debug:
            var: userdata.list
        - debug:
            var: path
    
        - set_fact:
            userdata: "{{ userdata.list|zip(path)|map('combine') }}"
        - debug:
            var: userdata