tbot.machine.connector
¶
Connectors are one of the three parts of a machine: The connector is responsible for establishing the initial channel for a machine. This can work in many different ways, either a simple subprocess, an ssh-connection, a serial-console device, or a telnet session.
All connectors should inherit from the
Connector
base-class.
Provided Connectors¶
For a lot of commonly used cases, tbot already has connectors at hand. These are:
Subprocess¶
- class tbot.machine.connector.SubprocessConnector[source]¶
Bases:
Connector
Connector using a subprocess shell.
Arguably the simplest connector; simply spawns a subprocess with a shell. This is the connector used by the default local lab-host.
Example:
from tbot.machine import connector, linux class MyMachine( connector.SubprocessConnector, linux.Bash, ): pass with MyMachine() as localhost: localhost.exec0("echo", "Hello!")
- classmethod from_context(ctx: Context) Iterator[M] [source]¶
Create this machine from a tbot context.
This method defines how tbot can automatically attempt creating this machine from a given context. It is usually defined by the connector but might be overridden by board config in certain more complex scenarios.
This method must return a context-manager that, upon entering, yields a fully initialized machine. In practical terms this means, the implementation must enter the “machine’s context” as well. As an example, the most basic implementation would look like this:
@contextlib.contextmanager def from_context(cls, ctx): # Create instance and enter its context, in this example, no # args are passed to the constructor ... with cls() as m: yield m
- ch: Channel¶
Channel to communicate with this machine.
Warning
Please refrain from interacting with the channel directly. Instead, write a
Shell
that wraps around the channel interaction. That way, the state of the channel is only managed in a single place and you won’t have to deal with nasty bugs when multiple parties make assumptions about the state of the channel.
Paramiko¶
- class tbot.machine.connector.ParamikoConnector(other: ParamikoConnector | None = None)[source]¶
Bases:
Connector
Connect to an ssh server using Paramiko.
When inheriting from this connector, you should overwrite the attributes documented below to make it connect to your remote.
Example:
from tbot.machine import connector, linux class MyMachine( connector.ParamikoConnector, linux.Bash, ): hostname = "78.79.32.85" username = "tbot-user" with MyMachine() as remotehost: remotehost.exec0("uname", "-a")
- Parameters:
other (ParamikoConnector) – Build this connection by opening a new channel in an existing ssh-connection.
- abstract property hostname: str¶
Hostname of this remote.
You must always specify this parameter in your Lab config!
- property username: str¶
Username to log in as.
Defaults to the username from
~/.ssh/config
or the local username.
- property authenticator: PasswordAuthenticator | PrivateKeyAuthenticator | NoneAuthenticator | UndefinedAuthenticator¶
Return an authenticator that allows logging in on this machine.
See
tbot.machine.linux.auth
for available authenticators.- Return type:
tbot.machine.linux.auth.Authenticator
- property port: int¶
Port the remote SSH server is listening on.
Defaults to
22
or the value ofPort
in~/.ssh/config
.
- property ignore_hostkey: bool¶
Ignore remote host key.
Set this to true if the remote changes its host key often.
Defaults to
False
or the value ofStrictHostKeyChecking
in~/.ssh/config
.
- classmethod from_context(ctx: Context) Iterator[Self] [source]¶
Create this machine from a tbot context.
This method defines how tbot can automatically attempt creating this machine from a given context. It is usually defined by the connector but might be overridden by board config in certain more complex scenarios.
This method must return a context-manager that, upon entering, yields a fully initialized machine. In practical terms this means, the implementation must enter the “machine’s context” as well. As an example, the most basic implementation would look like this:
@contextlib.contextmanager def from_context(cls, ctx): # Create instance and enter its context, in this example, no # args are passed to the constructor ... with cls() as m: yield m
- ch: Channel¶
Channel to communicate with this machine.
Warning
Please refrain from interacting with the channel directly. Instead, write a
Shell
that wraps around the channel interaction. That way, the state of the channel is only managed in a single place and you won’t have to deal with nasty bugs when multiple parties make assumptions about the state of the channel.
Serial Console¶
- class tbot.machine.connector.ConsoleConnector(mach: LinuxShell)[source]¶
Bases:
Connector
Connector for serial-consoles.
As this can work in many different ways, this connector is intentionally as generic as possible. To configure a serial connection, you need to implement the
ConsoleConnector.connect()
method. That methods gets a lab-host channel which it should transform into a channel connected to the board’s serial console.Example:
import tbot from tbot.machine import board, connector class MyBoard( connector.ConsoleConnector, board.Board, ): def connect(self, mach): return mach.open_channel("picocom", "-b", "115200", "/dev/ttyACM0") with tbot.acquire_local() as lo: with MyBoard(lo) as b: ...
- Parameters:
mach (LinuxShell) – A cloneable lab-host machine. The
ConsoleConnector
will try to clone this machine’s connection and use that to connect to the board. This means that you have to make sure you give the correct lab-host for your respective board to the constructor here.
- abstract connect(mach: LinuxShell) AbstractContextManager[Channel] [source]¶
Connect a machine to the serial console.
Overwrite this method with the necessary logic to connect the given machine
mach
to a channel connected to the board’s serial console.In most cases you’ll accomplish this using
mach.open_channel(...)
.
- classmethod from_context(ctx: Context) Iterator[M] [source]¶
Create this machine from a tbot context.
This method defines how tbot can automatically attempt creating this machine from a given context. It is usually defined by the connector but might be overridden by board config in certain more complex scenarios.
This method must return a context-manager that, upon entering, yields a fully initialized machine. In practical terms this means, the implementation must enter the “machine’s context” as well. As an example, the most basic implementation would look like this:
@contextlib.contextmanager def from_context(cls, ctx): # Create instance and enter its context, in this example, no # args are passed to the constructor ... with cls() as m: yield m
- ch: Channel¶
Channel to communicate with this machine.
Warning
Please refrain from interacting with the channel directly. Instead, write a
Shell
that wraps around the channel interaction. That way, the state of the channel is only managed in a single place and you won’t have to deal with nasty bugs when multiple parties make assumptions about the state of the channel.
Plain SSH¶
- class tbot.machine.connector.SSHConnector(host: LinuxShell | None = None)[source]¶
Bases:
Connector
Connect to remote using
ssh
by starting off from an existing machine.An
SSHConnector
is different from aParamikoConnector
as it requires an existing machine to start the connection from. This allows jumping via one host to a second.Example:
import tbot from tbot.machine import connector, linux # Connect into a container running on the (possibly remote) lab-host class MyRemote( connector.SSHConnector, linux.Bash, ): hostname = "localhost" port = 20220 username = "root" with tbot.acquire_lab() as lh: # lh might be a ParamikoConnector machine. with MyRemote(lh) as ssh_session: ssh_session.exec0("uptime")
- property ignore_hostkey: bool¶
Ignore host key.
Set this to true if the remote changes its host key often.
- property use_multiplexing: bool¶
Whether tbot should attempt to enable connection multiplexing.
Connection multiplexing is a mechanism to share a connection between multiple sessions. This can drastically speed up your tests when many connections to the same machine are opened and closed. Refer to ControlMaster in sshd_config(5) for details.
New in version 0.9.0.
- property username: str¶
Return the username for logging in on this machine.
Defaults to the username on the labhost.
- property authenticator: PasswordAuthenticator | PrivateKeyAuthenticator | NoneAuthenticator | UndefinedAuthenticator¶
Return an authenticator that allows logging in on this machine.
See
tbot.machine.linux.auth
for available authenticators.Danger
It is strongly advised to use key authentication. If you use password auth, THE PASSWORD WILL BE LEAKED and MIGHT EASILY BE STOLEN by other users on your labhost. It will also be visible in the log file.
If you decide to use this, you’re doing this on your own risk.
The only case where I support using passwords is when connecting to a test board with a default password.
- Return type:
tbot.machine.linux.auth.Authenticator
- property ssh_config: List[str]¶
Add additional ssh config options when connecting.
Example:
class MySSHMach(connector.SSHConnector, linux.Bash): ssh_config = ["ProxyJump=foo@example.com"]
New in version 0.6.2.
- classmethod from_context(ctx: Context) Iterator[Self] [source]¶
Create this machine from a tbot context.
This method defines how tbot can automatically attempt creating this machine from a given context. It is usually defined by the connector but might be overridden by board config in certain more complex scenarios.
This method must return a context-manager that, upon entering, yields a fully initialized machine. In practical terms this means, the implementation must enter the “machine’s context” as well. As an example, the most basic implementation would look like this:
@contextlib.contextmanager def from_context(cls, ctx): # Create instance and enter its context, in this example, no # args are passed to the constructor ... with cls() as m: yield m
- clone() SSHConnector [source]¶
Clone this machine.
- ch: Channel¶
Channel to communicate with this machine.
Warning
Please refrain from interacting with the channel directly. Instead, write a
Shell
that wraps around the channel interaction. That way, the state of the channel is only managed in a single place and you won’t have to deal with nasty bugs when multiple parties make assumptions about the state of the channel.
Base-Class¶
- class tbot.machine.connector.Connector[source]¶
Bases:
Machine
Base-class for machine connectors.
- abstract _connect() AbstractContextManager[Channel] [source]¶
Establish the channel.
This method will be called during machine-initialization and should yield a channel which will then be used for the machine.
This method’s return type is annotated as
typing.ContextManager[channel.Channel]
, to allow more complex setup & teardown. As channels implement the context-manager protocol, simple connectors can just return the channel. A more complex connector can use the following pattern:import contextlib class MyConnector(Connector): @contextlib.contextmanager def _connect(self) -> typing.Iterator[channel.Channel]: try: # Do setup ... yield ch finally: # Do teardown ...
- classmethod from_context(ctx: Context) AbstractContextManager[Self] [source]¶
Create this machine from a tbot context.
This method defines how tbot can automatically attempt creating this machine from a given context. It is usually defined by the connector but might be overridden by board config in certain more complex scenarios.
This method must return a context-manager that, upon entering, yields a fully initialized machine. In practical terms this means, the implementation must enter the “machine’s context” as well. As an example, the most basic implementation would look like this:
@contextlib.contextmanager def from_context(cls, ctx): # Create instance and enter its context, in this example, no # args are passed to the constructor ... with cls() as m: yield m
- abstract clone() Self [source]¶
Create a duplicate of this machine.
For a lot of connections, it is trivial to open a second one in parallel. This can be exploited to easily connect further from one host to the next, thus building a tunnel.
On the other hand, a serial connection to a board is unique and can’t be cloned. Such connectors should raise an exception is
.clone()
is called.Note
Important: You should always set the new machines
_orig
attribute to the original machine (eitherself._orig
or, if that isNone
,self
) so tbot knows these machines belong together! The common pattern is:def clone(self): new = ... new._orig = self._orig or self return new
Not setting
_orig
means that tbot will treat the new and old instances as separate machines which (theoretically) can’t interact with each other.
- ch: Channel¶
Channel to communicate with this machine.
Warning
Please refrain from interacting with the channel directly. Instead, write a
Shell
that wraps around the channel interaction. That way, the state of the channel is only managed in a single place and you won’t have to deal with nasty bugs when multiple parties make assumptions about the state of the channel.