Metadata-Version: 2.1
Name: implant
Version: 0.1.0
Summary: Remote execution via stdin/stdout messaging.
Home-page: https://github.com/diefans/implant
License: Apache-2.0
Keywords: asyncio,ssh,RPC,bot,implant,inject,code,remote
Author: Oliver Berger
Author-email: diefans@gmail.com
Requires-Python: >=3.5,<4.0
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Requires-Dist: click
Requires-Dist: ruamel.yaml (==0.15.37)
Description-Content-Type: text/x-rst

implant
**********

.. inclusion-marker-do-not-remove

.. image:: https://travis-ci.org/diefans/implant.svg?branch=master
   :target: https://travis-ci.org/diefans/implant

A proof-of-concept for asynchronous adhoc remote procedure calls in Python.

This is work in progress and serves basically as an exercise.


Features
========

- Python >= 3.5 asyncio

- adhoc transferable remote procedures

- remote part of a :py:obj:`implat.core.Command` may reside in a separate module

- a :py:obj:`implant.core.Command` specific :py:obj:`implant.core.Channel`
  enables arbitrary protocols between local and remote side

- events

- quite small core

- tests


Limitations
===========

- Python >= 3.5

- only pure Python modules are supported for remote import, if no venv is used

- :py:obj:`implant.core.Command` s must reside in a module other then `__main__`

- at the moment sudo must not ask for password



Example
=======


General application
-------------------

.. code:: python

    import asyncio
    import pathlib

    from implant import core, connect, commands


    async def remote_tasks():
        # create a connector for a python process
        connector = connect.Lxd(
            container='zesty',
            hostname='localhost'
        )
        connector_args = {
            'python_bin': pathlib.Path('/usr/bin/python3')
        }
        # connect to a remote python process
        remote = await connector.launch(**connector_args)

        # start remote communication tasks
        com_remote = asyncio.ensure_future(remote.communicate())
        try:
            # execute command
            cmd = commands.SystemLoad()
            result = await remote.execute(cmd)

            print("Remote system load:", result)

        finally:
            # stop communication tasks
            com_remote.cancel()
            await com_remote


    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        loop.run_until_complete(remote_tasks())
        loop.close()


An example Echo Command
-----------------------

.. code:: python

    import logging
    import os

    from implant import core


    log = logging.getLogger(__name__)


    class Echo(core.Command):

        """Demonstrate the basic command API."""

        async def local(self, context):
            """The local side of the RPC.

               :param context: :py:obj:`implant.core.DispatchLocalContext`
            """
            # custom protocol
            # first: send
            await context.channel.send_iteration("send to remote")

            # second: receive
            from_remote = []
            async for x in context.channel:
                from_remote.append(x)
            log.debug("************ receiving from remote: %s", from_remote)

            # third: wait for remote to finish and return result
            remote_result = await context.remote_future

            result = {
                'from_remote': ''.join(from_remote),
            }
            result.update(remote_result)
            return result

        async def remote(self, context):
            """The remote side of the RPC.

               :param context: :py:obj:`implant.core.DispatchRemoteContext`
            """
            # first: receive
            from_local = []
            async for x in context.channel:
                from_local.append(x)
            log.debug("************ receiving from local: %s", from_local)

            # second: send
            await context.channel.send_iteration("send to local")

            # third: return result
            return {
                'from_local': ''.join(from_local),
                'remote_self': self,
                'pid': os.getpid()
            }


Internals
=========

::

    master <-----------------------------------------> remote
                                |
                           stdin/stdout
                                |
                              chunks
                                |
                             channels
                                |
        --> send ---> |                   |  --> queue -->
                      | module:class/fqin |
        <-- queue <-- |                   |  <--- send <--


