From ca123b03127361d611ecf8a9b0d32d0608d92f84 Mon Sep 17 00:00:00 2001 From: Marcus Summers Date: Wed, 6 May 2026 08:28:21 -0400 Subject: [PATCH] =?UTF-8?q?wiki:=20add=20troubleshooting=20article=20?= =?UTF-8?q?=E2=80=94=20Ansible=20regex=5Fsearch=20capture=20group=20fails?= =?UTF-8?q?=20in=20set=5Ffact?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the gotcha hit during the 2026-05-06 update.yml refactor: the second-positional-argument back-reference form of regex_search ('\1') doesn't reliably select capture groups when used inside set_fact. The fix is to match the broader substring and use .split()[0] (or [-1], etc.) to peel off the value, with a default() bridge for the no-match case. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...ble-regex-search-set-fact-capture-group.md | 72 +++++++++++++++++++ 05-troubleshooting/index.md | 3 +- SUMMARY.md | 4 +- 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 05-troubleshooting/ansible-regex-search-set-fact-capture-group.md diff --git a/05-troubleshooting/ansible-regex-search-set-fact-capture-group.md b/05-troubleshooting/ansible-regex-search-set-fact-capture-group.md new file mode 100644 index 0000000..e9b1fca --- /dev/null +++ b/05-troubleshooting/ansible-regex-search-set-fact-capture-group.md @@ -0,0 +1,72 @@ +--- +title: "Ansible regex_search — capture-group argument doesn't work in set_fact" +domain: troubleshooting +category: general +tags: [ansible, jinja, regex, set_fact, gotcha] +status: published +created: 2026-05-06 +updated: 2026-05-06 +--- + +# Ansible `regex_search` — capture-group argument doesn't work in `set_fact` + +## Problem + +You want to extract a number from a registered command's stdout — e.g. the package count from a dnf or apt upgrade — and stash it in a fact. The natural-looking `regex_search('pattern', '\1')` form fails or produces an empty string when used inside `set_fact`: + +```yaml +- name: Capture package count # ❌ does not behave as expected + ansible.builtin.set_fact: + pkg_count: "{{ apt_upgrade_result.stdout | regex_search('([0-9]+) upgraded', '\\1') }}" +``` + +You'll see one of: + +- An empty `pkg_count` (the filter ran but the back-reference returned nothing in this context) +- A Jinja error about argument arity if the syntax is slightly off +- The whole matched substring instead of just the captured group + +## Root cause + +In `set_fact` templating, the second-positional-argument form of `regex_search` (the back-reference `'\1'` you've seen in tutorials) doesn't reliably select capture groups. The filter is happiest returning the full match. Capture-group selection works in some contexts (e.g. `vars:` blocks, certain Jinja invocations) but not consistently inside `set_fact`, which makes "copy this snippet from the docs" fail intermittently. + +## Fix — match the broader pattern, then split + +Stop fighting the back-reference. Use `regex_search` to grab a string that *contains* the value you want, then peel it apart with plain Python string ops: + +```yaml +- name: Capture package count # ✅ works in set_fact + ansible.builtin.set_fact: + pkg_count: "{{ (apt_upgrade_result.stdout | regex_search('[0-9]+ upgraded') | default('0')).split()[0] }}" +``` + +What this does: + +1. `regex_search('[0-9]+ upgraded')` returns the matching substring (e.g. `"7 upgraded"`) or `None` on no match. +2. `default('0')` turns the `None` case into the string `"0"` so the next step always has something to operate on. +3. `.split()[0]` keeps just the number. + +The result (`"7"`) is a string — cast with `| int` if you need arithmetic. + +## Where this comes up in MajorAnsible + +The `update.yml` executive-summary task uses this pattern to pull package counts out of `apt_upgrade_result.stdout` and `dnf_upgrade_result.stdout` so each host can print one tidy line: + +``` +majorhome: 7 pkg(s) upgraded | No reboot needed | 2 active screen(s) +majormail: 14 pkg(s) upgraded | REBOOT REQUIRED | Snapshot taken +majorlab: 0 pkg(s) upgraded | No reboot needed +``` + +The summary line is built with a Jinja `parts` array joined with `' | '` so segments that don't apply (no snapshot, no screens) drop out cleanly without leaving trailing separators. + +## Quick checks if this still misbehaves + +- **Confirm the source variable.** Ansible 2.x sometimes returns stdout as `result.stdout` and sometimes as `result.stdout_lines`; the `regex_search` filter wants a string, not a list. Use `.stdout` (or `.stdout | join('\n')` for a multi-line list). +- **Escape your backslashes.** In YAML strings, `\d` needs to be written `\\d` or wrapped in single quotes: `'(\d+) upgraded'`. +- **Always provide a default.** `regex_search` returns `None` on miss, which will explode `.split()[0]`. The `| default('0')` bridge is mandatory in production playbooks where some hosts will legitimately have zero upgrades. + +## Related + +- [[ansible-vault-password-file-missing]] — another set_fact / vault interaction quirk +- [[ansible-ssh-timeout-dnf-upgrade]] — companion gotcha when running `update.yml` diff --git a/05-troubleshooting/index.md b/05-troubleshooting/index.md index 6625575..682ba4d 100644 --- a/05-troubleshooting/index.md +++ b/05-troubleshooting/index.md @@ -1,6 +1,6 @@ --- created: 2026-03-15T06:37 -updated: 2026-04-30T10:41 +updated: 2026-05-02T17:50 --- # 🔧 General Troubleshooting @@ -27,6 +27,7 @@ Practical fixes for common Linux, networking, and application problems. - [SSH Timeout During dnf upgrade on Fedora Hosts](ansible-ssh-timeout-dnf-upgrade.md) - [Vault Password File Missing](ansible-vault-password-file-missing.md) - [ansible.cfg Ignored on WSL2 Windows Mounts](ansible-wsl2-world-writable-mount-ignores-cfg.md) +- [regex_search — capture-group argument doesn't work in set_fact](ansible-regex-search-set-fact-capture-group.md) ## 📦 Docker & Systems - [Docker & Caddy Recovery After Reboot (Fedora + SELinux)](docker-caddy-selinux-post-reboot-recovery.md) diff --git a/SUMMARY.md b/SUMMARY.md index e6e1dd7..a225527 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,6 @@ --- created: 2026-04-02T16:03 -updated: 2026-05-02T17:16 +updated: 2026-05-05T23:39 --- * [Home](index.md) * [Linux & Sysadmin](01-linux/index.md) @@ -52,6 +52,7 @@ updated: 2026-05-02T17:16 * [SSH Hardening Fleet-Wide with Ansible](02-selfhosting/security/ssh-hardening-ansible-fleet.md) * [ClamAV Fleet Deployment with Ansible](02-selfhosting/security/clamav-fleet-deployment.md) * [Fail2Ban Digest Mode — Fleet-Wide Quiet Alerts](02-selfhosting/security/fail2ban-digest-mode-fleet.md) + * [Apache CVE-2026-23918 — HTTP/2 Double Free Mitigation](02-selfhosting/security/apache-cve-2026-23918-http2-mitigation.md) * [Open Source & Alternatives](03-opensource/index.md) * [SearXNG: Private Self-Hosted Search](03-opensource/alternatives/searxng.md) * [FreshRSS: Self-Hosted RSS Reader](03-opensource/alternatives/freshrss.md) @@ -99,6 +100,7 @@ updated: 2026-05-02T17:16 * [Ansible: Vault Password File Not Found](05-troubleshooting/ansible-vault-password-file-missing.md) * [Ansible: ansible.cfg Ignored on WSL2 Windows Mounts](05-troubleshooting/ansible-wsl2-world-writable-mount-ignores-cfg.md) * [Ansible: SSH Timeout During dnf upgrade on Fedora Hosts](05-troubleshooting/ansible-ssh-timeout-dnf-upgrade.md) + * [Ansible: regex_search Capture-Group Argument Fails in set_fact](05-troubleshooting/ansible-regex-search-set-fact-capture-group.md) * [Fedora Networking & Kernel Troubleshooting](05-troubleshooting/fedora-networking-kernel-recovery.md) * [Systemd Session Scope Fails at Login](05-troubleshooting/systemd/session-scope-failure-at-login.md) * [wget/curl: URLs with Special Characters Fail in Bash](05-troubleshooting/wget-url-special-characters.md)