Skip to content

connectors.util: honour _temp_dir in askpass helpers#1666

Open
wowi42 wants to merge 6 commits intopyinfra-dev:3.xfrom
KalvadTech:feature/per-host-temp-dir
Open

connectors.util: honour _temp_dir in askpass helpers#1666
wowi42 wants to merge 6 commits intopyinfra-dev:3.xfrom
KalvadTech:feature/per-host-temp-dir

Conversation

@wowi42
Copy link
Copy Markdown
Collaborator

@wowi42 wowi42 commented Apr 14, 2026

Closes #1623.

Problem

server.reboot and other ops respected _temp_dir (operation kwarg, or the per-host default cascaded from host.data._temp_dir via the global-argument system) for file placement, but the internal SUDO_ASKPASS / SU_ASKPASS helper script was still dropped into config.TEMP_DIR (usually /tmp). On hosts where /tmp is unusable (noexec, restricted tmpfs, TrueNAS, etc.) the askpass helper would fail even when the user had configured a working temp directory.

Fix

  • _ensure_askpass_set_for_host now takes a temp_dir override and uses it for the mkstemp template.
  • make_unix_command_for_host threads the operation-level _temp_dir through to the askpass helpers.
  • The resolved temp_dir is stored alongside the cached path (<key>_temp_dir); if a later call resolves a different temp_dir the cache is invalidated so the script gets regenerated under the correct directory. Callers that read host.connector_data["sudo_askpass_path"] directly don't need to change.

Per-host defaults don't need any new code path: setting host.data._temp_dir already cascades as the default for the _temp_dir global argument on every op, which is what gets passed down.

Test plan

  • Askpass path for default, config.TEMP_DIR, and op-level _temp_dir.
  • Cache invalidation when temp_dir changes between calls.
  • Full path through make_unix_command_for_host with _sudo=True + _temp_dir.
  • uv run pytest (1613 passed), ruff / ruff format / mypy / scripts/lint_arguments_sync.py clean.

  • Pull request is based on the default branch (3.x)
  • Pull request includes tests for any new/updated operations/facts
  • Pull request includes documentation for any new/updated operations/facts (behaviour fix, no public API / docs change)
  • Tests pass (scripts/dev-test.sh)
  • Type checking & code style passes (scripts/dev-lint.sh)

Copy link
Copy Markdown
Member

@Fizzadar Fizzadar left a comment

Choose a reason for hiding this comment

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

Looks good, two things to fix.

Comment thread src/pyinfra/api/host.py Outdated
value = self.data.get("temp_dir")
if value:
return value
value = self.data.get("_temp_dir")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This shouldn't be required - we already apply global arguments in host data (so _temp_dir in host data will apply to all operations as an argument): https://github.com/pyinfra-dev/pyinfra/blob/3.x/src/pyinfra/api/arguments.py#L405

return _ensure_askpass_set_for_host(host, "sudo_askpass_path", SUDO_ASKPASS_ENV_VAR)
def _ensure_sudo_askpass_set_for_host(host: "Host", temp_dir: Optional[str] = None):
return _ensure_askpass_set_for_host(
host, "sudo_askpass_path", SUDO_ASKPASS_ENV_VAR, temp_dir=temp_dir
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should add the path to the key here or we'll incorrectly cache the wrong askpass path if the value is changed (very unlikely, but worth guarding against).

Suggested change
host, "sudo_askpass_path", SUDO_ASKPASS_ENV_VAR, temp_dir=temp_dir
host, f"sudo_askpass_path:{temp_dir}", SUDO_ASKPASS_ENV_VAR, temp_dir=temp_dir

Closes pyinfra-dev#1623. The SUDO_ASKPASS / SU_ASKPASS helper script is now placed
under the same directory the caller requested, fixing the case where an
operation-level _temp_dir (or a per-host default cascaded through the
_temp_dir global argument on host.data) was respected for file ops but
ignored by the internal askpass helpers.

Resolution order (unchanged outside the askpass path):

1. Operation-level _temp_dir (explicit caller)
2. config.TEMP_DIR / host.data._temp_dir via the existing
   global-argument cascade
3. TmpDir fact (_get_temp_directory only)
4. config.DEFAULT_TEMP_DIR

Changes:

- _ensure_askpass_set_for_host accepts a temp_dir override and tracks
  the resolved directory next to the cached path; if a later call
  resolves a different temp_dir the cached path is invalidated so the
  script gets regenerated under the correct directory.
- make_unix_command_for_host threads _temp_dir through to the askpass
  helpers so the op-level override takes effect.

Tests cover the askpass path for default, config, and op-level temp
directories, cache invalidation on temp_dir change, and the full path
through make_unix_command_for_host. Tests use unique hostnames to
avoid sharing the process-global memoize cache on _get_temp_directory.
@wowi42 wowi42 force-pushed the feature/per-host-temp-dir branch from dd7d9b4 to 8798dd1 Compare April 23, 2026 05:39
@wowi42
Copy link
Copy Markdown
Collaborator Author

wowi42 commented Apr 23, 2026

Thanks for the review, both addressed and rebased on 3.x.

Dropped the host.py bits entirely, you were right, _temp_dir on host.data already cascades via global arguments. The askpass helpers just pick it up from the op-level kwarg now.

For the cache key, went with storing the resolved temp_dir next to the path and invalidating when it changes, so readers like make_unix_command_for_host keep working unchanged. New test covers the invalidation.

PR description is now a bit stale, I'll clean it up next.

@wowi42 wowi42 changed the title api,connectors: support per-host temp_dir override connectors.util: honour _temp_dir in askpass helpers Apr 23, 2026
@DonDebonair
Copy link
Copy Markdown
Collaborator

DonDebonair commented Apr 24, 2026

@wowi42 there is one open comment you didn't address yet :)

Maybe @Fizzadar should comment on what he wants with the cache key

Copy link
Copy Markdown
Member

@Fizzadar Fizzadar left a comment

Choose a reason for hiding this comment

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

This seems overly complicated, could we not just add the tmp path to the key at call time if set? so we end up with keys like:

  • sudo_askpass_path__/tmp
  • sudo_askpass_path__/x

and so no? No need for the extra field or checks then (I think!)

wowi42 added 2 commits May 2, 2026 21:05
Per-host askpass paths are now cached under
``{sudo,su}_askpass_path__<temp_dir>`` instead of a bare key paired
with a separate temp_dir tracker field. Each (host, temp_dir) pair
gets its own cache slot, so a different ``_temp_dir`` on a later call
just misses the cache and creates a fresh script under the right
directory.

- ``_ensure_askpass_set_for_host`` (and its sudo/su wrappers) now
  return the resolved path. ``make_unix_command_for_host`` uses the
  return value directly instead of reading ``connector_data``.
- ``remove_any_sudo_askpass_file`` walks every cached variant so
  ``host.disconnect()`` cleans up askpass scripts under any temp dir
  the host produced during the run.
- New ``clear_askpass_cache`` helper drops every cached path without
  touching the remote, used by ``server.reboot`` around the reboot
  window where the previous connection (and its temp dir) is gone.
@wowi42
Copy link
Copy Markdown
Collaborator Author

wowi42 commented May 2, 2026

Good call, that is much cleaner. Pushed efc8158 with the simpler scheme:

  • _ensure_askpass_set_for_host now caches under f"{key}__{effective_temp_dir}" (e.g. sudo_askpass_path__/tmp, sudo_askpass_path__/dev/shm) and returns the resolved path. The separate tracker field and the invalidation block are gone, switching _temp_dir between calls just misses the cache and creates a fresh script under the right directory.
  • make_unix_command_for_host uses the returned path directly instead of round-tripping through connector_data.
  • remove_any_sudo_askpass_file now walks every {sudo,su}_askpass_path* variant so host.disconnect() cleans up scripts under any temp dir the host saw during the run. Added clear_askpass_cache for server.reboot, which clears the cache without touching the (gone) remote.

Tests updated to seed the temp_dir-suffixed keys and cover both the multi-variant cleanup path and same-temp_dir cache reuse.

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.

Per-host TEMP_DIR override

3 participants