Commit graph jw-pkg/src/python/jw/pkg/lib/ec/ssh/AsyncSSH.py
Author SHA1 Message Date
8210baa683 lib.ec.ssh.AsyncSSH: Code beautification
- Remove _run_on_conn() because it doesn't add any value

  - Add verbose try-except block around connect()

  - Add try-except block around failing close

  - Prefix private member variables with "__"

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-19 21:09:51 +02:00
baf09e32eb lib.ec.ssh.AsyncSSH: Re-use connection
Commit a19679fec reverted the first attempt to make AsyncSSH reuse
one connection during an instance lifetime. That failed because a lot
of distribution-specific properties were filled in a new event loop
thread started by AsyncRunner, and AsyncSSH didn't like that.

The last commit provided the needed properties as members of the
Distro class. This commit is the second part of the solution: Keep
one connection around as a class member and reuse it on every _run()
invocation.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-19 21:08:04 +02:00
c238a0d2d0 lib.ec.ssh.AsyncSSH: Replace legacy Caps.Env
AsyncSSH uses the legacy Caps.Env, replace it by Caps.ModEnv.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-19 20:59:16 +02:00
f1898941e7 lib.ec.ssh.AsyncSSH: Code beautification
Apply some style changes:

  - Replace double by single quotes for consistency

  - Add spaces around equal signs in long parameter lists

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-19 14:36:50 +02:00
54aecff8e4 lib.ExecContext.run(), .sudo(): Rename env
The name of the env parameter to ExecContext.run() and .sudo() is not
descriptive enough for which environment is supposed to be modified
and how, so rename and split it up as follows:

  - .run(): env -> mod_env

  - .sudo(): env -> mod_env_sudo and mod_env_cmd

The parameters have the following meaning:

  - "mod_env*" means that the environment is modified, not replaced

  - "mod_env" and "mod_env_cmd" modify the environment "cmd" runs in

  - "mod_env_sudo" modifies the environment sudo runs in

Fix the fallout of the API change all over jw-pkg.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-19 14:36:50 +02:00
9575339972 lib.ec.ssh.AsyncSSH: Declare Caps.Env
AsyncSSH's implementation already supports modifying the execution
environment via env, so declare it to the base class with Caps.Env.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-18 10:43:31 +02:00
a19679fecc lib.ec.ssh.AsyncSSH: Revert "Reuse connection"
This reverts commit 04fef1e67a.

Reusing AsyncSSH's connection is fine and fast, but only if it's not
combined with the AsyncRunner. See commit 67e51cf0 why it was
introduced in the first place, along with a reasoning why it may be a
bad idea. Looks like we're now reaping what we sowed.

The current plan to get this to fly is to sprinkle async / await all
over the code paths to App.os_release(). That is a lot of churn, so
postpone and revert for now to keep CI working.

  File "~/local/src/jw.dev/proj/jw-pkg/scripts/jw/pkg/lib/ec/ssh/AsyncSSH.py", line 463, in _run_ssh
    return await self._run_on_conn(
           ^^^^^^^^^^^^^^^^^^^^^^^^
    ...<7 lines>...
    )
    ^
  File "~/local/src/jw.dev/proj/jw-pkg/scripts/jw/pkg/lib/ec/ssh/AsyncSSH.py", line 403, in _run_on_conn
    proc = await conn.create_process(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<7 lines>...
    )
    ^
  File "/usr/lib/python3.13/site-packages/asyncssh/connection.py", line 4492, in create_process
    chan, process = await self.create_session(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
        SSHClientProcess, *args, **kwargs) # type: ignore
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/site-packages/asyncssh/connection.py", line 4385, in create_session
    session = await chan.create(session_factory, command, subsystem,
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<4 lines>...
                                bool(self._agent_forward_path))
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/site-packages/asyncssh/channel.py", line 1149, in create
    packet = await self._open(b'session')
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/site-packages/asyncssh/channel.py", line 717, in _open
    return await self._open_waiter

^^^^^^^^^^^^^^^^^^^^^^^

RuntimeError: Task <Task pending name='Task-1' coro=<App.__run() running at ~/local/src/jw.dev/proj/jw-pkg/scripts/jw/pkg/lib/App.py:137> cb=[_run_until_complete_cb() at /usr/lib64/python3.13/asyncio/base_events.py:181]> got Future <Future pending> attached to a different loop

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 16:07:11 +02:00
888c0495ec lib.base: Add module
Add lib.base to provide basic definitions.

For now, move the definiions of Result, Input and InputMode from
ExecContext into lib.base. Having to import them from the ExecContect
module is too heavy-handed for those simple types.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 12:57:04 +02:00
04fef1e67a lib.ec.ssh.AsyncSSH: Reuse connection
With CmdCopy as test case and ExecContext.close() in place, we can
actually implement connection reuse, so do it for AsyncSSH.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 12:56:55 +02:00
04b294917f lib.ExecContext: Support bytes-typed cmd_input
The Input instance passed as cmd_input to ExecContext.run() and
.sudo() currently may be of type str. Allow to pass bytes, too.

At the same time, disallow None to be passed as cmd_input. Force the
caller to be more explicit how it wants input to be handled, notably
with respect to interactivity.

Along the way fix a bug: Content in cmd_input should result in
CallContext.interactive == False but doesn't. Fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
84375cd482 lib.ec.ssh: Don't quote shell operators
Naively join()ing a command list to be executed remotely via SSH also
quotes shell operators which doesn't work, of course. Work around
that. The workaround will not always work but covers lots of cases.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:52:15 +02:00
b21d2d1c21 lib.ec.ssh.AsyncSSH: Add interactivity
Request a remote PTY from AsyncSSH, and wire the local terminal's
stdin up with it if interactive == True. This gives a real
interactive session if local stdin belongs to a terminal. Also,
thanks to AsyncSSH understanding that, forward terminal size changes
to the remote end.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
737cbc3e24 lib.ec.ssh.AsyncSSH: Add class
Add a SSHClient implementation using AsyncSSH. This is the first and
currently only class derived from SSHClient which implements
SSHClient.Cap.LogOutput, designed to consume and log command output
as it streams in. It felt like the lower hanging fruit not to do that
with Paramiko: Paramiko doesn't provide a native async API, so it
would need to spawn additional worker threads. I think.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00