Ansible Jinja2 – Access Dictionary Key Value in List of Dictionaries

ansiblejinja2

I am installing list of packages in ansible registering variable and outputting it with debug:

  community.general.homebrew:
    name: "{{ package }}"
    state: present
  register: package_install
  until: package_install is succeeded
  loop:
    - pam-reattach
    - pinentry-mac
    - jorgelbg/tap/pinentry-touchid
  loop_control:
    loop_var: package

- debug:
    msg: "{{ package_install }}"

The output looks like this:

msg:
  changed: true
  msg: All items completed
  results:
  - ansible_loop_var: package
    attempts: 1
    changed: false
    changed_pkgs: []
    failed: false
    invocation:
      module_args:
        install_options: []
        name:
        - pam-reattach
        path: /usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin
        state: present
        update_homebrew: false
        upgrade_all: false
        upgrade_options: []
    msg: 'Package already installed: pam-reattach'
    package: pam-reattach
    unchanged_pkgs:
    - pam-reattach
  - ansible_loop_var: package
    attempts: 1
    changed: true
    changed_pkgs:
    - pinentry-mac
    failed: false
    invocation:
      module_args:
        install_options: []
        name:
        - pinentry-mac
        path: /usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin
        state: present
        update_homebrew: false
        upgrade_all: false
        upgrade_options: []
    msg: 'Package installed: pinentry-mac'
    package: pinentry-mac
    unchanged_pkgs: []
  - ansible_loop_var: package
    attempts: 1
    changed: true
    changed_pkgs:
    - jorgelbg/tap/pinentry-touchid
    failed: false
    invocation:
      module_args:
        install_options: []
        name:
        - jorgelbg/tap/pinentry-touchid
        path: /usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin
        state: present
        update_homebrew: false
        upgrade_all: false
        upgrade_options: []
    msg: 'Package installed: jorgelbg/tap/pinentry-touchid'
    package: jorgelbg/tap/pinentry-touchid
    unchanged_pkgs: []
  skipped: false

The registered var package_install.results contains a list of dictionaries (or maps/hashes – please correct me if I am wrong) with the data related to each package installation.

I need to check if either pinentry-mac or pinentry-touchid packages have been installed during previous task (is the value of changed key within each item equal true or false) and if yes, then run a specified command e.g:

- command: <command>
  when: >
    `pinentry-mac` item's attribute `changed` is `True` within `package_install.results` \
    OR \
    `pinentry-touchid` item's attribute `changed` is `True` within `package_install.results`

How would I do this?

Right now I do the following:

  - command: <command>
    when: "'pinentry' in item.package and item.changed"
    loop: "{{ macterm_package_install.results }}"

But in this case the command will run twice if both packages were installed during previous step, although the command must be run only once.

Is there any way to do it properly? Any thoughts are highly appreciated.

UPDATE

The "best" way I was able to find is this (in two steps):

  - name: Check if any of the pinentry packages were installed during previous tasks
    set_fact:
      pinentry_changed: True
    when: "'pinentry-' in item.package and item.changed"
    loop: "{{ macterm_package_install.results }}"

  - command: <command>
    when: pinentry_changed | default(false)

But really, is there any more elegant way to solve this?

Best Answer

There are many options. Pick the one that fits your use case best.

  1. Create the dictionary
  package_changed: "{{ package_install.results|
                       items2dict(key_name='package', value_name='changed') }}"

gives

  package_changed:
    jorgelbg/tap/pinentry-touchid: true
    pam-reattach: false
    pinentry-mac: true

Then the conditions are trivial

    - command: <command>
      when:  package_changed['pinentry-mac'] or
             package_changed['jorgelbg/tap/pinentry-touchid']
  1. Create the list of changed packages
  changed_pkgs: "{{ package_install.results|
                    map(attribute='changed_pkgs')|flatten }}"

gives

  changed_pkgs:
  - pinentry-mac
  - jorgelbg/tap/pinentry-touchid

Either test each package

    - command: <command>
      when:  ('pinentry-mac' in changed_pkgs) or
             ('jorgelbg/tap/pinentry-touchid' in changed_pkgs)

, or intersect the lists if you can put the tested packages into a list

    - command: <command>
      when:  changed_pkgs|intersect(test_pkgs)|length > 0
      vars:
        test_pkgs: [pinentry-mac, jorgelbg/tap/pinentry-touchid]
  1. Create the list and map the basename
  changed_pkgs: "{{ package_install.results|
                    map(attribute='changed_pkgs')|flatten|
                    map('basename')|list }}"

gives

  changed_pkgs:
  - pinentry-mac
  - pinentry-touchid

Use the names of the packages only

    - command: <command>
      when:  ('pinentry-mac' in changed_pkgs) or
             ('pinentry-touchid' in changed_pkgs)