Skip to content

test: guard run_ansible_in_environment arg word-splitting#2499

Merged
berendt merged 1 commit into
mainfrom
test-guard-arg-word-split
Jul 21, 2026
Merged

test: guard run_ansible_in_environment arg word-splitting#2499
berendt merged 1 commit into
mainfrom
test-guard-arg-word-split

Conversation

@ideaship

Copy link
Copy Markdown
Contributor

What

Adds a unit-test regression guard for the argument-joining behaviour of
run_ansible_in_environment in osism/tasks/__init__.py. Test only — no
behaviour change.

Why

run_ansible_in_environment joins a list of arguments with plain spaces
(" ".join(arguments)) and runs the result through
subprocess.Popen(..., shell=True), so /bin/sh word-splits each element into
separate argv. Several callers deliberately rely on this — they pack multiple
shell words into a single list element:

  • commands/set.py, commands/noset.py: ["-e status=True", f"-l {host}"]
  • commands/validate.py: "-e kolla_action=config_validate"
  • commands/apply.py: [f"-e kolla_action={action}"] + arguments

The run-<environment>.sh scripts forward args via "$@" (no re-split), so the
outer-shell split is load-bearing.

This makes the join fragile in a non-obvious way: a value containing a shell
metacharacter (e.g. tempest_include_regex=(A|B)) breaks at /bin/sh
(Syntax error: "(" unexpected). The tempting one-line "fix" —
" ".join(shlex.quote(a) for a in arguments) — would regress the callers
above by gluing -e status=True into a single token, and the existing
test_run_ansible_list_arguments_joined would not catch it (it uses only
single-word elements, on which shlex.quote is a no-op).

What the guard does

Pins the multi-token behaviour: a space-containing element must stay word-split
and must not be per-element quoted. It fails under a naive per-element
shlex.quote, and its comment records the safe alternatives (split each element
on whitespace first then quote, or normalise the callers to emit already-split
tokens) so a future metacharacter-safety fix is done without breaking the
split-dependent callers.

🤖 Generated with Claude Code

@ideaship
ideaship marked this pull request as ready for review July 21, 2026 11:59
@ideaship ideaship moved this from New to Ready for review in Human Board Jul 21, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The regression test currently relies on command.endswith(...), which is a bit brittle; consider asserting the full exact command string or parsing the relevant flags from the command to ensure future unrelated changes to prefix/suffix formatting don’t accidentally bypass this guard.
  • The explanatory comment in test_run_ansible_list_multitoken_element_word_split_not_quoted is quite long; consider extracting the rationale into a brief reference (e.g., link to an issue or dev note) and keeping only the key invariants this test is enforcing so the test remains easy to scan.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The regression test currently relies on `command.endswith(...)`, which is a bit brittle; consider asserting the full exact command string or parsing the relevant flags from the command to ensure future unrelated changes to prefix/suffix formatting don’t accidentally bypass this guard.
- The explanatory comment in `test_run_ansible_list_multitoken_element_word_split_not_quoted` is quite long; consider extracting the rationale into a brief reference (e.g., link to an issue or dev note) and keeping only the key invariants this test is enforcing so the test remains easy to scan.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

run_ansible_in_environment joins a list of arguments with plain spaces
(" ".join(arguments)) and runs the result through
subprocess.Popen(..., shell=True), so /bin/sh word-splits each element
into separate argv. Several callers depend on this: they pack multiple
shell words into a single list element and rely on that split, e.g.
commands/set.py and commands/noset.py pass ["-e status=True",
"-l <host>"], and commands/validate.py / commands/apply.py prepend
"-e kolla_action=...". The run-<environment>.sh scripts forward args
via "$@", so the outer-shell split is load-bearing.

A well-meaning "fix" that shlex.quote()s each list element (to make a
value with shell metacharacters safe, such as a tempest_include_regex
of "(A|B)") would glue "-e status=True" into one token and silently
break -e/-l parsing for those callers. The existing
test_run_ansible_list_arguments_joined uses only single-word elements,
on which shlex.quote is a no-op, so it does not catch this.

Add a guard that pins the multi-token element behaviour: a
space-containing element must stay word-split and must not be
per-element quoted. It fails under a naive per-element quote and
records the safe alternatives (split each element on whitespace before
quoting, or normalise the callers to emit already-split tokens).

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Roger Luethi <luethi@osism.tech>
@ideaship
ideaship force-pushed the test-guard-arg-word-split branch from 5896519 to a2d13cc Compare July 21, 2026 14:18
@ideaship
ideaship requested a review from berendt July 21, 2026 14:36
@ideaship ideaship moved this from Ready for review to In review in Human Board Jul 21, 2026
@berendt
berendt merged commit d01c3cb into main Jul 21, 2026
3 checks passed
@berendt
berendt deleted the test-guard-arg-word-split branch July 21, 2026 16:52
@github-project-automation github-project-automation Bot moved this from In review to Done in Human Board Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants