This commit is contained in:
2026-07-14 15:14:12 +03:00
parent 4cd177f517
commit 13f3154379
10 changed files with 5564 additions and 0 deletions

BIN
files/2.2.6-r9926.bin Normal file

Binary file not shown.

View File

@@ -0,0 +1,100 @@
# update-indoor-scheduler.yml
# Single nbplay invocation:
# Phase 1: Cloud -> NetBox sync (via sot-updater-iponly.yml in wrapper mode)
# Phase 2: Refresh ansible_host from nb_ip, reset_connection
# Phase 3: Run update-indoor6-stable.yml
- hosts: all
gather_facts: no
vars:
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) }}"
tasks: []
- import_playbook: sot-updater-iponly.yml
vars:
sot_wrapper_mode: true
- name: Phase 2 | Refresh in-memory target IP after iponly
hosts: all
gather_facts: no
vars:
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) }}"
tasks:
- name: "Wrapper | Extract nb_ip from nb_onedevice_update.py stdout"
vars:
_lines: "{{ (nb_preflight.stdout_lines | default([])) | map('regex_replace','\r','') | map('trim') | list }}"
_ok_line: "{{ (_lines | select('match','^OK\\s+') | list | last | default('')) }}"
_fail_line: "{{ (_lines | select('match','^FAIL\\s+') | list | last | default('')) }}"
_ip_raw: "{{ _ok_line | regex_search('([0-9]{1,3}(?:\\.[0-9]{1,3}){3})') | default('') }}"
set_fact:
nb_ip: "{{ _ip_raw }}"
nb_ip_ok: "{{ (_ip_raw | length) > 0 }}"
nb_ip_err: "{{ _fail_line }}"
changed_when: false
- name: "Wrapper | Continue with inventory IP if nb_ip not available"
debug:
msg: "Wrapper preflight did not produce nb_ip, keeping current inventory ansible_host. FAIL line: {{ nb_ip_err | default('') }}"
when: not nb_ip_ok
changed_when: false
- name: "Wrapper | Debug before ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(before)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
- "nb_ip={{ nb_ip | default('UNSET') }}"
changed_when: false
- name: "Wrapper | Update ansible_host to nb_ip"
set_fact:
ansible_host: "{{ nb_ip }}"
when: nb_ip_ok
changed_when: false
- name: "Wrapper | Debug after ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(after)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Reset connection after ansible_host change"
meta: reset_connection
when: nb_ip_ok
- name: "Wrapper | Debug after reset_connection"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(post_reset)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Pause 2s before next playbook"
pause:
seconds: 2
changed_when: false
- import_playbook: update-indoor225-r6848.yml
vars:
is_run_by: "scheduler"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
# update-indoor-scheduler.yml
# Single nbplay invocation:
# Phase 1: Cloud -> NetBox sync (via sot-updater-iponly.yml in wrapper mode)
# Phase 2: Refresh ansible_host from nb_ip, reset_connection
# Phase 3: Run update-indoor6-stable.yml
- hosts: all
gather_facts: no
vars:
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) }}"
tasks: []
- import_playbook: sot-updater-iponly.yml
vars:
sot_wrapper_mode: true
- name: Phase 2 | Refresh in-memory target IP after iponly
hosts: all
gather_facts: no
vars:
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) }}"
tasks:
- name: "Wrapper | Extract nb_ip from nb_onedevice_update.py stdout"
vars:
_lines: "{{ (nb_preflight.stdout_lines | default([])) | map('regex_replace','\r','') | map('trim') | list }}"
_ok_line: "{{ (_lines | select('match','^OK\\s+') | list | last | default('')) }}"
_fail_line: "{{ (_lines | select('match','^FAIL\\s+') | list | last | default('')) }}"
_ip_raw: "{{ _ok_line | regex_search('([0-9]{1,3}(?:\\.[0-9]{1,3}){3})') | default('') }}"
set_fact:
nb_ip: "{{ _ip_raw }}"
nb_ip_ok: "{{ (_ip_raw | length) > 0 }}"
nb_ip_err: "{{ _fail_line }}"
changed_when: false
- name: "Wrapper | Continue with inventory IP if nb_ip not available"
debug:
msg: "Wrapper preflight did not produce nb_ip, keeping current inventory ansible_host. FAIL line: {{ nb_ip_err | default('') }}"
when: not nb_ip_ok
changed_when: false
- name: "Wrapper | Debug before ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(before)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
- "nb_ip={{ nb_ip | default('UNSET') }}"
changed_when: false
- name: "Wrapper | Update ansible_host to nb_ip"
set_fact:
ansible_host: "{{ nb_ip }}"
when: nb_ip_ok
changed_when: false
- name: "Wrapper | Debug after ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(after)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Reset connection after ansible_host change"
meta: reset_connection
when: nb_ip_ok
- name: "Wrapper | Debug after reset_connection"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(post_reset)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Pause 2s before next playbook"
pause:
seconds: 2
changed_when: false
- import_playbook: update-indoorbackup225r6848.yml
vars:
is_run_by: "scheduler"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
# update-outdoorbackup-scheduler.yml
# Single nbplay invocation:
# Phase 1: Cloud -> NetBox sync (via sot-updater-iponly.yml in wrapper mode)
# Phase 2: Refresh ansible_host from nb_ip, reset_connection
# Phase 3: Run update-outdoorbackup224.yml
- hosts: all
gather_facts: no
vars:
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) }}"
tasks: []
- import_playbook: sot-updater-iponly.yml
vars:
sot_wrapper_mode: true
- name: Phase 2 | Refresh in-memory target IP after iponly
hosts: all
gather_facts: no
vars:
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) }}"
tasks:
- name: "Wrapper | Extract nb_ip from nb_onedevice_update.py stdout"
vars:
_lines: "{{ (nb_preflight.stdout_lines | default([])) | map('regex_replace','\r','') | map('trim') | list }}"
_ok_line: "{{ (_lines | select('match','^OK\\s+') | list | last | default('')) }}"
_fail_line: "{{ (_lines | select('match','^FAIL\\s+') | list | last | default('')) }}"
_ip_raw: "{{ _ok_line | regex_search('([0-9]{1,3}(?:\\.[0-9]{1,3}){3})') | default('') }}"
set_fact:
nb_ip: "{{ _ip_raw }}"
nb_ip_ok: "{{ (_ip_raw | length) > 0 }}"
nb_ip_err: "{{ _fail_line }}"
changed_when: false
- name: "Wrapper | Continue with inventory IP if nb_ip not available"
debug:
msg: "Wrapper preflight did not produce nb_ip, keeping current inventory ansible_host. FAIL line: {{ nb_ip_err | default('') }}"
when: not nb_ip_ok
changed_when: false
- name: "Wrapper | Debug before ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(before)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
- "nb_ip={{ nb_ip | default('UNSET') }}"
changed_when: false
- name: "Wrapper | Update ansible_host to nb_ip"
set_fact:
ansible_host: "{{ nb_ip }}"
when: nb_ip_ok
changed_when: false
- name: "Wrapper | Debug after ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(after)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Reset connection after ansible_host change"
meta: reset_connection
when: nb_ip_ok
- name: "Wrapper | Debug after reset_connection"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(post_reset)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Pause 2s before next playbook"
pause:
seconds: 2
changed_when: false
- import_playbook: update-outdoorbackup226.yml
vars:
is_run_by: "scheduler"
- name: Phase 4 | Scheduled success only | Chain sot-updater-scheduler
hosts: all
gather_facts: no
vars:
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) }}"
tasks:
- name: Scheduled success | Set action_next to sot-updater-scheduler
when:
- hostvars[inventory_hostname].journal_success_payload is defined
delegate_to: localhost
ansible.builtin.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': 'action_next', 'task_result': 'sot-updater-scheduler' } | to_json }}"
payload_encoding: "string"
changed_when: false
- name: Scheduled success | Publish sot-updater-scheduler work message
when:
- hostvars[inventory_hostname].journal_success_payload is defined
delegate_to: localhost
ansible.builtin.uri:
url: "http://{{ rmq_host }}:{{ rmq_port }}/api/exchanges/{{ rmq_vhost | urlencode }}/{{ 'deviceconfig' | 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: "deviceconfig"
payload: "{{ { 'inscope_device': (ansible_hostname | default(inventory_hostname)), 'task_name': 'sot-updater-scheduler' } | to_json }}"
payload_encoding: "string"
changed_when: false

View File

@@ -0,0 +1,800 @@
---
- name: Upgrade firmware safely (no Python on target)
hosts: all
gather_facts: no
vars:
# RabbitMQ (pull from env if provided)
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) }}"
# Invocation context (default: manual; scheduler wrapper sets is_run_by=scheduler)
is_run_by_effective: "{{ is_run_by | default('manual') }}"
# NEW: Post-upgrade check scheduling (via holding queue -> DLX)
# The holding queue is bound to exchange 'deviceconfig.holding' with routing key 'deviceconfig'.
# Messages published here carry a per-message TTL (AMQP 'expiration' property, in ms).
# Once TTL elapses, messages dead-letter to exchange 'deviceconfig' with same routing key,
# where a consumer will perform the after-upgrade verification (attempt-based backoff lives on consumer side).
afterupgrade_hold_exchange: "{{ lookup('env','AFTERUP_HOLD_EXCHANGE') | default('deviceconfig.holding', true) }}"
afterupgrade_routing_key: "{{ lookup('env','AFTERUP_ROUTING_KEY') | default('deviceconfig', true) }}"
# Queue name is not used for publish; present for documentation/reference only
afterupgrade_hold_queue: "{{ lookup('env','AFTERUP_HOLD_QUEUE') | default('queue_deviceconfig_holdingzone', true) }}"
# REQUIRED (pass via -e)
firmware_path: /tmp/2.2.6-r9926.bin
firmware_sha256: "7a3cf094ec33e272468f6680409c9acb8eff662b1b1ef020c307852d1263221c"
# Helper computed vars
fw_base: "{{ firmware_path | basename }}"
fw_name: "{{ fw_base | regex_replace('\\.bin$', '') }}"
fw_banner_repr: "{{ fw_name | regex_replace('-r', ' rev ') }}"
fw_marker: "/tmp/prepared_for_{{ fw_name }}"
pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; "
tasks:
# ----------------------------- HOSTNAME PREFLIGHT -----------------------------
- name: Hostname preflight
block:
- name: Read remote HOSTNAME
ansible.builtin.raw: "{{ pathprefix }} echo \"$HOSTNAME\""
register: host_env
changed_when: false
- name: Debug hostnames
ansible.builtin.debug:
msg:
- "remote_hostname={{ host_env.stdout | trim }}"
- "inventory_hostname={{ inventory_hostname }}"
- name: Stop if connected hostname differs from inventory
ansible.builtin.fail:
msg: "Aborting: connected host reported hostname '{{ host_env.stdout | trim }}' which differs from inventory '{{ inventory_hostname }}'."
when: (host_env.stdout | trim) != inventory_hostname
rescue:
- name: Build failure task name and detail (hostname preflight)
ansible.builtin.set_fact:
fail_task_name: "{{ ansible_failed_task.name | default('hostname preflight') }}"
fail_detail_raw: >-
{{ ansible_failed_result.msg
| default(ansible_failed_result.stderr)
| default(ansible_failed_result.stdout)
| default('no additional error output')
| trim }}
- name: Build failure summary text (hostname preflight)
ansible.builtin.set_fact:
fail_summary: >-
Firmware update aborted at '{{ fail_task_name }}': {{ fail_detail_raw }}
- name: Truncate failure summary to ~400 chars (hostname preflight)
ansible.builtin.set_fact:
fail_summary_short: "{{ fail_summary | regex_replace('\\s+', ' ') | trim | truncate(400, True, '...') }}"
- name: Build control queue payload for failure journal (hostname preflight)
ansible.builtin.set_fact:
journal_failure_payload_pre:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "journal_add"
task_result: "{{ fail_summary_short }}"
- name: Bandaid | Pause before Publish failure journal to control queue (hostname preflight)
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish failure journal to control queue (hostname preflight)
ansible.builtin.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: "{{ journal_failure_payload_pre | to_json }}"
payload_encoding: "string"
register: rmq_journal_pre_resp
changed_when: (rmq_journal_pre_resp.json is defined) and (rmq_journal_pre_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_journal_pre_resp.status != 200) or
(rmq_journal_pre_resp.json is not defined) or
(not (rmq_journal_pre_resp.json.routed | default(false) | bool))
delegate_to: localhost
- name: Build control queue payload for update-aborted tag (hostname preflight)
ansible.builtin.set_fact:
tag_failed_payload_pre:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "tag_add"
task_result: "update-aborted"
- name: Bandaid | Pause before Publish update-aborted tag to control queue (hostname preflight)
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish update-aborted tag to control queue (hostname preflight)
ansible.builtin.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: "{{ tag_failed_payload_pre | to_json }}"
payload_encoding: "string"
register: rmq_tag_failed_pre_resp
changed_when: (rmq_tag_failed_pre_resp.json is defined) and (rmq_tag_failed_pre_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_tag_failed_pre_resp.status != 200) or
(rmq_tag_failed_pre_resp.json is not defined) or
(not (rmq_tag_failed_pre_resp.json.routed | default(false) | bool))
delegate_to: localhost
- name: Scheduler | Publish action_state failed (hostname preflight)
ansible.builtin.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': 'action_state',
'task_result': 'failed'
} | to_json }}"
payload_encoding: "string"
changed_when: false
delegate_to: localhost
when: is_run_by_effective == 'scheduler'
- name: Scheduler | Debug action_state failed published (hostname preflight)
ansible.builtin.debug:
msg: "scheduler-run detected; published action_state=failed (hostname preflight)"
when: is_run_by_effective == 'scheduler'
- name: Stop play after hostname preflight failure
ansible.builtin.meta: end_play
- name: Scheduler context | Debug is_run_by
ansible.builtin.debug:
msg: "is_run_by={{ is_run_by_effective }}"
- name: Scheduler | Publish action_state inprogress (scheduler-run)
ansible.builtin.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': 'action_state',
'task_result': 'inprogress'
} | to_json }}"
payload_encoding: "string"
changed_when: false
delegate_to: localhost
when: is_run_by_effective == 'scheduler'
- name: Scheduler | Debug action_state inprogress published
ansible.builtin.debug:
msg: "scheduler-run detected; published action_state=inprogress"
when: is_run_by_effective == 'scheduler'
# --------------------- Prepared marker check BEFORE SSID scan -----------------
- name: Check if any prepared marker exists
ansible.builtin.raw: "{{ pathprefix }} [ -e /tmp/prepared_for* ] && echo PRESENT || echo ABSENT"
register: prep_scan
changed_when: false
- name: Debug marker presence
ansible.builtin.debug:
msg: "prepared_marker={{ prep_scan.stdout | trim }}"
- name: Journal preparation already present, skipping update steps
ansible.builtin.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": (
"Preparation already present for " ~ fw_banner_repr ~
"; marker " ~ fw_marker ~
". Skipping update steps."
)
} | to_json
}}
payload_encoding: "string"
register: rmq_journal_prep_present
changed_when: (rmq_journal_prep_present.json is defined) and (rmq_journal_prep_present.json.routed | default(false) | bool)
failed_when: >
(rmq_journal_prep_present.status != 200) or
(rmq_journal_prep_present.json is not defined) or
(not (rmq_journal_prep_present.json.routed | default(false) | bool))
delegate_to: localhost
when: (prep_scan.stdout | trim) == 'PRESENT'
- name: End play for this host (already prepared)
ansible.builtin.meta: end_host
when: (prep_scan.stdout | trim) == 'PRESENT'
# ----------------------------- MAIN UPDATE BLOCK -----------------------------
- name: Firmware update main block
block:
- name: Check if firmware image is already on the device
ansible.builtin.raw: "{{ pathprefix }} [ -f '{{ firmware_path }}' ] && echo OK || echo MISSING"
register: fw_exists
changed_when: false
- name: Count fw_printenv lines
ansible.builtin.raw: "{{ pathprefix }} fw_printenv 2>/dev/null | wc -l"
register: env_line_count
changed_when: false
- name: Debug fw_printenv line count
ansible.builtin.debug:
msg: "fw_printenv_lines={{ env_line_count.stdout | trim }}"
- name: Stop if bootloader environment looks invalid (<7 lines)
ansible.builtin.fail:
msg: "Aborting: fw_printenv returned only {{ env_line_count.stdout | trim }} lines (<7) — environment missing or corrupted."
when: (env_line_count.stdout | trim | int) < 7
- name: Read /var/run/bootbanks.json (active and backup firmux versions)
ansible.builtin.raw: "{{ pathprefix }} cat /var/run/bootbanks.json"
register: bootbanks_raw
changed_when: false
- name: Parse bootbanks.json
ansible.builtin.set_fact:
bootbanks_obj: "{{ bootbanks_raw.stdout | from_json }}"
bootbanks_active_firmux: "{{ (bootbanks_raw.stdout | from_json).active.firmux | default('unknown') }}"
bootbanks_backup_firmux: "{{ (bootbanks_raw.stdout | from_json).backup.firmux | default('unknown') }}"
- name: current versions (active primary and backup)
ansible.builtin.debug:
msg:
- "active (primary) firmux: {{ bootbanks_active_firmux }}"
- "backup firmux: {{ bootbanks_backup_firmux }}"
- name: Build control queue payload for skip journal (backup already prepared)
ansible.builtin.set_fact:
journal_skip_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "journal_add"
task_result: >-
Backup bootbank already has {{ fw_banner_repr }} (backup.firmux={{ bootbanks_backup_firmux }}).
Skipping backup preparation (no flip, no reboot).
when: (bootbanks_backup_firmux | trim) == (fw_banner_repr | trim)
- name: Bandaid | Pause before Publish skip journal to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
when: journal_skip_payload is defined
- name: Publish skip journal to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ journal_skip_payload | to_json }}"
payload_encoding: "string"
register: rmq_journal_skip_resp
changed_when: (rmq_journal_skip_resp.json is defined) and (rmq_journal_skip_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_journal_skip_resp.status != 200) or
(rmq_journal_skip_resp.json is not defined) or
(not (rmq_journal_skip_resp.json.routed | default(false) | bool))
delegate_to: localhost
when: journal_skip_payload is defined
- name: End play for this host (backup already prepared; backup prep not needed)
ansible.builtin.meta: end_host
when: (bootbanks_backup_firmux | trim) == (fw_banner_repr | trim)
- name: Upload firmware to /tmp via scp (controller-side)
ansible.builtin.shell: |
set -e
SRC='{{ fw_src_local | default("/opt/containers/ansible-worker/app/2.2.6-r9926.bin") }}'
DST_USER='{{ ansible_user | default("root") }}'
DST_HOST='{{ ansible_host | default(inventory_hostname) }}'
test -f "$SRC"
sshpass -p '{{ ansible_ssh_pass }}' scp -o StrictHostKeyChecking=no -o PubkeyAuthentication=no \
"$SRC" "${DST_USER}@${DST_HOST}:{{ firmware_path }}"
delegate_to: localhost
when: fw_exists.stdout is not defined or (fw_exists.stdout | trim) != 'OK'
changed_when: true
- name: Re-check firmware presence after optional upload
ansible.builtin.raw: "{{ pathprefix }} test -f '{{ firmware_path }}' && echo OK || echo MISSING"
register: fw_exists2
changed_when: false
failed_when: (fw_exists2.stdout | trim) != 'OK'
- name: Compute sha256 of the uploaded image
ansible.builtin.raw: "{{ pathprefix }} sha256sum '{{ firmware_path }}' | awk '{print $1}'"
register: sha_out
changed_when: false
- name: Verify sha256 matches expected
ansible.builtin.fail:
msg: "SHA256 mismatch for {{ firmware_path }}. Got {{ sha_out.stdout | trim }}, expected {{ firmware_sha256 }}"
when: (sha_out.stdout | trim) != (firmware_sha256 | trim)
- name: sha256 verification debug
ansible.builtin.debug:
msg:
- "sha256sum is: {{ sha_out.stdout | trim }}"
- name: Check image validity (update -c must say 'valid')
ansible.builtin.raw: "{{ pathprefix }} update -c '{{ firmware_path }}'"
register: up_check
changed_when: false
failed_when: up_check.stdout.strip() != 'valid'
- name: image verification debug
ansible.builtin.debug:
msg:
- ".bin verification is: {{ up_check.stdout | trim }}"
# - name: forced stop before writing
# ansible.builtin.meta: end_play
- name: Write image (this will take a while)
ansible.builtin.raw: "{{ pathprefix }} update -w '{{ firmware_path }}'"
register: up_write
changed_when: true
failed_when: up_write.stdout is not search('update is complete')
- name: Create prepared marker
ansible.builtin.raw: "{{ pathprefix }} touch '{{ fw_marker }}'"
changed_when: true
- name: Cleanup prepared marker after successful backup-bank write
ansible.builtin.raw: "rm -f '{{ fw_marker }}'"
changed_when: true
when:
- fw_marker is defined
- fw_marker | length > 0
- name: Build control queue payload (progress & target version)
ansible.builtin.set_fact:
nbq2_payload_obj:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "update_outdoorbackup"
task_result: "backup_prepared"
task_add1: "{{ fw_banner_repr }}" # e.g., "2.2.0 rev 9739"
when: up_write is changed
- name: Bandaid | Pause before Publish message to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish message to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ nbq2_payload_obj | to_json }}"
payload_encoding: "string"
register: rmq_resp
changed_when: (rmq_resp.json is defined) and (rmq_resp.json.routed | default(false))
failed_when: >
(rmq_resp.status != 200) or
(rmq_resp.json is not defined) or
(rmq_resp.json.routed | default(false) | bool == false)
delegate_to: localhost
when: nbq2_payload_obj is defined
- name: Log control queue publish result
ansible.builtin.debug:
var: rmq_resp.json
when: rmq_resp is defined
- name: Summary
ansible.builtin.debug:
msg:
- "Active (primary) firmux before: {{ bootbanks_active_firmux }}"
- "Target version: {{ fw_banner_repr }}"
- "SHA256: OK ({{ sha_out.stdout | trim }})"
- "update -c: {{ up_check.stdout | trim }}"
- "update -w: OK"
- "Marker: {{ fw_marker }}"
# --- Success tag selection (ONLY CHANGE) --- (ONLY CHANGE) ---
- name: Build control queue payload for update-backup-prepared (rebootin == 0)
ansible.builtin.set_fact:
tag_auto_restarted_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "tag_add"
task_result: "update-backup-prepared"
when:
- nbq2_payload_obj is defined
- rebootin is defined
- (rebootin | int) == 0
- name: Build control queue payload for update-backup-prepared (rebootin >= 1)
ansible.builtin.set_fact:
tag_restart_scheduled_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "tag_add"
task_result: "update-backup-prepared"
when:
- nbq2_payload_obj is defined
- rebootin is defined
- (rebootin | int) >= 1
- name: Build control queue payload for update-backup-prepared tag (no reboot scheduled)
ansible.builtin.set_fact:
tag_waits_restart_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "tag_add"
task_result: "update-backup-prepared"
when:
- nbq2_payload_obj is defined
- rebootin is not defined
# --- Publish chosen tag (updated names only) ---
- name: Bandaid | Pause before Publish update-backup-prepared tag to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish update-backup-prepared tag to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ tag_waits_restart_payload | to_json }}"
payload_encoding: "string"
register: rmq_tag_waits_restart_resp
changed_when: (rmq_tag_waits_restart_resp.json is defined) and (rmq_tag_waits_restart_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_tag_waits_restart_resp.status != 200) or
(rmq_tag_waits_restart_resp.json is not defined) or
(not (rmq_tag_waits_restart_resp.json.routed | default(false) | bool))
delegate_to: localhost
when: tag_waits_restart_payload is defined
- name: Bandaid | Pause before Publish update-backup-prepared tag to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish update-backup-prepared tag to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ tag_auto_restarted_payload | to_json }}"
payload_encoding: "string"
register: rmq_tag_auto_restarted_resp
changed_when: (rmq_tag_auto_restarted_resp.json is defined) and (rmq_tag_auto_restarted_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_tag_auto_restarted_resp.status != 200) or
(rmq_tag_auto_restarted_resp.json is not defined) or
(not (rmq_tag_auto_restarted_resp.json.routed | default(false) | bool))
delegate_to: localhost
when: tag_auto_restarted_payload is defined
- name: Bandaid | Pause before Publish update-backup-prepared tag to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish update-backup-prepared tag to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ tag_restart_scheduled_payload | to_json }}"
payload_encoding: "string"
register: rmq_tag_restart_scheduled_resp
changed_when: (rmq_tag_restart_scheduled_resp.json is defined) and (rmq_tag_restart_scheduled_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_tag_restart_scheduled_resp.status != 200) or
(rmq_tag_restart_scheduled_resp.json is not defined) or
(not (rmq_tag_restart_scheduled_resp.json.routed | default(false) | bool))
delegate_to: localhost
when: tag_restart_scheduled_payload is defined
# --- Journal: preparation successful (only if fully successful) ---
- name: Build control queue payload for success journal
ansible.builtin.set_fact:
journal_success_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "journal_add"
task_result: >-
Backup bank prepared for {{ fw_banner_repr }}.
Active unchanged; marker {{ fw_marker }}.
when: nbq2_payload_obj is defined
- name: Bandaid | Pause before Publish success journal to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish success journal to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ journal_success_payload | to_json }}"
payload_encoding: "string"
register: rmq_journal_success_resp
changed_when: (rmq_journal_success_resp.json is defined) and (rmq_journal_success_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_journal_success_resp.status != 200) or
(rmq_journal_success_resp.json is not defined) or
(not (rmq_journal_success_resp.json.routed | default(false) | bool))
delegate_to: localhost
- name: Scheduler | Publish action_state done (success) (success)
ansible.builtin.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': 'action_state',
'task_result': 'done'
} | to_json }}"
payload_encoding: "string"
changed_when: false
delegate_to: localhost
when: is_run_by_effective == 'scheduler'
rescue:
- name: Build failure task name and detail
ansible.builtin.set_fact:
fail_task_name: "{{ ansible_failed_task.name | default('unknown step') }}"
fail_detail_raw: >-
{{ ansible_failed_result.msg
| default(ansible_failed_result.stderr)
| default(ansible_failed_result.stdout)
| default('no additional error output')
| trim }}
- name: Build failure summary text
ansible.builtin.set_fact:
fail_summary: >-
Firmware update aborted at '{{ fail_task_name }}': {{ fail_detail_raw }}
- name: Truncate failure summary to ~400 chars
ansible.builtin.set_fact:
fail_summary_short: "{{ fail_summary | regex_replace('\\s+', ' ') | trim | truncate(400, True, '...') }}"
- name: Build control queue payload for failure journal
ansible.builtin.set_fact:
journal_failure_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "journal_add"
task_result: "{{ fail_summary_short }}"
- name: Bandaid | Pause before Publish failure journal to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish failure journal to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ journal_failure_payload | to_json }}"
payload_encoding: "string"
register: rmq_journal_fail_resp
changed_when: (rmq_journal_fail_resp.json is defined) and (rmq_journal_fail_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_journal_fail_resp.status != 200) or
(rmq_journal_fail_resp.json is not defined) or
(not (rmq_journal_fail_resp.json.routed | default(false) | bool))
delegate_to: localhost
- name: Build control queue payload for update-aborted tag
ansible.builtin.set_fact:
tag_failed_payload:
inscope_device: "{{ ansible_hostname | default(inventory_hostname) }}"
task_name: "tag_add"
task_result: "update-aborted"
- name: Bandaid | Pause before Publish update-aborted tag to control queue via RabbitMQ HTTP API
ansible.builtin.pause:
seconds: 1
delegate_to: localhost
changed_when: false
- name: Publish update-aborted tag to control queue via RabbitMQ HTTP API
ansible.builtin.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: "{{ tag_failed_payload | to_json }}"
payload_encoding: "string"
register: rmq_tag_failed_resp
changed_when: (rmq_tag_failed_resp.json is defined) and (rmq_tag_failed_resp.json.routed | default(false) | bool)
failed_when: >
(rmq_tag_failed_resp.status != 200) or
(rmq_tag_failed_resp.json is not defined) or
(not (rmq_tag_failed_resp.json.routed | default(false) | bool))
delegate_to: localhost
- name: Scheduler | Publish action_state failed (update failure)
ansible.builtin.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': 'action_state',
'task_result': 'failed'
} | to_json }}"
payload_encoding: "string"
changed_when: false
delegate_to: localhost
when: is_run_by_effective == 'scheduler'
- name: Scheduler | Debug action_state failed published (update failure)
ansible.builtin.debug:
msg: "scheduler-run detected; published action_state=failed (update failure)"
when: is_run_by_effective == 'scheduler'

View File

@@ -0,0 +1,100 @@
# update-reboot-scheduler.yml
# Single nbplay invocation:
# Phase 1: Cloud -> NetBox sync (via sot-updater-iponly.yml in wrapper mode)
# Phase 2: Refresh ansible_host from nb_ip, reset_connection
# Phase 3: Run update-rebootin224.yml
- hosts: all
gather_facts: no
vars:
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) }}"
tasks: []
- import_playbook: sot-updater-iponly.yml
vars:
sot_wrapper_mode: true
- name: Phase 2 | Refresh in-memory target IP after iponly
hosts: all
gather_facts: no
vars:
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) }}"
tasks:
- name: "Wrapper | Extract nb_ip from nb_onedevice_update.py stdout"
vars:
_lines: "{{ (nb_preflight.stdout_lines | default([])) | map('regex_replace','\r','') | map('trim') | list }}"
_ok_line: "{{ (_lines | select('match','^OK\\s+') | list | last | default('')) }}"
_fail_line: "{{ (_lines | select('match','^FAIL\\s+') | list | last | default('')) }}"
_ip_raw: "{{ _ok_line | regex_search('([0-9]{1,3}(?:\\.[0-9]{1,3}){3})') | default('') }}"
set_fact:
nb_ip: "{{ _ip_raw }}"
nb_ip_ok: "{{ (_ip_raw | length) > 0 }}"
nb_ip_err: "{{ _fail_line }}"
changed_when: false
- name: "Wrapper | Continue with inventory IP if nb_ip not available"
debug:
msg: "Wrapper preflight did not produce nb_ip, keeping current inventory ansible_host. FAIL line: {{ nb_ip_err | default('') }}"
when: not nb_ip_ok
changed_when: false
- name: "Wrapper | Debug before ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(before)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
- "nb_ip={{ nb_ip | default('UNSET') }}"
changed_when: false
- name: "Wrapper | Update ansible_host to nb_ip"
set_fact:
ansible_host: "{{ nb_ip }}"
when: nb_ip_ok
changed_when: false
- name: "Wrapper | Debug after ansible_host override"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(after)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Reset connection after ansible_host change"
meta: reset_connection
when: nb_ip_ok
- name: "Wrapper | Debug after reset_connection"
debug:
msg:
- "inventory_hostname={{ inventory_hostname }}"
- "ansible_host(post_reset)={{ ansible_host | default('UNSET') }}"
- "ansible_port={{ ansible_port | default('22') }}"
changed_when: false
- name: "Wrapper | Pause 2s before next playbook"
pause:
seconds: 2
changed_when: false
- import_playbook: update-rebootin226.yml
vars:
is_run_by: "scheduler"

File diff suppressed because it is too large Load Diff

Binary file not shown.