800 lines
35 KiB
YAML
800 lines
35 KiB
YAML
---
|
|
- 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.5-r9858.bin
|
|
firmware_sha256: "fd28e4ebef67f12a70152b9261bc7f1bdf8890bda9548d670dfc7bad98f98350"
|
|
|
|
# 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.5-r9858.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' |