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
overlord: actually run hooks. #1511
overlord: actually run hooks. #1511
Conversation
The basic machinery for running hooks is in place, so it's time to connect the pieces and enable them. Updates: #1586465 Signed-off-by: Kyle Fazzari <kyle@canonical.com>
f720f2c
to
9fb7e1b
Compare
@@ -143,13 +153,38 @@ func (m *HookManager) doRunHook(task *state.Task, tomb *tomb.Tomb) error { | |||
return err | |||
} | |||
|
|||
// TODO: Actually dispatch the hook. | |||
_, err = RunHook(setup.Snap, setup.Revision, setup.Hook) | |||
if err != nil { |
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 it would be better if the runCommand error handling is done in the runCommand helper. Something like (if you don't need the output except for the error case which seems to be the case here):
func doRunCommand(name string, args...string) error {
if output, err := exec.Command(name, args...).CombinedOuptut(); err != nil {
return fmt.Errorf("failed to run command %q %q: %q (%s)", name, args, output, err)
}
return nil
}
Alternatively https://github.com/snapcore/snapd/blob/master/partition/utils.go#L33 might be something worth putting in osutil if you need the stdout of the 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.
Yeah agreed on the error handling. I'll investigate that method as well, thank you!
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.
Thanks for this branch. Some suggestions around the command hanlding. |
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
I think we likely want to tie the task tomb.Dying to calling cmd.Process.Kill() or cmd.Process.Signal combination |
Oh, good catch, yes. |
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
if err != nil { | ||
// "exit code 1" is not particularly helpful. So rewrite the error | ||
// using stdout and stderr instead. | ||
err = fmt.Errorf("%s", strings.Trim(string(output), " \n")) |
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 we want to give some more context to the error here. If for example a command just exits with exist-status 7 but no output the error would become "". Maybe more like: "fmt.Errorrf("failed to run command %q %q: %q (%s)", name, args, output, err)
or similar, I don't mind the exact message, but I would like to see the original error (permission denied, command not found, exit status N etc) plus what command it actually was that run and the output.
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.
Alright, done.
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
Alright, I've done this. Please take a look when you're able. |
…ofa/snapd into feature/1586465/enable_hooks
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
@@ -67,7 +67,7 @@ func (x *cmdRun) Execute(args []string) error { | |||
if x.Hook != "" && x.Command != "" { | |||
return fmt.Errorf("cannot use --hook and --command together") | |||
} | |||
if x.Revision != "" && x.Hook == "" { | |||
if x.Revision != "unset" && x.Revision != "" && x.Hook == "" { |
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 do we need "unset" here?
For context, one of the reasons why I put "unset" there originally was because we were passing around unset revisions as if they were valid in awkward places, and it ended up as far as reaching the file system in some cases. So with that string we have a visual clue that something is not right. Typing in --revision=unset is one of those cases.. where's that string coming from?
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.
Indeed, I don't envision anyone actually typing -r=unset
, but it simplified a bit of the code that runs hooks from the HookManager
. See this discussion for some background, and let me know if you'd like it changed back.
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.
The use case here makes it look like the revision should never be unset. Is that not 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.
No. snap run
accepts the -r
parameter for the revision, which previously defaulted to an empty string, meaning "no revision, run on the active one." That required some custom logic in the HookManager
to see if the requested hook had a revision set, and if not, to pass an empty string. This PR updates snap run
to also accept "unset" to mean "no revision, run on the active one," which means HookManager
can simply use snap.Revision.String()
without any custom logic, and things just 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.
Let me restate the question: do we have valid cases for having run-hook tasks without a revision?
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.
Curious.. so where does --revision even come from? How about killing it all until we have a concrete reason?
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.
Let me know if, after reading that discussion, you'd like it removed.
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.
notice also that during installation of a snap itself we don't have an exposed concept of current until very late (but we know revisions)
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.
After the discussion on telegram, I'll leave the revision handling the way it is in this PR, and we'll observe how it evolves as more hooks are added. If we run into issues with them they can be removed.
|
||
// newProcessRunner starts a new process with the requested parameters and | ||
// returns its ProcessRunner. | ||
func newProcessRunner(name string, args ...string) (ProcessRunner, error) { |
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 feels like there's way too much indirection to do something quite straightforward. We have the exactly the same functionality available in exec.Cmd itself.
I bet you can comfortably kill all of that, including even doStartCommand (which is unused!), ProcessRunner, newProcessRunner, and all its friends, and replace all of it by simply:
var buf bytes.Buffer
cmd := exec.Command("snap", "run", ...)
cmd.Stdin = &buf
cmd.Stdout = &buf
cmd.Start()
Inside snapRun
itself (old doRunHook
).
For testing, we have a good mechanism for mocking actual commands (executables).
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.
Note the comment on doStartCommand would then move there as well, as something along the lines of:
return fmt.Errorf("hook %q failed: %v", hookName, osutil.OutputErr(buf.Bytes(), err))
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.
Right, but this was done so that I could actually test that flow. If I just use exec.Cmd
, how do I verify that things are actually killed correctly?
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.
testutil.MockCommand(c, "snap", "sleep 60000")
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 good, thank you-- that will indeed simplify things 😃 .
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 an interesting philosophical point, note you're not actually testing that commands are killed correctly if you're mocking the thing that kills the commands out.
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.
True. I expected that exec.Cmd.Process.Kill()
would work as advertised-- I just wanted to test that it was called appropriately.
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.
The logic also assumed that it was called at all, and on the right process, which was actually set rather than nil, etc. These additional layers that were created in order to enable mocking may also be broken.
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.
Signed-off-by: Kyle Fazzari <kyle@canonical.com>
b35e2f4
to
4ee062a
Compare
Thank you! |
The basic machinery for running hooks is now in place, so it's time to make a bit more progress on LP: #1586465 and connect the pieces to enable them.