Skip to content

Fix crash when ansible_default_ipv4.address is undefined (PE-1998) - #371

Merged
steven-schattenberg-itential merged 2 commits into
itential:devfrom
mazam32-itential:PE-1998
Aug 28, 2026
Merged

Fix crash when ansible_default_ipv4.address is undefined (PE-1998)#371
steven-schattenberg-itential merged 2 commits into
itential:devfrom
mazam32-itential:PE-1998

Conversation

@mazam32-itential

@mazam32-itential mazam32-itential commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • redis_bind and redis_sentinel_bind (in roles/redis/defaults/main/redis.yml and roles/redis/defaults/main/sentinel.yml) assumed ansible_default_ipv4.address always exists.
  • On hosts with no default route, Ansible sets ansible_default_ipv4 to an empty dict, so accessing .address raised AnsibleUndefinedVariable and aborted the entire run.
  • Both variables now check ansible_default_ipv4.address is defined first, falling back to binding on 127.0.0.1 only when it's not present.
  • All other references to this fact in the codebase (the three *-validation-report.md.j2 templates) already guarded with | default('N/A') and needed no changes.

How this was tested

  1. Reproduced the crash with an ad-hoc Ansible command that simulates the "no default route" case (empty ansible_default_ipv4 fact), using the exact pre-fix expression:

    ansible localhost -m debug -a "msg={{ '127.0.0.1 ' ~ ansible_default_ipv4.address }}" -e '{"ansible_default_ipv4": {}}'

    Result: FAILED! => "'dict object' has no attribute 'address'" — confirming the exact failure mode described in the ticket.

  2. Verified the fix by running the same style of check against the new expression, for both the broken case and the normal case:

    # Undefined case — should not crash, should fall back to 127.0.0.1 only
    ansible localhost -m debug -a "msg={{ '127.0.0.1' ~ ((' ' + ansible_default_ipv4.address) if (ansible_default_ipv4.address is defined) else '') }}" -e '{"ansible_default_ipv4": {}}'
    # => SUCCESS, msg: "127.0.0.1"
    
    # Normal case — address present, should still be appended (no regression)
    ansible localhost -m debug -a "msg={{ '127.0.0.1' ~ ((' ' + ansible_default_ipv4.address) if (ansible_default_ipv4.address is defined) else '') }}" -e '{"ansible_default_ipv4": {"address": "10.0.0.5"}}'
    # => SUCCESS, msg: "127.0.0.1 10.0.0.5"
  3. Confirmed the YAML still parses to the identical single-line Jinja expression after wrapping it across two lines for line-length (folded scalar >-), using PyYAML to load both files and print the resulting redis_bind/redis_sentinel_bind values.

  4. Ran ansible-lint against both changed files — passes clean (0 failures, 0 warnings):

    ansible-lint roles/redis/defaults/main/redis.yml roles/redis/defaults/main/sentinel.yml
  5. End-to-end sanity check: this same Redis role (unmodified logic aside from this fix) was used minutes earlier in a full itential.deployer.site run against a live EC2 instance (Rocky Linux 8, AIO topology) where ansible_default_ipv4.address was defined — Redis, Sentinel, MongoDB, Platform, and Gateway all installed and started successfully, confirming this change doesn't alter behavior on hosts where the fact is present.

Test plan for reviewer

  • Confirm the two diffs only touch the redis_bind/redis_sentinel_bind default definitions
  • Re-run the ad-hoc ansible localhost -m debug commands above to reproduce/verify locally
  • Optionally run itential.deployer.redis against a host with no default route to confirm no crash end-to-end

redis_bind and redis_sentinel_bind assumed ansible_default_ipv4.address
always exists. On hosts with no default route, Ansible sets
ansible_default_ipv4 to an empty dict, so accessing .address raised
AnsibleUndefinedVariable and aborted the run. Both now check
`is defined` first and fall back to binding on 127.0.0.1 only.

Tested by:
- Reproducing the crash with an ad-hoc command simulating the empty-fact
  case: `ansible localhost -m debug -a "msg={{ '127.0.0.1 ' ~
  ansible_default_ipv4.address }}" -e '{"ansible_default_ipv4": {}}'`
  confirmed the pre-fix expression fails with
  "'dict object' has no attribute 'address'".
- Re-running the same ad-hoc check against the new expression for both
  the empty-fact case (renders "127.0.0.1", no crash) and a populated
  case (`-e '{"ansible_default_ipv4": {"address": "10.0.0.5"}}'`, renders
  "127.0.0.1 10.0.0.5"), confirming existing behavior is unchanged.
- Parsing both YAML files with PyYAML to confirm the folded scalar
  produces the identical single-line Jinja expression as before wrapping
  it for line length.
- ansible-lint roles/redis/defaults/main/redis.yml
  roles/redis/defaults/main/sentinel.yml passes clean (0 failures,
  0 warnings).
Comment thread roles/redis/defaults/main/redis.yml Outdated
Comment thread roles/redis/defaults/main/sentinel.yml Outdated
Replace the ternary conditional with the simpler, more idiomatic
`| default('')` filter, matching the pattern already used by every
other reference to ansible_default_ipv4.address in this codebase
(the three *-validation-report.md.j2 templates).

Tested by:
- Re-running the same ad-hoc reproduction checks against this new
  expression: undefined-fact case renders "127.0.0.1 " (trailing
  space, no crash); defined case renders "127.0.0.1 10.0.0.5" as
  before.
- Confirmed the trailing space is harmless to Redis itself: ran a
  real redis:latest Docker container with a config file containing
  "bind 127.0.0.1 " (trailing space) - it loaded the config and
  reached "Ready to accept connections" with no parse error.
- ansible-lint on both changed files passes clean (0 failures,
  0 warnings).
@steven-schattenberg-itential
steven-schattenberg-itential merged commit e7f41ca into itential:dev Aug 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants