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.91k stars 382 forks source link

[Feature] Run a function in an operations file, individually. #938

Open gnat opened 1 year ago

gnat commented 1 year ago

Is your feature request related to a problem? Please describe

Benefits

My /operations directory is becoming quite large with many highly related operations. It would be an amazing simplification to be able to group highly related / multi-step operations inside one file.

Describe the solution you'd like

Perhaps borrow the colon syntax : from gunicorn / uvicorn / sanic server. This will prevent conflicting with the existing syntax of calling built-in operations.

Fizzadar commented 1 year ago

Hi @gnat! So rather funny - I actually added partial support for this in v2.6: https://github.com/Fizzadar/pyinfra/commit/6b704b53a6ffb7cc63a1c9267ba3ee24d3c0638b

But it's not quite there - I like your example of passing a file by name with the colon selector as well, the current implementation only works for things that can be imported, ie that are in the PYTHONPATH.

gnat commented 1 year ago

Aha, nice additions! I like the direction pyinfra is headed here. Yeah it would be amazing to be able to call these without a module, and without explicitly setting PYTHONPATH before calling pyinfra.

It'll be great to be able to collapse all the various project-level task runners: bash, npm, fabric, gulp, make, etc. into a single pyinfra script at the root of a project- would make for a very cohesive experience with pyinfra also running the server ops stuff.

gnat commented 1 year ago

Can probably disregard this post if further development is in the works, but I'm continually running into Cannot assign to context base module after forcing PYTHONPATH to the correct search location (otherwise I just get No such module).

Not quite clear on usage yet but I get Cannot assign to context base module with either of these:

from pyinfra.operations import server
from pyinfra.api import operation

def test():
    server.shell("hello world")

@operation()
def test2():
    yield "echo 'hello world'"

Also related: https://github.com/Fizzadar/pyinfra/issues/833

Anyways, hoping for further development on this feature!

Fizzadar commented 1 year ago

@gnat how are you running the above?

$ cat testo.py
from pyinfra.operations import server
from pyinfra.api import operation

def test():
    server.shell("hello world")

@operation()
def test2():
    yield "echo 'hello world'"

Running both works fine:

$ PYTHONPATH=. pyinfra @local testo.test2

--> Loading config...

--> Loading inventory...

--> Connecting to hosts...
    [@local] Connected

--> Preparing operation...
    [@local] Ready: test2

--> Proposed changes:
    Groups: @local
    [@local]   Operations: 1   Change: 1   No change: 0

--> Beginning operation run...
--> Starting operation: Testo/Test2
    [@local] Success

--> Results:
    Groups: @local
    [@local]   Changed: 1   No change: 0   Errors: 0
gnat commented 1 year ago

Very cool trick with PYTHONPATH=.!

My bad, not sure what I was doing wrong all those weeks ago, works now with your example.

I wonder if we could eliminate needing to pass PYTHONPATH

gnat commented 1 year ago

Slightly off topic but I've re-discovered why I'm getting Cannot assign to context base module @Fizzadar

Minimum example:

$ cat testo.py
from pyinfra.operations import server
from pyinfra import state

# Print all output without -vvv. Problematic!
state.print_output = True
state.print_fact_output = True

def test():
    server.shell("echo 'hello world'")

PYTHONPATH=. pyinfra @local testo.test

Which seems to break pyinfra when used in this way.

Any alternatives for always getting output from echo?