Commit graph

266 commits

Author SHA1 Message Date
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
e811b2dc14 cmds.posix.CmdCopy: Add module
Add CmdCopy, designed to copy data from the filesystem to another
location in the filesystem. Not necessarily local file systems, the
URLs can be all URLs supported by ExecContext.run().

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 12:56:43 +02:00
16bb4e5bed cmds.CmdPosix: Add command
Add a new group of commands - "posix". The current command categories
are not a good fit for that: "projects" is for CI, "distro" is
distribution-specific for CD, and secrets is for handling secrets
specifically. Introduce the more general command group "posix", a
class of commands not POSIX compliant in the exposed API, but
primarily using POSIX utilities as workhorse.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 12:56:22 +02:00
63383cb683 lib.util.copy(): Add function
Add copy(src_uri, dst_uri), instatiating two ExecContext instances,
and doing the obvious with them - copying from src_uri to dst_uri.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 12:55:47 +02:00
d803c66f70 App.__os_release(): Use ExecContext.get()
The property App.__os_release uses _run(['cat', '/etc/os-release']),
use ExecContext.get() instead as the default way to fetch content.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:26 +02:00
60630676ef App.__aexit__(): Use to close exec context
Override lib.App.__aexit() to call close() on the App.exec_context
instance if it exists.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
3d27cc09d9 lib.App.run_sub_commands(): Instantiate as context manager
App currently has no hook to close async resources. Call it as
context manager, so that __aexit__() gets invoked if
run_sub_commands() exits.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
4f17a9cc93 lib.ExecContext.log_name: Add property
Add a .log_name property to be used in log messages.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
1214451c15 lib.ExecContext.close(): Add method
Add ExecContext.close() as a hook to clean up async resources living
longer than an ExecContext method call.

Also, implement __aenter__() and __aexit__(), to allow using
ExecContext as context manager.  close() is invoked it goes out of
scope.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
f158ab76a8 lib.ExecContext: Add get(), _get(), put() and _put()
Add wrapper methods get() and put(), plus their wrapped methods
_get() and _put(). The wrapped methods have default implementations,
using POSIX utilities on the target machine over _run().

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +02:00
dbf41d7b48 lib.ExecContext.create(): Support path-only URIs
ExecContext.create() relies on properly formed URLs with a schema for
deciding which backend gets created. Create a Local instance if an
URL doesn't have schema.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:25 +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
8ef478e63f lib.ExecContext: Code beautification
- Move _sudo() above sudo()

    To have a pattern in lib.ExecContext and avoid future churn: If a
    public wrapper calls a protected method, define the protected
    method above the respective wrapper.

  - sudo(): Make cmd_input default equal to run(): InputMode.OptInteractive

  - CallContext: Expose parameters throw, wd, cmd as properties for
    later use

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-16 10:21:16 +02:00
281cdf4ec7 cmds.projects.CmdCanonicalizeRemotes: Add command
CmdCanonicalizeRemotes / canonicalize-remotes and the respective
target in topdir.mk remove the /srv/git portion from all remotes'
URLs pointing to git.janware.com.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-13 12:23:02 +02:00
1b2ebab33a lib.App.__run(): Beautify error logging
Without --backtrace, the outmost try-catch block logs exceptions
plainly as their text. If it catches a key error, the exception text
only consists of the key itself, which can be easily mistaken for a
normal program output, so prefix it with a "Failed:".

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-13 12:14:03 +02:00
5ad65f85fd cmds.secrets.lib.util: Add module
To be able to use secret handling code from other modules, move the
bulk of it from the "secrets"-command centric implementation in
cmds.secrets.Cmd into a new module cmds.secrets.lib.util.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:56:21 +02:00
d680d6c5ed lib.ec.ssh.Exec._run_ssh(): run_cmd(throw=False)
_run_ssh() of ssh.Exec doesn't pass throw=False to run_cmd(), which
makes it throw exceptions, and effectively strips the caller of any
chance to get hold of stdout and stderr. Pass throw=False and let
run() decide according the the caller-provided throw parameter
whether or not a problem should propagate up as exception or return
value.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:55:30 +02:00
a58220a131 lib.ec.SSHClient.ssh_client(): Add type parameter
ssh_client() tries a predefined order of client class implementations
until it finds a workable candidate. For testing all, it's desirable
to be able to target the exact class. Add a "type" parameter to
achieve that.

I'm aware that type is also a function. But the semantics look so
compelling to me that I'm using the variable name anyway.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:53:12 +02:00
bafc7fed2a lib.ec.ssh.Exec: Honour self.interactive
The Exec SSHClient ignores the "interactive" argument passed to its
constructor, fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:53:07 +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
61c1a628a1 lib.ec.SSHClient: Exception for empty host name
Instantiating a SSHClient-derived class with an invalid or missing
uri parameter is accepted and fails later down the road. Raise an
Exception early on to make the error log more comprehensible.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:51:50 +02:00
0e18d4abac lib.ec.ssh.Exec|Paramiko: Don't # export
The SSHClient classes Paramiko and Exec are exported via # export.
This is a bad idea, because if Paramiko is not installed, none of the
other's can be instantiated either: On the attempt to load them,
__init__.py is loaded first and fails. SSHClient.ssh_client() knows
what to do, no need to auto-import them into the lib.ec.ssh module.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:51:19 +02:00
1caae611ba App.os_release: Fix debug logging
cat /etc/os-release sometimes fails over the wire, and the reason
should be logged but isn't. Fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-11 14:50:24 +02:00
98ad7442d9 cmds.projects.CmdListRepos: Beautify error logging
Call run_curl() with parse_json=True to make that explicit, and be a
little more verbose about the outcome.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-08 10:49:58 +02:00
f18575a267 lib.util.run_curl(): Add decode parameter
run_curl() has no clear API of whether or not the return values should
be decoded. It has parse_json, which should imply decoding, but there's
no way to specify that explicitly. Moreover, when it tries to decode, it
decodes on the coroutine returned from run_cmd(), not the awaited
coroutine return value.

Add a decode parameter, defaulting to False, change the parse_json
parameter's default from True to False, and fix the run_cmd() return
value evaluation.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-08 10:49:52 +02:00
5fded01d00 cmds.projects.CmdListRepos: Init SSHClient with app opts
Use the global --verbose and --interactive command-line options as
defaults for constructing a SSHClient instance for use with listing
repos over SSH.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-07 15:39:29 +02:00
40511947c9 cmds.projects.BaseCmdPkgRelations: Add --hide-jw-pkg
Support --hide-jw-pkg. This is a step towards replacing
required-os-pkg, which leaves out packages from pkg.requires.jw
sections.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-06 16:47:46 +02:00
ad0e03f14c cmds.projects.BaseCmdPkgRelations: Fix cross-sec deps
If a package P is added, only those of its dependendencies are added
along which are in the same os-cascade section as P. That's wrong,
fix it.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-06 16:02:34 +02:00
a0dcf59277 cmds.projects.BaseCmdPkgRelations: Better variables
Make variable names a little more readable and searchable within the
long pkg_relations_list() method by making their names longer and
truer to what they actually mean.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-06 16:02:34 +02:00
8c34dae526 cmds.projects.CmdPrereq: Remove class
CmdPrereq was mostly redundant to PkgRequired all along. CmdPrereq
has grown more versatile, and CmdPrereq is not used throughout jw-pkg
anylonger by now. Remove it.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-06 16:02:34 +02:00
18a2ee6c99 App.get_value(): Beautify debug logging
Enclose sections / keys taken from project.conf in [square.brackets],
hinting at what they are supposed to mean.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-06 16:02:27 +02:00
3c9ce19deb lib.ec.ssh.Paramiko: Fix exception logging
The catch-block around Paramiko's connect code throws another
exception, fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-04-03 17:29:44 +02:00
e589cdbdbf cmds and lib: Don't print() log messages
print() should be used to output information requested by a certain
command, but not for logging the process to achieve it. log() should
be used for the latter. The current code has the distinction not down
clearly, fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-30 08:11:39 +02:00
7cfe2c4775 lib.App._run(): default_completer=NoopCompleter()
By default, argcomplete uses argcomplete.FilesCompleter as default
for every argument. This mixes accessible files into the list of
possible completions. For most of jw-pkg's commands, that's unwanted,
so turn it off by defining a NoopCompleter class which does nothing,
and by set every arguments's default completer to a NoopCompleter
instance. If desired, completing files can be restored for an
argument by

   parser.add_argument("some-arg").completer = FilesCompleter()

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-28 12:45:50 +01:00
ce1b8b6744 lib.App: Fully parse argparse tree if _ARGCOMPLETE
Every module derived from lib.Cmd implements its own
parser.add_argument() logic. As a consequence, all Cmd derived
modules need to be loaded to have the full argument tree available.
This is avoided during normal program startup because it takes some
time. It's not necessary during normal program execution, nor for
showing help messages. It is, however, needed for argcomplete to do
its thing, so fully parse the command line if the program runs in
argcomplete mode, as determined by checking if _ARGCOMPLETE is
present in the environment.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-27 09:16:14 +01:00
9b208ecc1e lib.ExecContext.log_delim(): No interactive footer
lib.ExecContext.log_delim() logs a header not designed for enclosing
command output, and, hence, no footer should be output. This commit
suppresses it.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-27 09:16:14 +01:00
9b6ec109a1 cmds and lib: Don't print() log messages
print() should be used to output information requested by a certain
command, but not for logging the process to achieve it. log() should
be used for the latter. The current code has the distinction not down
clearly, fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-27 09:16:14 +01:00
e461b2815d cmds.projects.BaseCmdPkgRelations: Fix Debian simple deps
Simple dependencies (i.e. non-triplet dependencies, e.g gcc > 15.0)
raise an exception on Debian, fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 09:50:28 +00:00
6d876e88f6 lib.util.run_sudo(): Pass argument list on unchanged
run_sudo() is a thin wrapper around ExecContext.sudo(), so don't try
to make sense more arguments than necessary.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:37:56 +00: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
279b7789e2 lib.ec.SSHClient: Add property port
Add a port property to SSHClient, parsed from the ctor's URL, to
supply the obvious information.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
3a84408436 lib.ec.SSHClient.__init__(): Add parameter caps
Add an optional caps ("capabilities") argument to the constructor of
SSHClient. It is meant to be used by derived classes in order to
declare that they don't want the base class to handle a default
behaviour for a certain capability, but that they want to implement
it themselves instead.

Also, give the _run_ssh() callbacks the necessary info as parameters,
so that the derived classes have the means to do so.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
4393ca21fc App.os_cascade: Don't use platform.system()
Python's platform.system() outputs 'Linux', and to use it is
tempting. Sadly, that's wrong, because it reflects the host's idea of
the target system, not the execution context's, so replace it with
straight 'linux' if the distro is known, or, failing that, the output
of uname -s.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
b2e1e411f1 lib.pm.*.query_packages(): Make it non-interactive
lib.pm.query_packages() uses a TTY for doing its thing and outputs
half-digested stuff to the terminal, fix that.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
8280327602 App.os_release: cat os-release non-interactively
Even with --interactive=[true|auto], there's no point in trying to
read /etc/os-release interactively, so don't do that. Most notably,
this commit keeps the property method from spilling /etc/os-release's
content over the terminal.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:46 +01:00
0b05eb4c37 ExecContext: Make mode:xxx an enum
This commit introduces two new types, Input and InputMode. They
replace the more error-prone special strings cmd_input could be used
with. InputMode is an Enum, and Input can be either IntputMode, a
string or None.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:45 +01:00
bb13aea694 lib.ExecContext: Fix ignored --interactive
Whether or not the CallContext.interactive property should be True or
False, and hence, a call should be processed interactively, depends
on multiple factors, constituting matrix of options with multiple
preferences.

  --interactive is the application default and can be true, false,
    or auto

  - A call can be explicitly invoked as interactive, non-interactive
    or auto via the cmd_input parameter to ExecContext.run()

This commit adds more "mode:" options to make the latter more
explicit. It takes preference over the global --interactive
parameter: Global --interactive is only given a chance to decide if
cmd_input is None (default) or mode:opt-interactive.

This commit also fixes a bug: --interactive is ignored because the
interactive argument passed to ExecContext's constructor is ignored
later on in calls to the wrapped _run() and _sudo() methods.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:45 +01:00
3bda9bc826 lib.ExecContext.sudo(): Default None mod_env to {}
mod_env can be None. Make it an empty dict in that case to take a
little burden off the implementations in the derived classes.

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-03-25 07:32:45 +01:00
21e67291b5 Fix: Decode run_cmd() result
Since commit 02697af5, ExecContext.run() returns bytes for stdout and
stderr and fixes that in calling code. The thing it did not fix was
the code calling run_cmd(), which also made return bytes. This commit
catches up on that.

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