1
votes

I have a playbook that targets a small set of hosts, and a task there that changes some configuration files on one or more of these hosts, depending on how I've set my variables.

A simplified example result of running this task:

TASK [somerole : create config file] *************************************
ok: [server1] => (item=foo.conf)
changed: [server2] => (item=foo.conf)
changed: [server3] => (item=foo.conf)

If I use register on this task and then debug the variable it creates, the output of the debug: var=varname would look something like this (considerably simplified):

TASK [somerole : debug] **************************************************
ok: [server1] => {
    "varname": {
        "changed": false,
        "results": [{...}]
    }
}
ok: [server2] => {
    "varname": {
        "changed": true,
        "results": [{...}]
    }
}
ok: [server3] => {
    "varname": {
        "changed": true,
        "results": [{...}]
    }
}

The config change necessitates a service restart that currently has to be done manually by a human (there's a manual eyeballing validation step). To help with this, I would like to use the pause module at the end of the playbook to display a prompt to the user, looking something like this:

- pause:
    prompt: |-
      The configuration has changed on the following servers: {{ changed_servers|join(', ' }}

      Please restart service X on these servers after validating Y and Z.

So, how do I build the changed_servers variable? Given the examples above, it would be this list:["server2", "server3"] (since they were the hosts changed by the task this time).

I first thought I'd try to combine the varname variable that register produced for each host affected by the task, looking at the changed boolean in each, but the name of the host is not present anywhere inside varname so that I could combine the changed attribute with the name of the corresponding host.

Is there actually a way to do this?

1

1 Answers

2
votes

Q: "Store all hosts affected by a task in a variable"

A: Register a variable with the result of the task. Then use extract to collect the results. For example, the playbook below creates the file foo.conf registers the result and creates the dictionary dict_foo_conf

- hosts: test_11,test_12,test_13
  tasks:
    - command:
        cmd: touch foo.conf
        creates: foo.conf
        warn: false
      register: result_foo_conf
    - set_fact:
        dict_foo_conf: "{{ dict(ansible_play_hosts_all|zip(my_results)) }}"
      vars:
        my_results: "{{ ansible_play_hosts_all|
                        map('extract', hostvars, ['result_foo_conf', 'changed'])|
                        list }}"
      run_once: true

gives


TASK [command] *************************************************************
changed: [test_11]
changed: [test_12]
changed: [test_13]
  dict_foo_conf:
    test_11: true
    test_12: true
    test_13: true

There are no changes when you run the playbook again

TASK [command] ******************************************************************
ok: [test_13]
ok: [test_11]
ok: [test_12]
  dict_foo_conf:
    test_11: false
    test_12: false
    test_13: false

Let's remove the files from hosts test_12 and test_13

shell> ssh admin@test_12 rm foo.conf
shell> ssh admin@test_13 rm foo.conf

The playbook gives

TASK [command] ******************************************************************
changed: [test_12]
ok: [test_11]
changed: [test_13]
  dict_foo_conf:
    test_11: false
    test_12: true
    test_13: true

Then select hosts that changed, for example

  - set_fact:
      changed_hosts: "{{ dict_foo_conf|dict2items|json_query('[?value].key') }}"

gives

  changed_hosts:
  - test_12
  - test_13