skip to Main Content

Here is the Ansible code for selecting services that have been running for more than a certain time:

---
- name: Find running services
  hosts: remote_host
  gather_facts: no

  vars:
    hours_threshold: 15  

  tasks:
    - name: Get list of running services
      command: systemctl list-units --type=service --state=running 
      register: running_services

    - name: Filter services
      set_fact:
        s_services: >-
          {{
            running_services.stdout_lines
            | select('search', 'nginx’)
            | list
          }}

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

    - name: Get details of services
      command: systemctl show {{ item }} --property=ActiveEnterTimestamp
      register: s_service_details
      loop: "{{ s_services }}"
      when: s_services | length > 0

    - name: Debug output of Redis services with start time
      debug:
        msg: "s service: {{ item.item }} started at {{ item.stdout }}"
      loop: "{{ s_service_details.results }}"
      when: s_service_details.results | length > 0

    - name: s_service_details.results
      debug:
        msg: "{{ s_service_details.results }}"

    - name: Set the threshold time for filtering
      command: date -d "{{ hours_threshold }} hours ago" +%s
      register: threshold_time

    - name: threshold_time
      debug:
        msg: "{{ threshold_time }}"

    - name: Extract and convert ActiveEnterTimestamp to epoch
      set_fact:
        redis_service_details_epoch: >-
          {{
            redis_service_details.results
            | map(attribute='stdout')
            | map('regex_findall', 'ActiveEnterTimestamp=(.*)')
            | map('first')
            | select('match', '.+') 
            | map('trim')
            | zip(redis_services | map('regex_replace', '.[^.]*$', ''))  
            | map('join', ' : ')
            | map('first')
            | map('to_datetime', '%a %Y-%m-%d %H:%M:%S %Z')
            | map('timestamp')
            | list
          }}

    - name: s_service_details_epoch
      debug:
        msg: "{{ redis_service_details_epoch }}"


The task "Set the threshold time for filtering" gets a timestamp in epoch format from the set value. With this label, you need to compare the start time of each service and the services that work more than the set value, output them to the final list in order to run the list data in a loop and call the desired playbook. to pass the names of each found service in turn as a variable ‘service_name’.

however, in a loop, it is not possible to perform such a conversion. I’m getting errors.

at the output of this block of code


 | map(attribute='stdout')
            | map('regex_findall', 'ActiveEnterTimestamp=(.*)')
            | map('first')
            | select('match', '.+') 
            | map('trim')
            | zip(redis_services | map('regex_replace', '.[^.]*$', ''))  
            | map('join', ' : ')

I get a value like :

 "Fri 2024-10-18 22:26:53 CEST : nginx-reactive"


Then it needs to be converted to the format:

 "1729181112 : nginx-reactive"


Then you need to compare all the epoch values of each found service with the value in the variable : threshold_time

Then, you need to save the resulting list into a separate variable that you can work with further.

This is how you can perform the conversion of a single service:


---
- name: Find running Redis services
  hosts: localhost
  gather_facts: no

  vars:
    expire_date: Thu 2024-09-17 11:05:12 CEST    

  tasks:
    - name: Convert expire_date to epoch
      set_fact:
        expire_date_epoch: "{{ (expire_date | to_datetime('%a %Y-%m-%d %H:%M:%S %Z')).timestamp() | int }}"

    - name: Display epoch time
      debug:
        msg: "Epoch time: {{ expire_date_epoch }}"

, but that’s all I know so far

Does anyone have any ideas on how to perform the conversion?

2

Answers


  1. After you get the running services

        - command: systemctl list-units --type=service --state=running
          register: running_services
    

    Use the filter community.general.jc to parse the stdout. You must install jc on the controller.

      rs_list: "{{ running_services.stdout | community.general.jc('systemctl') }}"
    

    gives

      rs_list:
        - {active: active, description: Accounts Service, load: loaded, sub: running, unit: accounts-daemon.service}
        - {active: active, description: ACPI event daemon, load: loaded, sub: running, unit: acpid.service}
       ...
    

    In your example, you’re looking for the nginx service. I’m running this example in my notebook without nginx installed. Let’s test ssh instead

      s_services: "{{ rs_list | selectattr('unit', 'match', 'ssh') }}"
    

    gives

      s_services:
      - active: active
        description: OpenBSD Secure Shell server
        load: loaded
        sub: running
        unit: ssh.service
    

    Then you iterate this list and get the property ActiveEnterTimestamp

        - command: systemctl show {{ item.unit }} --property=ActiveEnterTimestamp
          loop: "{{ s_services }}"
          register: s_service_details
    

    When you iterate the results

        - debug:
            msg: "{{ item.item.unit }} started at {{ item.stdout }}"
          loop: "{{ s_service_details.results }}"
    

    gives (abridged)

      msg: ssh.service started at ActiveEnterTimestamp=Mon 2024-10-14 17:06:28 CEST
    

    Declare the lists of units, stdout, and create the dictionary s_dict

        s_stdt: "{{ s_service_details.results | map(attribute='stdout') |
                                                map('split', '=') |
                                                map('last') }}"
        s_unit: "{{ s_service_details.results | map(attribute='item.unit') |
                                                map('split', '.') |
                                                map('first') }}"
        s_dict: "{{ dict(s_unit | zip(s_stdt)) }}"
    

    gives

      ['Mon 2024-10-14 17:06:28 CEST']
      ['ssh']
      {'ssh': 'Mon 2024-10-14 17:06:28 CEST'}
    

    Iterate the dictionary and convert the date to epoch

        - debug:
            msg: "{{ item.key }} {{ (item.value | to_datetime('%a %Y-%m-%d %H:%M:%S %Z')).strftime('%s') }}"
          loop: "{{ s_dict | dict2items }}"
    

    gives

      msg: ssh 1728918388
    

    Further processing should be trivial. For example, selecting systemd

      s_services: "{{ rs_list | selectattr('unit', 'match', 'systemd') }}"
    

    gives (abridged)

      msg: systemd-journald 1728918378
      msg: systemd-logind 1728918388
      msg: systemd-networkd 1728918378
      msg: systemd-resolved 1728918386
      msg: systemd-udevd 1728918378
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        rs_list: "{{ running_services.stdout | community.general.jc('systemctl') }}"
        s_services: "{{ rs_list | selectattr('unit', 'match', 'ssh') }}"
        s_stdt: "{{ s_service_details.results | map(attribute='stdout') |
                                                map('split', '=') |
                                                map('last') }}"
        s_unit: "{{ s_service_details.results | map(attribute='item.unit') |
                                                map('split', '.') |
                                                map('first') }}"
        s_dict: "{{ dict(s_unit | zip(s_stdt)) }}"
    
      tasks:
    
        - command: systemctl list-units --type=service --state=running
          register: running_services
    
        - debug:
            var: rs_list | to_yaml
    
        - debug:
            var: s_services
    
        - command: systemctl show {{ item.unit }} --property=ActiveEnterTimestamp
          loop: "{{ s_services }}"
          register: s_service_details
    
        - debug:
            msg: "{{ item.item.unit }} started at {{ item.stdout }}"
          loop: "{{ s_service_details.results }}"
    
        - debug:
            msg: |
              {{ s_stdt }}
              {{ s_unit }}
              {{ s_dict }}
    
        - debug:
            msg: "{{ item.key }} {{ (item.value | to_datetime('%a %Y-%m-%d %H:%M:%S %Z')).strftime('%s') }}"
          loop: "{{ s_dict | dict2items }}"
    

    Login or Signup to reply.
  2. Your assumption that you need to convert to an epoch in order to do date comparison is incorrect. As soon as you have the dates of your list and the threshold date as a Python datetime object, you can do comparisons between them.

    For example, if you want to list all items of a list that are prior to a threshold date you can use the reject filter on your list.

    Given:

    - debug:
        msg: >-
          {{
            dates
              | map('to_datetime', _format)
              | reject('>', threshold | to_datetime(_format))
          }}
      vars:
        _format: '%a %Y-%m-%d %H:%M:%S'
        dates:
          - Thu 2024-09-17 10:26:53
          - Sat 2024-10-19 23:26:53
        threshold: Thu 2024-09-17 11:05:12
    

    Which yields:

    ok: [localhost] => 
      msg: '[datetime.datetime(2024, 9, 17, 10, 26, 53)]'
    

    And so, if you have a list of services in one list and a list of dates in the other, you can list the services running prior to the threshold

    - debug:
        msg: >-
          {{
            services 
              | zip(dates | map('to_datetime', _format))
              | rejectattr(1, '>', threshold | to_datetime(_format))
              | map(attribute=0)
          }}
      vars:
        _format: '%a %Y-%m-%d %H:%M:%S'
        dates:
          - Thu 2024-09-17 10:26:53
          - Sat 2024-10-19 23:26:53
        services:
          - foo
          - bar
        threshold: Thu 2024-09-17 11:05:12
    

    Gives:

    ok: [localhost] => 
      msg:
      - foo
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search