pyinfra
pyinfra copied to clipboard
Capturing standard output/error/status from operations
As far as I can see, there is no way of capturing the output/error output of operations that are run against a server. This would be really useful - for example, you could capture the output of an operation and then use it in the next operation. I know you can do it within one operation using python.call
but I can't see a way of doing it between operations.
Unfortunately this is a limitation of pyinfra's execution model, and so there's no way currently to get the output of an operation before execution.
It is possible, however, to get arbitrary information from the target machine during fact gathering using the server.Command
fact. Would this satisfy your use-case?
I have been considering the execution model in general recently, am keen to hear use-cases that it doesn't work for and look into possible solutions.
It is possible, however, to get arbitrary information from the target machine during fact gathering using the server.Command fact. Would this satisfy your use-case?
Yes, this is actually great for me. My use case is this:
- Download a zip file on a remote machine
- Find the name of the root directory within that zip file
- Extract the zip file
- Use the name of that root directory to do other operations
So for me, this is fine I think.
However, it is not inconceivable to think of a case where you need the stdout of a command that has just run, and there being no way to infer it with a separate command, and in this type of case, having a way to record the standard out would be great.
However, it is not inconceivable to think of a case where you need the stdout of a command that has just run, and there being no way to infer it with a separate command, and in this type of case, having a way to record the standard out would be great.
100% - this is quite a common request. It may be possible to have some kind of object that you can pass between operations that containts the stdout at runtime (but not plan time). This would be very similar to the dynamic execution example except that you could pass in the output of a previous operation to the function.
@themanifold The one way I've found to do this, which I use quite extensively at this time is host.fact.command()
Example for adding stuff to PATH.
from pyinfra import host, local, config
from pyinfra.operations import server
config.SUDO = False # Normal user only.
config.USER = USER = host.data.user_name
# Add extra bin locations to PATH if not done already (for Rootless Docker, CockroachDB, etc)
output = host.fact.command(f"[[ $(cat /home/{USER}/.bashrc | grep '/.local/bin:') = */.local/bin:* ]] && echo 'YES' || echo 'NO'")
if output == "NO":
server.shell(f"sed -i '1iexport PATH=/home/{USER}/bin:/home/{USER}/.local/bin:$PATH' ~/.bashrc")
Unfortunately this is a limitation of pyinfra's execution model, and so there's no way currently to get the output of an operation before execution.
I am confused by this. There is an example show a .changed
property on the object returned from the operation. Is it not possible to include a .stdout
property?
~~I'll add that output = host.fact.command('/export terse')
does not work on devices without POSIX shells (IE: "dumb" devices like switches with proprietary shells) because sh -c
is prepended to the command issued. There is a workaround for this in the server.shell
operation but it isn't apparent if that is also present for the Command fact. If it isn't, it should be. I'd be happy to add this feature and send a PR if someone could point me to where the relevant location.~~ I am dumb...
The following works with cranky proprietary non-POSIX environments pretty well it seems:
from pyinfra import config, host
from pyinfra.facts.server import Command
config.SHELL = None
export_output = host.get_fact(Command, '/export terse')
with open('backups/backup.rsc', 'w') as f:
f.write(export_output)
Is there a specific place in the documentation where this could be contributed as an example? The fact that pyinfra does not rely on python on the target device (like another popular config management tool) makes it ideal for interacting with "dumb" devices where the only decent management method is ssh. It would be nice to highlight how it can be used with such devices in the docs a bit more.