1

NOTE: Original question expanded on here: Is there a way in Ansible to replace a dictionary value based on k:v lookup to another dictionary?

I have 3 dictionaries, the first 2 are k:v with string:integer type values; My 3rd dictionary is a k:v of string:string which I want to loop through first with dict #1 and to replace the k with the k:v and then the same with dict #2 but replacing v with k:v.

"dict_1": {
    "office-core01": 85,
    "office-core02": 86,
    "office-fw01": 87,
    "office-fw02": 88,
    "office-server-sw01": 91,
    "office-vpn01": 92,
    "office-vpn02": 93
}
"dict_2": {
    "con1": 129,
    "con2": 130,
    "con3": 131,
    "con4": 132,
    "con5": 133,
    "con6": 134,
    "con7": 135,
    "con8": 136,
    "con9": 137
}
"dict_3": {
    "office-core01": "con1", 
    "office-core02": "con2", 
    "office-fw01": "con3", 
    "office-fw02": "con4", 
    "office-server-sw01": "con7", 
    "office-vpn01": "con5", 
    "office-vpn02": "con6"
}

In the end I need a dictionary of k:v pairs of integers; For example in the first iteration I need the hostnames/keys (office-core01) in dict_3 replaced with value from dict_1 (85) and then the 2nd run to replace the ports/values (con1) replaced with key from dict_2 (129) however using code supplied by Vladimir in original question complains about object of type 'int' has no len().

Include task (console-portid.yml):

---
  - name: Replace Console Hostname ID
    set_fact:
      port_mapping: "{{ port_mapping | difference([item]) +
                        [dict(my_value | zip(my_keys))] }}"
    vars:
      my_key: "{{ item.keys() | list }}"
      my_value: "{{ item.values() | list }}"
      my_keys: "{{ my_key | map('regex_replace', port_id.key, port_id.value) | list }}"
    loop: "{{ dict_3 | dict2items }}"

Invocation:

- name: Replace Device Console Ports ID
  include_tasks: console-portid.yml
  loop: "{{ dict_1 | dict2items }}"
  loop_control:
    loop_var: port_id
Niall
  • 37
  • 7

2 Answers2

1

Q: "I want to turn office-core01: con1 into 85: 129"

A: The task below does the job

    - set_fact:
        dict_3a: "{{ dict_3a|default({})|
                     combine({dict_1[item.key]: dict_2[item.value]}) }}"
      loop: "{{ dict_3|dict2items }}"
    - debug:
        var: dict_3a

give

    "dict_3a": {
        "85": 129, 
        "86": 130, 
        "87": 131, 
        "88": 132, 
        "91": 135, 
        "92": 133, 
        "93": 134
    }

Q: "Is there a reason key is string and value still integer?"

A: Internal type of the variable is preserved. The variables evaluate to strings unless configured by DEFAULT_JINJA2_NATIVE. Quoting "This option preserves variable types during template operations. This requires Jinja2 >= 2.10."


Q: "Is there a reason key is string and value still integer? Does key always have to be a string?"

A: There are no restrictions on the keys. Quoting from 3.2.1.1. Nodes

"A YAML node represents a single native data structure. Such nodes have content of one of three kinds: scalar, sequence, or mapping. In addition, each node has a tag which serves to restrict the set of possible values the content can have."

"Mapping: The content of a mapping node is an unordered set of key: value node pairs, with the restriction that each of the keys is unique. YAML places no further restrictions on the nodes. In particular, keys may be arbitrary n odes, the same node may be used as the value of several key: value pairs, and a mapping could even contain itself as a key or a value (directly or indirectly)."

Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Not exactly, in the end I want to turn `office-core01: con1` into `85: 129`, but the code above points me in a new direction I can try out. Basically with 3a + 3b to create a new dictionary with the values in each kept in order. – Niall Mar 13 '20 at 14:41
  • Thats spot on thanks. Is there a reason key is string and value still integer? Does key always have to be a string? – Niall Mar 13 '20 at 15:10
1
---
- hosts: localhost
  gather_facts: no
  tasks:
  - name: Loop over dict_3
    debug:
      msg: "{{ item.key }}: {{ item.value }}"
    with_items: "{{ lookup('dict', dict_3) }}"

  - name: Loop over dict_3 with replacements
    debug:
      msg: "{{ dict_1[item.key] }}: {{ dict_2[item.value] }}"
    with_items: "{{ lookup('dict', dict_3) }}"

  - name: Create new dictionary
    set_fact:
      dict_4: "{{ dict_4|default({}) | combine( {dict_1[item.key]: dict_2[item.value]} ) }}"
    with_items: "{{ lookup('dict', dict_3) }}"

  - name: Show dict_4
    debug:
      var: dict_4
Jack
  • 5,801
  • 1
  • 15
  • 20
  • Ah thats cool, so this is the output from those on my full dicts (showing just 1 entry): `ok: [localhost] => (item={'key': u'duboff04-core01', 'value': u'con1'}) => { "msg": "85: 129" ok: [localhost] => (item={'key': u'duboff04-core01', 'value': u'con1'}) ok: [localhost] => { "dict_4": { "85": 129` – Niall Mar 13 '20 at 15:00
  • Strange how the new key that was an integer is now a string, but value still int.. – Niall Mar 13 '20 at 15:02
  • Even typing it in as an integer in a `group_vars/all/` file, the key still comes out as a string. – Jack Mar 13 '20 at 15:12
  • Right I get you, so probably enforced by the combine filter for a dictionary. Anyhow its great, I can work with this at least, cheers. – Niall Mar 13 '20 at 15:20