Compare commits
35 Commits
fix/scalin
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fdf0e59a4 | |||
| e1ae30c4ce | |||
| 8574891864 | |||
| 02e567730a | |||
| f5b82755fd | |||
| 7e7da74f55 | |||
| 398392445e | |||
| f75c5fd2b4 | |||
| 6208ff4c9e | |||
| aa51ba382f | |||
| e1258a554f | |||
| 419834e4ca | |||
| ec1b9838a9 | |||
| 9f6e93827a | |||
| 8519e0f9e7 | |||
| acf2fff6f5 | |||
| b340c713df | |||
| 03b4a7e953 | |||
| dddf1a8ab0 | |||
| c22fb85acc | |||
| 5b63184aa3 | |||
| 1dd48c2b9d | |||
| 7df9a96965 | |||
| c1f533bf83 | |||
| c7a9e7ebe0 | |||
| 225f405ab7 | |||
| 1a7f79fed2 | |||
| 9396728ddd | |||
| e84e26b393 | |||
| 9e9acff4c1 | |||
| 450179c641 | |||
| 421df547eb | |||
| 397e3409cb | |||
| 9797e305ae | |||
| e3e7eb6181 |
@@ -1,35 +1,34 @@
|
||||
---
|
||||
# updater.yml — read Dev1 & Dev2 firmware versions and publish to controls → netbox-reporter
|
||||
# Run: nbplay updater.yml <device>
|
||||
# sot-updater.yml — Read fw on Dev1 + Dev2, publish to NetBox via Rabbit (telemetry mode)
|
||||
|
||||
- name: Read fw on Dev1 + Dev2, publish NetBox custom fields
|
||||
- name: Read fw on Dev1 + Dev2, publish NetBox custom fields (full base, AIRPINGs, soft-fail telemetry)
|
||||
hosts: all
|
||||
gather_facts: no
|
||||
|
||||
vars:
|
||||
# Busybox-safe PATH prefix for raw calls on DEV1 (as in update-rebootin222)
|
||||
pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; "
|
||||
|
||||
# DEV1 credentials (as in update-indoor)
|
||||
dev1_user: "root"
|
||||
dev1_pass: "wavewave"
|
||||
ssh_timeout: 30
|
||||
|
||||
# Tunnel target DEV2 behind DEV1 (as in update-indoor)
|
||||
dev2_host: "192.168.1.1"
|
||||
dev2_port: 22
|
||||
dev2_ssh_user: "root"
|
||||
dev2_passfiles: [ "basicpass", "basicpass2" ]
|
||||
|
||||
dev2_side_ip: "192.168.1.11/24"
|
||||
dev1_iface: "br-wan"
|
||||
arping_iface: "eth0"
|
||||
dev2_side_ip_addr: "{{ dev2_side_ip.split('/')[0] }}"
|
||||
|
||||
ssh_opts_common: >-
|
||||
-o PreferredAuthentications=password
|
||||
-o PubkeyAuthentication=no
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o NumberOfPasswordPrompts=1
|
||||
-o ConnectTimeout=30
|
||||
|
||||
# Password files sequence (as in update-indoor: try basicpass then basicpass2)
|
||||
# NOTE: The selection below mirrors the exact “try #1 → select → try #2 → select → NONE” flow from update-indoor.
|
||||
# Do not alter ordering.
|
||||
# (Values are file names as used in your repo/environment.)
|
||||
# We do not loop; we replicate the same task structure.
|
||||
# The read of firmux uses whichever got selected.
|
||||
# — Pavel’s rule: on-device commands remain immutable; we’re only orchestrating controller-side steps here.
|
||||
|
||||
# Rabbit journaling (mirrors update-indoor / rebootin222)
|
||||
rmq_host: "{{ lookup('env','RMQ_HOST') | default('10.210.12.2', true) }}"
|
||||
rmq_port: "{{ lookup('env','RMQ_PORT') | default('15672', true) }}"
|
||||
rmq_user: "{{ lookup('env','RMQ_USER') | default('admin', true) }}"
|
||||
@@ -39,24 +38,189 @@
|
||||
control_queue: "{{ lookup('env','CONTROLQUEUE') | default('queue_controls', true) }}"
|
||||
|
||||
tasks:
|
||||
# ----------------------------- DEV1: read current banner (exactly like update-rebootin222) -----------------------------
|
||||
- name: Dev1 | Read first /etc/banner line containing 'rev'
|
||||
ansible.builtin.raw: "{{ pathprefix }} cat /etc/banner | grep -i rev | head -n1"
|
||||
register: dev1_banner
|
||||
- name: "⚙️ Start | Dev1 banner → tunnel/auth → Dev2 firmux (telemetry mode)"
|
||||
debug:
|
||||
msg:
|
||||
- "Device: {{ inventory_hostname }}"
|
||||
- "Mode: report-only (soft-fail; publish journals on failures)"
|
||||
|
||||
# ----------------------- Temp IP on DEV1 -----------------------
|
||||
- name: Add temporary IP on DEV1
|
||||
raw: "{{ pathprefix }} ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}"
|
||||
register: add_ip
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
# ---------------------------- Idempotent temp IP on DEV1 ----------------------------
|
||||
- name: Add temporary IP on DEV1 (tolerate 'File exists')
|
||||
ansible.builtin.raw: >
|
||||
{{ pathprefix }}
|
||||
ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}
|
||||
register: add_ip
|
||||
changed_when: add_ip.rc == 0
|
||||
failed_when: >
|
||||
add_ip.rc != 0
|
||||
and ('File exists' not in (add_ip.stdout | default('')))
|
||||
and ('File exists' not in (add_ip.stderr | default('')))
|
||||
|
||||
- name: Debug result of adding temp IP to DEV1
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "add_ip.rc={{ add_ip.rc | default('') }}"
|
||||
- "add_ip.stdout={{ (add_ip.stdout | default('')) | trim }}"
|
||||
- "add_ip.stderr={{ (add_ip.stderr | default('')) | trim }}"
|
||||
|
||||
# ---------------------------- Discover MAC via bridge FDB and add static ARP ----------------------------
|
||||
- name: Discover DEV2 MAC via bridge fdb on DEV1 (best-effort)
|
||||
ansible.builtin.raw: >
|
||||
{{ pathprefix }}
|
||||
bridge fdb show {{ dev1_iface }} | grep eth0 | grep -v permanent | grep master | awk '{print $1}' | head -n1
|
||||
register: dev2_mac_scan
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Capture discovered DEV2 MAC (if any)
|
||||
ansible.builtin.set_fact:
|
||||
dev2_mac: "{{ (dev2_mac_scan.stdout | default('') ) | trim }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Dev1 | Normalize '... rev NNNN' → '...-rNNNN'
|
||||
delegate_to: localhost
|
||||
ansible.builtin.set_fact:
|
||||
dev1_fw_norm: >-
|
||||
{{
|
||||
(dev1_banner.stdout | default('') | trim)
|
||||
| regex_replace('\\s*[Rr][Ee][Vv]\\.??\\s*([0-9]+)\\s*$', '-r\\1')
|
||||
}}
|
||||
- name: Clear existing ARP entry for DEV2 on DEV1 (best-effort)
|
||||
ansible.builtin.raw: >
|
||||
{{ pathprefix }}
|
||||
ip neigh del {{ dev2_host }} dev {{ dev1_iface }} 2>/dev/null || true
|
||||
register: dev2_arp_del
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Publish → controls | custom_field_set fw_version (Dev1)
|
||||
- name: Add static ARP entry on DEV1 (locks DEV2 IP → discovered MAC)
|
||||
when: dev2_mac is defined and dev2_mac | length > 0
|
||||
ansible.builtin.raw: >
|
||||
{{ pathprefix }}
|
||||
ip neigh add {{ dev2_host }} lladdr {{ dev2_mac }} dev {{ dev1_iface }} nud permanent
|
||||
register: dev2_arp_add
|
||||
changed_when: dev2_arp_add.rc == 0
|
||||
failed_when: >
|
||||
dev2_arp_add.rc != 0
|
||||
and ('File exists' not in (dev2_arp_add.stdout | default('')))
|
||||
and ('File exists' not in (dev2_arp_add.stderr | default('')))
|
||||
|
||||
- name: Debug ARP action summary on DEV1
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "dev2_mac={{ dev2_mac | default('UNSET') }}"
|
||||
- "arp_add.rc={{ dev2_arp_add.rc | default('') }}"
|
||||
- "arp_add.out={{ (dev2_arp_add.stdout | default('')) | trim }}"
|
||||
- "arp_add.err={{ (dev2_arp_add.stderr | default('')) | trim }}"
|
||||
|
||||
- name: Note skipping static ARP add (no MAC discovered)
|
||||
when: dev2_mac is not defined or dev2_mac | length == 0
|
||||
ansible.builtin.debug:
|
||||
msg: "No suitable dynamic MAC found via bridge fdb; skipping static ARP add on DEV1"
|
||||
|
||||
# ---------------------------- ARP refresh ----------------------------
|
||||
- name: Refresh ARP #1
|
||||
ansible.builtin.raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
|
||||
- name: Refresh ARP #1
|
||||
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
# ----------------------- Local tunnel prep -----------------------
|
||||
- name: Pick a free local TCP port for the tunnel
|
||||
delegate_to: localhost
|
||||
ansible.builtin.uri:
|
||||
shell: |
|
||||
for i in $(seq 1 50); do
|
||||
p="$(shuf -i 20000-39999 -n 1)"
|
||||
ss -ltn | awk '{print $4}' | grep -qE "(:|\.)${p}$" || { echo $p; exit 0; }
|
||||
done
|
||||
exit 1
|
||||
register: pick_port
|
||||
failed_when: false
|
||||
|
||||
- name: Record chosen port and create control dir
|
||||
delegate_to: localhost
|
||||
shell: "mktemp -d"
|
||||
register: mktemp_dir
|
||||
failed_when: false
|
||||
|
||||
- name: Set facts for tunnel paths
|
||||
delegate_to: localhost
|
||||
set_fact:
|
||||
_local_port: "{{ (pick_port.stdout | default('') | trim) }}"
|
||||
_ctrl_dir: "{{ (mktemp_dir.stdout | default('') | trim) }}"
|
||||
_ctrl_sock: "{{ (mktemp_dir.stdout | default('') | trim) }}/ssh_tunnel_ctl"
|
||||
|
||||
# ----------------------- AIRPING #2 -----------------------
|
||||
- name: Refresh ARP #2
|
||||
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
# ----------------------- Start tunnel -----------------------
|
||||
- name: Start SSH tunnel via DEV1
|
||||
delegate_to: localhost
|
||||
shell: |
|
||||
set -e
|
||||
USER="{{ dev1_user }}"
|
||||
HOST="{{ ansible_host | default(inventory_hostname) }}"
|
||||
sshpass -p '{{ dev1_pass }}' ssh -f -N {{ ssh_opts_common }} \
|
||||
-M -S "{{ _ctrl_sock }}" \
|
||||
-L "127.0.0.1:{{ _local_port }}:{{ dev2_host }}:{{ dev2_port }}" \
|
||||
"${USER}@${HOST}"
|
||||
args: { executable: /bin/bash }
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Probe tunnel
|
||||
delegate_to: localhost
|
||||
shell: "nc -z -w5 127.0.0.1 {{ _local_port }}"
|
||||
register: nc_probe
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Set tunnel_ok
|
||||
delegate_to: localhost
|
||||
set_fact:
|
||||
tunnel_ok: "{{ nc_probe.rc == 0 }}"
|
||||
|
||||
# ----------------------- DEV1 banner -----------------------
|
||||
- name: Dev1 | Probe banner
|
||||
delegate_to: localhost
|
||||
shell: |
|
||||
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
|
||||
"{{ dev1_user }}@{{ ansible_host | default(inventory_hostname) }}" \
|
||||
"cat /etc/banner | grep -i rev | head -n1"
|
||||
register: dev1_banner
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Dev1 | Normalize banner → X.X.X-rYYYY (POSIX tools)
|
||||
delegate_to: localhost
|
||||
shell: |
|
||||
printf '%s\n' "{{ dev1_banner.stdout | trim }}" \
|
||||
| awk -F '|' '{print $1}' \
|
||||
| sed -E 's/[[:space:]]+rev[[:space:]]+/-r/' \
|
||||
| grep -Eo '[0-9]+\.[0-9]+\.[0-9]+-r[0-9]+' || true
|
||||
register: dev1_fw_clean_cmd
|
||||
changed_when: false
|
||||
|
||||
- name: Dev1 | Set final fw string
|
||||
delegate_to: localhost
|
||||
set_fact:
|
||||
dev1_fw_clean: "{{ dev1_fw_clean_cmd.stdout | trim }}"
|
||||
|
||||
- name: Debug Dev1 normalized firmware
|
||||
delegate_to: localhost
|
||||
debug:
|
||||
msg: "Dev1 fw_version {{ dev1_fw_clean | default('N/A') }}"
|
||||
|
||||
- name: Publish Dev1 fw_version
|
||||
delegate_to: localhost
|
||||
uri:
|
||||
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
|
||||
method: POST
|
||||
user: "{{ rmq_user }}"
|
||||
@@ -72,212 +236,86 @@
|
||||
'inscope_device': (ansible_hostname | default(inventory_hostname)),
|
||||
'task_name': 'custom_field_set',
|
||||
'task_add1': 'fw_version',
|
||||
'task_result': (dev1_fw_norm | default('unknown'))
|
||||
'task_result': (dev1_fw_clean if (dev1_fw_clean|length>0) else 'unavailable')
|
||||
} | to_json }}"
|
||||
payload_encoding: "string"
|
||||
changed_when: false
|
||||
|
||||
# ----------------------------- TUNNEL PREP (update-indoor blocks copied) -----------------------------
|
||||
- name: Add temporary IP on DEV1 (tolerate 'File exists')
|
||||
ansible.builtin.raw: >
|
||||
{{ pathprefix }}
|
||||
ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}
|
||||
register: add_ip
|
||||
changed_when: add_ip.rc == 0
|
||||
failed_when: add_ip.rc != 0
|
||||
and ('File exists' not in (add_ip.stdout | default('')))
|
||||
and ('File exists' not in (add_ip.stderr | default('')))
|
||||
|
||||
- name: Pick a free local TCP port for the tunnel (controller)
|
||||
# ----------------------- DEV2 auth -----------------------
|
||||
- name: Try DEV2 login
|
||||
when: tunnel_ok | default(false)
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
pick() {
|
||||
for i in $(seq 1 25); do
|
||||
p="$(shuf -i 20000-39999 -n 1)"
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
if ! ss -ltn | awk '{print $4}' | grep -qE "(:|\.)${p}$"; then
|
||||
echo "$p"; return 0
|
||||
fi
|
||||
else
|
||||
if ! nc -z 127.0.0.1 "$p" >/dev/null 2>&1; then
|
||||
echo "$p"; return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
pick
|
||||
register: pick_port
|
||||
changed_when: false
|
||||
|
||||
- name: Stop if no free local port was found
|
||||
ansible.builtin.meta: end_host
|
||||
when: (pick_port.stdout | trim | length) == 0
|
||||
|
||||
- name: Record chosen local port
|
||||
delegate_to: localhost
|
||||
set_fact:
|
||||
_local_port: "{{ pick_port.stdout | trim }}"
|
||||
|
||||
- name: Create ControlMaster socket dir (mktemp)
|
||||
delegate_to: localhost
|
||||
set_fact:
|
||||
_ctrl_dir: "{{ lookup('pipe', 'mktemp -d') }}"
|
||||
|
||||
- name: Compose ControlMaster socket path
|
||||
delegate_to: localhost
|
||||
set_fact:
|
||||
_ctrl_sock: "{{ _ctrl_dir }}/ssh_tunnel_ctl"
|
||||
|
||||
|
||||
- name: Start SSH ControlMaster and forward 127.0.0.1:local → DEV2:22 via DEV1
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
USER="{{ dev1_user }}"
|
||||
HOST="{{ ansible_host | default(inventory_hostname) }}"
|
||||
sshpass -p '{{ dev1_pass }}' ssh -f -N \
|
||||
-o PreferredAuthentications=password -o PubkeyAuthentication=no \
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||
-o NumberOfPasswordPrompts=1 -o ConnectTimeout=30 \
|
||||
-M -S "{{ _ctrl_sock }}" \
|
||||
-L "127.0.0.1:{{ _local_port }}:{{ dev2_host }}:{{ dev2_port }}" \
|
||||
"${USER}@${HOST}"
|
||||
args: { executable: /bin/bash }
|
||||
register: start_tunnel
|
||||
changed_when: true
|
||||
|
||||
- name: Probe TCP reachability to DEV2 through the tunnel
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: "nc -z -w5 127.0.0.1 {{ _local_port }}"
|
||||
register: nc_probe
|
||||
changed_when: false
|
||||
shell: |
|
||||
for f in basicpass basicpass2; do
|
||||
PORT="{{ _local_port }}"
|
||||
if sshpass -f "$f" ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
||||
-p "$PORT" root@127.0.0.1 "echo ok" >/dev/null 2>&1; then
|
||||
echo "$f"; exit 0
|
||||
fi
|
||||
done
|
||||
exit 1
|
||||
register: dev2_auth
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Stop if tunnel TCP probe failed
|
||||
ansible.builtin.meta: end_host
|
||||
when: nc_probe.rc != 0
|
||||
|
||||
# ----------------------------- DEV2 AUTH PICK (exact task sequence from update-indoor) -----------------------------
|
||||
- name: Try DEV2 login with 'basicpass' (root)
|
||||
- name: Set dev2_passfile_used
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
PORT="{{ _local_port }}"
|
||||
sshpass -f basicpass ssh \
|
||||
-o AddressFamily=inet \
|
||||
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
||||
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
||||
-o ConnectTimeout=30 \
|
||||
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
|
||||
register: dev2_try_basicpass
|
||||
changed_when: false
|
||||
set_fact:
|
||||
dev2_passfile_used: "{{ (dev2_auth.rc == 0) | ternary(dev2_auth.stdout | trim, 'NONE') }}"
|
||||
|
||||
# ----------------------- AIRPING #3 -----------------------
|
||||
- name: Refresh ARP #3
|
||||
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Select 'basicpass' if previous login succeeded
|
||||
when: dev2_try_basicpass.rc == 0
|
||||
delegate_to: localhost
|
||||
ansible.builtin.set_fact:
|
||||
dev2_passfile_used: "basicpass"
|
||||
changed_when: false
|
||||
|
||||
- name: Try DEV2 login with 'basicpass2' (only if first failed)
|
||||
when: dev2_passfile_used is not defined
|
||||
# ----------------------- DEV2 firmux (simplified, literal) -----------------------
|
||||
- name: Dev2 | Read /usr/lib/release/firmux
|
||||
when: tunnel_ok | default(false) and dev2_passfile_used != 'NONE'
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
PORT="{{ _local_port }}"
|
||||
sshpass -f basicpass2 ssh \
|
||||
-o AddressFamily=inet \
|
||||
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
||||
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
||||
-o ConnectTimeout=30 \
|
||||
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
|
||||
register: dev2_try_basicpass2
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Select 'basicpass2' if previous login succeeded
|
||||
when: dev2_passfile_used is not defined and dev2_try_basicpass2.rc == 0
|
||||
delegate_to: localhost
|
||||
ansible.builtin.set_fact:
|
||||
dev2_passfile_used: "basicpass2"
|
||||
changed_when: false
|
||||
|
||||
- name: Mark DEV2 auth as NONE if both attempts failed
|
||||
when: dev2_passfile_used is not defined
|
||||
delegate_to: localhost
|
||||
ansible.builtin.set_fact:
|
||||
dev2_passfile_used: "NONE"
|
||||
changed_when: false
|
||||
|
||||
# ----------------------------- DEV2 firmware read (update-indoor firmux primary check) -----------------------------
|
||||
- name: Dev2 | Read /usr/lib/release/firmux (if present)
|
||||
when: dev2_passfile_used != "NONE"
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
shell: |
|
||||
PORT="{{ _local_port }}"
|
||||
sshpass -f "{{ dev2_passfile_used }}" ssh \
|
||||
-o AddressFamily=inet \
|
||||
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
||||
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
||||
-o ConnectTimeout=30 \
|
||||
-o ConnectTimeout=15 \
|
||||
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
||||
"cat /usr/lib/release/firmux 2>/dev/null || true"
|
||||
args: { executable: /bin/bash }
|
||||
register: dev2_firmux
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Dev2 | Fallback to banner 'rev' if firmux not available
|
||||
when: dev2_passfile_used != "NONE" and ((dev2_firmux.stdout | default('') | trim) | length == 0)
|
||||
- name: Dev2 | Extract firmware version line (prefer 'rev', else first non-empty)
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
PORT="{{ _local_port }}"
|
||||
sshpass -f "{{ dev2_passfile_used }}" ssh \
|
||||
-o AddressFamily=inet \
|
||||
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
|
||||
-o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 \
|
||||
-o ConnectTimeout=30 \
|
||||
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
|
||||
"grep -i rev /etc/banner 2>/dev/null | head -n1 || true"
|
||||
args: { executable: /bin/bash }
|
||||
register: dev2_banner
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Dev2 | Choose raw string (firmux preferred, else banner)
|
||||
delegate_to: localhost
|
||||
ansible.builtin.set_fact:
|
||||
indoor_fw_raw: >-
|
||||
{{
|
||||
(dev2_firmux.stdout | default('') | trim)
|
||||
if ((dev2_firmux.stdout | default('') | trim) | length > 0)
|
||||
else (dev2_banner.stdout | default('') | trim)
|
||||
}}
|
||||
|
||||
- name: Dev2 | Normalize to X.Y.Z-rNNNN (convert trailing 'rev NNNN' → '-rNNNN')
|
||||
delegate_to: localhost
|
||||
ansible.builtin.set_fact:
|
||||
run_once: true
|
||||
set_fact:
|
||||
indoor_fw_norm: >-
|
||||
{{
|
||||
((indoor_fw_raw | default('') | trim | lower) is search('-r[0-9]+$'))
|
||||
| ternary(
|
||||
(indoor_fw_raw | default('') | trim),
|
||||
((indoor_fw_raw | default('') | trim) | regex_replace('\\s*[Rr][Ee][Vv]\\.??\\s*([0-9]+)\\s*$', '-r\\1'))
|
||||
)
|
||||
(
|
||||
(
|
||||
dev2_firmux.stdout | default('') | regex_replace('\r','')
|
||||
) | split('\n')
|
||||
| map('trim')
|
||||
| select('truthy')
|
||||
| list
|
||||
) | select('match', '(?i).*\\brev\\s*[0-9]+.*')
|
||||
| list
|
||||
| first
|
||||
| default(
|
||||
((dev2_firmux.stdout | default('') | regex_replace('\r',''))
|
||||
| split('\n')
|
||||
| map('trim')
|
||||
| select('truthy')
|
||||
| list
|
||||
| first
|
||||
| default(''))
|
||||
)
|
||||
| trim
|
||||
}}
|
||||
|
||||
- name: Publish → controls | custom_field_set indoor_fwver (Dev2)
|
||||
when:
|
||||
- dev2_passfile_used != "NONE"
|
||||
- indoor_fw_norm is defined
|
||||
- (indoor_fw_norm | length) > 0
|
||||
- name: Publish Dev2 indoor_fwver
|
||||
delegate_to: localhost
|
||||
ansible.builtin.uri:
|
||||
uri:
|
||||
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
|
||||
method: POST
|
||||
user: "{{ rmq_user }}"
|
||||
@@ -293,31 +331,41 @@
|
||||
'inscope_device': (ansible_hostname | default(inventory_hostname)),
|
||||
'task_name': 'custom_field_set',
|
||||
'task_add1': 'indoor_fwver',
|
||||
'task_result': indoor_fw_norm
|
||||
'task_result': (indoor_fw_norm if indoor_fw_norm|length>0 else 'unavailable')
|
||||
} | to_json }}"
|
||||
payload_encoding: "string"
|
||||
changed_when: false
|
||||
|
||||
post_tasks:
|
||||
- name: Close SSH ControlMaster (best-effort)
|
||||
delegate_to: localhost
|
||||
ansible.builtin.shell: "ssh -S '{{ _ctrl_sock | default('/dev/null') }}' -O exit 2>/dev/null || true"
|
||||
changed_when: false
|
||||
- name: "✅ Cleanup start"
|
||||
debug:
|
||||
msg: "Closing tunnel and removing temporary IP"
|
||||
|
||||
- name: Refresh ARP #4
|
||||
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove tunnel control dir (best-effort)
|
||||
- name: Close SSH ControlMaster
|
||||
delegate_to: localhost
|
||||
ansible.builtin.file:
|
||||
shell: "ssh -S '{{ _ctrl_sock | default('/dev/null') }}' -O exit 2>/dev/null || true"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove tunnel control dir
|
||||
delegate_to: localhost
|
||||
file:
|
||||
path: "{{ _ctrl_dir | default('/tmp/none') }}"
|
||||
state: absent
|
||||
ignore_errors: true
|
||||
|
||||
- name: Remove temporary IP on DEV1 (tolerate 'Cannot assign requested address')
|
||||
ansible.builtin.raw: >
|
||||
{{ pathprefix }}
|
||||
ip a del {{ dev2_side_ip }} dev {{ dev1_iface }}
|
||||
register: del_ip
|
||||
changed_when: del_ip.rc == 0
|
||||
failed_when: del_ip.rc != 0
|
||||
and ('Cannot assign requested address' not in (del_ip.stdout | default('')))
|
||||
and ('Cannot assign requested address' not in (del_ip.stderr | default('')))
|
||||
- name: Remove temporary IP
|
||||
raw: "{{ pathprefix }} ip a del {{ dev2_side_ip }} dev {{ dev1_iface }}"
|
||||
failed_when: false
|
||||
ignore_errors: true
|
||||
|
||||
- name: "✅ Completed | Device processed"
|
||||
debug:
|
||||
msg:
|
||||
- "Device: {{ inventory_hostname }}"
|
||||
- "Status: DONE"
|
||||
|
||||
Reference in New Issue
Block a user