-
Notifications
You must be signed in to change notification settings - Fork 192
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
CLI: Add the subcommand verdi computer export
#6389
Conversation
92fe8e3
to
1b7c3e0
Compare
@@ -105,6 +105,7 @@ Below is a list with all available subcommands. | |||
disable Disable the computer for the given user. | |||
duplicate Duplicate a computer allowing to change some parameters. | |||
enable Enable the computer for the given user. | |||
export Exports the Authinfo details for a computer (and user). |
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.
Why add the user
here? Only computer
should be enough, no? I suppose this is because Computer.get_configuration
allows specifying a user? Though, as verdi computer export
in its current state doesn't provide that option, we could remove its mentioning here, or, alternatively, allow specifying an optional user
via @options.USER
.
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.
I wonder if we should phrase even slighly more differently still. What actually happens is that verdi computer setup
creates a Computer
instance and verdi computer configure
then creates a AuthInfo
, which joins a Computer
and User
together. This is because a computer can be configured for multiple users.
So really what we are exporting is a file describing the Computer
instance and a file describing the AuthInfo
. Now I think that most users are not really familiar with the AuthInfo
, so I wonder if we should include it here in the docstring. How about
export Export a computer and its transport configuration
Note that exactly what data goes into the AuthInfo
depends on the chosen transport type for the computer. That is also why verdi computer configure
has the transport plugin as a subcommand, because that determines which options should be exposed.
I think users are more familiar with transport than AuthInfo
.
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.
Ah right, I did not fully understand this. Thanks for the catch. I now added the user option for export.
I am not sure if "transport" is more understandable for the user. I have the feeling "authentication info" (writing it out) is quite understandable while "transport" seems more abstract to me. @GeigerJ2 what do you think?
In any case I copied this from the verdi computer configure
description, so would be in this case consistent and change it in both places
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.
See commit for changes 521228b
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.
For me, "authentication info" seems nicer than AuthInfo
especially for users who are not yet familiar with the AiiDA classes. If that captures the essence of what is being configured, then that's fine for me.
@arguments.COMPUTER() | ||
@arguments.OUTPUT_FILE(type=click.Path(exists=False)) | ||
@with_dbenv() | ||
def computer_export_setup(computer, output_file): |
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.
As we recently added the option to make the sorting optional for the code export (see PR #6345), I propose we also add it here as an additional option.
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.
Don't you think, this should be just be on always? No user option here? I feel like it is not a meaningful option for a user to have a random order in the yaml file the developer decided or sorted. Sorting is also not a computational expensive postprocessing task so I would always sort the yaml.
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.
Personally, I find append_text
appearing in the beginning of the YAML file confusing, as that is one of (if not the) last option one configures when running the command interactively. @sphuber and I discussed this also in the linked PR #6345. While I don't think it's super important, the aiida-code-registry
and the aiida-resource-registry
currently contain YAML files which are not alphabetically sorted, but instead in the order in which the options are defined in the src
which should also be the order in which the user is prompted when running interactively. So if we consider these three use cases: 1) interactive CLI command, 2) import from the YAML files in the registry, and 3) export from a profile, it should be consistent throughout, rather than sorting alphabetically for 3) if that is not done for 1) and 2).
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.
I see the point here. The default would sort it more logically, similar to order the prompt asks the user. I then agree with you
with open(output_file, 'w', encoding='utf-8') as yfhandle: | ||
yaml.dump(computer_setup_data, yfhandle) | ||
echo.echo_success(f"Computer<{computer.pk}> {computer.label} setup exported to file '{output_file}'.") | ||
except Exception: |
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.
Can we make this more explicit? Do we know which exceptions to expect here? FileExistsError
should be captured already in the @arguments.OUTPUT_FILE
. Anything else? Possibly also communicate the failure via echo.echo_error
/echo.echo_critical
rather than just raising the Exception.
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 this. Ideally we narrow the scope of the exceptions being caught unless we really don't know what could be thrown. But definitely we should not reraise but simply do echo.echo_critical(f'Export failed: {exception}')
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.
I put the error message into an echo.
I put a logging of the whole traceback into the debug logger, please let me know if this makes sense, I moved the logger to the cmdline level, since it seems to make more sense to me than in utils. I just import the logger from echo module instead of creating a new one. I am not sure if should not use a custom logger for the file CMDLINE_LOGGER = logging.getLogger('verdi computer')
. I can also put this change to another PR.
FileExistsError should be captured already in the @arguments.OUTPUT_FILE
Seems not really to take take. One can overwrite files currently but verdi code export
does overwrite it too, so it is consistent. We could change behavior in both commands to not overwrite and add an --overwrite
flag? But I would do it in a different PR. What do you think @GeigerJ2 ? The only thing it does if a path with the same name exists it does not overwrite
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.
Ah, seems like I misunderstood the behavior of exists=False
in
@arguments.OUTPUT_FILE(type=click.Path(exists=False))
Thought that would raise if the file exists already, but it just means it doesn't necessarily need to exist ^^
I think exporting configuration files, it's fine to overwrite by default, as accidentally overwriting is not too critical. In my dumping PR, I added it as a click
option with default=False
, as there I'm really doing a recursive deletion of files and directories, so one might lose more relevant data.
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.
You think it is worth to make an issue with priority nice-to-have for a --no-overwrite
feature for the export commands?
|
||
@computer_export.command('setup') | ||
@arguments.COMPUTER() | ||
@arguments.OUTPUT_FILE(type=click.Path(exists=False)) |
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, more general, some idea for the future: We could also make the OUTPUT_FILE
optional, and use f{label}-setup/config.yaml
as default (also for the code export), as this is probably how people would usually name the output file?
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.
Makes sense, but are you suggesting that it is created in a folder? Or should it just create a file?
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.
I am also in favor of this, I'll make an issue. I did not do it because I wanted it to be consistent with verdi code export which requires an output file name
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.
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.
Very nice work, thanks @agoscinski! For me, the naming is fine. I just dogfooded it a bit by running verdi computer export --help
, and exporting and re-importing my daint-gpu
and localhost
(from verdi presto
) computers. Everything worked well, I just noted a few minor things (see code comments). In addition, when exporting localhost
, the config
YAML only contains safe_interval: 0
, so using the interactive verdi computer config
still asks for Use login shell when executing command
. Is it possible to also include that in the exported YAML, so that all possible options are contained there? This was the case for daint-gpu
, where without running -n/--non-interactive
during import I didn't get prompted for anything as everything was contained in the YAML file. If not, it's also fine as it's just one option and using -n
sets the default.
Maybe @khsrali and @sphuber can try it with their computers, or have other comments, then I think this should be ready to go soon 🚀
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 a lot @agoscinski , looking good. I added a few more comments.
This sound like |
Forwarding that to @sphuber as |
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 @agoscinski . Just a few more remaining minor comments. I have no strong opinion on the sorting. As can be read from the discussion that @GeigerJ2 linked, I don't really see why this should matter. But now that it has been added, I am also fine with adding the option to optionally sort as was done for verdi code export
.
} | ||
try: | ||
output_file.write_text(yaml.dump(computer_setup, sort_keys=True), 'utf-8') | ||
echo.echo_success(f"Computer<{computer.pk}> {computer.label} setup exported to file '{output_file}'.") |
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.
I think this should rather go in the else
-block of the try/except. Otherwise, if there would be a bug hiding in this line that raises, it would be caught by the exception that is designed to catch errors in the actual dumping
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.
done
if user is None: | ||
user = User.collection.get_default() | ||
computer_configuration = computer.get_configuration(user) |
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.
This can be simplified as Computer.get_configuration
already takes care of getting the default if user is None
if user is None: | |
user = User.collection.get_default() | |
computer_configuration = computer.get_configuration(user) | |
computer_configuration = computer.get_configuration(user) |
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.
done
user = User.collection.get_default() | ||
computer_configuration = computer.get_configuration(user) | ||
output_file.write_text(yaml.dump(computer_configuration, sort_keys=True), 'utf-8') | ||
echo.echo_success(f"Computer<{computer.pk}> {computer.label} configuration exported to file '{output_file}'.") |
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.
Same, put this is else
-clause
echo.echo_critical( | ||
f'Unexpected error while exporting configuration for Computer<{computer.pk}> {computer.label}' | ||
f' and User<{user.pk}> {user.email}: {e!s}.' |
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.
I think there is actually a bug here. If the user is not specified, it will be None
and user.pk
and user.email
will raise.
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.
I mean it depends, if I use before
if user is None:
user = User.collection.get_default()
then this should work
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.
Okay get_default can return None
I see, then I will do here some check and get the user from computer
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.
I think technically one gets an error of the form
Critical: no default profile defined: None
and because it immediately exists one never has the issue with user is None, but this requires knowledge of the layers below, so I added an if then else changing the error message depending on if the user is None
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.
done
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.
I think technically one gets an error of the form
Critical: no default profile defined: None
I don't understand where that exception would come from.
and because it immediately exists one never has the issue with user is None, but this requires knowledge of the layers below, so I added an if then else changing the error message depending on if the user is None
The situation I imagine is computer.get_configuration
raising for some reason. The code then goes to the except
clause and if there you use user
but it is None
you will get another exception. And user
will be None
if the caller does not explicitly specify one, which is usually the case.
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.
I don't understand where that exception would come from.
Sorry, cannot recreate, must being doing something weird or my environment was broken.
The situation I imagine is computer.get_configuration raising for some reason. The code then goes to the except clause and if there you use user but it is None you will get another exception. And user will be None if the caller does not explicitly specify one, which is usually the case.
Yes I think it is fair point. I thought that somehow it will always exit beforehand because of some precheck.
6a71ae0
to
a742c9c
Compare
I squashed all changes from last review into the first commit, so all new commits are from the second round. The incomplete config @GeigerJ2 mentioned when using verdi presto is still an issue, but I don't know where I should solve it. The cmdline submodule is definitely the wrong place to impose defaults. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #6389 +/- ##
==========================================
+ Coverage 77.51% 77.69% +0.18%
==========================================
Files 560 562 +2
Lines 41444 41699 +255
==========================================
+ Hits 32120 32392 +272
+ Misses 9324 9307 -17 ☔ View full report in Codecov by Sentry. |
@@ -240,7 +240,7 @@ def show(code): | |||
'--sort/--no-sort', | |||
is_flag=True, | |||
default=True, | |||
help='Sort the keys of the output YAML.', | |||
help='Sort the keys of the output YAML. Default --no-sort.', |
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.
help='Sort the keys of the output YAML. Default --no-sort.', | |
help='Sort the keys of the output YAML.', |
We shouldn't manually encode the default. click
options take show_default=True
if you want that. There might be a bug in our InteractiveOption
class that causes it not to be rendered, but that should be fixed separately.
Besides, is --no-sort
actually the default? It seems that you define default=True
which I think would mean it would sort by default, right?
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 for the catch, changed it everywhere to show_default
@@ -673,7 +675,7 @@ def get_command(self, ctx, name): | |||
|
|||
@verdi_computer.group('configure', cls=LazyConfigureGroup) | |||
def computer_configure(): | |||
"""Configure the Authinfo details for a computer (and user).""" | |||
"""Configure the authentication info for a computer and user.""" |
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.
I think this new version is an improvement, but I wonder if we should actually call it "authentication". As I mentioned in this comment in another issue, even though it does create an AuthInfo
object, it really configures the transport. Some of these options don't necessarily have to do with authentication at all, e.g., the safe_interval
and use_login_shell
for example. So what would be more correct is
Configure the transport for a computer and user.
I can see how transport might be a bit vague to users (although it does come up in the verdi computer setup
that precedes this command). So maybe the following could work?
Configure a computer for a given user.
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.
I like the first one actually more. Having only this
configure Configure a computer for a given user.
setup Create a new computer.
does not transport much information. And transport
is something one can look up in the documentation and find actually materials.
echo.echo_critical( | ||
f'Unexpected error while exporting configuration for Computer<{computer.pk}> {computer.label}' | ||
f' and User<{user.pk}> {user.email}: {e!s}.' |
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.
I think technically one gets an error of the form
Critical: no default profile defined: None
I don't understand where that exception would come from.
and because it immediately exists one never has the issue with user is None, but this requires knowledge of the layers below, so I added an if then else changing the error message depending on if the user is None
The situation I imagine is computer.get_configuration
raising for some reason. The code then goes to the except
clause and if there you use user
but it is None
you will get another exception. And user
will be None
if the caller does not explicitly specify one, which is usually the case.
) | ||
@with_dbenv() | ||
def computer_export_config(computer, output_file, user, sort): | ||
"""Export the configuration of the authentication info for a computer and user to a yaml file.""" |
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.
"""Export the configuration of the authentication info for a computer and user to a yaml file.""" | |
"""Export computer configuration to a yaml file.""" |
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.
I did
"""Export computer transport configuration for a user to a YAML file."""
1db2b6f
to
a8f77fc
Compare
Enables the export of the configuration and setup of a computer to yaml file similar to `verdi code export`. Since the setup and configuration are two seperate steps, one has to specify with `config` or `setup` which of the two should be exported.
move echo_success messages into else branch of the try-except in computer_export_config gerror message includes user information only if user is specified
Co-authored-by: Sebastiaan Huber <[email protected]>
a8f77fc
to
51999ec
Compare
Thanks a lot @agoscinski |
This command has two subcommands, `setup` and `config`, that dump the definition of a `Computer` and an associated `AuthInfo` to a YAML file that can be used to recreate it using `verdi computer setup` and `verdi computer configure` using the `--config` option, respectively.
Enables the export of the configuration and setup of a computer to yaml file similar to
verdi code export
. Since the setup and configuration are two seperate steps, one has to specify withconfig
orsetup
which of the two should be exported.There was the option to implement the commands
verdi computer configure export
andverdi computer setup export
but sinceverdi computer setup
does not take any additional required arguments it would be less intuitive for the user to have an optional additional argument that changes the command type. There was also the option to export both files in one command, but that seems to be less consistent with the rest of the CLI. The wordconfig
is used inverdi computer export config
since in this case we are specifying theconfiguration
, but it might be a bit confusing withverdi config
(which I think should be calledverdi configure
for consistency). If anyone has strong opinions I can also rename it toverdi computer export configuration
orverdi computer export configure
as the latter seems to be used in other projects like aiida-core-registryI am adding tests now, but already open a draft for commentsRelevant issues: