dzbeda
dzbeda

Reputation: 303

ansible task - Locate running port on a list of servers

I have a list of servers that only part of them are running port 9090. I need to create two tasks, each should loop the server's hostnames. the first task should define inside a register the first server hostname that was found running port 9090 , the second task should define in a register all server's hostnames that are running port 9090. If no server is running port 9090 the tasks should fail

I have nc installed on the server and I thought about using a shell with the following task:

server_details:
  - server1
  - server2
  - server3
  - server4
  - server5

- name: locate servers running port 9090 
  shell: nc -zv {{ item.hostname }} 9090
  register: results_port_9090
  with_items:
    - "{{ server_details }}"
  ignore_errors: yes

But I don't know how to filter the answer and add the hostname to a new register - I assume an additional task is required that runs on the existing register and creates a new register with the relevant outputs

For example:
server1
server2
server3 #running 9090
server4
server5 #running 9090

Upvotes: 1

Views: 733

Answers (3)

Khaled
Khaled

Reputation: 838

To get the open ports use listen_ports_facts

- hosts: all
  gather_facts: no
  vars:
    check_port: 9090 ################ desired port ################
  tasks:
  - name: Gather facts on listening ports
    community.general.listen_ports_facts:


   ################ Filter ports and store them in a variable ################
  - name: List all ports
    set_fact:
      ports_list: "{{ (ansible_facts.tcp_listen + ansible_facts.udp_listen) | map(attribute='port') | unique | sort | list }}"


  # Check if the desired port exists in the listed ports and write output according to that #
  - name: Check if the desired port exists in the listed ports
    debug:
      msg: |
        {% if check_port in ports_list %}
        {{ inventory_hostname }} #running {{check_port}}
        {% else %}
        {{ inventory_hostname }} #not running {{check_port}}
        {% endif %}
    register: checkoutput

  ################ Combine the output for all hosts ################
  - name: Combine the output for all hosts
    debug:
      msg: "{{ ansible_play_hosts | map('extract', hostvars, 'checkoutput') | map(attribute='msg') | list }}"
    register: finaloutput
    run_once: yes

The list of ports in debug mode:

TASK [List all ports] *********************************************************************
task path: /root/ansible-test/dictionary.yml:10
ok: [test-001] => {
    "ansible_facts": {
        "ports_list": [
            22,
            68,
            111,
            2049,
            34007,
            37539,
            38611,
            41237,
            45851,
            46679,
            48941,
            50640,
            52215,
            52637,
            52772,
            54317,
            54750,
            56105,
            58060,
            58842
        ]
    },
    "changed": false
}
ok: [test-002] => {
    "ansible_facts": {
        "ports_list": [
            22,
            68,
            111,
            323,
            782,
            9090,
            35717,
            35799,
            36247,
            44387
        ]
    },
    "changed": false
}
TASK [Check if the desired port exists in the listed ports] ******************************
ok: [test-001] => {
    "msg": "test-001 #not running 9090\n"
}
ok: [test-002] => {
    "msg": "test-002 #running 9090\n"
}

TASK [Combine the output for all hosts] **************************************************
ok: [test-001] => {
    "msg": [
        "test-001 #not running 9090\n",
        "test-002 #running 9090\n"
    ]
}

Upvotes: 0

Vladimir Botka
Vladimir Botka

Reputation: 68034

Q: "If no server is running port the play should fail."

A: Use wait_for to test a port. For example, given the variables

server_details: [test_11, test_12, test_13]
time_out: 3
fail: "{{ out.results|items2dict(key_name='item', value_name='failed') }}"

The task below

        - wait_for:
            host: "{{ item }}"
            port: "{{ test_port }}"
            timeout: "{{ time_out }}"
          loop: "{{ server_details }}"
          register: out

gives, for example when test_port=22

  fail:
    test_11: false
    test_12: false
    test_13: false

This means that port 22 is open at all remote hosts. Test it

        - name: "If no server is running port {{ test_port }} the tasks should fail."
          fail:
            msg: "No server is running port {{ test_port }}"
          when: fail.values()|list is all

Example of a complete playbook for testing

If you want to use the module command, instead of wait_for, the second block provides the same functionality. Use the tags to select the block. Optionally, enable debug to see the dictionaries

shell> cat pb.yml
- hosts: localhost

  vars:

    server_details: [test_11, test_12, test_13]
    time_out: 3

    fail: "{{ out.results|items2dict(key_name='item', value_name='failed') }}"
    rc: "{{ out.results|items2dict(key_name='item', value_name='rc') }}"

  tasks:

    - assert:
        that: test_port is defined
        fail_msg: The variable test_port is mandatory.
      tags: always

    - block:
        - wait_for:
            host: "{{ item }}"
            port: "{{ test_port }}"
            timeout: "{{ time_out }}"
          loop: "{{ server_details }}"
          register: out
      always:
        - debug:
            var: fail
          when: debug|d(false)|bool
        - name: "If no server is running port {{ test_port }} the tasks should fail."
          fail:
            msg: "No server is running port {{ test_port }}"
          when: fail.values()|list is all
      tags: t1

    - block:
        - command: "nc -w {{ time_out }} -zv {{ item }} {{ test_port }}"
          loop: "{{ server_details }}"
          register: out
      always:
        - debug:
            var: rc
          when: debug|d(false)|bool
        - name: "If no server is running port {{ test_port }} the tasks should fail."
          fail:
            msg: "No server is running port {{ test_port }}"
          when: rc.values()|list is all
      tags: t2
  1. Test wait_for port 22
shell> ansible-playbook pb.yml -t t1 -e test_port=22 -e debug=true

PLAY [localhost] *****************************************************************************

TASK [assert] ********************************************************************************
ok: [localhost] => changed=false 
  msg: All assertions passed

TASK [wait_for] ******************************************************************************
ok: [localhost] => (item=test_11)
ok: [localhost] => (item=test_12)
ok: [localhost] => (item=test_13)

TASK [debug] *********************************************************************************
ok: [localhost] => 
  fail:
    test_11: false
    test_12: false
    test_13: false

TASK [If no server is running port 22 the tasks should fail.] ********************************
skipping: [localhost]

PLAY RECAP ***********************************************************************************
localhost: ok=3    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
  1. Test wait_for port 80
shell> ansible-playbook pb.yml -t t1 -e test_port=80 -e debug=true

PLAY [localhost] *****************************************************************************

TASK [assert] ********************************************************************************
ok: [localhost] => changed=false 
  msg: All assertions passed

TASK [wait_for] ******************************************************************************
failed: [localhost] (item=test_11) => changed=false 
  ansible_loop_var: item
  elapsed: 4
  item: test_11
  msg: Timeout when waiting for test_11:80
failed: [localhost] (item=test_12) => changed=false 
  ansible_loop_var: item
  elapsed: 4
  item: test_12
  msg: Timeout when waiting for test_12:80
failed: [localhost] (item=test_13) => changed=false 
  ansible_loop_var: item
  elapsed: 4
  item: test_13
  msg: Timeout when waiting for test_13:80

TASK [debug] *********************************************************************************
ok: [localhost] => 
  fail:
    test_11: true
    test_12: true
    test_13: true

TASK [If no server is running port 80 the tasks should fail.] ********************************
fatal: [localhost]: FAILED! => changed=false 
  msg: No server is running port 80

PLAY RECAP ***********************************************************************************
localhost: ok=2    changed=0    unreachable=0    failed=2    skipped=0    rescued=0    ignored=0
  1. Test command port 22
shell> ansible-playbook pb.yml -t t2 -e test_port=22 -e debug=true

PLAY [localhost] *****************************************************************************

TASK [assert] ********************************************************************************
ok: [localhost] => changed=false 
  msg: All assertions passed

TASK [command] *******************************************************************************
changed: [localhost] => (item=test_11)
changed: [localhost] => (item=test_12)
changed: [localhost] => (item=test_13)

TASK [debug] *********************************************************************************
ok: [localhost] => 
  rc:
    test_11: 0
    test_12: 0
    test_13: 0

TASK [If no server is running port 22 the tasks should fail.] ********************************
skipping: [localhost]

PLAY RECAP ***********************************************************************************
localhost: ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
  1. Test command port 80
shell> ansible-playbook pb.yml -t t2 -e test_port=80 -e debug=true

PLAY [localhost] *****************************************************************************

TASK [assert] ********************************************************************************
ok: [localhost] => changed=false 
  msg: All assertions passed

TASK [command] *******************************************************************************
failed: [localhost] (item=test_11) => changed=true 
  ansible_loop_var: item
  cmd:
  - nc
  - -w
  - '3'
  - -zv
  - test_11
  - '80'
  delta: '0:00:03.006955'
  end: '2022-08-21 17:11:30.449997'
  item: test_11
  msg: non-zero return code
  rc: 1
  start: '2022-08-21 17:11:27.443042'
  stderr: 'nc: connect to test_11 port 80 (tcp) timed out: Operation now in progress'
  stderr_lines: <omitted>
  stdout: ''
  stdout_lines: <omitted>
failed: [localhost] (item=test_12) => changed=true 
  ansible_loop_var: item
  cmd:
  - nc
  - -w
  - '3'
  - -zv
  - test_12
  - '80'
  delta: '0:00:03.005854'
  end: '2022-08-21 17:11:33.656814'
  item: test_12
  msg: non-zero return code
  rc: 1
  start: '2022-08-21 17:11:30.650960'
  stderr: 'nc: connect to test_12 port 80 (tcp) timed out: Operation now in progress'
  stderr_lines: <omitted>
  stdout: ''
  stdout_lines: <omitted>
failed: [localhost] (item=test_13) => changed=true 
  ansible_loop_var: item
  cmd:
  - nc
  - -w
  - '3'
  - -zv
  - test_13
  - '80'
  delta: '0:00:03.009158'
  end: '2022-08-21 17:11:36.860258'
  item: test_13
  msg: non-zero return code
  rc: 1
  start: '2022-08-21 17:11:33.851100'
  stderr: 'nc: connect to test_13 port 80 (tcp) timed out: Operation now in progress'
  stderr_lines: <omitted>
  stdout: ''
  stdout_lines: <omitted>

TASK [debug] *********************************************************************************
ok: [localhost] => 
  rc:
    test_11: 1
    test_12: 1
    test_13: 1

TASK [If no server is running port 80 the tasks should fail.] ********************************
fatal: [localhost]: FAILED! => changed=false 
  msg: No server is running port 80

PLAY RECAP ***********************************************************************************
localhost: ok=2    changed=0    unreachable=0    failed=2    skipped=0    rescued=0    ignored=0

Upvotes: 2

Chen A.
Chen A.

Reputation: 11280

Your thinking is correct. What you can use to filter results is the rc (return code) of the shell command.

When nc succeeds to open a connection, the exist code would be 0, otherwise usually 1. You should iterate the returned list, and check for exit codes, creating a new array.

You can do something like this:

- name: look for live services
  set_fact:
    live_services: "{{ live_services|default([]) + [item] }}
  when: item.rc == 0
  loop:
    - "{{ results_port_9090 }}"

Then, to get the first server the has this service running you can check if this array isn't empty and if so, the first server would be placed at live_services[0].

Upvotes: 0

Related Questions