Comments (4)
Small update on this one: this is on the roadmap for v3. Will initially just be restoring the API docs but I’ll also work on some example code.
from pyinfra.
In case this is of use to anyone else encountering the same issues, below I've included code adapted from the pyinfra_cli
package (mostly main.py
), which can be used to execute deploy scripts (or python code contained in a string) on multiple hosts using the PyInfra API.
There is no parallelization - it would be up to the user to implement that however they wished (PyInfra's CLI uses gevent
), to construct an Inventory with additional data or groups, and so on.
I couldn't honestly say I understand the details of what's being done with the State and Config objects that are created, but the code below works for me, with PyInfra version 2.8. Perhaps that's something that could be clarified if an example like this were added to the documentation.
import os
import sys
import logging
from typing import Callable, List, Tuple
from pyinfra import logger, state
from pyinfra.api import Config, Inventory, State
from pyinfra.api.connect import connect_all
from pyinfra.api.operations import run_ops
from pyinfra.context import ctx_config, ctx_host, ctx_state
# Don't write out deploy.pyc etc
sys.dont_write_bytecode = True
# Force line buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 1)
sys.stderr = os.fdopen(sys.stderr.fileno(), "w", 1)
class LogHandler(logging.Handler):
"handle log records"
def emit(self, record):
try:
message = self.format(record)
print(message, file=sys.stderr)
except Exception:
self.handleError(record)
def exec_file(filename: str):
"""
Execute a Python file and optionally return its attributes as a dict.
"""
state.current_exec_filename = filename
with open(filename, "r", encoding="utf-8") as f:
code = f.read()
compiled_code = compile(code, filename, "exec")
# Execute the code with locals/globals going into the dict
globals_dict = {}
exec(compiled_code, globals_dict)
return globals_dict
def exec_str(code: str, filename: str):
"""
Execute a Python module string and optionally return its attributes as a dict.
"""
filename = "(none)"
state.current_exec_filename = filename
compiled_code = compile(code, filename, "exec")
# Execute the code with locals/globals going into the dict
globals_dict = {}
exec(compiled_code, globals_dict)
return globals_dict
def pyinfra_run(hosts: List[str], operations: List[Tuple[str,Callable]]):
logger.setLevel(logging.INFO)
handler = LogHandler()
logger.addHandler(handler)
# Setup state, config & inventory
cwd = os.getcwd()
state = State()
state.cwd = cwd
ctx_state.set(state)
config = Config()
config.lock_current_state()
print("--> Loading inventory...", file=sys.stderr)
inventory = Inventory( (hosts, {}) )
# Initialise the state
state.init(inventory, config)
# Connect to the hosts & start handling the user commands
print("--> Connecting to hosts...", file=sys.stderr)
connect_all(state)
for i, (filename, callback) in enumerate(operations):
logger.info(f"Loading: {filename}")
state.current_op_file_number = i
state.current_deploy_filename = filename
for host in state.inventory.iter_active_hosts():
with ctx_config.use(state.config.copy()):
with ctx_host.use(host):
callback()
logger.info(
"{0}{1} {2}".format(host.print_prefix, "Ready:", filename),
)
# Remove any config changes introduced by the deploy file & any includes
config.reset_locked_state()
# if desired: the logic from pyinfra_cli.prints.print_meta could be copied,
# for pretty-printing of proposed changes
#print("--> Proposed changes:", file=sys.stderr)
#print_meta(state)
print("--> Beginning operation run...", file=sys.stderr)
run_ops(state, serial=True, no_wait=False)
# if desired: the logic from pyinfra_cli.prints.print_results could be copied,
# for pretty-printing of final results
#print("--> Results:", file=sys.stderr)
#print_results(state)
if __name__ == "__main__":
hosts = ["host1.example.com", "host2.example.com"]
operations = [
("mydeploy.py", lambda: exec_file("mydeploy.py")),
("(nofile)", lambda: exec_str("print('hi there')", "(nofile)")),
]
pyinfra_run(hosts, operations)
from pyinfra.
Just to add a bit of further information ...
In my version of the above code, I've amended pyinfra_run()
to take an Inventory object rather than a list of hostnames - that seems more flexible than just assuming an SSH connector/host.
The code currently in pyinfra_cli.inventory.make_inventory_from_files – here:
pyinfra/pyinfra_cli/inventory.py
Line 153 in 52c3fa6
seems generally useful for quickly turning CLI-style inventory-strings (e.g. @docker/some-ctr-id,@ssh/some.hostname.com
) into an Inventory, so I've basically just copy-and-pasted it. (I haven't had a chance to go through the logic and see exactly what it's doing, and if it can be tidied up.)
If you'd be interested in a pull request to add a new example in examples
, showing how scripts can be created dynamically at runtime using the API, I'd be happy to create one – I'd be keen to hear what you think might be best practices in such an example before I do so.
from pyinfra.
Awesome! I'm looking forward to it :)
from pyinfra.
Related Issues (20)
- Add Type Hints to operations, facts & connectors HOT 1
- files.block is not working as expected when path exists and line missing
- files.sync is not normalizing paths when syncing from Windows to Linux HOT 1
- Operation for executing python code on remote? HOT 1
- DNF and YUM operation improvements
- Implement secure temporary file handling HOT 2
- https://docs.pyinfra.com/en/2.x/inventory-data.html should mention pyinfra.host.groups
- Missing distutils with pipx on 3.12 HOT 6
- Vagrant connector: Invalid connector data `ssh_port`:: str is not an instance of int
- Multiple entries in UserKnownHostsFile causes connection failure
- Use typos (+ drop flake8-spellcheck)
- Apt: Only update package lists if packages need to be installed
- The sudo password doesn't seem to be properly escaped HOT 1
- Fact errors during "Preparing Operations" mark host as failed even if fact will later succeed
- server.reboot: Never detect remote system is rebooted
- `--quiet` command line flag has no effect
- User-friendly but still somehow secure sudo-password saved in system KeyChain (via python-keyring) HOT 4
- files.block unnecessarily using restricted chown command
- Text file manipulation that is simpler than files.Block
- `files.copy`
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from pyinfra.