pyinfra icon indicating copy to clipboard operation
pyinfra copied to clipboard

Capturing standard output/error/status from operations

Open themanifold opened this issue 3 years ago • 5 comments

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.

themanifold avatar Oct 27 '21 12:10 themanifold

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.

Fizzadar avatar Oct 27 '21 16:10 Fizzadar

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:

  1. Download a zip file on a remote machine
  2. Find the name of the root directory within that zip file
  3. Extract the zip file
  4. 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.

themanifold avatar Oct 28 '21 06:10 themanifold

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.

Fizzadar avatar Oct 29 '21 07:10 Fizzadar

@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")

gnat avatar Nov 26 '21 02:11 gnat

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.

jmpolom avatar Feb 09 '22 02:02 jmpolom