# sot-updater-iponly.yml — NetBox IP sync ONLY via nb_onedevice_update.py (no device scan) - name: NetBox IP sync only (Cloud → NetBox via nb_onedevice_update.py); do not scan devices hosts: all gather_facts: no vars: # Wrapper mode: # false (default): standalone behavior (may end_host) # true: composable behavior for sot-updater-wrapper.yml (no end_host) sot_wrapper_mode: false 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 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([''])) }}" - "stderr_lines:" - "{{ (nb_preflight.stderr_lines | default([''])) }}" - "raw stdout (joined): {{ nb_preflight.stdout | default('') | trim }}" - name: "NB preflight | Detect cloud failure" when: nb_script.stat.exists delegate_to: localhost delegate_facts: true 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 delegate_facts: true 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 delegate_facts: true 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 | 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 tasks: - name: "IP-only | Stop after NetBox sync (no device scan)" when: not (sot_wrapper_mode | default(false)) meta: end_host