Source code for ocrd_network.runtime_data.connection_clients
from __future__ import annotations
from docker import APIClient, DockerClient
from docker.transport import SSHHTTPAdapter
from paramiko import AutoAddPolicy, SSHClient
[docs]
class CustomDockerClient(DockerClient):
    """Wrapper for docker.DockerClient to use an own SshHttpAdapter.
    This makes it possible to use provided password/keyfile for connecting with
    python-docker-sdk, which otherwise only allows to use ~/.ssh/config for
    login
    XXX: inspired by https://github.com/docker/docker-py/issues/2416 . Should be replaced when
    docker-sdk provides its own way to make it possible to use custom SSH Credentials. Possible
    Problems: APIClient must be given the API-version because it cannot connect prior to read it. I
    could imagine this could cause Problems. This is not a rushed implementation and was the only
    workaround I could find that allows password/keyfile to be used (by default only keyfile from
    ~/.ssh/config can be used to authenticate via ssh)
    XXX 2: Reasons to extend DockerClient: The code-changes regarding the connection should be in
    one place, so I decided to create `CustomSshHttpAdapter` as an inner class. The super
    constructor *must not* be called to make this workaround work. Otherwise, the APIClient
    constructor would be invoked without `version` and that would cause a connection-attempt before
    this workaround can be applied.
    """
    def __init__(self, user: str, host: str, **kwargs) -> None:
        # The super-constructor is not called on purpose. It solely instantiates the APIClient.
        # Missing 'version' in that call would raise an error.
        # The APIClient is provided here as a replacement for what the super-constructor does
        if not (user and host):
            raise ValueError("Missing 'user' and 'host' - both must be provided")
        if ("password" in kwargs) and ("keypath" in kwargs):
            if kwargs["password"] and kwargs["keypath"]:
                raise ValueError("Both 'password' and 'keypath' provided - one must be provided")
        if ("password" not in kwargs) and ("keypath" not in kwargs):
            raise ValueError("Missing 'password' or 'keypath' - one must be provided")
        self.api = APIClient(base_url=f"ssh://{host}", use_ssh_client=True, version="1.41")
        self.api.mount(
            prefix="http+docker://ssh", adapter=self.CustomSshHttpAdapter(base_url=f"ssh://{user}@{host}:22", **kwargs)
        )
[docs]
    class CustomSshHttpAdapter(SSHHTTPAdapter):
        def __init__(self, base_url, password: str = "", keypath: str = "") -> None:
            self.password = password
            self.keypath = keypath
            if not self.password and not self.keypath:
                raise Exception("Missing 'password' or 'keypath' - one must be provided")
            if self.password and self.keypath:
                raise Exception("Both 'password' and 'keypath' provided - one must be provided")
            super().__init__(base_url)
        def _create_paramiko_client(self, base_url: str) -> None:
            """
            this method is called in the superclass constructor. Overwriting allows to set
            password/keypath for the internal paramiko-client
            """
            super()._create_paramiko_client(base_url)
            if self.password:
                self.ssh_params["password"] = self.password
            elif self.keypath:
                self.ssh_params["key_filename"] = self.keypath
            self.ssh_client.set_missing_host_key_policy(AutoAddPolicy) 
 
[docs]
def create_docker_client(address: str, username: str, password: str = "", keypath: str = "") -> CustomDockerClient:
    return CustomDockerClient(username, address, password=password, keypath=keypath) 
[docs]
def create_ssh_client(address: str, username: str, password: str = "", keypath: str = "") -> SSHClient:
    client = SSHClient()
    client.set_missing_host_key_policy(AutoAddPolicy)
    try:
        client.connect(hostname=address, username=username, password=password, key_filename=keypath)
    except Exception as error:
        raise Exception(f"Error creating SSHClient of host '{address}', reason: {error}") from error
    return client