-
Notifications
You must be signed in to change notification settings - Fork 727
Add command aliases to the CLI client #1986
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
Conversation
Codecov Report
@@ Coverage Diff @@
## main #1986 +/- ##
==========================================
+ Coverage 83.30% 83.97% +0.67%
==========================================
Files 190 203 +13
Lines 9756 10108 +352
==========================================
+ Hits 8127 8488 +361
+ Misses 1629 1620 -9
Continue to review full report at Codecov.
|
5fed3ae
to
772b5b9
Compare
43791bb
to
e4d10db
Compare
5d39c93
to
51f84cd
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.
Hi @luis4a0, I did a high-level, superficial review pass. It looks very nice overall! 👏
The one thing that concerns me a bit, from an architectural point of view, is the multiple AliasDict
objects. They are meant to always represent the same mapping, but the multiple copies open a space for conflicts. They need to be loaded and saved multiple times, which increases the potential for concurrency issues. Admittedly I have only just considered this, but it seems to me that it would be better to have a single, central object owned by the client, that the relevant commands operated on via a reference. At that point, I am not sure the separate type is beneficial (relatively to a map). Then again, perhaps it's worth keeping it so that it could be reused in the GUI client. But please let me know if there are other things I am not considering.
I suspect you went with the multiple objects because the Command
objects are created with Client::add_command
, which takes no arguments, while a central dict would have to be passed only to a few commands. But you can modify Client::add_command
to receive and pass on whatever arguments each Command
's ctor takes, with a technique called perfect forwarding. It's what things like emplace
methods or make_unique
use, and it can even move rvalues as appropriate. I will submit a PR that you can use if you want. (Done: #2032.)
I didn't quite get the ClientFormatter
separate from the existing Formatter
, but I confess I didn't look closely. I think we discussed something related to that, but I forget what the issue was. We can discuss further if you want.
Hey @ricab, thanks a lot for the review! About the multiple About formatters, we already discussed on the topic. The current formatters work with RPC objects, while the new formatter work with a CLI-only thing, which is the alias dictionary. Implementing the dict formatters inside the current formatters would imply to link against RPC all things which use formatters, which is a big refactor. I opted to separate both, i.e., adding a "client" formatter, which formats data structures defined in the client only. |
Hey @luis4a0, about With a single A singleton would not allow that read/write separation. It's advantages would not be very useful here: not something to be used in arbitrary parts of the code (like settings or std paths); not something that avoids deep parameter passing all over ( For the formatters, right, the dependency on the RPC... thanks for reminding me. I wish there was a way to isolate the dependency and otherwise homogenize things, but I don't have a proposal right now. I guess we could always revisit. |
Thanks for the explanation @ricab ! Now it makes complete sense to me what you proposed. |
Sorry, wrong button :X |
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.
Hey, looks good overall! A handful of comments inline.
src/client/cli/cmd/alias.cpp
Outdated
parser->addPositionalArgument("definition", "Alias definition in the form <instance>:'<command> [<args>]'", | ||
"<definition>"); | ||
|
||
auto status = parser->commandParse(this); | ||
if (status != ParseCode::Ok) | ||
return status; | ||
|
||
if (parser->positionalArguments().count() != 2) | ||
{ | ||
cerr << "Wrong number of arguments given\n"; | ||
return ParseCode::CommandLineError; | ||
} |
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.
We've not thought this through. We don't want to be splitting the args on space, or we're into escape sequences and things.
There's two options we have here, both involving the --
separator:
<name> <instance>:<command> [--] [<args> ...]
<name> <instance> <command> [--] [<args> ...]
The colon is probably overkill here, as <instance>
is mandatory (as opposed to in transfer
that works both ways). Option 2 follows multipass exec
closest, so that'd be my preference.
@ricab, @townsend2010, WDYT?
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.
Yeah, totally agree about not splitting args on spaces and I think option 2 is the best choice.
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! About option 2: is --
alone optional? Shouldn't it be <name> <instance> <command> [-- <args> ...]
?
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.
It is optional, you only need it if any of the <args>
starts with a -
. All positional arguments beyond <command>
are arguments to the alias, so there's no need for it.
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.
+1 for no spaces in cmd, preferring no colon, and matching exec
— this problem was already solved for that cmd, which works quite well. The only thing that surprised me is the placement of the [--]
. Shouldn't it come before <command>
?
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.
So I think I'm changing my vote to option 3:
multipass alias <instance>:<command> [<alias>]
, where<alias>
defaults to<command>
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.
And in case we skip arguments and stick to 3, who wants arguments can create a script on the instance. But in case we got requests to add arguments to aliases, I think we'll have to add a new syntax to define an alias with commands, thus complicating the interface.
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.
Hmm, not allowing arguments and requiring a script/alias/fn inside the instance would be quite limiting. But the arguments would still be allowed if quoted with option 3, right? So the mismatch with exec
worries me more.
The awkwardness in ln
isn't intrinsic, is it? Other commands have the target first (e.g. tar
). It's often the case when an equals sign is implicit (e.g. git config --global alias.co checkout
). With ln
, I think it's rather the mismatch with cp
and mv
that makes it awkward. I fear we could be causing the same feel for someone coming from exec
:
# if you're used to
multipass -v exec foo -- ip -br -c a
# it's perhaps less awkward to say
multipass -v alias fooip foo -- ip -br -c -a
# then
multipass -v alias foo:"ip -br -c -a" fooip
# idk
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.
Let me summarize what we talked about:
ssh
(the client, not the protocol) is weird (aka broken) in that:I.e. it always has a shell interpret the command line. So the user has to be very careful on how to quote things to become separate arguments.$ ssh localhost mkdir foo\ bar $ ssh localhost ls foo bar $ ssh localhost echo \$HOME /home/ubuntu
multipass exec
is not using a shell:We definitely want to maintain that in the$ multipass exec primary mkdir foo\ bar $ multipass exec primary ls 'foo bar' $ multipass exec primary echo \$HOME $HOME
alias
feature, that exactly the arguments provided are passed through to the<command>
executable in the instance. If we support arguments, you could still have a shell interpret the command withsh -c "..."
. If<command>
includes special characters, the executable has to be called exactly that.alias
fromexec
is different in thatalias
is creating an entity (the alias), and it's prevalent in CLIs that the thing you create is last on the command line:$ cp <src> <tgt> $ mv <src> [<src> ...] <tgt> $ ln <target> [<link>] $ snap alias <snap.app> [<alias>]
- There will still be even more complex use cases that are probably out of scope, e.g. templating/placeholders.
- The most common use case of aliases will be exporting a command from inside the instance to the host, and so I'd like to optimize for this:
multipass alias <instance>:<command> [<alias>]
- While awkward, it's still possible to allow, with time, the addition of arguments, possibly like so:
multipass alias <instance>:<command> [<alias>] [-- <arg> …]
- Note this still supports arguments when invoking the alias:
Equivalent to:
multipass <alias> [<arg> ...] <alias> [<arg> ...]
multipass exec <instance> -- <command> [<arg> ...]
I vote for implementing multipass alias <instance>:<command> [<alias>]
at this stage.
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.
Hey, thanks for the comments! I've been considering all this. Now I'm convinced that, in terms of usability and simplicity, multipass alias <instance>:<command> [<aliasname>]
would be the best choice at the moment. I don't think adding parameters after --
would be a big issue. In fact, since we have three positional arguments being one of them optional, we can't avoid using --
. Circumventing the use of --
would imply to either make one argument mandatory, thus losing the simplicity of the approach.
src/client/cli/cmd/alias.cpp
Outdated
|
||
std::string instance = definition.left(colon_pos).toStdString(); | ||
QString full_command = definition.right(definition.size() - colon_pos - 1); | ||
QStringList full_command_split = full_command.split(' ', QString::SkipEmptyParts); |
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.
QStringList full_command_split = full_command.split(' ', QString::SkipEmptyParts); |
🔥 🔥 🔥
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.
Yeah, fire in the hole! This was now removed because we removed arguments from the interface.
ecc58f1
to
4da9193
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.
Ok, let's do this! Whoop! Whoop!
bors merge
Build failed: |
This PR adds the ability to create aliases to be executed in determined instances.
Private bits are in https://github.com/canonical/multipass-private/pull/386