-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Topic/extensions v2 #112
Topic/extensions v2 #112
Conversation
@robinkrahl I've reworked the extension patch stack that I created back in the day to work with |
The code looks good to me! I’ll have a closer look later today.
But I want to bring up some issues regarding the extension ecosystem:
# Backwards compatibility
One issue that I see with this approach is that a non-breaking change in
nitrocli, adding a new global option that is passed to the extensions,
could easily break older extensions (unsupported argument). So should
we either consider adding a global option to be a breaking change, or
should we recommend that extensions only print a warning for unsupported
arguments instead of aborting?
# Extension support crate
It would be nice to have an extension support crate, let’s say
nitrocli-ext, that provides the pass-through options and utility methods
to invoke nitrocli. We could even use this crate in nitrocli to avoid
code duplication.
# Curated extensions
We will have some core extensions that are useful for most nitrocli
users, for example getting an OTP code by name. In my opinion, we
should treat these extensions as a part of nitrocli regarding
maintenance and support. We could even include them in the main
nitrocli repository, or we could create a new nitrocli-extensions
repository for curated extensions.
# Data directories
We should define a place where extensions should store their data (in
terms of the XDG Base Directory spec: config, cache, data). My
suggestion is: We reserve the subdirectories of the nitrocli data
directories for extensions. We could even allow extensions to put their
configuration in sections of nitrocli’s main config file.
|
Thanks for your comments! These are all valid points.
I don't even think we do have a notion of what is a breaking change for But yes, I do share your sentiment. I am fairly sure we touched on that in the past, and wanted to have a policy that extensions just ignore arguments they don't understand. But we should have an "extension writer's guide" or something, perhaps (or at least cover it in the man page).
I didn't want to get into the business of having a library providing a stable API to be honest (arguably that's more dogma than anything else at this point, though...). In all likelihood we will need some better way to get data provided by
Yes, agreed. I am happy to include them in the repository itself.
That sounds pretty good to me! |
That’s true. Technically, we don’t have to deal with breaking changes while we are still 0.*, but we should definitely write up a short description of our stability promises before we reach v1.0.0.
I don’t want a stable API to nitrocli – or at least that’s not what I meant –, but utility methods for extensions written in Rust, for example:
The Maybe this pseudo code makes it clearer ( #[derive(StructOpt)]
struct Args {
name: String,
#[structopt(flatten)]
ext: nitrocli_ext::Args,
}
fn main() -> anyhow::Result<()> {
let args = Args::from_args()?;
let slots = args.ext.nitrocli()
.args(&["otp", "list"])
.json()
.context("Could not list OTP slots")?;
let slot = slots.iter()
.filter_map(|v| v.as_object())
.filter(|o| o.get("name").as_str() == Some(&args.name))
.filter_map(|o| o.get("slot").as_i64())
.next()
.context("Could not find OTP slot with the given name")?;
let code = args.ext.nitrocli()
.args(&["otp", "get", &slot.to_string()])
.text()
.context("Could not generate OTP code")?;
println!("{}", code);
Ok(())
}
Indeed. I’m not against having JSON as an output format, I just wanted (and still want) to also have a simpler output format that does not require a JSON parser. |
I see. Yeah, we could think about that. I didn't see too many use cases for extensions written in Rust itself, but why not. And if I recall correctly you already had some QR code utility that may lend itself towards being extensionized.
I suppose if we could express this non-JSON format in the form of a serde data format we could fudge both cases in one go. |
True: https://git.ireas.org/nitrocli-otp-qr/ I will also prepare a prototype for an extension that generates a OTP by slot name.
We could use the |
Here is a prototype for generating one-time passwords by slot name, based on the extensions-v2 branch: https://github.com/robinkrahl/nitrocli/tree/otp-cache It’s working quite well, but I have not yet found a good way to tell structopt to store additional arguments in a vector that we can then pass to nitrocli. Also, it’s a bit annoying that I can’t pass on the --verbositiy option to nitrocli. |
Really nice! Yeah, I think this one we could include as a core extension.
Isn't the standard for these cases doing something along the lines of: Then you can capture But yeah, overall not many command line argument parsers will lend themselves too well towards what we are doing. That's certainly the biggest concern I have. I'd of course still be willing to go with a different way for interfacing with extensions (or them interfacing with
Can you elaborate? |
Isn't the standard for these cases doing something along the lines of:
`nitrocli otp-cache --your-fancy-otp-cache-option=42 --
--model=storage`
I think typically it’s the other way round: `nitrocli otp-cache
--global-option -- --extension-option`. But we could use this as a
convention for the extensions. Still, it only allows me to catch all
pass-through options as strings. I’d like to parse the known options
using structopt so that I can use them in my code and collect only the
unknown options as strings to be compatible with newer nitrocli
versions.
> Also, it’s a bit annoying that I can’t pass on the --verbositiy
> option to nitrocli.
Can you elaborate?
nitrocli calls the extension with a --verbosity <n> option. If the
extension in return calls nitrocli, it would want to set the verbosity
for nitrocli. But nitrocli expects a --verbose flag, so I have to
transform --verbosity <n> into n times --verbose.
|
Yeah, that has always been a concern of mine. If I recall correctly in Python it's more or less easily possible, but in Rust I wouldn't know, off hand. Do you know whether other argument parsing libraries would support that better? I'd hate for us to use a different crate for that, at least for core extensions, but if it's an easy way out, perhaps.
That I would hope is fixable. Preferably we could see if recognizing both --verbose=X and multiple --verbose can be made to work. If not we could still mirror the --verbosity argument in nitrocli directly. Or we just switch to --verbose=X everywhere (though I kind of dislike that, as X has no obvious semantic to the user, it's just a number [is higher more or less?]). |
If you don’t mind, I’m going to prepare a prototype for setting the output format as a basis for further discussion, okay? |
I actually thought about this prototype some more. I think the extension lookup is fine, but the "recursive" invocation of That's the first point. The second is that after a bit more thought I'd want to try out using environment variables for conveying the "execution context". I think I just prototyped with arguments because it was simpler and I had not weighed all pros and cons. But I agree with your comment that they may be more suitable. Does that make any sense to you? Or are we just starting back at zero at this point? From my perspective not a whole lot changes for this pull request: just the way we pass the context. The first point is more long term and directly related to your question about future work. |
I think it wouldn’t be that hard to add support for stable output formats. And as long as we have both a format that is easy to parse (e. g. tsv) and a structured format (e. g. JSON), I’m not unhappy with this solution. Regarding the invocation method, let’s take
With our current approach, I don’t really see the benefits of a library crate. It would still have to parse the arguments one more time etc. But it would probably be harder to maintain extensions because the extension’s nitrocli lib version would always have to match the nitrocli binary’s version. On the other hand, re-invoking nitrocli makes sure that both the “outer” and the “inner” version are the same. But even if we go with the library crate, I think it would be good to have a stable output format for scripting. Consider for example the Regarding the environment variables: I think your suggestion would be to set |
I was actually thinking one environment variable per argument. Can you explain what complication you see? Would having a single variable buy us that much? You'd still have to run some more or less complex parsing on the contents (as opposed to a rather simple XXX-to-string conversion), which I am not sure is helpful. |
You’re right, I was thinking about the subcommands. But we would only use the global options that already support environment variables, right? So it would result in something like this:
It would be a small improvement because we would no longer have to separate the extension arguments and the nitrocli base arguments, but it wouldn’t be much. |
Yep, exactly. |
Ah, okay :) I'll have to think through things a bit more, I guess. Anyway, doesn't hurt to experiment with output some. So if you want to come up with something, let me just share my preliminary thoughts: Given that we could not really agree on a format or sort of agreed on going with two, I was thinking about the following: I am pretty sure that would work easily with JSON (and others such as YAML). I am not quite sure whether we can make it work with TSV. It sort of depends on how you want it to look. Theoretically we could just print keys followed by a tab followed by a value and have some indentation. That should map fairly cleanly. It's just a thought. Perhaps there is so little duplication/difference in the paths for two formats that it doesn't matter. |
That’s similar to what I had in mind: On my output-formats branch, I added the output module that contains data types for the output. These data types can be converted to human-readable text, JSON or tsv. (I’m currently using the csv crate to generate the tsv data, but we could probably also just print the data directly.) The prototype still needs some work, but I think it demonstrates that this approach is valid. The least elegant part are the Example 1: objects
Example 2: tables
|
223c848
to
862fc62
Compare
Haven't look in too much detail yet, but at a high level it looks great! I've rebased this pull request against top of tree |
862fc62
to
cf6ca7c
Compare
One reason for picking up the extension work again is because I am freakin' annoyed by Nitrokey/libnitrokey#137 and the OTP hang (now that I am actually using |
Me too. I’ve made it a habit to remove my Nitrokey after every OTP use, but that’s pretty annoying. |
cf6ca7c
to
153f146
Compare
What’s the next step for this PR? |
I intend to roll this support out locally for me, get some experience with your
FYI: |
eef8204
to
23ecfe4
Compare
Okay, I think this logic is ready for review now. I suppose we could add a bit more to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, Daniel! The architecture looks good to me, I just have some comments regarding implementation details.
And I’d like to define the extensions’ data and configuration directories in the man page, as described in my first comment in this PR (last heading).
23ecfe4
to
fbd6f17
Compare
Thanks for your review, Robin! I addressed some of your comments. Will have to think a bit about the "Writing extensions" section. Not happening today. |
FYI, for me the following is broken: $ NITROCLI_SERIAL_NUMBERS='' nitrocli status
Failed to parse environment variables
Caused by:
Library error: The supplied string is not in hexadecimal format (I noticed that because that's what I think supporting an empty variable for a |
Thanks for the update! I’ve opened an issue for the vector deserialization problem: softprops/envy#50 |
This change reorders and subdivides the Environment section we have in the manual. The first subsection in it is about variables pertaining the program configuration and the second one about those influencing password & PIN entry. Having these dedicated subsections will subsequently allow us to reference them in follow up changes. The reordering is meant to reflect the more general applicability that configuration variables have.
84e3d6b
to
817bced
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I still want to change the man page (separate the information for users and programmers, define the data directories), but we can discuss that separately.
Yes, absolutely. I'll look into this soon. |
This change introduces support for discovering and executing user-provided extensions to the program. Extensions are useful for allowing users to provide additional functionality on top of the nitrocli proper. Implementation wise we stick to an approach similar to git or cargo subcommands in nature: we search the directories listed in the PATH environment variable for a file that starts with "nitrocli-", followed by the extension name. This file is then executed. It is assumed that the extension recognizes (or at least not prohibits) the following arguments: --nitrocli (providing the path to the nitrocli binary), --model (with the model passed to the main program), and --verbosity (the verbosity level).
With recent changes we are able to execute user-provided extensions through the program. However, discoverability is arguably lacking, because nitrocli provides no insight into what extensions are available to begin with. This patch changes this state of affairs by listing available extensions in the help text.
817bced
to
7e8e4d6
Compare
Okay, I've adjusted the Do you think we should list these directories in the |
Okay, I've adjusted the `man` page a bit. Not super happy with it, but
perhaps it will do. Let me know what you think.
Thanks, I’ll have a closer look later today!
I'd prefer to use bold & italic for the `extension` part in
`\fB${XDG_DATA_HOME}/\fIextension/\fR`. But I can't seem to figure out
the syntax...
As far as I know, you can select only one of roman (= regular), italic
and bold. Combining bold and italic is not possible.
Do you think we should list these directories in the `FILES` section?
No, I think that’s not necessary. Extensions should have their own man
pages that list the files they actually use. Many extensions might not
even need it.
|
I've added some copy-to-clipboard functionality to |
Merged. |
Seems envy is no longer maintained. At least not in any acceptable form, if pull requests are not acknowledged in a week's time. Any suggestions what we do about that? |
No description provided.