# sot-updater-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 full sot-updater-current.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('(?:^|\\s)ip=([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)(?:\\s|$)', '\\1') | default('') }}" _ip: >- {%- if _ip_raw is string -%} {{ _ip_raw }} {%- elif _ip_raw is sequence and (_ip_raw | length) > 0 -%} {{ _ip_raw[0] }} {%- else -%} "" {%- endif -%} set_fact: wrapper_ok_line: "{{ _ok_line }}" wrapper_fail_line: "{{ _fail_line }}" wrapper_nb_ip: "{{ _ip | trim }}" - name: "Wrapper | Debug extracted values" ansible.builtin.debug: msg: - "wrapper_ok_line='{{ wrapper_ok_line }}'" - "wrapper_fail_line='{{ wrapper_fail_line | default('') }}'" - "wrapper_nb_ip='{{ wrapper_nb_ip }}'" # ----------------------- Option A: Cloud offline / no IP -> Journal + stop cleanly ----------------------- - name: "Wrapper | Journal: device not online in cloud (skip full scan)" delegate_to: localhost when: (wrapper_fail_line | default('') | length) > 0 vars: _journal_msg: >- during sot-updater scan the device was not online in cloud (no IP). please try later. details: {{ wrapper_fail_line | default('') }} 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': _journal_msg } | to_json }}" payload_encoding: "string" changed_when: false - name: "Wrapper | Mark host to skip full scan (cloud-offline)" when: (wrapper_fail_line | default('') | length) > 0 set_fact: wrapper_skip_full_scan: true - name: "Wrapper | Stop host after cloud-offline journal (no full scan)" when: (wrapper_fail_line | default('') | length) > 0 meta: end_host # ----------------------- Normal path: we have OK ip=... -> proceed ----------------------- - name: "Wrapper | Assert IP extracted successfully" ansible.builtin.assert: that: - nb_script is defined - nb_script.stat.exists | default(false) - (wrapper_ok_line | length) > 0 - (wrapper_nb_ip | length) > 0 fail_msg: >- Wrapper stop: could not extract IP from nb_onedevice_update.py output. ok_line='{{ wrapper_ok_line | default('') }}' fail_line='{{ wrapper_fail_line | default('') }}' stdout_tail={{ (nb_preflight.stdout_lines | default([]))[-12:] }} - name: "Wrapper | Override ansible_host to refreshed IP ({{ wrapper_nb_ip }})" set_fact: ansible_host: "{{ wrapper_nb_ip }}" - name: "Wrapper | Reset SSH connection context (ensure new ansible_host is used)" meta: reset_connection - name: Phase 3 gate | Skip full scan if cloud-offline hosts: all gather_facts: no tasks: - name: "Wrapper | Gate: end_host if wrapper_skip_full_scan is set" when: wrapper_skip_full_scan | default(false) meta: end_host - import_playbook: sot-updater.yml - name: Phase 4 | Update action fields after sot-updater 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: "Scheduler | Set action_state done" 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': 'action_state', 'task_result': 'done' } | to_json }}" payload_encoding: "string" changed_when: false - name: "Scheduler | Set action_last sot-updater-scheduler" 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': 'action_last', 'task_result': 'sot-updater-scheduler' } | to_json }}" payload_encoding: "string" changed_when: false