How to access the results of "register: results"

I have a task to collect information from a bunch of servers. Here is what I have started with:

  tasks:
    - name: Check cert end date
      shell: |
        date
        hostname
        openssl x509 -enddate -noout -in /etc/pki/vdsm/libvirt-spice/ca-cert.pem | cut -d "=" -f 2
        date -r /etc/pki/vdsm/libvirt-spice/ca-cert.pem -u
      args:
        executable: /bin/bash
        warn: false
      register: result

    - name: Result
      debug:
        msg: "{{ result }}"

How do I itinerate over result to produce a CSV file, one line per server?

You should be able to have a task that delegates to localhost (if you want the CSV file local), and use a loop through the data in the variable + ansible.builtin.lineinfile to add lines

1 Like

Donā€™t use lifeline, use a template with run_once and loop over all hosts, lineinfile does not handle parallelism and will end up overwriting entries.

template

host, date, hostname, certificate
{% for host in ansible_play_hosts %}
{% if 'result' in hostvars[host] %}
{{host}}, {{ hostvars[host]['result']['stdout'].split('\n', 3) | join(',') }}
{% endif %}
{% endfor %}

use in play

    - name: Create csv.
      template:
        path: csvfile.csv
        src:  mytemplate.j2
      delegate_to: localhost
      run_once: True
2 Likes

Yup, lineinfile does not handle parallelism well.

1 Like

I tried this:

    - name: Create csv
      template:
        dest: "{{ output_fn }}"
        src: mytemplate.j2
      delegate_to: localhost
      run_once: True

But I only get four lines, not the 17 I was expecting.

In looking at the output, I see four lines, all from the first server, one line for each piece of data I am looking for.

OK, I am missing something really basic here.

If I print ā€œresultā€, I get multiple elements(?), one for each server.

To make it simple, I tried this:

    - name: Result
      debug:
        msg: "{{ reand I get this:
.stdout
And  }}"ok: [compute13.slc.csswx.nas.faa.gov] => {
    "msg": "Thu Sep 26 17:37:01 UTC 2024\ncompute13.slc.csswx.nas.faa.gov\nDec  2 21:24:48 2033 GMT\nTue Dec  5 22:29:42 UTC 2023"
}
ok: [compute14.slc.csswx.nas.faa.gov] => {
    "msg": "Thu Sep 26 17:37:00 UTC 2024\ncompute14.slc.csswx.nas.faa.gov\nDec  2 21:24:48 2033 GMT\nTue Dec  5 22:34:19 UTC 2023"
}
ok: [compute15.slc.csswx.nas.faa.gov] => {
    "msg": "Thu Sep 26 17:37:00 UTC 2024\ncompute15.slc.csswx.nas.faa.gov\nDec  2 21:24:48 2033 GMT\nTue Dec  5 22:38:

18 UTC 2023"
}

How do I access each of these elements (or whatever they are called) in a loop?

The run_once is only for the template action, not the shell one.

@jrglynn2 donā€™t use connection: local this only works if controller and targets are configured the same, but for those in heterogeneous environments that breaks, use delegate_to: localhost

Here is where I am now:

- name: Add data to CSV file
  ansible.builtin.lineinfile:
    path: "{{ output_fn }}"
    line: "{{ result.stdout_lines[0]|string, result.stdout_lines[1]|string,result.stdout_lines[2]|string,result.stdout_lines[3]|string }}"
    state: present
  with_items: slc_computes

But the output in the file is this:

(u'Thu Sep 26 18:12:43 UTC 2024', u'compute01.slc.csswx.nas.faa.gov', u'Dec 2 21:24:48 2033 GMT', u'Tue Dec 5 21:28:15 UTC 2023')
Where are the ā€œuā€™ā€ coming from? How can I get rid of them and the parens?

the u is for ā€˜python unicode stringā€™, it happens when you stringify some python objects using their default representation. The parens and the u show that you are stringifying a tuple, which you are creating with the , s

iā€™ve had problems with 2 forks, either your controller is slow parallelizing or your hosts have very different/stepped response times.

1 Like

What finally worked for me. Thanks for all your help.

    - name: Add data to CSV file
      connection: local
      ansible.builtin.lineinfile:
        path: "{{ output_fn }}"
        line: "{{ result.stdout_lines | list  | join(',') }}"
        state: present
        insertafter: EOF
      with_items: servers

Then youā€™ve gotten lucky (or just havenā€™t noticed when it failed to work.) This approach is not safe and should not be used ever. Even if it seems to work perfectly for you it is inherently a flawed pattern that relies on luck to win the race.

2 Likes

IĀ“ve really only used lineinfile in conjunction with delegate_to: localhost and run_once: true as a gateway of last resort once - which should be ok if a specific module does not exist and template is unpractical. And yes, I would have noticed if it failed to work :slightly_smiling_face: However, I can see that parallel writing to the same file is not a good idea.

I totally understand why lineinfile is not a great option. Iā€™ve never used templating before. I tried to use the code example you gave me but failed horribly. What other options do I have? I need to run this script against 250+ VMs to collect the data I need.

Thanks.

Dave

We are not talking about lineinfile in the abstract, we are talking about the concrete approach of using it to update a single file on localhost for multiple hosts, since that is what you suggested doing. Specifically, this was your suggestion:

    - name: Add data to CSV file
      delegate_to: localhost
      ansible.builtin.lineinfile:
        path: "/tmp/CSV-FILE.csv"
        line: "{{ tags.Name, private_ip_address, item.path }}"
        state: present
      with_items: "{{ file_list.files }}"

This introduces a race condition if itā€™s in a play with multiple targets and will eventually fail to record some data.

Figure out why you failed horribly? It works just fine.

- hosts: Class_mx
  tasks:
    - name: Check cert end date
      shell: |
        date
        hostname
        openssl x509 -enddate -noout -in /etc/pki/collab/private/mx.x.mail.umich.edu.ecc.crt | cut -d "=" -f 2
        date -r /etc/pki/collab/private/mx.x.mail.umich.edu.ecc.crt -u
      register: result

    - name: Create csv
      template:
        dest: csvfile.csv
        src:  mytemplate.j2
      delegate_to: localhost
      run_once: true
$ ansible-playbook ~/test.yml                          
                                                                                
PLAY [Class_mx] ****************************************************************
                                                                                
TASK [Check cert end date] *****************************************************
changed: [hushed-kameosa.mx.x.mail.umich.edu]                                   
changed: [laureate-ungaikyo.mx.x.mail.umich.edu]                              
changed: [small-jatai.mx.x.mail.umich.edu]                                      
                                        
TASK [Create csv.] *************************************************************
changed: [laureate-ungaikyo.mx.x.mail.umich.edu -> localhost]                   
                                                                                                                              
PLAY RECAP *********************************************************************                                              
hushed-kameosa.mx.x.mail.umich.edu : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
laureate-ungaikyo.mx.x.mail.umich.edu : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0    
small-jatai.mx.x.mail.umich.edu : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0       
                                                               
$ cat ~/csvfile.csv                                                                                  
host, date, hostname, certificate                              
laureate-ungaikyo.mx.x.mail.umich.edu, Fri Sep 27 11:30:21 AM EDT 2024,laureate-ungaikyo.mx.x.mail.umich.edu,Dec 12 23:59:59 2024 GMT,Thu Sep 26 10:38:50 PM UTC 2024
small-jatai.mx.x.mail.umich.edu, Fri Sep 27 11:30:21 AM EDT 2024,small-jatai.mx.x.mail.umich.edu,Dec 12 23:59:59 2024 GMT,Thu Sep 26 10:38:50 PM UTC 2024
hushed-kameosa.mx.x.mail.umich.edu, Fri Sep 27 11:30:21 AM EDT 2024,hushed-kameosa.mx.x.mail.umich.edu,Dec 12 23:59:59 2024 GMT,Thu Sep 26 10:38:50 PM UTC 2024

Then you have not used the construct you suggested, got incredibly lucky, or just didnā€™t notice when it failed. This is not about my experience, it is an inherent flaw in how that task works.

- hosts: aws_ec2
  tasks:
    - name: Remove CSV file
      delegate_to: localhost
      ansible.builtin.file:
        path: /tmp/CSV-FILE.csv
        state: absent

    - name: Add data to CSV file
      delegate_to: localhost
      ansible.builtin.lineinfile:
        path: "/tmp/CSV-FILE.csv"
        line: "{{ tags.Name, private_ip_address, item }}"
        state: present
        create: true
      loop:
        - foo
        - bar
        - baz
$ for i in {1..10} ; do ansible-playbook test.yml &>/dev/null; wc -l /tmp/CSV-FILE.csv ; done
48 /tmp/CSV-FILE.csv
47 /tmp/CSV-FILE.csv
48 /tmp/CSV-FILE.csv
45 /tmp/CSV-FILE.csv
48 /tmp/CSV-FILE.csv
48 /tmp/CSV-FILE.csv
48 /tmp/CSV-FILE.csv
48 /tmp/CSV-FILE.csv
47 /tmp/CSV-FILE.csv
47 /tmp/CSV-FILE.csv

The argument arises because you provided conceptually broken tasks that should not be learned from, and will cause problems for people that can be subtle and hard to notice. I donā€™t know why youā€™re still arguing that itā€™s a safe thing to do when itā€™s demonstrably not, as shown in my immediately previous post. I didnā€™t have to try to break it or jump through hoops to get those results, I just ran it ten times and it failed on 4 of them.

Donā€™t write code with race conditions. Itā€™s a bad idea.

1 Like