pyinfra-dev / pyinfra

pyinfra turns Python code into shell commands and runs them on your servers. Execute ad-hoc commands and write declarative operations. Target SSH servers, local machine and Docker containers. Fast and scales from one server to thousands.
https://pyinfra.com
MIT License
3.84k stars 374 forks source link

Nested operations do not support programmatic sudo #806

Open themanifold opened 2 years ago

themanifold commented 2 years ago

Describe the bug

Trying to use nested operations which require programmatic sudo access throws AttributeError

UPDATE: perhaps this should just be that nested operations do not support custom config attributes(?)

To Reproduce

from pyinfra import config

config.USE_REMOTE_SUDO_PASSWORD = "something"

def callback():
    result = server.shell(
        commands=["cat foo"],
        _sudo=True,
        _use_sudo_password=config.USE_REMOTE_SUDO_PASSWORD
    )

def caller(vm_name):
    python.call(
        name="Execute callback function",
        function=callback,
    )

[server.foo] Unexpected error in Python callback: AttributeError("'Config' object has no attribute 'USE_REMOTE_SUDO_PASSWORD'",)

Expected behavior

I would expect a nested operation to support programmtic sudo access

Meta

Fizzadar commented 2 years ago

The config variable here should be USE_SUDO_PASSWORD, that should work as expected!

themanifold commented 2 years ago

OK, so I think what I used to do was create a global variable in the config object call USE_REMOTE_PASSWORD and that I passed to use_sudo_password. Now what I'm doing is just setting config.USE_SUDO_PASSWORD and config.SUDO at the top of the code and everything now works apart from the nested operation which just seems to ignore the config settings and not embed the shell snippet within the usual env SUDO_ASKPASS=pyinfra-sudo-askpass *** sh -c '<shell snippet'> like I would expect.

Fizzadar commented 1 year ago

Very slow getting back on this, apologies. So the reason this fails is because the config object is unique per host and also per stage (first fact gathering then execution) - but the config for one host is not shared between each stage. This the function call here uses the original config without the extra attribute:

https://github.com/Fizzadar/pyinfra/blob/fa23aba0ac8742c246bbfacfb889caab1c796a98/pyinfra/api/command.py#L202

Quick workaround would be adding the attribute in config.py (or whatever --config X.py filename). Long term I think the original example you provide should work as expected, so config may need to be scoped by host rather than state to achieve that.