Skip to content
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

o/hookstate/ctlcmd: unify the error message when context is missing #10606

Merged
merged 1 commit into from Aug 27, 2021

Conversation

mardy
Copy link
Contributor

@mardy mardy commented Aug 11, 2021

Make the error message more user friendly, since the concept of a
"context" is not obvious to the end user. Also let it be translated, and
unified across the various snapctl subcommands.

@mardy mardy added the Simple 😃 A small PR which can be reviewed quickly label Aug 11, 2021
Copy link
Contributor

@MiguelPires MiguelPires left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed there's a few usages of context() that pass the context into runServiceCommand (which then performs this check), could we use ensureContext() there as well? It'd have to be slightly refactored though

@mardy
Copy link
Contributor Author

mardy commented Aug 11, 2021

I noticed there's a few usages of context() that pass the context into runServiceCommand (which then performs this check), could we use ensureContext() there as well? It'd have to be slightly refactored though

I noticed that, but then I forgot to change that. I now added a commit which changes the error message for the service commands too, but I cannot think of a smart way to refactor this.

Copy link
Contributor

@MiguelPires MiguelPires left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@@ -66,6 +68,13 @@ func (c *baseCommand) context() *hookstate.Context {
return c.c
}

func (c *baseCommand) ensureContext() (context *hookstate.Context, err error) {
if c.c == nil {
err = errors.New(i18n.G(`The "snapctl" command can only be invoked from within a snap`))
Copy link
Contributor

@mvo5 mvo5 Aug 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, the change itself is great and a nice idea!

Just a quick drive-by highlevel comment - I think we should not have go error types localized. Instead I would suggest the pattern is something like:

var ErrNoContext = errors.New("cannot use snapctl without a context")

 func (c *baseCommand) ensureContext() (context *hookstate.Context, err error) {
    ...
    return ErrNoContext
}

and then in the relevant "main" (cmd/snapctl/main.go I presume) we check for ErrNoContext and if we see that we print the localized error and exit.

I hope this makes sense, sorry that it's a bit terse, I'm a bit short on time right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm... I do recognize the problem, but I think that a proper solution cannot be done in the context of this PR. The problem is far from trivial: we have a process boundary between snapctl (which knows the user's locale) and snapd (which doesn't). There are (at least) two possible approaches:

  1. Have snapd return errors in machine form, either as an untranslated string (as you suggest), or as error codes. Then the client will translate them properly when presenting them to the user.
  2. Have the client pass locale information to snapd over the REST API, and make i18n.G() (or some other function) aware of the context so that it would translate the messages to the locale encoded in the context.

The first option has the big downside that it would only work for fixed messages (a %s in them would immediately make the message non translatable), unless we design a way to pass more context variables back to the client process and manage to build a sensible error message there.

Anyway, regardless of the path we choose, both require a substantial amount of work, as both snapctl and snapd are not ready for either solution. That's why I propose to leave this for later. :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed this is more complicated due to how snapctl is implemented, we already have similar case with ctlcmd.UnsuccessfulError type and respective client.ErrorKindUnsuccessful which requires changes in api_snapctl.go, cmd/snapctl/main.go (as well as overlord/hookstate/ctlcmd/..); I'm not sure it's worth doing atm, but if we want it localized, then we should follow this pattern.

But I agree with the high level remark from @mvo5, the error itself shouldn't be localized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and nitpick^2, the error isn't strictly true because we deliberately allow 'snapctl --help' invocation from the shell; I think the error message I suggested below in the other comment would solve it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I removed the i18n call. However, there are still many of such calls in this module; whould I remove all of them (maybe in a separate PR)?

@@ -60,9 +60,9 @@ func init() {
}

func (c *fdeSetupRequestCommand) Execute(args []string) error {
context := c.context()
if context == nil {
return fmt.Errorf("cannot run fde-setup-request without a context")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think including snapctl subcommand name in the message was nice/useful; maybe "'snapctl %s' can only be invoked from within a snap"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@anonymouse64 anonymouse64 self-requested a review August 18, 2021 12:53
@pedronis pedronis self-requested a review August 18, 2021 14:33
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new error message is a bit confusing. I need to think of a suggestion

@@ -66,7 +71,16 @@ func (c *baseCommand) context() *hookstate.Context {
return c.c
}

func (c *baseCommand) ensureContext() (context *hookstate.Context, err error) {
if c.c == nil {
err = fmt.Errorf(`The "snapctl %s" command can only be invoked from within a snap`, c.name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the slight problem with this new error is that it makes almost sound like there are snapctl commands that can be invoked without a snap, but there are none.

The old messages weren't good but din't have this problem

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use:

"cannot invoke snapctl operation commands (here %q) from outside of a snap", c.name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

@pedronis pedronis self-assigned this Aug 19, 2021
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my comments on error messages

@@ -132,7 +132,7 @@ func queueCommand(context *hookstate.Context, tts []*state.TaskSet) error {
func runServiceCommand(context *hookstate.Context, inst *servicestate.Instruction) error {
if context == nil {
// this message is reused in health.go
return fmt.Errorf(i18n.G("cannot %s without a context"), inst.Action)
return fmt.Errorf(i18n.G(`The snapctl %s action can only be invoked from within a snap`), inst.Action)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use the same message as well but command name here is inst.Action

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mardy was this part addressed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it now uses the same error message as in other parts: https://github.com/mardy/snapd/blob/snapctl-context-err/overlord/hookstate/ctlcmd/helpers.go#L133-L135

(if you are merging this yourself please remember to squash :-) )

@pedronis pedronis removed their assignment Aug 19, 2021
@mardy mardy added the Squash-merge Please squash this PR when merging. label Aug 20, 2021
Copy link
Collaborator

@bboozzoo bboozzoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

overlord/hookstate/ctlcmd/helpers.go Outdated Show resolved Hide resolved
@@ -32,10 +32,19 @@ import (
"github.com/snapcore/snapd/strutil"
)

func createMissingContextError(subcommand string) error {
return fmt.Errorf(`Cannot invoke snapctl operation commands (here %q) from outside of a snap`, subcommand)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT errors in go lang shouldn't be capitalized (because they can be appended/wrapped in another error message and it then looks weird); I'm slightly surprised it's not caught by our static checks, I could swear I saw an error about such issue before...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because I used a cyrillic "С" instead ;-)

Fixed now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@codecov-commenter
Copy link

Codecov Report

Merging #10606 (1a6ebbb) into master (8170654) will decrease coverage by 2.40%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #10606      +/-   ##
==========================================
- Coverage   80.71%   78.30%   -2.41%     
==========================================
  Files         727      882     +155     
  Lines       58158    99422   +41264     
==========================================
+ Hits        46940    77856   +30916     
- Misses       7544    16665    +9121     
- Partials     3674     4901    +1227     
Flag Coverage Δ
unittests 78.30% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
cmd/snap/wait.go
gadget/mountedfilesystem.go
logger/logger.go
daemon/api_systems.go
systemd/emulation.go
cmd/snap/cmd_model.go
interfaces/builtin/kubernetes_support.go
cmd/snap-update-ns/xdg.go
cmd/snap-update-ns/utils.go
interfaces/udev/udev.go
... and 1516 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 308f4eb...1a6ebbb. Read the comment docs.

Copy link
Contributor

@stolowski stolowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It nicely simplified these checks, thank you! +1

@@ -32,10 +32,19 @@ import (
"github.com/snapcore/snapd/strutil"
)

func createMissingContextError(subcommand string) error {
Copy link
Contributor

@mvo5 mvo5 Aug 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick/bike-shed) I wonder if something like:

index 20b8b09067..76bac68240 100644
--- a/overlord/hookstate/ctlcmd/ctlcmd.go
+++ b/overlord/hookstate/ctlcmd/ctlcmd.go
@@ -32,8 +32,12 @@ import (
        "github.com/snapcore/snapd/strutil"
 )
 
-func createMissingContextError(subcommand string) error {
-       return fmt.Errorf(`cannot invoke snapctl operation commands (here %q) from outside of a snap`, subcommand)
+type MissingContextError struct {
+       subcommand string
+}
+
+func (e *MissingContextError) Error() string {
+       return fmt.Sprintf(`cannot invoke snapctl operation commands (here %q) from outside of a snap`, e.subcommand)
 }
 
 type baseCommand struct {
@@ -77,7 +81,7 @@ func (c *baseCommand) context() *hookstate.Context {
 
 func (c *baseCommand) ensureContext() (context *hookstate.Context, err error) {
        if c.c == nil {
-               err = createMissingContextError(c.name)
+               err = &MissingContextError{c.name}
        }
        return c.c, err
 }
diff --git a/overlord/hookstate/ctlcmd/helpers.go b/overlord/hookstate/ctlcmd/helpers.go
index 5260027224..595d99560b 100644
--- a/overlord/hookstate/ctlcmd/helpers.go
+++ b/overlord/hookstate/ctlcmd/helpers.go
@@ -131,7 +131,7 @@ func queueCommand(context *hookstate.Context, tts []*state.TaskSet) error {
 
 func runServiceCommand(context *hookstate.Context, inst *servicestate.Instruction) error {
        if context == nil {
-               return createMissingContextError(inst.Action)
+               return &MissingContextError{inst.Action}
        }
 
        st := context.State()

is a bit more idiomatic go. I.e. having an error type for this instead of creating different error strings. The end result is the same so it's not really a big deal but I could not not mention it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, applied!

Make the error message more user friendly, since the concept of a
"context" is not obvious to the end user.
@anonymouse64 anonymouse64 removed their request for review August 25, 2021 23:41
@stolowski stolowski removed the Simple 😃 A small PR which can be reviewed quickly label Aug 26, 2021
@mardy
Copy link
Contributor Author

mardy commented Aug 26, 2021

@mvo5, I'm afraid I need your magic powers to merge this. :-)

@mvo5 mvo5 merged commit 03f6f5e into snapcore:master Aug 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Squash-merge Please squash this PR when merging.
Projects
None yet
7 participants