Writing Connectors¶
Connectors enable pyinfra to directly integrate with other tools and systems. Connectors are written as Python classes.
Connectors come in three variations:
Inventory only
Execution only
Inventory and Execution
The last case just requires combining the two examples below and including any host/group gathering logic in make_names_data
.
Inventory Connector¶
Inventory connectors can return one or more hosts each invocation of make_names_data
.
In the example below make_names_data
is yielding a hostname, data, groups tuple. As long as the final yield
is a tuple though, the gathered data
could be stored and processed in any data structure.
class InventoryConnector(BaseConnector):
handles_execution = False
@staticmethod
def make_names_data(_=None):
"""
Generate inventory targets.
Yields:
tuple: (name, data, groups)
"""
# connect to api/parse files/process data here, resulting in a list of tuples;
gathered_hosts = [
('@local', {}, ['@local']),
('foundhost', {'ip': 198.51.100.4}, ['remote', 'example'])
]
for loop_host in gathered_hosts:
yield gathered_hosts[0], gathered_hosts[1], gathered_hosts[2]
To use the inventory connector call pyinfra @[name of connector in pyinfra.connectors] [deployment script].py
The connector can also be run using pyinfra @[name of connector in pyinfra.connectors]/[hostname] [deployment script].py
. If executed this way
(inventory/data requested for a single host), make_names_data(_=None)
should be updated to make_names_data(name)
and the name == None
case
handled in code separately from name
being a valid string.
Executing Connector¶
A connector that implements execution requires a few more methods:
class LocalConnector(BaseConnector):
handles_execution = True
@staticmethod
def make_names_data(_=None):
# Unlike InventoryConnector above, this connector can only return one host each invocation of make_names_data().
... # see above
def run_shell_command(
self,
command: StringCommand,
print_output: bool = False,
print_input: bool = False,
**arguments: Unpack["ConnectorArguments"],
) -> Tuple[bool, CommandOutput]:
"""
Execute a command on the local machine.
Args:
command (StringCommand): actual command to execute
print_output (bool): whether to print command output
print_input (bool): whether to print command input
arguments: (ConnectorArguments): connector global arguments
Returns:
tuple: (bool, CommandOutput)
Bool indicating success and CommandOutput with stdout/stderr lines.
"""
def put_file(
self,
filename_or_io,
remote_filename,
remote_temp_filename=None, # ignored
print_output: bool = False,
print_input: bool = False,
**arguments,
) -> bool:
"""
Upload a local file or IO object by copying it to a temporary directory
and then writing it to the upload location.
Returns:
bool: indicating success or failure.
"""
def get_file(
self,
remote_filename,
filename_or_io,
remote_temp_filename=None, # ignored
print_output: bool = False,
print_input: bool = False,
**arguments,
) -> bool:
"""
Download a local file by copying it to a temporary location and then writing
it to our filename or IO object.
Returns:
bool: indicating success or failure.
"""
Where to make changes¶
Connectors enable pyinfra to expand work done in its 5 stages <deploy-process.html#how-pyinfra-works>
_ by providing methods which can be called at
appropriate times.
To hook in to to the various steps with the methods outlined below.
--> Loading config...
--> Loading inventory...
make_names_data
is used to supply inventory data about a host while at ‘Loading inventory’ stage.
Its worth being aware up front that due to make_names_data
being a staticmethod
it has no automatic access to the parent classes attributes.
To work around this - eg to configure an API connector - configuration will have to happen outside the function and be imported in. Two examples
(getattr
and a function) are provided below.
def load_settings():
settings = {}
# logic here
return settings
class InventoryConnector(BaseConnector):
api_instance = external.ApiClient()
...
@staticmethod
def make_names_data(_=None)
api_client = getattr(InventoryConnector, 'api_instance')
api_settings = load_settings()
...
--> Connecting to hosts...
[pytest.example.com] Connected
connect
can be used to check access to a host is possible. If the connection fails ConnectError
should be raised with a message to display on
screen.
--> Preparing operations...
--> Preparing Operations...
Loading: deploy_create_users.py
[pytest.example.com] Ready: deploy_create_users.py
--> Detected changes:
[list of changes here]
--> Beginning operation run...
--> Starting operation: sshd_install.py | Install OpenSSH server
[pytest.example.com] No changes
run_shell_command
, put_file
, get_file
and rsync
can be used to change behaviour of pyinfra as it performs operations.
--> Results:
Operation Hosts Success Error No Change
--> Disconnecting from hosts...
disconnect
can be used after all operations complete to clean up any connection/s remaining to the hosts being managed.
pyproject.toml¶
In order for pyinfra to gain knowledge about your connector, you need to add the following snippet to your connector’s pyproject.toml
:
[project.entry-points.'pyinfra.connectors']
# Key = Entry point name
# Value = module_path:class_name
custom = 'pyinfra_custom_connector.connector:LoggingConnector'
If modifying pyinfra directly, pyinfra.connectors
should be added to setup.py
.