Files
ansible-worker/files/ansible-playbooks/sot-updater.yml
2026-09-23 16:26:08 +03:00

3250 lines
124 KiB
YAML

# sot-updater.yml — Read fw on Dev1 + Dev2, publish to NetBox via Rabbit (telemetry mode, no upgrade scheduling)
- name: Read fw on Dev1 + Dev2, publish NetBox custom fields (full base, AIRPINGs, soft-fail telemetry, no upgrade scheduling)
hosts: all
gather_facts: no
vars:
pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; "
dev1_user: "root"
dev1_pass: "wavewave"
ssh_timeout: 30
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=15
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) }}"
rmq_pass: "{{ lookup('env','RMQ_PASS') | default('change_me', true) }}"
rmq_vhost: "{{ lookup('env','RMQ_VHOST') | default('app', true) }}"
rmq_exchange: "{{ lookup('env','RMQ_EXCHANGE') | default('controls', true) }}"
control_queue: "{{ lookup('env','CONTROLQUEUE') | default('queue_controls', true) }}"
# Cloud API (offboard provisioning telemetry)
cloud_config_base: "https://cloud.ikeja.co.za/v2/devices"
cloud_bearer: "{{ lookup('env','CLOUD_BEARER') | default('', true) }}"
# NetBox API (delta compare; user may override)
netbox_url: "http://netbox.gt-tiso.ikeja.co.za"
netbox_token: "7648e4f5ee370cda7834682e61b47c2ee8e95623"
# --- Retry attempt tracking (default 0 if missing) ---
requeue_attempt: "{{ (lookup('env','REQUEUE_ATTEMPT') | default('0', true)) | int }}"
pre_tasks:
- name: "NB preflight | Verify script exists"
delegate_to: localhost
ansible.builtin.stat:
path: "/opt/containers/ansible-worker/app/nb_onedevice_update.py"
register: nb_script
- name: "NB preflight | Abort softly if script missing"
when: not nb_script.stat.exists
delegate_to: localhost
ansible.builtin.debug:
msg:
- "NB preflight skipped: /opt/containers/ansible-worker/app/nb_onedevice_update.py not found."
- "Tip: adjust chdir/path or script name."
- name: "NB preflight | Run nb_onedevice_update.py for {{ inventory_hostname }}"
when: nb_script.stat.exists
delegate_to: localhost
environment:
PYTHONUNBUFFERED: "1"
args:
chdir: "/opt/containers/ansible-worker/app"
executable: /bin/bash
shell: |
set -o pipefail
python3 -u nb_onedevice_update.py "{{ inventory_hostname }}" --chatty 2>&1
register: nb_preflight
changed_when: false
failed_when: false
- name: "NB preflight | Show results"
when: nb_script.stat.exists
delegate_to: localhost
debug:
msg:
- "rc={{ nb_preflight.rc }}"
- "stdout_lines:"
- "{{ (nb_preflight.stdout_lines | default(['<no stdout>'])) }}"
- "stderr_lines:"
- "{{ (nb_preflight.stderr_lines | default(['<no stderr>'])) }}"
- "raw stdout (joined): {{ nb_preflight.stdout | default('') | trim }}"
- name: "NB preflight | Detect cloud failure"
when: nb_script.stat.exists
delegate_to: localhost
vars:
_out: "{{ (nb_preflight.stdout | default('') | regex_replace('\r','')) }}"
_lines: >-
{{ _out.split('\n') | map('trim') | list }}
_cloud_fail_line: >-
{{ (_lines | select('match', '^FAIL\\s+Cloud\\b') | list | last | default('')) }}
_error_line: >-
{{ (_lines | select('match', '^FAIL\\s+') | list | last | default('')) }}
_rc_is_3: "{{ (nb_preflight.rc | default(1)) | int == 3 }}"
_cloud_error_type: >-
{%- if _error_line is search('network error')
and _error_line is search('500 error responses') -%}
cloud_http_500
{%- elif (_cloud_fail_line | length) > 0 -%}
cloud_logic
{%- elif _rc_is_3 -%}
cloud_other
{%- else -%}
none
{%- endif -%}
_cloud_retryable: >-
{{ _cloud_error_type not in ['cloud_http_500'] }}
set_fact:
cloud_fail_line: "{{ _cloud_fail_line }}"
cloud_bad: "{{ _rc_is_3 or (( _cloud_fail_line | length ) > 0) }}"
cloud_error_type: "{{ _cloud_error_type }}"
cloud_retryable: "{{ _cloud_retryable }}"
# --- Extract upgrade_cmd line if any ---
- name: "NB preflight | Extract upgrade_cmd line"
when: nb_script.stat.exists
delegate_to: localhost
set_fact:
nb_upgrade_line: >-
{{
(
nb_preflight.stdout_lines | default([]) | map('regex_replace','\r','') | map('trim')
| select('match', '^NB:\\s*upgrade_cmd\\s*=')
| list | first
) | default('')
}}
- name: "NB preflight | Parse upgrade_cmd value"
when: nb_upgrade_line | length > 0
delegate_to: localhost
shell: |
printf '%s\n' "{{ nb_upgrade_line }}" | awk -F'=' '{print $2}' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
args: { executable: /bin/bash }
register: up_cmd_sh
changed_when: false
- name: "NB preflight | Set parsed upgrade_cmd"
when: nb_upgrade_line | length > 0
delegate_to: localhost
set_fact:
nb_upgrade_cmd: "{{ up_cmd_sh.stdout | default('') | trim }}"
- name: "NB preflight | Debug parsed upgrade_cmd"
when: nb_upgrade_line | length > 0
delegate_to: localhost
debug:
msg:
- "upgrade_cmd='{{ nb_upgrade_cmd }}' (len={{ nb_upgrade_cmd|length }})"
- name: "NB preflight | Parse OK line"
when: nb_script.stat.exists
delegate_to: localhost
vars:
nb_lines: >-
{{ (nb_preflight.stdout | default('') | regex_replace('\r','')) | split('\n') | map('trim') | list }}
nb_ok_line: >-
{{ (nb_lines | select('match', '^OK\\s+') | list | last | default('')) }}
nb_tokens: >-
{{ (nb_ok_line | regex_replace('^OK\\s+', '')).split() }}
nb_kv: >-
{{
dict(
nb_tokens
| select('match', '^[a-zA-Z_]+=')
| map('split', '=', 1)
| map('list')
)
}}
set_fact:
nb_ok: "{{ (nb_preflight.rc|default(1)) == 0 and (nb_ok_line|length)>0 }}"
nb_dev: "{{ (nb_tokens|first|default('')) if (nb_tokens|length>0) else '' }}"
nb_ip: "{{ nb_kv.get('ip','') }}"
nb_fw: "{{ nb_kv.get('fw','') }}"
nb_node: "{{ nb_kv.get('node','') }}"
nb_sector: "{{ nb_kv.get('sector','') }}"
nb_small: "{{ nb_kv.get('small','') }}"
nb_ok_line: "{{ nb_ok_line }}"
- name: "NB preflight | Detect IP change"
when: nb_script.stat.exists
delegate_to: localhost
vars:
out: "{{ (nb_preflight.stdout | default('') | regex_replace('\r','')) }}"
reason: >-
{%- if 'IP: moving ' in out -%}moving
{%- elif 'IP: create new ' in out -%}create new
{%- elif 'IP: pruning stale ' in out -%}pruning stale
{%- else -%}none{%- endif -%}
set_fact:
nb_ip_changed: "{{ reason != 'none' }}"
nb_change_reason: "{{ reason }}"
- name: "NB preflight | Verdict"
when: nb_script.stat.exists
delegate_to: localhost
debug:
msg:
- "Preflight verdict:"
- "Cloud fail: {{ cloud_bad|default(false) }}"
- "IP changed: {{ nb_ip_changed|default(false) }}"
- "Reason: {{ nb_change_reason|default('none') }}"
- name: "NB preflight | Compute next_attempt"
delegate_to: localhost
set_fact:
next_attempt: "{{ (requeue_attempt | int) + 1 }}"
# --- Requeue control: only 3 attempts, 300s apart ---
- name: "NB preflight | Publish delayed requeue (300s (10s)) if needed"
when:
- nb_script.stat.exists
- (nb_ip_changed | default(false)) or (
(cloud_bad | default(false)) and
(cloud_error_type | default('none')) != 'cloud_http_500'
)
- (requeue_attempt | int) < 3
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/app/deviceconfig.delayed/publish"
method: POST
user: "admin"
password: "change_me"
force_basic_auth: true
status_code: 200
return_content: yes
headers: { content-type: "application/json" }
body_format: json
body:
properties:
content_type: "application/json"
headers: { x-delay: 900000 }
routing_key: "deviceconfig"
payload: "{{ {
'inscope_device': (ansible_hostname | default(inventory_hostname)),
'task_name': 'sot-updater',
'attempt': next_attempt
} | to_json }}"
payload_encoding: "string"
register: rmq_requeue
changed_when: false
failed_when: false
- name: "NB preflight | Log requeue publish response"
when:
- nb_script.stat.exists
- ((cloud_bad | default(false)) or (nb_ip_changed | default(false)))
- (requeue_attempt | int) < 3
delegate_to: localhost
debug:
msg:
- "Requeue published (attempt {{ (requeue_attempt | int) + 1 }})"
- "HTTP: {{ rmq_requeue.status|default('n/a') }}"
- "JSON: {{ rmq_requeue.json|default('<none>') }}"
- name: "NB preflight | Stop host after requeue"
when:
- nb_script.stat.exists
- (
(nb_ip_changed | default(false)) or
(
(cloud_bad | default(false)) and
(cloud_error_type | default('none')) != 'cloud_http_500'
)
)
meta: end_host
- name: "NB preflight | Pause 1s if OK"
when:
- nb_script.stat.exists
- not ((cloud_bad | default(false)) or (nb_ip_changed | default(false)))
delegate_to: localhost
pause:
seconds: 1
# ----------------------- NetBox baseline (for delta compare) -----------------------
- name: NB baseline | Init baseline facts
delegate_to: localhost
set_fact:
nb_baseline_ok: false
nb_device_id: ""
nb_custom_fields_current: {}
changed_when: false
- name: NB baseline | Debug netbox_url used
delegate_to: localhost
debug:
msg:
- "netbox_url={{ netbox_url }}"
- "inventory_hostname={{ inventory_hostname }}"
changed_when: false
- name: NB baseline | Read id and custom_fields (fields)
delegate_to: localhost
uri:
url: "{{ netbox_url }}/api/dcim/devices/?name={{ inventory_hostname }}&fields=id,custom_fields"
method: GET
headers:
Authorization: "Token {{ netbox_token }}"
Accept: "application/json"
return_content: true
status_code: 200
timeout: 10
register: nb_cf_lookup
failed_when: false
changed_when: false
- name: NB baseline | Debug raw response if json not parsed
delegate_to: localhost
debug:
msg:
- "nb_cf_lookup.status={{ nb_cf_lookup.status | default('NA') }}"
- "nb_cf_lookup.content_type={{ nb_cf_lookup.content_type | default('NA') }}"
- "nb_cf_lookup.json_defined={{ (nb_cf_lookup.json is defined) }}"
- "nb_cf_lookup.content_head={{ (nb_cf_lookup.content | default('') | regex_replace('\\n',' ') )[:200] }}"
when: nb_cf_lookup.json is not defined
changed_when: false
- name: NB baseline | Set baseline facts (best effort; never crash play)
delegate_to: localhost
block:
- name: NB baseline | Choose parsed payload (json or content)
set_fact:
nb_cf_json_local: >-
{{
(nb_cf_lookup.json is defined and nb_cf_lookup.json)
| ternary(nb_cf_lookup.json, (nb_cf_lookup.content | from_json))
}}
changed_when: false
- name: NB baseline | Set baseline facts from payload
set_fact:
nb_baseline_ok: true
nb_device_id: "{{ (nb_cf_json_local.results | default([]) | first | default({})).id | default('') }}"
nb_custom_fields_current: "{{ (nb_cf_json_local.results | default([]) | first | default({})).custom_fields | default({}) }}"
changed_when: false
rescue:
- name: NB baseline | Baseline parse failed (disable delta mode)
set_fact:
nb_baseline_ok: false
nb_device_id: ""
nb_custom_fields_current: {}
changed_when: false
- name: NB baseline | Abort delta mode if baseline missing or unusable
delegate_to: localhost
set_fact:
nb_baseline_ok: false
nb_custom_fields_current: {}
nb_device_id: ""
when: nb_device_id | length == 0
changed_when: false
- name: NB baseline | Debug baseline ok and a sample key
delegate_to: localhost
debug:
msg:
- "nb_baseline_ok={{ nb_baseline_ok }}"
- "nb_device_id={{ nb_device_id }}"
- "baseline fw_version={{ nb_custom_fields_current.get('fw_version','<missing>') }}"
changed_when: false
# ----------------------- Scan status tracking (journal finalization) -----------------------
- name: Init scan status tracking
delegate_to: localhost
set_fact:
scan_ok: []
scan_fail: []
changed_when: false
# ----------------------- Delta tracking (skip unchanged custom fields) -----------------------
- name: Delta tracking | Init diff flag
delegate_to: localhost
set_fact:
non_ts_diff_found: false
changed_when: false
# ----------------------- 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
- name: Add temporary IP on DEV1 (tolerate 'File exists')
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 temp IP result
debug:
msg:
- "add_ip.rc={{ add_ip.rc|default('') }}"
- "add_ip.out={{ add_ip.stdout|default('')|trim }}"
- "add_ip.err={{ add_ip.stderr|default('')|trim }}"
- name: Discover DEV2 MAC via bridge fdb
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 MAC
set_fact:
dev2_mac: "{{ dev2_mac_scan.stdout|default('')|trim }}"
- name: Clear existing ARP entry
raw: >
{{ pathprefix }}
ip neigh del {{ dev2_host }} dev {{ dev1_iface }} 2>/dev/null || true
changed_when: false
failed_when: false
- name: Add static ARP if MAC found
when: dev2_mac|length > 0
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 summary
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
when: dev2_mac|length == 0
debug:
msg: "No MAC discovered; skipping static ARP add."
- name: Refresh ARP
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
failed_when: false
ignore_errors: true
# ----------------------- Tunnel setup -----------------------
- name: Pick free local port
delegate_to: localhost
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: Create control dir
delegate_to: localhost
shell: "mktemp -d"
register: mktemp_dir
failed_when: false
- name: Set tunnel facts
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"
- name: Start SSH tunnel
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: Debug nc_probe
delegate_to: localhost
debug:
msg:
- "nc_probe.rc={{ nc_probe.rc | default('n/a') }}"
- "nc_probe.stdout={{ nc_probe.stdout | default('') | trim }}"
- "nc_probe.stderr={{ nc_probe.stderr | default('') | trim }}"
- 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
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 }}"
# ----------------------- Scan tracking: Dev1 fw_version -----------------------
- name: Scan tracking | Dev1 fw_version outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['fw_version'] if ((dev1_fw_clean | default('') | length) > 0) else []) }}"
scan_fail: "{{ scan_fail + (['fw_version=unavailable'] if ((dev1_fw_clean | default('') | length) == 0) else []) }}"
changed_when: false
- name: Publish Dev1 fw_version
when:
- (not (nb_baseline_ok | default(false))) or (((dev1_fw_clean if (dev1_fw_clean|length>0) else 'unavailable')) != (nb_custom_fields_current.get('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 }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'fw_version',
'task_result': (dev1_fw_clean if (dev1_fw_clean|length>0) else 'unavailable')
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed fw_version
when:
- (not (nb_baseline_ok | default(false))) or (((dev1_fw_clean if (dev1_fw_clean|length>0) else 'unavailable')) != (nb_custom_fields_current.get('fw_version')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 bootbanks backup firmux → fw_version_backup -----------------------
- name: Dev1 | Read bootbanks backup firmux via jq
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"jq -r '.backup.firmux // empty' /var/run/bootbanks.json 2>/dev/null || true"
register: dev1_fw_backup_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize fw_version_backup value
delegate_to: localhost
set_fact:
fw_version_backup_value: >-
{{
(dev1_fw_backup_raw.stdout | default('') | trim)
if ((dev1_fw_backup_raw.stdout | default('') | trim | length) > 0)
else 'unavailable'
}}
changed_when: false
- name: Dev1 | fw_version_backup raw debug
delegate_to: localhost
debug:
msg:
- "dev1_fw_backup_raw.rc={{ dev1_fw_backup_raw.rc | default('n/a') }}"
- "dev1_fw_backup_raw.stdout={{ dev1_fw_backup_raw.stdout | default('') | trim }}"
- "dev1_fw_backup_raw.stderr={{ dev1_fw_backup_raw.stderr | default('') | trim }}"
- name: Dev1 | fw_version_backup final debug
delegate_to: localhost
debug:
msg:
- "fw_version_backup_value={{ fw_version_backup_value | default('unset') }}"
- name: Scan tracking | Dev1 fw_version_backup outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['fw_version_backup'] if ((fw_version_backup_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['fw_version_backup=unavailable'] if ((fw_version_backup_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev1 fw_version_backup
when:
- (not (nb_baseline_ok | default(false))) or ((fw_version_backup_value) != (nb_custom_fields_current.get('fw_version_backup')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'fw_version_backup',
'task_result': fw_version_backup_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed fw_version_backup
when:
- (not (nb_baseline_ok | default(false))) or ((fw_version_backup_value) != (nb_custom_fields_current.get('fw_version_backup')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 SSID count (jq-based, schema-aware) -----------------------
- name: Dev1 | SSID profiles summary via jq (AP radios only)
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"jq -r '
[
.wireless.radios
| to_entries[]
| select(.value.mode == \"ap\")
| (.value.vaps // [])[ ]
| select(has(\"ssid\"))
| {ssid:.ssid, enabled:(.enabled // false)}
]
| unique_by(.ssid, .enabled)
| {
e: (map(select(.enabled==true)) | length),
d: (map(select(.enabled==false)) | length)
}
| \"\\(.e + .d) (\\(.e)e \\(.d)d)\"
' /tmp/config.json 2>/dev/null || echo '0 (0e 0d)'"
register: ssid_profiles_summary_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize SSID profiles summary
delegate_to: localhost
set_fact:
multissidfix_value: "{{ (ssid_profiles_summary_raw.stdout | default('0 (0e 0d)')) | trim }}"
# ----------------------- Scan tracking: Dev1 multissidfix -----------------------
- name: Scan tracking | Dev1 multissidfix outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + ['multissidfix'] }}"
changed_when: false
- name: Dev1 | multissidfix debug
delegate_to: localhost
when: multissidfix_value is defined
debug:
msg: "multissidfix_value={{ multissidfix_value }}"
- name: Publish Dev1 multissidfix custom field
when:
- (not (nb_baseline_ok | default(false))) or ((multissidfix_value) != (nb_custom_fields_current.get('multissidfix')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'multissidfix',
'task_result': multissidfix_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed multissidfix
when:
- (not (nb_baseline_ok | default(false))) or ((multissidfix_value) != (nb_custom_fields_current.get('multissidfix')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 auto_ssid config flag → auto_ssid -----------------------
- name: Dev1 | Read auto_ssid from config.json
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"grep -E '\"auto_ssid\":' /tmp/config.json 2>/dev/null | head -n1 | sed -nE 's/^[[:space:]]*\"auto_ssid\"[[:space:]]*:[[:space:]]*(true|false)[[:space:]]*,?[[:space:]]*$/\1/p' || true"
register: auto_ssid_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize auto_ssid value
delegate_to: localhost
vars:
auto_ssid_raw_clean: "{{ auto_ssid_raw.stdout | default('') | trim | lower }}"
set_fact:
auto_ssid_value: >-
{{
auto_ssid_raw_clean
if auto_ssid_raw_clean in ['true', 'false']
else 'n/a'
}}
changed_when: false
# ----------------------- Scan tracking: Dev1 auto_ssid -----------------------
- name: Scan tracking | Dev1 auto_ssid outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['auto_ssid'] if ((auto_ssid_value | default('n/a')) != 'n/a') else []) }}"
scan_fail: "{{ scan_fail + (['auto_ssid=n/a'] if ((auto_ssid_value | default('n/a')) == 'n/a') else []) }}"
changed_when: false
- name: Dev1 | auto_ssid debug
delegate_to: localhost
debug:
msg:
- "auto_ssid_raw={{ auto_ssid_raw.stdout | default('') | trim }}"
- "auto_ssid_value={{ auto_ssid_value | default('n/a') }}"
- name: Publish Dev1 auto_ssid custom field
when:
- (not (nb_baseline_ok | default(false))) or ((auto_ssid_value) != (nb_custom_fields_current.get('auto_ssid')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'auto_ssid',
'task_result': auto_ssid_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed auto_ssid
when:
- (not (nb_baseline_ok | default(false))) or ((auto_ssid_value) != (nb_custom_fields_current.get('auto_ssid')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 scroll24.sh check & publish -----------------------
- name: Dev1 | Check scroll24.sh presence
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"test -f /root/scroll24.sh && echo present || echo missing"
register: scroll24_present_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Set scroll24 presence fact
delegate_to: localhost
set_fact:
scroll24_present: "{{ (scroll24_present_raw.stdout | default('') | trim) == 'present' }}"
- name: Dev1 | Read scroll24.sh version line (may be empty -> v1)
when: scroll24_present | default(false)
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"grep -i 'scroll24_version' /root/scroll24.sh 2>/dev/null | head -n1 || true"
register: scroll24_ver_line
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize scroll24 version
when: scroll24_present | default(false)
delegate_to: localhost
set_fact:
scroll24_ver_clean: >-
{{
(
scroll24_ver_line.stdout | default('') | trim
)
| regex_replace('^.*=', '')
| trim
if (scroll24_ver_line is defined
and (scroll24_ver_line.stdout | default('') | trim | length) > 0)
else 'v1'
}}
- name: Dev1 | Read scroll24 crontab entry (first match)
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"crontab -l 2>/dev/null | grep 'scroll24.sh' | head -n1 || true"
register: scroll24_cron_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize scroll24 cron line
delegate_to: localhost
set_fact:
scroll24_cron_line: "{{ scroll24_cron_raw.stdout | default('') | trim }}"
- name: Dev1 | Derive scroll24 schedule summary
delegate_to: localhost
shell: |
line="{{ scroll24_cron_line | default('') }}"
# No crontab hit or no scroll24.sh line
if [ -z "$line" ]; then
echo nocron
exit 0
fi
# Extract first 5 cron fields
min="$(echo "$line" | awk '{print $1}')"
hour="$(echo "$line" | awk '{print $2}')"
# Pattern 1: */N * * * * -> every N minutes
case "$min" in
'*/'*)
n="${min#*/}"
if [ -n "$n" ]; then
echo "${n}m"
exit 0
fi
;;
esac
# Pattern 2: M */H * * * -> every H hours (we ignore the minute)
case "$hour" in
'*/'*)
h="${hour#*/}"
if [ -n "$h" ]; then
echo "${h}h"
exit 0
fi
;;
esac
# Fallback
echo custom
args:
executable: /bin/bash
register: scroll24_sched_raw
changed_when: false
failed_when: false
- name: Dev1 | Set scroll24 schedule fact
delegate_to: localhost
set_fact:
scroll24_schedule: "{{ scroll24_sched_raw.stdout | default('') | trim }}"
- name: Dev1 | Compute scroll24 value
delegate_to: localhost
set_fact:
scroll24_value: >-
{%- if not (scroll24_present | default(false)) -%}
missing
{%- elif scroll24_schedule in ['nocron', ''] -%}
{{ scroll24_ver_clean | default('v1') }}_nocron
{%- elif scroll24_schedule == 'custom' -%}
{{ scroll24_ver_clean | default('v1') }}_custom
{%- else -%}
{{ scroll24_ver_clean | default('v1') }}_{{ scroll24_schedule }}
{%- endif -%}
# ----------------------- Scan tracking: Dev1 scroll24 -----------------------
- name: Scan tracking | Dev1 scroll24 outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + ['scroll24'] }}"
changed_when: false
- name: Dev1 | scroll24 debug
delegate_to: localhost
debug:
msg:
- "scroll24_present={{ scroll24_present | default(false) }}"
- "scroll24_ver_clean={{ scroll24_ver_clean | default('n/a') }}"
- "scroll24_cron_line={{ scroll24_cron_line | default('<none>') }}"
- "scroll24_schedule={{ scroll24_schedule | default('n/a') }}"
- "scroll24_value={{ scroll24_value | default('n/a') }}"
- name: Publish Dev1 scroll24 custom field
when:
- (not (nb_baseline_ok | default(false))) or ((scroll24_value) != (nb_custom_fields_current.get('scroll24')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'scroll24',
'task_result': scroll24_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed scroll24
when:
- (not (nb_baseline_ok | default(false))) or ((scroll24_value) != (nb_custom_fields_current.get('scroll24')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 wifidebug.sh check & publish -----------------------
- name: Dev1 | Check wifidebug.sh presence
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"test -f /root/wifidebug.sh && echo present || echo missing"
register: wd_present
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Set presence fact
delegate_to: localhost
set_fact:
wifidebug_present: "{{ (wd_present.stdout | default('') | trim) == 'present' }}"
- name: Dev1 | Read wifidebug.sh version (SCRIPTVERSION)
when: wifidebug_present | default(false)
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"grep -m1 'SCRIPTVERSION' /root/wifidebug.sh 2>/dev/null | awk -F'=' '{print \$2}' | tr -d '[:space:]' | sed -E 's/^v?([0-9].*)$/\1/'"
register: wd_version_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize wifidebug version
delegate_to: localhost
set_fact:
wd_version_clean: "{{ wd_version_raw.stdout | default('') | trim }}"
- name: Dev1 | Compute wifidebug value
delegate_to: localhost
set_fact:
wifidebug_value: >-
{%- if not (wifidebug_present | default(false)) -%}missing
{%- elif (wd_version_clean | default('') | length) > 0 -%}v{{ wd_version_clean }}
{%- else -%}n/a{%- endif -%}
# ----------------------- Dev1 connstats.sh check & publish -----------------------
- name: Dev1 | Check connstats.sh presence
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"test -f /root/connstats.sh && echo present || echo missing"
register: cs_present
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Set connstats presence fact
delegate_to: localhost
set_fact:
connstats_present: "{{ (cs_present.stdout | default('') | trim) == 'present' }}"
- name: Dev1 | Read connstats.sh version (SCRIPTVERSION)
when: connstats_present | default(false)
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"grep -m1 'SCRIPTVERSION' /root/connstats.sh 2>/dev/null | awk -F'=' '{print \$2}' | tr -d '[:space:]' | sed -E 's/^v?([0-9].*)$/\1/'"
register: cs_version_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize connstats version
delegate_to: localhost
set_fact:
cs_version_clean: "{{ cs_version_raw.stdout | default('') | trim }}"
- name: Dev1 | Compute connstats value
delegate_to: localhost
set_fact:
connstats_value: >-
{%- if not (connstats_present | default(false)) -%}missing
{%- elif (cs_version_clean | default('') | length) > 0 -%}v{{ cs_version_clean }}
{%- else -%}n/a{%- endif -%}
# ----------------------- Scan tracking: Dev1 connstats -----------------------
- name: Scan tracking | Dev1 connstats outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + ['connstats'] }}"
changed_when: false
- name: Publish Dev1 connstats custom field
when:
- (not (nb_baseline_ok | default(false))) or ((connstats_value) != (nb_custom_fields_current.get('connstats')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'connstats',
'task_result': connstats_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed connstats
when:
- (not (nb_baseline_ok | default(false))) or ((connstats_value) != (nb_custom_fields_current.get('connstats')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Scan tracking: Dev1 wifidebug -----------------------
- name: Scan tracking | Dev1 wifidebug outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + ['wifidebug'] }}"
changed_when: false
- name: Publish Dev1 wifidebug custom field
when:
- (not (nb_baseline_ok | default(false))) or ((wifidebug_value) != (nb_custom_fields_current.get('wifidebug')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'wifidebug',
'task_result': wifidebug_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed wifidebug
when:
- (not (nb_baseline_ok | default(false))) or ((wifidebug_value) != (nb_custom_fields_current.get('wifidebug')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 fw_printenv size → cpe_bootenv -----------------------
- name: Dev1 | Read fw_printenv size (line count)
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"fw_printenv 2>/dev/null | wc -l"
args:
executable: /bin/bash
register: dev1_bootenv_wc
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize cpe_bootenv value
delegate_to: localhost
set_fact:
cpe_bootenv_value: >-
{{
'unavailable'
if (
(dev1_bootenv_wc.rc | default(1)) != 0 or
(dev1_bootenv_wc.stdout | default('') | trim) in ['', '0']
)
else (dev1_bootenv_wc.stdout | default('') | trim)
}}
# ----------------------- Scan tracking: Dev1 cpe_bootenv -----------------------
- name: Scan tracking | Dev1 cpe_bootenv outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['cpe_bootenv'] if ((cpe_bootenv_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['cpe_bootenv=unavailable'] if ((cpe_bootenv_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev1 cpe_bootenv custom field
when:
- (not (nb_baseline_ok | default(false))) or ((cpe_bootenv_value) != (nb_custom_fields_current.get('cpe_bootenv')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'cpe_bootenv',
'task_result': cpe_bootenv_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed cpe_bootenv
when:
- (not (nb_baseline_ok | default(false))) or ((cpe_bootenv_value) != (nb_custom_fields_current.get('cpe_bootenv')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 bandwidth shaping → bw_up / bw_down -----------------------
- name: Dev1 | Read bw_up from ifb0 shaping
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"tc -d class show dev ifb0 2>/dev/null | grep 'root burst' | awk -F' ' '{ print \$12 }'"
args:
executable: /bin/bash
register: dev1_bw_up_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize bw_up value
delegate_to: localhost
set_fact:
bw_up_value: >-
{{
(dev1_bw_up_raw.stdout | default('') | trim)
if ((dev1_bw_up_raw.stdout | default('') | trim | length) > 0)
else 'unavailable'
}}
changed_when: false
- name: Scan tracking | Dev1 bw_up outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['bw_up'] if ((bw_up_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['bw_up=unavailable'] if ((bw_up_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev1 bw_up custom field
when:
- (not (nb_baseline_ok | default(false))) or ((bw_up_value) != (nb_custom_fields_current.get('bw_up')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'bw_up',
'task_result': bw_up_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed bw_up
when:
- (not (nb_baseline_ok | default(false))) or ((bw_up_value) != (nb_custom_fields_current.get('bw_up')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
- name: Dev1 | Read bw_down from eth0 shaping
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"tc -d qdisc show dev eth0 2>/dev/null | grep 'root' | awk -F' ' '{ print \$10 }'"
args:
executable: /bin/bash
register: dev1_bw_down_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize bw_down value
delegate_to: localhost
set_fact:
bw_down_value: >-
{{
(dev1_bw_down_raw.stdout | default('') | trim)
if ((dev1_bw_down_raw.stdout | default('') | trim | length) > 0)
else 'unavailable'
}}
changed_when: false
- name: Scan tracking | Dev1 bw_down outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['bw_down'] if ((bw_down_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['bw_down=unavailable'] if ((bw_down_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev1 bw_down custom field
when:
- (not (nb_baseline_ok | default(false))) or ((bw_down_value) != (nb_custom_fields_current.get('bw_down')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'bw_down',
'task_result': bw_down_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed bw_down
when:
- (not (nb_baseline_ok | default(false))) or ((bw_down_value) != (nb_custom_fields_current.get('bw_down')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev1 hostname (for Dev2 mismatch check) -----------------------
- name: Dev1 | Read kernel hostname
delegate_to: localhost
shell: |
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"cat /proc/sys/kernel/hostname 2>/dev/null || hostname 2>/dev/null || echo ''"
args:
executable: /bin/bash
register: dev1_hostname_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Normalize hostname
delegate_to: localhost
set_fact:
dev1_hostname: >-
{{
(dev1_hostname_raw.stdout | default('') | trim)
if ((dev1_hostname_raw.stdout | default('') | trim | length) > 0)
else (inventory_hostname | default(''))
}}
# ----------------------- Cloud offboard provisioning telemetry -----------------------
- name: Cloud offboard provisioning | Resolve bearer used by nb_onedevice_update.py
delegate_to: localhost
environment:
OFFP_CLOUD_BEARER: "{{ cloud_bearer | default('') }}"
shell: |
if [ -n "${OFFP_CLOUD_BEARER:-}" ]; then
printf '%s\n' "$OFFP_CLOUD_BEARER"
exit 0
fi
python3 - <<'PY'
import ast
from pathlib import Path
path = Path('/opt/containers/ansible-worker/app/nb_onedevice_update.py')
tree = ast.parse(path.read_text())
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if any(isinstance(target, ast.Name) and target.id == 'CLOUD_BEARER' for target in node.targets):
value = ast.literal_eval(node.value)
if isinstance(value, str) and value:
print(value)
raise SystemExit(0)
raise SystemExit(1)
PY
args:
executable: /bin/bash
register: offp_cloud_bearer_cmd
changed_when: false
failed_when: false
- name: Cloud offboard provisioning | Set bearer fact
delegate_to: localhost
set_fact:
offp_cloud_bearer: "{{ offp_cloud_bearer_cmd.stdout | default('') | trim }}"
changed_when: false
- name: Cloud offboard provisioning | Resolve cloud_id and initialize values
delegate_to: localhost
set_fact:
offp_cloud_id: >-
{{
(nb_custom_fields_current.get('cloud_id','') | string | trim)
if ((nb_custom_fields_current.get('cloud_id','') | string | trim | length) > 0)
else (
(
(nb_preflight | default({})).get('stdout','')
| regex_findall('CL: fetch liveness cloud_id=([^\s]+)')
| first
) | default('')
)
}}
offp_config_ok: false
offp_status_value: "missing"
offp_sync_value: "missing"
indoor_offp_vlan_value: "missing"
changed_when: false
- name: Cloud offboard provisioning | Read reported config
when:
- (offp_cloud_id | default('') | length) > 0
- (offp_cloud_bearer | default('') | length) > 0
delegate_to: localhost
uri:
url: "{{ cloud_config_base }}/{{ offp_cloud_id }}/config"
method: GET
headers:
Authorization: "Bearer {{ offp_cloud_bearer }}"
Accept: "application/json"
return_content: true
status_code: 200
timeout: 30
register: offp_config_lookup
failed_when: false
changed_when: false
- name: Cloud offboard provisioning | Normalize reported values
when:
- offp_config_lookup is defined
- (offp_config_lookup.status | default(0) | int) == 200
- offp_config_lookup.json is defined
delegate_to: localhost
vars:
_reported_data: "{{ (offp_config_lookup.json.get('reported', {})).get('data', {}) }}"
_offp_services: "{{ (_reported_data.get('services', {})).get('offboardProvisioning', {}) }}"
_offp_vlan: "{{ (((((((_reported_data.get('offboardProvisioned', {})).get('fox200', {})).get('ethernet', {})).get('ports', {})).get('eth0', {})).get('network', {})).get('dataVlan', {})) }}"
set_fact:
offp_config_ok: true
offp_status_value: >-
{%- if (_offp_services is mapping) and ('isEnabled' in _offp_services) -%}
{%- if _offp_services.get('isEnabled') == true -%}enabled
{%- elif _offp_services.get('isEnabled') == false -%}disabled
{%- else -%}missing{%- endif -%}
{%- else -%}missing{%- endif -%}
offp_sync_value: >-
{%- if (_offp_services is mapping) and ('isSynchronized' in _offp_services) -%}
{%- if _offp_services.get('isSynchronized') == true -%}enabled
{%- elif _offp_services.get('isSynchronized') == false -%}disabled
{%- else -%}missing{%- endif -%}
{%- else -%}missing{%- endif -%}
indoor_offp_vlan_value: >-
{%- if (_offp_vlan is mapping) and ('isEnabled' in _offp_vlan) -%}
{%- set _vlan_id =
(_offp_vlan.get('id') | string | trim)
if (
('id' in _offp_vlan) and
(_offp_vlan.get('id') is not none) and
((_offp_vlan.get('id') | string | trim | length) > 0)
)
else 'missing'
-%}
{%- if _offp_vlan.get('isEnabled') == true -%}enabled-{{ _vlan_id }}
{%- elif _offp_vlan.get('isEnabled') == false -%}disabled-{{ _vlan_id }}
{%- else -%}missing{%- endif -%}
{%- else -%}missing{%- endif -%}
changed_when: false
- name: Cloud offboard provisioning | Debug
delegate_to: localhost
debug:
msg:
- "cloud_id={{ offp_cloud_id | default('<missing>') }}"
- "config_http={{ offp_config_lookup.status | default('not-requested') }}"
- "offp_status={{ offp_status_value | default('missing') }}"
- "offp_sync={{ offp_sync_value | default('missing') }}"
- "indoor_offp_vlan={{ indoor_offp_vlan_value | default('missing') }}"
- name: Scan tracking | Cloud offboard provisioning outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['offp_status', 'offp_sync', 'indoor_offp_vlan'] if (offp_config_ok | default(false)) else []) }}"
scan_fail: "{{ scan_fail + ([] if (offp_config_ok | default(false)) else ['offp_status=unavailable', 'offp_sync=unavailable', 'indoor_offp_vlan=unavailable']) }}"
changed_when: false
- name: Publish Cloud offp_status custom field
when:
- offp_config_ok | default(false)
- (not (nb_baseline_ok | default(false))) or ((offp_status_value) != (nb_custom_fields_current.get('offp_status')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'offp_status',
'task_result': offp_status_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed offp_status
when:
- offp_config_ok | default(false)
- (not (nb_baseline_ok | default(false))) or ((offp_status_value) != (nb_custom_fields_current.get('offp_status')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
- name: Publish Cloud offp_sync custom field
when:
- offp_config_ok | default(false)
- (not (nb_baseline_ok | default(false))) or ((offp_sync_value) != (nb_custom_fields_current.get('offp_sync')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'offp_sync',
'task_result': offp_sync_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed offp_sync
when:
- offp_config_ok | default(false)
- (not (nb_baseline_ok | default(false))) or ((offp_sync_value) != (nb_custom_fields_current.get('offp_sync')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
- name: Publish Cloud indoor_offp_vlan custom field
when:
- offp_config_ok | default(false)
- (not (nb_baseline_ok | default(false))) or ((indoor_offp_vlan_value) != (nb_custom_fields_current.get('indoor_offp_vlan')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_offp_vlan',
'task_result': indoor_offp_vlan_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_offp_vlan
when:
- offp_config_ok | default(false)
- (not (nb_baseline_ok | default(false))) or ((indoor_offp_vlan_value) != (nb_custom_fields_current.get('indoor_offp_vlan')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev2 auth + firmux -----------------------
- name: Try DEV2 login
when: tunnel_ok|default(false)
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
# First try: basicpass
if timeout {{ ssh_timeout }}s sshpass -f basicpass ssh {{ ssh_opts_common }} \
-o ConnectionAttempts=1 -o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" "echo ok" >/dev/null 2>&1; then
echo basicpass
exit 0
fi
# Second try: basicpass2
if timeout {{ ssh_timeout }}s sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
-o ConnectionAttempts=1 -o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" "echo ok" >/dev/null 2>&1; then
echo basicpass2
exit 0
fi
# Both failed
echo NONE
exit 1
args:
executable: /bin/bash
register: dev2_auth
failed_when: false
ignore_errors: true
- name: Set dev2_passfile_used
delegate_to: localhost
set_fact:
dev2_passfile_used: >-
{{
(
(dev2_auth is defined)
and ((dev2_auth.rc | default(1)) == 0)
)
| ternary((dev2_auth.stdout | default('') | trim), 'NONE')
}}
- name: Dev2 | Auth debug
delegate_to: localhost
debug:
msg:
- "dev2_auth.rc={{ dev2_auth.rc | default('n/a') }}"
- "dev2_auth.stdout={{ dev2_auth.stdout | default('') | trim }}"
- "dev2_passfile_used={{ dev2_passfile_used }}"
- name: Dev2 | Read firmux (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-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
# ------ LLDP candidate IP when Dev2 login failed (tunnel up but Dev2 unreachable) ------
- name: Dev1 | Try LLDP-derived Dev2 IP (auth to Dev2 failed)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
delegate_to: localhost
shell: |
DIGITS="{{ inventory_hostname | regex_replace('[^0-9]', '') }}"
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"cat /var/run/lldp_server.json 2>/dev/null | grep ${DIGITS} -A 10 | grep address | grep -vE 'subtype|ipv6' | awk -F'\"' '{ print \$4 }' | head -n1"
register: lldp_ip_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Show LLDP candidate IP (Dev2, after SSH auth failure)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
delegate_to: localhost
debug:
msg:
- >-
LLDP-derived Dev2 IP (raw best-effort):
{{
(lldp_ip_raw.stdout | default('') | trim)
if ((lldp_ip_raw.stdout | default('') | trim | length) > 0)
else '<none>'
}}
- >-
LLDP 10.0.0.0/8 usable:
{{
((lldp_ip_raw.stdout | default('') | trim) is match('^10\.'))
| ternary('yes (10/8)', 'no (not 10/8) or none')
}}
- name: Set LLDP IP fact (10.0.0.0/8 only)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
delegate_to: localhost
set_fact:
lldp_dev2_ip_raw: "{{ lldp_ip_raw.stdout | default('') | trim }}"
lldp_dev2_ip: >-
{{
(lldp_ip_raw.stdout | default('') | trim)
if ((lldp_ip_raw.stdout | default('') | trim) is match('^10\.'))
else ''
}}
- name: Try DEV2 login via direct LLDP IP
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
delegate_to: localhost
shell: |
for f in basicpass basicpass2; do
if timeout 20s sshpass -f "$f" ssh {{ ssh_opts_common }} \
-o ConnectionAttempts=1 -o ConnectTimeout=10 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" "echo ok" >/dev/null 2>&1; then
echo "$f"; exit 0
fi
done
exit 1
register: dev2_auth_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | LLDP path selection debug
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
delegate_to: localhost
debug:
msg:
- "LLDP candidate IP={{ lldp_dev2_ip }}"
- "LLDP auth rc={{ dev2_auth_lldp.rc | default('n/a') }}"
- "passfile (if any) will follow in next step"
- name: Set dev2_passfile_used_lldp
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
delegate_to: localhost
set_fact:
dev2_passfile_used_lldp: >-
{{
((dev2_auth_lldp.rc | default(1)) == 0)
| ternary(dev2_auth_lldp.stdout | default('') | trim, 'NONE')
}}
- name: Dev2 | LLDP passfile decision
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
delegate_to: localhost
debug:
msg:
- "Using passfile for LLDP path: {{ dev2_passfile_used_lldp }}"
- name: Dev2 | Check TCP/22 reachability (LLDP)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
delegate_to: localhost
shell: "nc -z -w3 {{ lldp_dev2_ip }} 22"
register: lldp_ssh_tcp
changed_when: false
failed_when: false
ignore_errors: true
# ------ LLDP IPv6 fallback (Dev1 → Dev2 on link-local) ------
- name: Dev1 | Try LLDP-derived Dev2 IPv6 (auth to Dev2 failed, IPv4 LLDP unusable)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
delegate_to: localhost
shell: |
DIGITS="{{ inventory_hostname | regex_replace('[^0-9]', '') }}"
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"cat /var/run/lldp_server.json 2>/dev/null | grep ${DIGITS} -A 15 | grep 'address_ipv6' | awk -F'\\\"' '{ print \\$4 }' | head -n1"
args:
executable: /bin/bash
register: lldp_ip6_raw
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev1 | Set LLDP IPv6 fact
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
delegate_to: localhost
set_fact:
lldp_dev2_ip6: "{{ lldp_ip6_raw.stdout | default('') | trim }}"
- name: Dev1 | Show LLDP IPv6 candidate
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
delegate_to: localhost
debug:
msg:
- "LLDP-derived Dev2 IPv6 (best-effort): {{ lldp_dev2_ip6 | default('<none>') }}"
- name: Dev1 | Copy Dev2 passfiles for IPv6 LLDP use
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
copy:
src: "{{ item }}"
dest: "/tmp/{{ item }}"
mode: "0600"
loop: "{{ dev2_passfiles }}"
ignore_errors: true
- name: Dev1 | Try DEV2 login via LLDP IPv6 (nested SSH via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
if [ -z "$IP6" ]; then
exit 1
fi
for f in {{ dev2_passfiles | join(' ') }}; do
if sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"timeout {{ ssh_timeout }}s sshpass -f '/tmp/${f}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' 'echo ok' " \
>/dev/null 2>&1; then
echo "$f"
exit 0
fi
done
exit 1
args:
executable: /bin/bash
register: dev2_auth_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | LLDP IPv6 path debug
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
delegate_to: localhost
debug:
msg:
- "LLDP IPv6 candidate={{ lldp_dev2_ip6 | default('<none>') }}"
- "LLDP IPv6 auth rc={{ dev2_auth_lldp6.rc | default('n/a') }}"
- name: Set dev2_passfile_used_lldp6
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
delegate_to: localhost
set_fact:
dev2_passfile_used_lldp6: >-
{{
((dev2_auth_lldp6.rc | default(1)) == 0)
| ternary(dev2_auth_lldp6.stdout | default('') | trim, 'NONE')
}}
- name: Dev2 | IPv6 LLDP passfile decision
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
delegate_to: localhost
debug:
msg:
- "Using passfile for LLDP IPv6 path: {{ dev2_passfile_used_lldp6 | default('NONE') }}"
- name: Dev2 | Read firmux (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
'cat /usr/lib/release/firmux 2>/dev/null || true'"
args:
executable: /bin/bash
register: dev2_firmux_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read firmux (direct LLDP IP)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"cat /usr/lib/release/firmux 2>/dev/null || true"
register: dev2_firmux_lldp
failed_when: false
ignore_errors: true
- name: Dev2 | Choose firmux output (tunnel, IPv4 LLDP, then IPv6 LLDP)
delegate_to: localhost
set_fact:
dev2_firmux_text: >-
{{
(dev2_firmux.stdout | default('') | trim)
if ((dev2_firmux.stdout | default('') | trim | length) > 0)
else (
(dev2_firmux_lldp.stdout | default('') | trim)
if (
(dev2_firmux_lldp is defined) and
((dev2_firmux_lldp.stdout | default('') | trim | length) > 0)
)
else (dev2_firmux_lldp6.stdout | default('') | trim)
)
}}
- name: Dev2 | firmux selection debug
delegate_to: localhost
debug:
msg:
- "dev2_firmux_text='{{ dev2_firmux_text | default('') | regex_replace('\n','\\n') }}'"
- "indoor_fw_norm='{{ indoor_fw_norm | default('') }}'"
- name: Dev2 | Extract firmware version line
delegate_to: localhost
run_once: true
set_fact:
indoor_fw_norm: >-
{{
(
(dev2_firmux_text | default('') | regex_replace('\r',''))|split('\n')
| map('trim') | select('truthy') | list
) | select('match','(?i).*\\brev\\s*[0-9]+.*')
| list | first | default(
((dev2_firmux_text | default('') | regex_replace('\r',''))
| split('\n')|map('trim')|select('truthy')|list|first|default(''))
) | trim
}}
- name: Dev2 | firmux selection debug
delegate_to: localhost
debug:
msg:
- "dev2_firmux_text='{{ dev2_firmux_text | default('') | regex_replace('\n','\\n') }}'"
- "indoor_fw_norm='{{ indoor_fw_norm | default('') }}'"
# ----------------------- Scan tracking: Dev2 indoor_fwver -----------------------
- name: Scan tracking | Dev2 indoor_fwver outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['indoor_fwver'] if ((indoor_fw_norm | default('') | length) > 0) else []) }}"
scan_fail: "{{ scan_fail + (['indoor_fwver=unavailable'] if ((indoor_fw_norm | default('') | length) == 0) else []) }}"
changed_when: false
- name: Publish Dev2 indoor_fwver
when:
- (not (nb_baseline_ok | default(false))) or (((indoor_fw_norm if indoor_fw_norm|length>0 else 'unavailable')) != (nb_custom_fields_current.get('indoor_fwver')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_fwver',
'task_result': (indoor_fw_norm if indoor_fw_norm|length>0 else 'unavailable')
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_fwver
when:
- (not (nb_baseline_ok | default(false))) or (((indoor_fw_norm if indoor_fw_norm|length>0 else 'unavailable')) != (nb_custom_fields_current.get('indoor_fwver')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev2 bootbanks backup firmux → indoor_fwver_backup -----------------------
- name: Dev2 | Read bootbanks backup firmux (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"jq -r '.backup.firmux // empty' /var/run/bootbanks.json 2>/dev/null || true"
args:
executable: /bin/bash
register: dev2_fwver_backup_tunnel
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read bootbanks backup firmux (LLDP IPv4)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp is defined
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"jq -r '.backup.firmux // empty' /var/run/bootbanks.json 2>/dev/null || true"
args:
executable: /bin/bash
register: dev2_fwver_backup_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read bootbanks backup firmux (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
'jq -r \".backup.firmux // empty\" /var/run/bootbanks.json 2>/dev/null || true'"
args:
executable: /bin/bash
register: dev2_fwver_backup_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read prepared_backup marker version (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} -o ConnectTimeout={{ ssh_timeout }} -p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" 'f=$(ls -1 /root/prepared_backup_* 2>/dev/null | head -n1); f=${f##*/}; ver=$(printf "%s\n" "$f" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+-r[0-9]+" | head -n1); [ -n "$ver" ] && echo "${ver/-r/ rev }" || true'
args:
executable: /bin/bash
register: dev2_fwver_backup_marker_tunnel
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read prepared_backup marker version (LLDP IPv4)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp is defined
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout=15 "{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" 'f=$(ls -1 /root/prepared_backup_* 2>/dev/null | head -n1); f=${f##*/}; ver=$(printf "%s\n" "$f" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+-r[0-9]+" | head -n1); [ -n "$ver" ] && echo "${ver/-r/ rev }" || true'
args:
executable: /bin/bash
register: dev2_fwver_backup_marker_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read prepared_backup marker version (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} "{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" "sshpass -f '/tmp/${F}' ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout={{ ssh_timeout }} '{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' 'f=\$(ls -1 /root/prepared_backup_* 2>/dev/null | head -n1); f=\${f##*/}; ver=\$(printf \"%s\n\" \"\$f\" | grep -Eo \"[0-9]+\.[0-9]+\.[0-9]+-r[0-9]+\" | head -n1); [ -n \"\$ver\" ] && echo \"\${ver/-r/ rev }\" || true'"
args:
executable: /bin/bash
register: dev2_fwver_backup_marker_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Select bootbanks backup firmux output
delegate_to: localhost
set_fact:
indoor_fwver_backup_raw: >-
{{
(dev2_fwver_backup_tunnel.stdout | default('') | trim)
if (
(dev2_fwver_backup_tunnel is defined) and
((dev2_fwver_backup_tunnel.stdout | default('') | trim | length) > 0)
)
else (
(dev2_fwver_backup_lldp.stdout | default('') | trim)
if (
(dev2_fwver_backup_lldp is defined) and
((dev2_fwver_backup_lldp.stdout | default('') | trim | length) > 0)
)
else (
(dev2_fwver_backup_lldp6.stdout | default('') | trim)
if (
(dev2_fwver_backup_lldp6 is defined) and
((dev2_fwver_backup_lldp6.stdout | default('') | trim | length) > 0)
)
else (
(dev2_fwver_backup_marker_tunnel.stdout | default('') | trim)
if (
(dev2_fwver_backup_marker_tunnel is defined) and
((dev2_fwver_backup_marker_tunnel.stdout | default('') | trim | length) > 0)
)
else (
(dev2_fwver_backup_marker_lldp.stdout | default('') | trim)
if (
(dev2_fwver_backup_marker_lldp is defined) and
((dev2_fwver_backup_marker_lldp.stdout | default('') | trim | length) > 0)
)
else (dev2_fwver_backup_marker_lldp6.stdout | default('') | trim)
)
)
)
)
}}
changed_when: false
- name: Dev2 | Normalize indoor_fwver_backup value
delegate_to: localhost
set_fact:
indoor_fwver_backup_value: >-
{{
(indoor_fwver_backup_raw | default('') | trim)
if ((indoor_fwver_backup_raw | default('') | trim | length) > 0)
else '0.0.0 rev 0'
}}
changed_when: false
- name: Dev2 | indoor_fwver_backup raw debug
delegate_to: localhost
debug:
msg:
- "tunnel rc={{ dev2_fwver_backup_tunnel.rc | default('n/a') }} out={{ dev2_fwver_backup_tunnel.stdout | default('') | trim }} err={{ dev2_fwver_backup_tunnel.stderr | default('') | trim }}"
- "lldp4 rc={{ dev2_fwver_backup_lldp.rc | default('n/a') }} out={{ dev2_fwver_backup_lldp.stdout | default('') | trim }} err={{ dev2_fwver_backup_lldp.stderr | default('') | trim }}"
- "lldp6 rc={{ dev2_fwver_backup_lldp6.rc | default('n/a') }} out={{ dev2_fwver_backup_lldp6.stdout | default('') | trim }} err={{ dev2_fwver_backup_lldp6.stderr | default('') | trim }}"
- name: Dev2 | indoor_fwver_backup final debug
delegate_to: localhost
debug:
msg:
- "indoor_fwver_backup_value={{ indoor_fwver_backup_value | default('unset') }}"
# ----------------------- Scan tracking: Dev2 indoor_fwver_backup -----------------------
- name: Scan tracking | Dev2 indoor_fwver_backup outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['indoor_fwver_backup'] if ((indoor_fwver_backup_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['indoor_fwver_backup=unavailable'] if ((indoor_fwver_backup_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev2 indoor_fwver_backup
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_fwver_backup_value) != (nb_custom_fields_current.get('indoor_fwver_backup')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_fwver_backup',
'task_result': indoor_fwver_backup_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_fwver_backup
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_fwver_backup_value) != (nb_custom_fields_current.get('indoor_fwver_backup')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev2 fw_printenv size → indoor_bootenv -----------------------
- name: Dev2 | Read fw_printenv size (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"fw_printenv 2>/dev/null | wc -l"
args:
executable: /bin/bash
register: dev2_bootenv_wc_tunnel
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read fw_printenv size (LLDP)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp is defined
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"fw_printenv 2>/dev/null | wc -l"
args:
executable: /bin/bash
register: dev2_bootenv_wc_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read fw_printenv size (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
'fw_printenv 2>/dev/null | wc -l'"
args:
executable: /bin/bash
register: dev2_bootenv_wc_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Select fw_printenv size output
delegate_to: localhost
set_fact:
indoor_bootenv_raw: >-
{{
(dev2_bootenv_wc_tunnel.stdout | default('') | trim)
if (
(dev2_bootenv_wc_tunnel is defined) and
((dev2_bootenv_wc_tunnel.rc | default(1)) == 0) and
((dev2_bootenv_wc_tunnel.stdout | default('') | trim | length) > 0)
)
else (
(dev2_bootenv_wc_lldp.stdout | default('') | trim)
if (
(dev2_bootenv_wc_lldp is defined) and
((dev2_bootenv_wc_lldp.rc | default(1)) == 0) and
((dev2_bootenv_wc_lldp.stdout | default('') | trim | length) > 0)
)
else (
(dev2_bootenv_wc_lldp6.stdout | default('') | trim)
if (
(dev2_bootenv_wc_lldp6 is defined) and
((dev2_bootenv_wc_lldp6.rc | default(1)) == 0) and
((dev2_bootenv_wc_lldp6.stdout | default('') | trim | length) > 0)
)
else ''
)
)
}}
- name: Dev2 | Normalize indoor_bootenv value
delegate_to: localhost
set_fact:
indoor_bootenv_value: >-
{{
'unavailable'
if (indoor_bootenv_raw | default('') | trim) in ['', '0']
else (indoor_bootenv_raw | default('') | trim)
}}
# ----------------------- Scan tracking: Dev2 indoor_bootenv -----------------------
- name: Scan tracking | Dev2 indoor_bootenv outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['indoor_bootenv'] if ((indoor_bootenv_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['indoor_bootenv=unavailable'] if ((indoor_bootenv_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev2 indoor_bootenv custom field
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_bootenv_value) != (nb_custom_fields_current.get('indoor_bootenv')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_bootenv',
'task_result': indoor_bootenv_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_bootenv
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_bootenv_value) != (nb_custom_fields_current.get('indoor_bootenv')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev2 hostname check + publish -----------------------
- name: Dev2 | Read hostname (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"cat /proc/sys/kernel/hostname 2>/dev/null || hostname 2>/dev/null || echo ''"
args:
executable: /bin/bash
register: dev2_hostname_tunnel
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read hostname (LLDP IPv4)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp is defined
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"cat /proc/sys/kernel/hostname 2>/dev/null || hostname 2>/dev/null || echo ''"
args:
executable: /bin/bash
register: dev2_hostname_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read hostname (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
'cat /proc/sys/kernel/hostname 2>/dev/null || hostname 2>/dev/null || echo '''"
args:
executable: /bin/bash
register: dev2_hostname_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Select hostname output
delegate_to: localhost
set_fact:
indoor_hostname_raw: >-
{{
(dev2_hostname_tunnel.stdout | default('') | trim)
if (
(dev2_hostname_tunnel is defined) and
((dev2_hostname_tunnel.stdout | default('') | trim | length) > 0)
)
else (
(dev2_hostname_lldp.stdout | default('') | trim)
if (
(dev2_hostname_lldp is defined) and
((dev2_hostname_lldp.stdout | default('') | trim | length) > 0)
)
else (
(dev2_hostname_lldp6.stdout | default('') | trim)
if (
(dev2_hostname_lldp6 is defined) and
((dev2_hostname_lldp6.stdout | default('') | trim | length) > 0)
)
else ''
)
)
}}
- name: Dev2 | Normalize indoor_hostname
delegate_to: localhost
set_fact:
indoor_hostname_value: >-
{{
'unavailable'
if (indoor_hostname_raw | default('') | trim) == ''
else (
(
'!' ~ (indoor_hostname_raw | default('') | trim)
)
if (
(dev1_hostname | default(inventory_hostname))
!= (indoor_hostname_raw | default('') | trim)
)
else (indoor_hostname_raw | default('') | trim)
)
}}
# ----------------------- Scan tracking: Dev2 indoor_hostname -----------------------
- name: Scan tracking | Dev2 indoor_hostname outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['indoor_hostname'] if ((indoor_hostname_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['indoor_hostname=unavailable'] if ((indoor_hostname_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Dev2 | indoor_hostname debug
delegate_to: localhost
debug:
msg:
- "dev1_hostname={{ dev1_hostname | default('n/a') }}"
- "indoor_hostname_raw={{ indoor_hostname_raw | default('n/a') }}"
- "indoor_hostname_value={{ indoor_hostname_value | default('n/a') }}"
- name: Publish Dev2 indoor_hostname custom field
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_hostname_value) != (nb_custom_fields_current.get('indoor_hostname')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_hostname',
'task_result': indoor_hostname_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_hostname
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_hostname_value) != (nb_custom_fields_current.get('indoor_hostname')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev2 payredirect config flag -> payredirect -----------------------
- name: Dev2 | Read payredirect (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"cat /tmp/config.json 2>/dev/null | grep -i block -A2 | grep enab | head -n1"
args:
executable: /bin/bash
register: dev2_payredirect_tunnel
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read payredirect (LLDP IPv4)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp is defined
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"cat /tmp/config.json 2>/dev/null | grep -i block -A2 | grep enab | head -n1"
args:
executable: /bin/bash
register: dev2_payredirect_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read payredirect (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
'cat /tmp/config.json 2>/dev/null | grep -i block -A2 | grep enab | head -n1'"
args:
executable: /bin/bash
register: dev2_payredirect_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Select payredirect output
delegate_to: localhost
set_fact:
payredirect_raw: >-
{{
(dev2_payredirect_tunnel.stdout | default('') | trim)
if (
(dev2_payredirect_tunnel is defined) and
((dev2_payredirect_tunnel.stdout | default('') | trim | length) > 0)
)
else (
(dev2_payredirect_lldp.stdout | default('') | trim)
if (
(dev2_payredirect_lldp is defined) and
((dev2_payredirect_lldp.stdout | default('') | trim | length) > 0)
)
else (
(dev2_payredirect_lldp6.stdout | default('') | trim)
if (
(dev2_payredirect_lldp6 is defined) and
((dev2_payredirect_lldp6.stdout | default('') | trim | length) > 0)
)
else ''
)
)
}}
changed_when: false
- name: Dev2 | Normalize payredirect value
delegate_to: localhost
vars:
_payredirect_raw_clean: "{{ payredirect_raw | default('') | trim | lower }}"
set_fact:
payredirect_value: "{{ 'true' if 'true' in _payredirect_raw_clean else ('false' if 'false' in _payredirect_raw_clean else 'na') }}"
changed_when: false
- name: Dev2 | payredirect debug
delegate_to: localhost
debug:
msg:
- "tunnel rc={{ dev2_payredirect_tunnel.rc | default('n/a') }} out={{ dev2_payredirect_tunnel.stdout | default('') | trim }} err={{ dev2_payredirect_tunnel.stderr | default('') | trim }}"
- "lldp4 rc={{ dev2_payredirect_lldp.rc | default('n/a') }} out={{ dev2_payredirect_lldp.stdout | default('') | trim }} err={{ dev2_payredirect_lldp.stderr | default('') | trim }}"
- "lldp6 rc={{ dev2_payredirect_lldp6.rc | default('n/a') }} out={{ dev2_payredirect_lldp6.stdout | default('') | trim }} err={{ dev2_payredirect_lldp6.stderr | default('') | trim }}"
- "payredirect_raw={{ payredirect_raw | default('') | trim }}"
- "payredirect_value={{ payredirect_value | default('na') }}"
- name: Scan tracking | Dev2 payredirect outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['payredirect'] if ((payredirect_value | default('na')) != 'na') else []) }}"
scan_fail: "{{ scan_fail + (['payredirect=na'] if ((payredirect_value | default('na')) == 'na') else []) }}"
changed_when: false
- name: Publish Dev2 payredirect custom field
when:
- (not (nb_baseline_ok | default(false))) or ((payredirect_value) != (nb_custom_fields_current.get('payredirect')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'payredirect',
'task_result': (payredirect_value | string | lower)
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed payredirect
when:
- (not (nb_baseline_ok | default(false))) or ((payredirect_value) != (nb_custom_fields_current.get('payredirect')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
- name: Indoor | Normalize Dev2 passfile facts (avoid undefined)
delegate_to: localhost
set_fact:
dev2_passfile_used_lldp: "{{ dev2_passfile_used_lldp | default('NONE') }}"
dev2_passfile_used_lldp6: "{{ dev2_passfile_used_lldp6 | default('NONE') }}"
changed_when: false
# ----------------------- Dev2 indoor_ip resolution (ppp0/eth0.4000 preferred) -----------------------
- name: Indoor | Compute fallback connected IP (as used for Dev2 reachability)
delegate_to: localhost
set_fact:
indoor_conn_ip_fallback: >-
{%- if (tunnel_ok | default(false)) and (dev2_passfile_used | default('NONE')) != 'NONE' -%}
{{ dev2_host }}
{%- elif (dev2_passfile_used_lldp is defined)
and (dev2_passfile_used_lldp | default('NONE')) != 'NONE'
and (lldp_dev2_ip | default('') | length) > 0 -%}
{{ lldp_dev2_ip }}
{%- elif (dev2_passfile_used_lldp6 is defined)
and (dev2_passfile_used_lldp6 | default('NONE')) != 'NONE'
and (lldp_dev2_ip6 | default('') | length) > 0 -%}
{{ lldp_dev2_ip6 }}
{%- else -%}
na
{%- endif -%}
changed_when: false
- name: Indoor | Normalize LLDP IP facts (avoid undefined)
delegate_to: localhost
set_fact:
lldp_dev2_ip: "{{ lldp_dev2_ip | default('') }}"
lldp_dev2_ip6: "{{ lldp_dev2_ip6 | default('') }}"
changed_when: false
- name: Dev2 | Read ppp0 10/8 IPv4 (preferred)
when:
- (dev2_passfile_used | default('NONE')) != 'NONE'
or ((dev2_passfile_used_lldp | default('NONE')) != 'NONE')
or ((dev2_passfile_used_lldp6 | default('NONE')) != 'NONE')
delegate_to: localhost
shell: |
set -o pipefail
# Choose connection method (tunnel > LLDP IPv4 > LLDP IPv6)
if [ "{{ dev2_passfile_used | default('NONE') }}" != "NONE" ]; then
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"ip -o -4 addr show dev ppp0 2>/dev/null | awk '{print \$4}' | cut -d/ -f1 | grep -m1 '^10\\.' || true"
exit 0
fi
if [ "{{ dev2_passfile_used_lldp | default('NONE') }}" != "NONE" ] && [ -n "{{ lldp_dev2_ip | default('') }}" ]; then
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"ip -o -4 addr show dev ppp0 2>/dev/null | awk '{print \$4}' | cut -d/ -f1 | grep -m1 '^10\\.' || true"
exit 0
fi
if [ "{{ dev2_passfile_used_lldp6 | default('NONE') }}" != "NONE" ] && [ -n "{{ lldp_dev2_ip6 | default('') }}" ]; then
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
\"ip -o -4 addr show dev ppp0 2>/dev/null | awk '{print \\\\\$4}' | cut -d/ -f1 | grep -m1 '^10\\\\.' || true\""
exit 0
fi
exit 0
args:
executable: /bin/bash
register: dev2_ppp0_ip
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read eth0.4000 10/8 IPv4 (only if ppp0 not usable)
when:
- (dev2_ppp0_ip.stdout | default('') | trim) == ''
- (dev2_passfile_used | default('NONE')) != 'NONE'
or ((dev2_passfile_used_lldp | default('NONE')) != 'NONE')
or ((dev2_passfile_used_lldp6 | default('NONE')) != 'NONE')
delegate_to: localhost
shell: |
set -o pipefail
# Choose connection method (tunnel > LLDP IPv4 > LLDP IPv6)
if [ "{{ dev2_passfile_used | default('NONE') }}" != "NONE" ]; then
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} \
-o ConnectTimeout={{ ssh_timeout }} \
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"ip -o -4 addr show dev eth0.4000 2>/dev/null | awk '{print \$4}' | cut -d/ -f1 | grep -m1 '^10\\.' || true"
exit 0
fi
if [ "{{ dev2_passfile_used_lldp | default('NONE') }}" != "NONE" ] && [ -n "{{ lldp_dev2_ip | default('') }}" ]; then
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout=15 \
"{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" \
"ip -o -4 addr show dev eth0.4000 2>/dev/null | awk '{print \$4}' | cut -d/ -f1 | grep -m1 '^10\\.' || true"
exit 0
fi
if [ "{{ dev2_passfile_used_lldp6 | default('NONE') }}" != "NONE" ] && [ -n "{{ lldp_dev2_ip6 | default('') }}" ]; then
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} \
"{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" \
"sshpass -f '/tmp/${F}' ssh \
-o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
-o ConnectTimeout={{ ssh_timeout }} \
'{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' \
\"ip -o -4 addr show dev eth0.4000 2>/dev/null | awk '{print \\\\\$4}' | cut -d/ -f1 | grep -m1 '^10\\\\.' || true\""
exit 0
fi
exit 0
args:
executable: /bin/bash
register: dev2_eth04000_ip
changed_when: false
failed_when: false
ignore_errors: true
- name: Indoor | Select indoor_ip (ppp0 > eth0.4000 > fallback)
delegate_to: localhost
set_fact:
indoor_ip_value: >-
{%- if (dev2_ppp0_ip.stdout | default('') | trim) is match('^10\.') -%}
{{ dev2_ppp0_ip.stdout | trim }}
{%- elif (dev2_eth04000_ip.stdout | default('') | trim) is match('^10\.') -%}
{{ dev2_eth04000_ip.stdout | trim }}
{%- else -%}
{{ indoor_conn_ip_fallback | default('na') | trim }}
{%- endif -%}
changed_when: false
- name: Dev2 | indoor_ip debug
delegate_to: localhost
debug:
msg:
- "ppp0_ip={{ dev2_ppp0_ip.stdout | default('') | trim }}"
- "eth0.4000_ip={{ dev2_eth04000_ip.stdout | default('') | trim }}"
- "fallback_ip={{ indoor_conn_ip_fallback | default('') | trim }}"
- "selected indoor_ip={{ indoor_ip_value | default('') | trim }}"
- name: Publish Dev2 indoor_ip custom field
when:
- (not (nb_baseline_ok | default(false))) or (((indoor_ip_value | default('na') | trim)) != (nb_custom_fields_current.get('indoor_ip')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_ip',
'task_result': (indoor_ip_value | default('na') | trim)
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_ip
when:
- (not (nb_baseline_ok | default(false))) or (((indoor_ip_value | default('na') | trim)) != (nb_custom_fields_current.get('indoor_ip')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- Dev2 default gateway interface → indoor_defgw -----------------------
- name: Dev2 | Read default gateway interface (tunnel)
when:
- tunnel_ok | default(false)
- dev2_passfile_used != 'NONE'
delegate_to: localhost
shell: |
PORT="{{ _local_port }}"
sshpass -f "{{ dev2_passfile_used }}" ssh {{ ssh_opts_common }} -o ConnectTimeout={{ ssh_timeout }} -p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" "ip route show 2>/dev/null | grep default | awk '{print \$NF}' | head -n1 || true"
args:
executable: /bin/bash
register: dev2_defgw_tunnel
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read default gateway interface (LLDP IPv4)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (lldp_dev2_ip | default('') | length) > 0
- dev2_passfile_used_lldp is defined
- dev2_passfile_used_lldp != 'NONE'
delegate_to: localhost
shell: |
sshpass -f "{{ dev2_passfile_used_lldp }}" ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout=15 "{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" "ip route show 2>/dev/null | grep default | awk '{print \$NF}' | head -n1 || true"
args:
executable: /bin/bash
register: dev2_defgw_lldp
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Read default gateway interface (LLDP IPv6 via Dev1)
when:
- tunnel_ok | default(false)
- dev2_passfile_used == 'NONE'
- (dev2_passfile_used_lldp is not defined or dev2_passfile_used_lldp == 'NONE')
- lldp_dev2_ip6 is defined
- lldp_dev2_ip6 | length > 0
- dev2_passfile_used_lldp6 is defined
- dev2_passfile_used_lldp6 != 'NONE'
delegate_to: localhost
shell: |
IP6="{{ lldp_dev2_ip6 }}"
F="{{ dev2_passfile_used_lldp6 }}"
if [ -z "$IP6" ] || [ -z "$F" ] || [ "$F" = "NONE" ]; then
exit 0
fi
sshpass -p '{{ dev1_pass }}' ssh {{ ssh_opts_common }} "{{ dev1_user }}@{{ ansible_host|default(inventory_hostname) }}" "sshpass -f '/tmp/${F}' ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -o ConnectTimeout={{ ssh_timeout }} '{{ dev2_ssh_user }}@\[${IP6}%{{ dev1_iface }}\]' "ip route show 2>/dev/null | grep default | awk '{print \\\$NF}' | head -n1 || true""
args:
executable: /bin/bash
register: dev2_defgw_lldp6
changed_when: false
failed_when: false
ignore_errors: true
- name: Dev2 | Select default gateway interface output
delegate_to: localhost
set_fact:
indoor_defgw_raw: >-
{{
(dev2_defgw_tunnel.stdout | default('') | trim)
if (
(dev2_defgw_tunnel is defined) and
((dev2_defgw_tunnel.stdout | default('') | trim | length) > 0)
)
else (
(dev2_defgw_lldp.stdout | default('') | trim)
if (
(dev2_defgw_lldp is defined) and
((dev2_defgw_lldp.stdout | default('') | trim | length) > 0)
)
else (
(dev2_defgw_lldp6.stdout | default('') | trim)
if (
(dev2_defgw_lldp6 is defined) and
((dev2_defgw_lldp6.stdout | default('') | trim | length) > 0)
)
else ''
)
)
}}
changed_when: false
- name: Dev2 | Normalize indoor_defgw value
delegate_to: localhost
set_fact:
indoor_defgw_value: >-
{{
'unavailable'
if (indoor_defgw_raw | default('') | trim) == ''
else (indoor_defgw_raw | default('') | trim)
}}
changed_when: false
- name: Scan tracking | Dev2 indoor_defgw outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['indoor_defgw'] if ((indoor_defgw_value | default('unavailable')) != 'unavailable') else []) }}"
scan_fail: "{{ scan_fail + (['indoor_defgw=unavailable'] if ((indoor_defgw_value | default('unavailable')) == 'unavailable') else []) }}"
changed_when: false
- name: Publish Dev2 indoor_defgw custom field
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_defgw_value) != (nb_custom_fields_current.get('indoor_defgw')))
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'indoor_defgw',
'task_result': indoor_defgw_value
} | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Delta tracking | Mark changed indoor_defgw
when:
- (not (nb_baseline_ok | default(false))) or ((indoor_defgw_value) != (nb_custom_fields_current.get('indoor_defgw')))
delegate_to: localhost
set_fact:
non_ts_diff_found: true
changed_when: false
# ----------------------- SOT timestamp (container local time) → sot_ts -----------------------
- name: SOT ts | Get container timestamp
delegate_to: localhost
shell: "date '+%y%m%d-%H%M%S'"
args:
executable: /bin/bash
register: sot_ts_cmd
changed_when: false
failed_when: false
- name: SOT ts | Set sot_ts value
delegate_to: localhost
set_fact:
sot_ts_value: "{{ sot_ts_cmd.stdout | default('') | trim }}"
changed_when: false
- name: SOT ts | Debug
delegate_to: localhost
debug:
msg:
- "sot_ts_value={{ sot_ts_value | default('unset') }}"
- name: Scan tracking | sot_ts outcome
delegate_to: localhost
set_fact:
scan_ok: "{{ scan_ok + (['sot_ts'] if ((sot_ts_value | default('') | length) > 0) else []) }}"
scan_fail: "{{ scan_fail + (['sot_ts=unavailable'] if ((sot_ts_value | default('') | length) == 0) else []) }}"
changed_when: false
- name: Publish sot_ts
delegate_to: localhost
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost|urlencode }}/{{ rmq_exchange|urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname|default(inventory_hostname)),
'task_name': 'custom_field_set',
'task_add1': 'sot_ts',
'task_result': (sot_ts_value if (sot_ts_value|length>0) else 'unavailable')
} | to_json }}"
payload_encoding: "string"
changed_when: false
# ----------------------- Final journal: scan status -----------------------
- name: Journal final scan status (success / partial / fail)
when: non_ts_diff_found | default(false)
delegate_to: localhost
vars:
_ok_n: "{{ (scan_ok | default([])) | length }}"
_fail_n: "{{ (scan_fail | default([])) | length }}"
_reason: "{{ (scan_fail | default([])) | join('; ') }}"
_msg: >-
{%- if (_fail_n | int) == 0 -%}
scan was successful
{%- elif (_ok_n | int) == 0 -%}
scanning failed — {{ _reason }}
{%- else -%}
scan was completed partially — {{ _reason }}
{%- endif -%}
uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ rmq_exchange | urlencode }}/publish"
method: POST
user: "{{ rmq_user }}"
password: "{{ rmq_pass }}"
force_basic_auth: true
status_code: 200
headers: { content-type: "application/json" }
body_format: json
body:
properties: { content_type: "application/json" }
routing_key: "{{ control_queue }}"
payload: "{{ {
'inscope_device': (ansible_hostname | default(inventory_hostname)),
'task_name': 'journal_add',
'task_result': _msg
} | to_json }}"
payload_encoding: "string"
changed_when: false
post_tasks:
- name: "✅ Cleanup start"
debug:
msg: "Closing tunnel and removing temporary IP"
- name: Refresh ARP (cleanup)
ignore_unreachable: true
raw: "{{ pathprefix }} arping -U -I {{ arping_iface }} {{ dev2_side_ip_addr }} -c 3"
failed_when: false
ignore_errors: true
- name: Close SSH ControlMaster
delegate_to: localhost
shell: "ssh -S '{{ _ctrl_sock|default('/dev/null') }}' -O exit 2>/dev/null || true"
failed_when: false
ignore_errors: true
- name: Remove control dir
delegate_to: localhost
file:
path: "{{ _ctrl_dir|default('/tmp/none') }}"
state: absent
ignore_errors: true
- name: Remove temporary IP
ignore_unreachable: true
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 (telemetry only)"