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

overlord: actually run hooks. #1511

Merged
merged 21 commits into from Aug 2, 2016

Conversation

kyrofa
Copy link
Contributor

@kyrofa kyrofa commented Jul 7, 2016

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.

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>
@kyrofa kyrofa force-pushed the feature/1586465/enable_hooks branch from f720f2c to 9fb7e1b Compare July 7, 2016 16:34
@@ -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 {
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 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.

Copy link
Contributor Author

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!

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.

@mvo5
Copy link
Contributor

mvo5 commented Jul 8, 2016

Thanks for this branch. Some suggestions around the command hanlding.

kyrofa and others added 2 commits July 8, 2016 13:36
@pedronis
Copy link
Collaborator

pedronis commented Jul 8, 2016

I think we likely want to tie the task tomb.Dying to calling cmd.Process.Kill() or cmd.Process.Signal combination

@kyrofa
Copy link
Contributor Author

kyrofa commented Jul 8, 2016

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"))
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 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.

Copy link
Contributor Author

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>
@kyrofa
Copy link
Contributor Author

kyrofa commented Jul 8, 2016

I think we likely want to tie the task tomb.Dying to calling cmd.Process.Kill() or cmd.Process.Signal combination.

Alright, I've done this. Please take a look when you're able.

@@ -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 == "" {
Copy link
Contributor

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?

Copy link
Contributor Author

@kyrofa kyrofa Jul 15, 2016

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

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.

Copy link
Collaborator

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)

Copy link
Contributor Author

@kyrofa kyrofa Jul 15, 2016

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) {
Copy link
Contributor

@niemeyer niemeyer Jul 15, 2016

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).

Copy link
Contributor

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))

Copy link
Contributor Author

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?

Copy link
Contributor

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")

Copy link
Contributor Author

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 😃 .

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

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.

@kyrofa kyrofa force-pushed the feature/1586465/enable_hooks branch from b35e2f4 to 4ee062a Compare July 30, 2016 16:14
@niemeyer
Copy link
Contributor

niemeyer commented Aug 2, 2016

Thank you!

@niemeyer niemeyer merged commit c92230c into snapcore:master Aug 2, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants